Merge "MediaRecorderTest: Increase the file size in MediaRecorderTest#testRecordExceedFileSizeLimit" into android13-tests-dev am: fd35825f90 am: 27a296003f am: 44f163b468
Original change: https://android-review.googlesource.com/c/platform/cts/+/2505836
Change-Id: Ia0affe13a882a73d28f1b081e98637c54f730908
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
diff --git a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
index bf2b551..d76c7b9 100644
--- a/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
+++ b/common/device-side/device-info/src/com/android/compatibility/common/deviceinfo/LocaleDeviceInfo.java
@@ -20,17 +20,23 @@
import android.util.Log;
import androidx.annotation.Nullable;
import com.android.compatibility.common.util.DeviceInfoStore;
+import com.google.common.base.Strings;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -40,6 +46,9 @@
public final class LocaleDeviceInfo extends DeviceInfo {
private static final String TAG = "LocaleDeviceInfo";
+ private static final Pattern HYPHEN_BINARY_PATTERN = Pattern.compile("hyph-(.*?).hyb");
+ private static final String HYPHEN_BINARY_LOCATION = "/system/usr/hyphen-data/";
+
@Override
protected void collectDeviceInfo(DeviceInfoStore store) throws Exception {
List<String> locales = Arrays.asList(
@@ -59,6 +68,26 @@
}
store.addListResult("icu_locale", icuLocales);
+ // Collect hyphenation supported locale
+ List<String> hyphenLocalesList = new ArrayList<>();
+ // Retrieve locale from the file name of binary
+ try (Stream<Path> stream = Files.walk(Paths.get(HYPHEN_BINARY_LOCATION))) {
+ hyphenLocalesList = stream
+ .filter(file -> !Files.isDirectory(file))
+ .map(Path::getFileName)
+ .map(Path::toString)
+ .filter(HYPHEN_BINARY_PATTERN.asPredicate())
+ .map(s -> {
+ Matcher matcher = HYPHEN_BINARY_PATTERN.matcher(s);
+ return matcher.find() ? Strings.nullToEmpty(matcher.group(1)) : "";
+ })
+ .sorted()
+ .collect(Collectors.toList());
+ } catch (IOException e) {
+ Log.w(TAG,"Hyphenation binary folder is not exist" , e);
+ }
+ store.addListResult("hyphenation_locale", hyphenLocalesList);
+
collectLocaleDataFilesInfo(store);
}
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
index cd66d3e..2b16fc1 100644
--- a/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/StorageHostTest.java
@@ -26,6 +26,10 @@
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import com.google.common.truth.Truth;
import junit.framework.AssertionFailedError;
@@ -53,6 +57,10 @@
private static final String CLASS = "com.android.cts.storageapp.StorageTest";
private static final String CLASS_NO_APP_STORAGE =
"com.android.cts.noappstorage.NoAppDataStorageTest";
+ private static final String EXTERNAL_STORAGE_PATH = "/storage/emulated/%d/";
+ private static final String ERROR_MESSAGE_TAG = "[ERROR]";
+
+ private static final int CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS = 20000;
private int[] mUsers;
@@ -154,6 +162,12 @@
}
@Test
+ public void testVerifyStatsExternalForClonedUser() throws Exception {
+ int mCloneUserIdInt = createCloneUserAndInstallDeviceTestApk();
+ runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternal", mCloneUserIdInt, true);
+ }
+
+ @Test
public void testVerifyStatsExternalConsistent() throws Exception {
for (int user : mUsers) {
runDeviceTests(PKG_STATS, CLASS_STATS, "testVerifyStatsExternalConsistent", user, true);
@@ -293,4 +307,60 @@
return false;
}
}
+
+ private int createCloneUserAndInstallDeviceTestApk() throws Exception {
+ // Create clone user.
+ String output = getDevice().executeShellCommand(
+ "pm create-user --profileOf 0 --user-type android.os.usertype.profile.CLONE "
+ + "testUser");
+ String sCloneUserId = output.substring(output.lastIndexOf(' ') + 1).replaceAll("[^0-9]",
+ "");
+ Truth.assertThat(sCloneUserId).isNotEmpty();
+ // Start clone user.
+ CommandResult out = getDevice().executeShellV2Command("am start-user -w " + sCloneUserId);
+ Truth.assertThat(isSuccessful(out)).isTrue();
+
+ Integer mCloneUserIdInt = Integer.parseInt(sCloneUserId);
+ String sCloneUserStoragePath = String.format(EXTERNAL_STORAGE_PATH,
+ Integer.parseInt(sCloneUserId));
+ // Check that the clone user directories have been created
+ eventually(() -> getDevice().doesFileExist(sCloneUserStoragePath, mCloneUserIdInt),
+ CLONE_PROFILE_DIRECTORY_CREATION_TIMEOUT_MS);
+ // Install the DeviceTest APK for Clone User.
+ installPackage(APK_STATS, "--user all");
+ return mCloneUserIdInt;
+ }
+
+ private void eventually(ThrowingRunnable r, long timeoutMillis) {
+ long start = System.currentTimeMillis();
+
+ while (true) {
+ try {
+ r.run();
+ return;
+ } catch (Throwable e) {
+ if (System.currentTimeMillis() - start < timeoutMillis) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException ignored) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new RuntimeException(e);
+ }
+ }
+ }
+ }
+
+ private boolean isSuccessful(CommandResult result) {
+ if (!CommandStatus.SUCCESS.equals(result.getStatus())) {
+ return false;
+ }
+ String stdout = result.getStdout();
+ if (stdout.contains(ERROR_MESSAGE_TAG)) {
+ return false;
+ }
+ String stderr = result.getStderr();
+ return (stderr == null || stderr.trim().isEmpty());
+ }
}
diff --git a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
index 74d59f7..452ffd6 100644
--- a/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
+++ b/hostsidetests/car/src/android/car/cts/CarWatchdogHostTest.java
@@ -117,6 +117,8 @@
private static final String APPLY_DISABLE_DISPLAY_POWER_POLICY_CMD =
"cmd car_service apply-power-policy cts_car_watchdog_disable_display";
+ private static final String FAILED_CUSTOM_COLLECTION_MSG = "Cannot start a custom collection";
+
private static final long FIFTY_MEGABYTES = 1024 * 1024 * 50;
private static final long TWO_HUNDRED_MEGABYTES = 1024 * 1024 * 200;
@@ -128,7 +130,8 @@
private static final Pattern FOREGROUND_BYTES_PATTERN = Pattern.compile(
"foregroundModeBytes = (\\d+)");
- private static final long WATCHDOG_ACTION_TIMEOUT_MS = 15_000;
+ private static final long START_CUSTOM_COLLECTION_TIMEOUT_MS = 30_000;
+ private static final long WATCHDOG_ACTION_TIMEOUT_MS = 30_000;
private boolean mDidModifyDateTime;
private long mOriginalForegroundBytes;
@@ -153,7 +156,7 @@
GET_IO_OVERUSE_FOREGROUNG_BYTES_CMD));
executeCommand("%s %d", SET_IO_OVERUSE_FOREGROUNG_BYTES_CMD, TWO_HUNDRED_MEGABYTES);
executeCommand("logcat -c");
- executeCommand(START_CUSTOM_PERF_COLLECTION_CMD);
+ startCustomCollection();
executeCommand(RESET_RESOURCE_OVERUSE_CMD);
}
@@ -380,4 +383,11 @@
executeCommand("date %s", now.plusHours(1));
CLog.d(TAG, "DateTime changed from %s to %s", now, now.plusHours(1));
}
+
+ private void startCustomCollection() throws Exception {
+ PollingCheck.check("Could not start the custom collection.",
+ START_CUSTOM_COLLECTION_TIMEOUT_MS,
+ () -> !executeCommand(START_CUSTOM_PERF_COLLECTION_CMD)
+ .contains(FAILED_CUSTOM_COLLECTION_MSG));
+ }
}
diff --git a/hostsidetests/car_builtin/OWNERS b/hostsidetests/car_builtin/OWNERS
index f0afac5..c5bee2d 100644
--- a/hostsidetests/car_builtin/OWNERS
+++ b/hostsidetests/car_builtin/OWNERS
@@ -1,4 +1,2 @@
# Bug component: 526680
-keunyoung@google.com
-felipeal@google.com
-gurunagarajan@google.com
+include platform/packages/services/Car:/OWNERS
diff --git a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
index 518144a..46fd3dc 100644
--- a/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
+++ b/hostsidetests/dumpsys/src/android/dumpsys/cts/BatteryStatsDumpsysTest.java
@@ -489,6 +489,7 @@
assertInteger(parts[8]); // attention
assertInteger(parts[9]); // faceDown
assertInteger(parts[10]); // deviceState
+
}
private void checkUserActivityCompat(String[] parts) {
diff --git a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
index cd9378d..72cd866 100644
--- a/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
+++ b/hostsidetests/scopedstorage/host/src/android/scopedstorage/cts/host/ScopedStorageHostTest.java
@@ -20,6 +20,8 @@
import android.platform.test.annotations.AppModeFull;
+import com.android.modules.utils.build.testing.DeviceSdkLevel;
+import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
import com.android.tradefed.testtype.junit4.DeviceTestRunOptions;
@@ -147,24 +149,36 @@
@Test
public void testCheckInstallerAppAccessToObbDirs() throws Exception {
allowAppOps("android:request_install_packages");
- grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+ if (isSdkLevelLessThanT()) {
+ grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
try {
runDeviceTest("testCheckInstallerAppAccessToObbDirs");
} finally {
denyAppOps("android:request_install_packages");
- revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+ if (isSdkLevelLessThanT()) {
+ revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
}
}
@Test
public void testCheckInstallerAppCannotAccessDataDirs() throws Exception {
allowAppOps("android:request_install_packages");
- grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+ if (isSdkLevelLessThanT()) {
+ grantPermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
try {
runDeviceTest("testCheckInstallerAppCannotAccessDataDirs");
} finally {
denyAppOps("android:request_install_packages");
- revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ // WRITE_EXTERNAL_STORAGE is no-op for Installers T onwards
+ if (isSdkLevelLessThanT()) {
+ revokePermissions("android.permission.WRITE_EXTERNAL_STORAGE");
+ }
}
}
@@ -389,4 +403,9 @@
executeShellCommand("cmd appops set --uid android.scopedstorage.cts " + op + " deny");
}
}
+
+ private boolean isSdkLevelLessThanT() throws DeviceNotAvailableException {
+ DeviceSdkLevel deviceSdkLevel = new DeviceSdkLevel(getDevice());
+ return !deviceSdkLevel.isDeviceAtLeastT();
+ }
}
diff --git a/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java b/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java
index a72fe96..0fdc3ea 100644
--- a/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java
+++ b/hostsidetests/security/src/android/security/cts/FileSystemPermissionTest.java
@@ -1,9 +1,5 @@
package android.security.cts;
-import static org.hamcrest.CoreMatchers.anyOf;
-import static org.hamcrest.CoreMatchers.is;
-import static org.hamcrest.MatcherAssert.assertThat;
-
import com.android.tradefed.device.DeviceNotAvailableException;
import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.testtype.DeviceTestCase;
@@ -74,19 +70,28 @@
//
// This test asserts that, if present, /dev/hw_random must:
//
- // 1. Have ownership root:root or prng_seeder:prng_seeder
- // 2. Have permissions 0600 (the kernel default) or 0400 (prng seeder default)
+ // 1. Have ownership prng_seeder:prng_seeder
+ // 2. Have permissions 0400 - The only user space process requiring
+ // access is the PRNG seeder daemon which only needs read access.
// 3. Be a character device with major:minor 10:183 (the kernel
// default), which in the hex output by stat corresponds to a:b7
//
// That translates to "stat -c %t,%T:%a:%U:%G: output that corresponds to one
// of the options below.
- String command = "stat -c %t,%T:%a:%U:%G " + HW_RNG_DEVICE;
- String output = mDevice.executeShellCommand(command).trim();
- assertThat("WRONG major, minor, mode or ownership on " + HW_RNG_DEVICE, output,
- anyOf(is("a,b7:600:root:root"),
- is("a,b7:400:prng_seeder:prng_seeder")));
+ // That translates to `ls -l` output like this:
+ // cr-------- 1 prng_seeder prng_seeder 10, 183 2021-02-11 17:55 /dev/hw_random
+ String command = "ls -l " + HW_RNG_DEVICE;
+ String output = mDevice.executeShellCommand(command).trim();
+ if (!output.endsWith(" " + HW_RNG_DEVICE)) {
+ fail("Unexpected output from " + command + ": \"" + output + "\"");
+ }
+ String[] outputWords = output.split("\\s");
+ assertEquals("Wrong mode on " + HW_RNG_DEVICE, "cr--------", outputWords[0]);
+ assertEquals("Wrong owner of " + HW_RNG_DEVICE, "prng_seeder", outputWords[2]);
+ assertEquals("Wrong group of " + HW_RNG_DEVICE, "prng_seeder", outputWords[3]);
+ assertEquals("Wrong device major on " + HW_RNG_DEVICE, "10,", outputWords[4]);
+ assertEquals("Wrong device minor on " + HW_RNG_DEVICE, "183", outputWords[5]);
}
}
diff --git a/hostsidetests/statsdatom/Android.bp b/hostsidetests/statsdatom/Android.bp
index 9585493..063d3f8 100644
--- a/hostsidetests/statsdatom/Android.bp
+++ b/hostsidetests/statsdatom/Android.bp
@@ -42,6 +42,7 @@
"src/**/net/*.java",
"src/**/notification/*.java",
"src/**/perfetto/*.java",
+ "src/**/performancehintmanager/*.java",
"src/**/permissionstate/*.java",
"src/**/settingsstats/*.java",
"src/**/sizecompatrestartbutton/*.java",
diff --git a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
index f4b26cc..55f0aaf 100644
--- a/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
+++ b/hostsidetests/statsdatom/apps/statsdapp/src/com/android/server/cts/device/statsdatom/AtomTests.java
@@ -20,6 +20,8 @@
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assert.assertNotNull;
+
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.ActivityManager;
@@ -62,6 +64,7 @@
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
+import android.os.PerformanceHintManager;
import android.os.PowerManager;
import android.os.Process;
import android.os.RemoteException;
@@ -1164,4 +1167,18 @@
GameManager gameManager = context.getSystemService(GameManager.class);
gameManager.setGameState(new GameState(true, GameState.MODE_CONTENT, 1, 2));
}
+
+ @Test
+ public void testCreateHintSession() throws Exception {
+ final long targetNs = 16666666L;
+ Context context = InstrumentationRegistry.getContext();
+ PerformanceHintManager phm = context.getSystemService(PerformanceHintManager.class);
+
+ assertNotNull(phm);
+
+ PerformanceHintManager.Session session =
+ phm.createHintSession(new int[]{Process.myPid()}, targetNs);
+
+ assertNotNull(session);
+ }
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java
index fcd1048..86d8bcb 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/alarm/AlarmStatsTests.java
@@ -229,9 +229,22 @@
List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
assertThat(data.size()).isAtLeast(1);
boolean found = false;
+ final int expectedUid = getUid(ALARM_ATOM_TEST_PACKAGE);
for (int i = 0; i < data.size(); i++) {
AtomsProto.AlarmBatchDelivered abd = data.get(i).getAtom().getAlarmBatchDelivered();
- found |= abd.getWakeups() >= 1 && abd.getNumAlarms() >= 1;
+ int expectedNumAlarms = 0;
+ int expectedWakeups = 0;
+ for (int j = 0; j < abd.getUidsCount(); j++) {
+ expectedNumAlarms += abd.getNumAlarmsPerUid(j);
+ expectedWakeups += abd.getNumWakeupsPerUid(j);
+ if (abd.getUids(j) == expectedUid) {
+ assertThat(abd.getNumAlarmsPerUid(j)).isEqualTo(1);
+ assertThat(abd.getNumWakeupsPerUid(j)).isEqualTo(1);
+ found = true;
+ }
+ }
+ assertThat(abd.getNumAlarms()).isEqualTo(expectedNumAlarms);
+ assertThat(abd.getWakeups()).isEqualTo(expectedWakeups);
}
assertThat(found).isTrue();
}
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
index 081e08f..6701860 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/appstart/AppStartStatsTests.java
@@ -23,6 +23,7 @@
import android.cts.statsdatom.lib.DeviceUtils;
import android.cts.statsdatom.lib.ReportUtils;
+import com.android.annotations.Nullable;
import com.android.os.AtomsProto;
import com.android.os.StatsLog;
import com.android.tradefed.build.IBuildInfo;
@@ -76,12 +77,46 @@
}
public void testAppStartOccurred() throws Exception {
+ runTestAppStartOccurredCommon(null, atom -> {
+ assertThat(atom.getType()).isEqualTo(
+ AtomsProto.AppStartOccurred.TransitionType.COLD);
+ assertThat(atom.getProcessState()).isEqualTo(
+ AtomsProto.AppStartOccurred.AppProcessState.PROCESS_STATE_NONEXISTENT);
+ });
+ }
+
+ public void testAppStartOccurredWarm() throws Exception {
+ runTestAppStartOccurredCommon(() -> {
+ DeviceUtils.executeBackgroundService(getDevice(), "action.end_immediately");
+ Thread.sleep(WAIT_TIME_MS);
+ }, atom -> {
+ assertThat(atom.getType()).isEqualTo(
+ AtomsProto.AppStartOccurred.TransitionType.WARM);
+ assertThat(atom.getProcessState()).isEqualTo(
+ AtomsProto.AppStartOccurred.AppProcessState.PROCESS_STATE_CACHED_EMPTY);
+ });
+ }
+
+ private interface RunnableWithException {
+ void run() throws Exception;
+ }
+
+ private interface ConsumerWithException<T> {
+ void accept(T t) throws Exception;
+ }
+
+ private void runTestAppStartOccurredCommon(@Nullable RunnableWithException prolog,
+ @Nullable ConsumerWithException<AtomsProto.AppStartOccurred> epilog) throws Exception {
final int atomTag = AtomsProto.Atom.APP_START_OCCURRED_FIELD_NUMBER;
ConfigUtils.uploadConfigForPushedAtomWithUid(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
atomTag, /*uidInAttributionChain=*/false);
getDevice().executeShellCommand(getGlobalHibernationCommand(
DeviceUtils.STATSD_ATOM_TEST_PKG, false));
+ if (prolog != null) {
+ prolog.run();
+ }
+
DeviceUtils.runActivity(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
STATSD_CTS_FOREGROUND_ACTIVITY, "action", "action.sleep_top", WAIT_TIME_MS);
@@ -97,6 +132,9 @@
assertThat(atom.getActivityStartTimestampMillis()).isGreaterThan(0L);
assertThat(atom.getTransitionDelayMillis()).isGreaterThan(0);
assertThat(atom.getIsHibernating()).isFalse();
+ if (epilog != null) {
+ epilog.accept(atom);
+ }
}
public void testHibernatingAppStartOccurred() throws Exception {
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
index be665ba..7f29d44 100644
--- a/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/lib/ConfigUtils.java
@@ -69,10 +69,6 @@
.addAllowedLogSource("AID_SYSTEM")
.addAllowedLogSource("AID_BLUETOOTH")
.addAllowedLogSource("com.android.bluetooth")
- // TODO(b/236681553): Remove this.
- .addAllowedLogSource("com.android.bluetooth.services")
- // TODO(b/236681553): Remove this.
- .addAllowedLogSource("com.google.android.bluetooth.services")
.addAllowedLogSource("AID_LMKD")
.addAllowedLogSource("AID_MEDIA")
.addAllowedLogSource("AID_RADIO")
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/OWNERS b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/OWNERS
new file mode 100644
index 0000000..4b34064
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/OWNERS
@@ -0,0 +1,5 @@
+# Owners of ADPF atoms and the CTS test
+chingtangyu@google.com
+lpy@google.com
+mattbuckley@google.com
+xwxw@google.com
diff --git a/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/PerformanceHintManagerStatsTests.java b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/PerformanceHintManagerStatsTests.java
new file mode 100644
index 0000000..fe1378d
--- /dev/null
+++ b/hostsidetests/statsdatom/src/android/cts/statsdatom/performancehintmanager/PerformanceHintManagerStatsTests.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.cts.statsdatom.performancehintmanager;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.cts.statsdatom.lib.AtomTestUtils;
+import android.cts.statsdatom.lib.ConfigUtils;
+import android.cts.statsdatom.lib.DeviceUtils;
+import android.cts.statsdatom.lib.ReportUtils;
+
+import com.android.os.AtomsProto;
+import com.android.os.StatsLog;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.testtype.DeviceTestCase;
+import com.android.tradefed.testtype.IBuildReceiver;
+
+import java.util.List;
+
+/**
+ * Test for Performance Hint Manager stats.
+ * This test is mainly to test ADPF data collection
+ *
+ * <p>Build/Install/Run:
+ * atest CtsStatsdAtomHostTestCases:PerformanceHintManagerStatsTests
+ */
+public class PerformanceHintManagerStatsTests extends DeviceTestCase implements IBuildReceiver {
+ private IBuildInfo mCtsBuild;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ assertThat(mCtsBuild).isNotNull();
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.installStatsdTestApp(getDevice(), mCtsBuild);
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+ }
+
+ @Override
+ protected void tearDown() throws Exception {
+ ConfigUtils.removeConfig(getDevice());
+ ReportUtils.clearReports(getDevice());
+ DeviceUtils.uninstallStatsdTestApp(getDevice());
+ super.tearDown();
+ }
+
+ @Override
+ public void setBuild(IBuildInfo buildInfo) {
+ mCtsBuild = buildInfo;
+ }
+
+ public void testCreateHintSessionStatsd() throws Exception {
+ if (Boolean.parseBoolean(
+ DeviceUtils.getProperty(getDevice(), "debug.hwui.use_hint_manager"))) {
+ ConfigUtils.uploadConfigForPushedAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.PERFORMANCE_HINT_SESSION_REPORTED_FIELD_NUMBER);
+ DeviceUtils.runDeviceTestsOnStatsdApp(getDevice(),
+ ".AtomTests", "testCreateHintSession");
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<StatsLog.EventMetricData> data = ReportUtils.getEventMetricDataList(getDevice());
+ assertThat(data.size()).isAtLeast(1);
+ AtomsProto.PerformanceHintSessionReported a0 =
+ data.get(0).getAtom().getPerformanceHintSessionReported();
+ assertThat(a0.getPackageUid()).isGreaterThan(10000); // Not a system service UID.
+ assertThat(a0.getSessionId()).isNotEqualTo(0);
+ assertThat(a0.getTargetDurationNs()).isEqualTo(16666666L);
+ assertThat(a0.getTidCount()).isEqualTo(1);
+ }
+ }
+
+ public void testAdpfSystemComponentStatsd() throws Exception {
+ final boolean isSurfaceFlingerCpuHintEnabled = Boolean.parseBoolean(
+ DeviceUtils.getProperty(getDevice(), "debug.sf.enable_adpf_cpu_hint"));
+ final boolean isHwuiHintEnabled = Boolean.parseBoolean(
+ DeviceUtils.getProperty(getDevice(), "debug.hwui.use_hint_manager"));
+ ConfigUtils.uploadConfigForPulledAtom(getDevice(), DeviceUtils.STATSD_ATOM_TEST_PKG,
+ AtomsProto.Atom.ADPF_SYSTEM_COMPONENT_INFO_FIELD_NUMBER);
+ AtomTestUtils.sendAppBreadcrumbReportedAtom(getDevice());
+ Thread.sleep(AtomTestUtils.WAIT_TIME_LONG);
+
+ List<AtomsProto.Atom> data = ReportUtils.getGaugeMetricAtoms(getDevice());
+ assertThat(data.size()).isAtLeast(1);
+ AtomsProto.ADPFSystemComponentInfo a0 = data.get(0).getAdpfSystemComponentInfo();
+ assertThat(a0.getSurfaceflingerCpuHintEnabled()).isEqualTo(isSurfaceFlingerCpuHintEnabled);
+ assertThat(a0.getHwuiHintEnabled()).isEqualTo(isHwuiHintEnabled);
+ }
+}
diff --git a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
index 035b679..ef1d686 100644
--- a/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
+++ b/tests/accessibilityservice/src/android/accessibilityservice/cts/AccessibilityWindowQueryTest.java
@@ -27,8 +27,6 @@
import static android.accessibilityservice.cts.utils.AsyncUtils.DEFAULT_TIMEOUT_MS;
import static android.accessibilityservice.cts.utils.DisplayUtils.VirtualDisplaySession;
import static android.accessibilityservice.cts.utils.DisplayUtils.getStatusBarHeight;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_PRIMARY;
-import static android.app.WindowConfiguration.WINDOWING_MODE_SPLIT_SCREEN_SECONDARY;
import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED;
import static android.view.accessibility.AccessibilityEvent.TYPE_VIEW_CLICKED;
@@ -59,12 +57,10 @@
import static org.junit.Assume.assumeTrue;
import android.accessibility.cts.common.AccessibilityDumpOnFailureRule;
-import android.accessibilityservice.AccessibilityService;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.accessibilityservice.cts.activities.AccessibilityWindowQueryActivity;
import android.accessibilityservice.cts.activities.NonDefaultDisplayActivity;
import android.app.Activity;
-import android.app.ActivityTaskManager;
import android.app.Instrumentation;
import android.app.UiAutomation;
import android.graphics.Rect;
@@ -88,7 +84,6 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.TestUtils;
import org.hamcrest.Description;
import org.hamcrest.TypeSafeMatcher;
diff --git a/tests/app/src/android/app/cts/NotificationTest.java b/tests/app/src/android/app/cts/NotificationTest.java
index d79f909..540303b 100644
--- a/tests/app/src/android/app/cts/NotificationTest.java
+++ b/tests/app/src/android/app/cts/NotificationTest.java
@@ -22,6 +22,7 @@
import android.app.Notification;
import android.app.Notification.Action.Builder;
+import android.app.Notification.CallStyle;
import android.app.Notification.MessagingStyle;
import android.app.Notification.MessagingStyle.Message;
import android.app.NotificationChannel;
@@ -981,6 +982,20 @@
assertFalse(mNotification.hasImage());
}
+ public void testCallStyle_setsChronometerExtra() {
+ Person person = new Person.Builder().setName("Test name").build();
+ PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0,
+ new Intent(), PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ CallStyle cs = CallStyle.forIncomingCall(person, pendingIntent, pendingIntent);
+ Notification.Builder builder = new Notification.Builder(mContext, CHANNEL.getId())
+ .setStyle(cs)
+ .setUsesChronometer(true);
+
+ Notification notification = builder.build();
+ Bundle extras = notification.extras;
+ assertTrue(extras.getBoolean(Notification.EXTRA_SHOW_CHRONOMETER));
+ }
+
private static void assertMessageEquals(
Notification.MessagingStyle.Message expected,
Notification.MessagingStyle.Message actual) {
diff --git a/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt b/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt
index f4692c7..1b03bd0 100644
--- a/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt
+++ b/tests/app/src/android/app/cts/UpdateMediaTapToTransferReceiverDisplayTest.kt
@@ -92,7 +92,7 @@
}
@Test
- @Ignore("b/236749332")
+ @Ignore("b/252781795")
fun closeToSender_displaysChip() {
statusBarManager.updateMediaTapToTransferReceiverDisplay(
StatusBarManager.MEDIA_TRANSFER_RECEIVER_STATE_CLOSE_TO_SENDER,
@@ -108,7 +108,7 @@
}
@Test
- @Ignore("236749332")
+ @Ignore("b/252781795")
fun farFromSender_hidesChip() {
// First, make sure we display the chip
statusBarManager.updateMediaTapToTransferReceiverDisplay(
diff --git a/tests/attentionservice/Android.bp b/tests/attentionservice/Android.bp
index a515cf7..84c765a 100644
--- a/tests/attentionservice/Android.bp
+++ b/tests/attentionservice/Android.bp
@@ -16,6 +16,17 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
+java_library {
+ name: "CtsAttentionServiceDevice",
+ srcs: ["src/**/CtsTestAttentionService.java"],
+ static_libs: [
+ "androidx.test.rules",
+ "androidx.test.ext.junit",
+ "compatibility-device-util-axt",
+ "platform-test-annotations",
+ ],
+}
+
android_test {
name: "CtsAttentionServiceDeviceTestCases",
defaults: ["cts_defaults"],
@@ -34,5 +45,5 @@
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
test_suites: ["cts"],
-// sdk_version: "system_current",
+ // sdk_version: "system_current",
}
diff --git a/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java b/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java
index 01adafd..a16cbcd 100644
--- a/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java
+++ b/tests/attentionservice/src/android/attentionservice/cts/CtsTestAttentionService.java
@@ -37,7 +37,7 @@
@Override
public void onCancelAttentionCheck(AttentionCallback callback) {
callback.onFailure(ATTENTION_FAILURE_CANCELLED);
- reset();
+ resetAttentionCheck();
sRespondLatch.countDown();
}
@@ -49,12 +49,20 @@
@Override
public void onStopProximityUpdates() {
- reset();
+ resetProximity();
sRespondLatch.countDown();
}
public static void reset() {
+ resetAttentionCheck();
+ resetProximity();
+ }
+
+ public static void resetAttentionCheck() {
sCurrentAttentionCallback = null;
+ }
+
+ public static void resetProximity() {
sCurrentProximityUpdateCallback = null;
}
@@ -62,14 +70,14 @@
if (sCurrentAttentionCallback != null) {
sCurrentAttentionCallback.onSuccess(code, 0);
}
- reset();
+ resetAttentionCheck();
}
public static void respondFailure(int code) {
if (sCurrentAttentionCallback != null) {
sCurrentAttentionCallback.onFailure(code);
}
- reset();
+ resetAttentionCheck();
}
public static void respondProximity(double distance) {
diff --git a/tests/autofillservice/AndroidManifest.xml b/tests/autofillservice/AndroidManifest.xml
index bc84a98..a69e252 100644
--- a/tests/autofillservice/AndroidManifest.xml
+++ b/tests/autofillservice/AndroidManifest.xml
@@ -154,6 +154,8 @@
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
+ <activity android:name=".activities.FadeInActivity"/>
+ <activity android:name=".activities.MultipleStepsSignInActivity"/>
<activity android:name=".activities.FieldsNoPasswordActivity"/>
<activity android:name=".activities.AugmentedAuthActivity" />
<activity android:name=".activities.SimpleAfterLoginActivity"/>
diff --git a/tests/autofillservice/res/layout/fade_in_activity.xml b/tests/autofillservice/res/layout/fade_in_activity.xml
new file mode 100644
index 0000000..3efc4d6
--- /dev/null
+++ b/tests/autofillservice/res/layout/fade_in_activity.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ -->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Password" />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:textCursorDrawable="@android:color/transparent"
+ android:imeOptions="flagNoFullscreen" />
+ </LinearLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/tests/autofillservice/res/layout/multiple_steps_activity.xml b/tests/autofillservice/res/layout/multiple_steps_activity.xml
new file mode 100644
index 0000000..df00cfa
--- /dev/null
+++ b/tests/autofillservice/res/layout/multiple_steps_activity.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/status"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+ <LinearLayout
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:orientation="horizontal">
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal">
+
+ <Button
+ android:id="@+id/prev"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="PREV" />
+
+ <Button
+ android:id="@+id/next"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="NEXT" />
+
+ <Button
+ android:id="@+id/finish"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="FINISH" />
+ </LinearLayout>
+
+</LinearLayout>
diff --git a/tests/autofillservice/res/layout/password.xml b/tests/autofillservice/res/layout/password.xml
new file mode 100644
index 0000000..39d60d6
--- /dev/null
+++ b/tests/autofillservice/res/layout/password.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/password_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Password" />
+
+ <EditText
+ android:id="@+id/password"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:inputType="textPassword"
+ android:textCursorDrawable="@android:color/transparent"
+ android:imeOptions="flagNoFullscreen" />
+
+</LinearLayout>
diff --git a/tests/autofillservice/res/layout/username.xml b/tests/autofillservice/res/layout/username.xml
new file mode 100644
index 0000000..a77a1d3
--- /dev/null
+++ b/tests/autofillservice/res/layout/username.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal" >
+
+ <TextView
+ android:id="@+id/username_label"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:text="Username" />
+
+ <EditText
+ android:id="@+id/username"
+ android:minEms="2"
+ android:maxEms="5"
+ android:maxLength="25"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textCursorDrawable="@android:color/transparent"
+ android:imeOptions="flagNoFullscreen" />
+
+</LinearLayout>
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
index 70e04b7..af30990 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/AbstractAutoFillActivity.java
@@ -28,6 +28,7 @@
import android.os.Bundle;
import android.view.PixelCopy;
import android.view.View;
+import android.view.WindowInsets;
import android.view.autofill.AutofillManager;
import androidx.annotation.NonNull;
@@ -183,4 +184,11 @@
public void clearFocus() {
throw new UnsupportedOperationException("Not implemented by " + getClass());
}
+
+ /**
+ * Get insets of the root window
+ */
+ public WindowInsets getRootWindowInsets() {
+ return getWindow().getDecorView().getRootWindowInsets();
+ }
}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/FadeInActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/FadeInActivity.java
new file mode 100644
index 0000000..c4a1911
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/FadeInActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+
+/**
+ * Activity that do fade-in animation at beginning:
+ */
+public class FadeInActivity extends AbstractAutoFillActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.fade_in_activity);
+
+ overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
index fae0587..f05529f 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/LoginActivity.java
@@ -28,7 +28,6 @@
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
-import android.view.WindowInsets;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
@@ -398,13 +397,6 @@
}
/**
- * Get insets of the root window
- */
- public WindowInsets getRootWindowInsets() {
- return mUsernameLabel.getRootWindowInsets();
- }
-
- /**
* Request to hide soft input
*/
public void hideSoftInput() {
diff --git a/tests/autofillservice/src/android/autofillservice/cts/activities/MultipleStepsSignInActivity.java b/tests/autofillservice/src/android/autofillservice/cts/activities/MultipleStepsSignInActivity.java
new file mode 100644
index 0000000..3bf4f87
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/activities/MultipleStepsSignInActivity.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.autofillservice.cts.activities;
+
+import android.autofillservice.cts.R;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.autofill.AutofillManager;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.Button;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.google.common.collect.ImmutableList;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class MultipleStepsSignInActivity extends AbstractAutoFillActivity {
+
+ private static final String TAG = "AbstractMultipleStepsActivity";
+ private static final String MESSAGE_STEP = "Showing step ";
+ private static final String MESSAGE_FINISH = "Finished";
+
+ private static MultipleStepsSignInActivity sCurrentActivity;
+
+ /**
+ * Gests the latest instance.
+ *
+ * <p>Typically used in test cases that rotates the activity
+ */
+ @SuppressWarnings("unchecked") // Its up to caller to make sure it's setting the right one
+ public static <T extends MultipleStepsSignInActivity> T getCurrentActivity() {
+ return (T) sCurrentActivity;
+ }
+
+ private TextView mStatus;
+ private ViewGroup mContainer;
+
+ private Button mPrevButton;
+ private Button mNextButton;
+ private Button mFinishButton;
+
+ private int mCurrentStep;
+ private boolean mFinished;
+
+ protected List<LinearLayout> mSteps;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ sCurrentActivity = this;
+ mCurrentStep = 0;
+
+ setContentView(R.layout.multiple_steps_activity);
+
+ mStatus = findViewById(R.id.status);
+ mContainer = findViewById(R.id.container);
+ mPrevButton = findViewById(R.id.prev);
+ mNextButton = findViewById(R.id.next);
+ mFinishButton = findViewById(R.id.finish);
+
+ View.OnClickListener onClickListener = (v) -> {
+ if (v == mPrevButton) {
+ showStep(mCurrentStep - 1);
+ } else if (v == mNextButton) {
+ showStep(mCurrentStep + 1);
+ } else {
+ finishSelf();
+ }
+ };
+ mPrevButton.setOnClickListener(onClickListener);
+ mNextButton.setOnClickListener(onClickListener);
+ mFinishButton.setOnClickListener(onClickListener);
+
+ mSteps = getStepsMap();
+
+ showStep(0);
+ }
+
+ public void nextPage() {
+ runOnUiThread(() -> mNextButton.performClick());
+ }
+
+ public void prevPage() {
+ runOnUiThread(() -> mPrevButton.performClick());
+ }
+
+ public void hideSoftInput() {
+ final InputMethodManager imm = getSystemService(InputMethodManager.class);
+ imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0);
+ }
+
+ private void showStep(int i) {
+ if (mFinished || i < 0 || i >= mSteps.size()) {
+ Log.w(TAG, String.format("Invalid step: %d (finished=%b, range=[%d,%d])",
+ i, mFinished, 0, mSteps.size() - 1));
+ return;
+ }
+
+ View step = mSteps.get(i);
+ mStatus.setText(MESSAGE_STEP + i);
+ Log.d(TAG, "Showing step " + i);
+
+ if (mContainer.getChildCount() > 0) {
+ mContainer.removeViewAt(0);
+ }
+ mContainer.addView(step);
+ mCurrentStep = i;
+
+ mPrevButton.setEnabled(mCurrentStep != 0);
+ mNextButton.setEnabled(mCurrentStep != mSteps.size() - 1);
+ }
+
+ private void finishSelf() {
+ mStatus.setText(MESSAGE_FINISH);
+ mContainer.removeAllViews();
+ mFinished = true;
+ AutofillManager afm = getSystemService(AutofillManager.class);
+ if (afm != null) {
+ afm.commit();
+ }
+ }
+
+ protected List<LinearLayout> getStepsMap() {
+ List<LinearLayout> steps = new ArrayList<>(2);
+ steps.add(newStep(R.layout.username));
+ steps.add(newStep(R.layout.password));
+ return ImmutableList.copyOf(steps);
+ }
+
+ private LinearLayout newStep(int resId) {
+ final LayoutInflater inflater = LayoutInflater.from(this);
+ return (LinearLayout) inflater.inflate(resId, null);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
index 9ab86ce..8ca37c4 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/commontests/FillEventHistoryCommonTestCase.java
@@ -135,7 +135,7 @@
assertFillEventForDatasetShown(events.get(0), "clientStateKey",
"clientStateValue", presentationType);
assertFillEventForDatasetAuthenticationSelected(events.get(1), "name",
- "clientStateKey", "clientStateValue");
+ "clientStateKey", "clientStateValue", presentationType);
}
@Test
@@ -180,7 +180,7 @@
assertFillEventForDatasetShown(events.get(0), "clientStateKey",
"clientStateValue", presentationType);
assertFillEventForAuthenticationSelected(events.get(1), NULL_DATASET_ID,
- "clientStateKey", "clientStateValue");
+ "clientStateKey", "clientStateValue", presentationType);
assertFillEventForDatasetShown(events.get(2), "clientStateKey",
"clientStateValue", presentationType);
assertFillEventForDatasetSelected(events.get(3), "name",
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dialog/FadeInActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dialog/FadeInActivityTest.java
new file mode 100644
index 0000000..df62027
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dialog/FadeInActivityTest.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dialog;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.enableFillDialogFeature;
+import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
+
+import android.autofillservice.cts.activities.FadeInActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.CannedFillResponse.CannedDataset;
+import android.autofillservice.cts.testcore.Helper;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService.FillRequest;
+import android.content.Intent;
+
+import org.junit.Test;
+
+
+/**
+ * This is the test cases about fade-in animation for the fill dialog UI.
+ */
+public class FadeInActivityTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+
+ @Test
+ public void testShowFillDialog_withFadeInAnimation() throws Exception {
+ // Enable feature and test service
+ enableFillDialogFeature(sContext);
+ enableService();
+
+ // Set response
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedDataset.Builder()
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Dropdown Presentation"))
+ .setDialogPresentation(createPresentation("Dialog Presentation"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_PASSWORD);
+ sReplier.addResponse(builder.build());
+
+ // Start activity
+ startFadeInActivity();
+
+ // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+ final FillRequest fillRequest = sReplier.getNextFillRequest();
+ assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+ // Click on password field to trigger fill dialog
+ mUiBot.selectByRelativeId(ID_PASSWORD);
+ mUiBot.waitForIdleSync();
+
+ // Set expected value, then select dataset
+ mUiBot.assertFillDialogDatasets("Dialog Presentation");
+ }
+
+ private void startFadeInActivity() throws Exception {
+ final Intent intent = new Intent(mContext, FadeInActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent);
+ mUiBot.assertShownByRelativeId(Helper.ID_PASSWORD_LABEL);
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/dialog/MultipleStepsSignInActivityTest.java b/tests/autofillservice/src/android/autofillservice/cts/dialog/MultipleStepsSignInActivityTest.java
new file mode 100644
index 0000000..cd942b6
--- /dev/null
+++ b/tests/autofillservice/src/android/autofillservice/cts/dialog/MultipleStepsSignInActivityTest.java
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.autofillservice.cts.dialog;
+
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD;
+import static android.autofillservice.cts.testcore.Helper.ID_PASSWORD_LABEL;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME;
+import static android.autofillservice.cts.testcore.Helper.ID_USERNAME_LABEL;
+import static android.autofillservice.cts.testcore.Helper.assertHasFlags;
+import static android.autofillservice.cts.testcore.Helper.assertMockImeStatus;
+import static android.autofillservice.cts.testcore.Helper.enableFillDialogFeature;
+import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
+
+import android.autofillservice.cts.activities.MultipleStepsSignInActivity;
+import android.autofillservice.cts.commontests.AutoFillServiceTestCase;
+import android.autofillservice.cts.testcore.CannedFillResponse;
+import android.autofillservice.cts.testcore.InstrumentedAutoFillService;
+import android.content.Intent;
+import android.util.Log;
+
+import org.junit.After;
+import org.junit.Test;
+
+
+/**
+ * The tests for showing fill dialog for an Activity that only updates the content for login
+ * steps, the app doesn't go to the new activty.
+ */
+public class MultipleStepsSignInActivityTest extends AutoFillServiceTestCase.ManualActivityLaunch {
+ MultipleStepsSignInActivity mActivity;
+ @After
+ public void teardown() {
+ if (mActivity != null) {
+ mActivity.finish();
+ }
+ mActivity = null;
+ }
+ @Test
+ public void testShowFillDialog_contentChanged_shownFillDialog() throws Exception {
+ // Enable feature and test service
+ enableFillDialogFeature(sContext);
+ enableService();
+
+ // Set response
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("Menu Username"))
+ .setDialogPresentation(createPresentation("Dialog Username"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_USERNAME);
+ sReplier.addResponse(builder.build());
+
+ // Start activity
+ mActivity = startMultipleStepsSignInActivity();
+
+ // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+ final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+ assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+ // Click on username field to trigger fill dialog
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+
+ // Verify fill dialog shown
+ mUiBot.assertFillDialogDatasets("Dialog Username");
+
+ // Do nothing for fill dialog. Click outside to hide fill dialog and IME
+ hideFillDialogAndIme(mActivity);
+
+ // Set response for second page
+ final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Menu Password"))
+ .setDialogPresentation(createPresentation("Dialog Password"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_PASSWORD);
+ sReplier.addResponse(builder2.build());
+
+ mActivity.nextPage();
+
+ mUiBot.assertShownByRelativeId(ID_PASSWORD_LABEL);
+
+ // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+ final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+ assertHasFlags(fillRequest2.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+ // Click on password field to trigger fill dialog
+ mUiBot.selectByRelativeId(ID_PASSWORD);
+ mUiBot.waitForIdleSync();
+
+ // Verify fill dialog shown
+ mUiBot.assertFillDialogDatasets("Dialog Password");
+ }
+
+ @Test
+ public void testShowFillDialog_backPrevPage_notShownFillDialog() throws Exception {
+ // Enable feature and test service
+ enableFillDialogFeature(sContext);
+ enableService();
+
+ // Set response
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("Menu Username"))
+ .setDialogPresentation(createPresentation("Dialog Username"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_USERNAME);
+ sReplier.addResponse(builder.build());
+
+ // Start activity
+ mActivity = startMultipleStepsSignInActivity();
+
+ Log.e("tymtest", "autofill etst 1");
+ // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+ final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+ assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+ Log.e("tymtest", "autofill etst 2");
+ // Set response for second page
+ final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Menu Password"))
+ .setDialogPresentation(createPresentation("Dialog Password"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_PASSWORD);
+ sReplier.addResponse(builder2.build());
+
+ // Do nothing on the 1st page and go to next page
+ mActivity.nextPage();
+
+ mUiBot.assertShownByRelativeId(ID_PASSWORD_LABEL);
+
+ // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+ final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+ assertHasFlags(fillRequest2.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+ // Click on password field to trigger fill dialog
+ mUiBot.selectByRelativeId(ID_PASSWORD);
+ mUiBot.waitForIdleSync();
+
+ // Verify fill dialog shown
+ mUiBot.assertFillDialogDatasets("Dialog Password");
+
+ // Go back previous page
+ mActivity.prevPage();
+
+ mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+
+ // Verify there is no any fill request because response already exists
+ sReplier.assertNoUnhandledFillRequests();
+
+ // Click on username field to trigger menu UI
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+
+ // Verify fill menu shown
+ mUiBot.assertDatasets("Menu Username");
+ }
+
+ @Test
+ public void testShowFillDialog_doNothingThenBackPrevPage_notShownFillDialog() throws Exception {
+ // Enable feature and test service
+ enableFillDialogFeature(sContext);
+ enableService();
+
+ // Set response
+ final CannedFillResponse.Builder builder = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_USERNAME, "dude")
+ .setPresentation(createPresentation("Menu Username"))
+ .setDialogPresentation(createPresentation("Dialog Username"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_USERNAME);
+ sReplier.addResponse(builder.build());
+
+ // Start activity
+ mActivity = startMultipleStepsSignInActivity();
+
+ // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+ final InstrumentedAutoFillService.FillRequest fillRequest = sReplier.getNextFillRequest();
+ assertHasFlags(fillRequest.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+ // Set response for second page
+ final CannedFillResponse.Builder builder2 = new CannedFillResponse.Builder()
+ .addDataset(new CannedFillResponse.CannedDataset.Builder()
+ .setField(ID_PASSWORD, "sweet")
+ .setPresentation(createPresentation("Menu Password"))
+ .setDialogPresentation(createPresentation("Dialog Password"))
+ .build())
+ .setDialogHeader(createPresentation("Dialog Header"))
+ .setDialogTriggerIds(ID_PASSWORD);
+ sReplier.addResponse(builder2.build());
+
+ // Do nothing on the 1st page and go to next page
+ mActivity.nextPage();
+
+ mUiBot.assertShownByRelativeId(ID_PASSWORD_LABEL);
+
+ // Check onFillRequest has the flag: FLAG_SUPPORTS_FILL_DIALOG
+ final InstrumentedAutoFillService.FillRequest fillRequest2 = sReplier.getNextFillRequest();
+ assertHasFlags(fillRequest2.flags, FLAG_SUPPORTS_FILL_DIALOG);
+
+ // Do nothing and go back previous page
+ mActivity.prevPage();
+
+ mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+
+ // Verify there is no any fill request because response already exists
+ sReplier.assertNoUnhandledFillRequests();
+
+ // Click on username field to trigger menu UI
+ mUiBot.selectByRelativeId(ID_USERNAME);
+ mUiBot.waitForIdleSync();
+
+ // Verify fill menu shown
+ mUiBot.assertDatasets("Menu Username");
+ }
+
+ private void hideFillDialogAndIme(MultipleStepsSignInActivity activity) throws Exception {
+ // Hide fill dialog via touch outside, the ime will appear.
+ mUiBot.touchOutsideDialog();
+ mUiBot.waitForIdleSync();
+
+ assertMockImeStatus(activity, /* expectedImeShow= */ true);
+
+ // Hide the IME before the next test.
+ activity.hideSoftInput();
+
+ assertMockImeStatus(activity, /* expectedImeShow= */ false);
+ }
+
+ private MultipleStepsSignInActivity startMultipleStepsSignInActivity() throws Exception {
+ final Intent intent = new Intent(mContext, MultipleStepsSignInActivity.class)
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+ mContext.startActivity(intent);
+ mUiBot.assertShownByRelativeId(ID_USERNAME_LABEL);
+ return MultipleStepsSignInActivity.getCurrentActivity();
+ }
+}
diff --git a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
index 3a40ca9..fc34db6 100644
--- a/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
+++ b/tests/autofillservice/src/android/autofillservice/cts/testcore/Helper.java
@@ -39,7 +39,7 @@
import android.app.assist.AssistStructure.ViewNode;
import android.app.assist.AssistStructure.WindowNode;
import android.autofillservice.cts.R;
-import android.autofillservice.cts.activities.LoginActivity;
+import android.autofillservice.cts.activities.AbstractAutoFillActivity;
import android.content.AutofillOptions;
import android.content.ComponentName;
import android.content.ContentResolver;
@@ -1254,12 +1254,12 @@
* @param event event to be asserted
* @param key the only key expected in the client state bundle
* @param value the only value expected in the client state bundle
- * @param expectedPresentation the exptected ui presentation type
+ * @param uiType the expected ui presentation type
*/
public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
- @NonNull String key, @NonNull String value, int expectedPresentation) {
+ @NonNull String key, @NonNull String value, int uiType) {
assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, key, value, null);
- assertFillEventPresentationType(event, expectedPresentation);
+ assertFillEventPresentationType(event, uiType);
}
/**
@@ -1269,9 +1269,9 @@
* @param event event to be asserted
*/
public static void assertFillEventForDatasetShown(@NonNull FillEventHistory.Event event,
- int expectedPresentation) {
+ int uiType) {
assertFillEvent(event, TYPE_DATASETS_SHOWN, NULL_DATASET_ID, null, null, null);
- assertFillEventPresentationType(event, expectedPresentation);
+ assertFillEventPresentationType(event, uiType);
}
/**
@@ -1283,11 +1283,13 @@
* @param datasetId dataset set id expected in the event
* @param key the only key expected in the client state bundle
* @param value the only value expected in the client state bundle
+ * @param uiType the expected ui presentation type
*/
public static void assertFillEventForDatasetAuthenticationSelected(
@NonNull FillEventHistory.Event event,
- @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+ @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) {
assertFillEvent(event, TYPE_DATASET_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+ assertFillEventPresentationType(event, uiType);
}
/**
@@ -1298,11 +1300,13 @@
* @param datasetId dataset set id expected in the event
* @param key the only key expected in the client state bundle
* @param value the only value expected in the client state bundle
+ * @param uiType the expected ui presentation type
*/
public static void assertFillEventForAuthenticationSelected(
@NonNull FillEventHistory.Event event,
- @Nullable String datasetId, @NonNull String key, @NonNull String value) {
+ @Nullable String datasetId, @NonNull String key, @NonNull String value, int uiType) {
assertFillEvent(event, TYPE_AUTHENTICATION_SELECTED, datasetId, key, value, null);
+ assertFillEventPresentationType(event, uiType);
}
public static void assertFillEventForFieldsClassification(@NonNull FillEventHistory.Event event,
@@ -1701,7 +1705,7 @@
/**
* Asserts whether mock IME is showing
*/
- public static void assertMockImeStatus(LoginActivity activity,
+ public static void assertMockImeStatus(AbstractAutoFillActivity activity,
boolean expectedImeShow) throws Exception {
Timeouts.MOCK_IME_TIMEOUT.run("assertMockImeStatus(" + expectedImeShow + ")",
() -> {
diff --git a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
index 90b3bf3..2c88064 100644
--- a/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
+++ b/tests/camera/src/android/hardware/camera2/cts/CameraManagerTest.java
@@ -27,6 +27,7 @@
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraDevice.StateCallback;
import android.hardware.camera2.CameraManager;
import android.hardware.camera2.cts.Camera2ParameterizedTestCase;
import android.hardware.camera2.cts.CameraTestUtils.HandlerExecutor;
@@ -166,15 +167,6 @@
ids.length == 0 ||
mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_ANY));
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
- // Camera placement on automotive device is different than usual front/back
- // on mobile phones and use automotive.lens.facing instead. lens.facing is only used for
- // external camera.testCameraManagerAutomotiveCameras ensures that lens.facing is only
- // used for EXTERNAL camera. Hence, skipping this test for automotive implementations
- Log.i(TAG, "Skip rest of the test on automotive device implementations");
- return;
- }
-
/**
* Test: that if the device has front or rear facing cameras, then there
* must be matched system features.
@@ -1051,23 +1043,15 @@
* android.automotive.lens.facing values
*/
Map<Pair<Integer, Integer>, ArrayList<String>> cameraGroup = new HashMap<>();
- boolean externalCameraConnected = false;
for (String cameraId : cameraIds) {
CameraCharacteristics props = mCameraManager.getCameraCharacteristics(cameraId);
assertNotNull(
String.format("Can't get camera characteristics from: ID %s", cameraId), props);
Integer lensFacing = props.get(CameraCharacteristics.LENS_FACING);
-
- if (lensFacing != null) {
- // Automotive device implementations can use android.lens.facing
- // only for external cameras
- assertTrue("android.lens.facing should only be used for external cameras",
- lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL);
- // Test that there is matching feature flag
- assertTrue("System doesn't have external camera feature",
- mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL));
- externalCameraConnected = true;
+ if (lensFacing != null && lensFacing == CameraCharacteristics.LENS_FACING_EXTERNAL) {
+ // Automotive device implementations may have external cameras but they are exempted
+ // from this test case.
continue;
}
@@ -1098,13 +1082,6 @@
}
}
- // Test an external camera is connected if FEATURE_CAMERA_EXTERNAL is advertised
- if (mPackageManager.hasSystemFeature(PackageManager.FEATURE_CAMERA_EXTERNAL)) {
- assertTrue("External camera is not connected on device with FEATURE_CAMERA_EXTERNAL",
- externalCameraConnected);
- }
-
-
for (Map.Entry<Pair<Integer, Integer>, ArrayList<String>> entry : cameraGroup.entrySet()) {
ArrayList<String> cameraIdsToVerify = entry.getValue();
if (cameraIdsToVerify.size() > 1) {
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
index 5ffe5fa..caa9d2e 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyAppStreamingPolicyTest.java
@@ -18,6 +18,10 @@
import static android.Manifest.permission.READ_NEARBY_STREAMING_POLICY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL;
+
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
@@ -28,17 +32,21 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
import com.android.bedstead.harrier.annotations.enterprise.PolicyAppliesTest;
import com.android.bedstead.harrier.annotations.enterprise.PolicyDoesNotApplyTest;
import com.android.bedstead.harrier.policies.NearbyAppStreamingPolicy;
import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
-import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(BedsteadJUnit4.class)
@@ -52,55 +60,68 @@
private static final DevicePolicyManager sLocalDevicePolicyManager =
sContext.getSystemService(DevicePolicyManager.class);
- private RemoteDevicePolicyManager mDevicePolicyManager;
-
- @Before
- public void setUp() {
- mDevicePolicyManager = sDeviceState.dpc().devicePolicyManager();
- }
-
@PolicyAppliesTest(policy = NearbyAppStreamingPolicy.class)
public void getNearbyAppStreamingPolicy_defaultToSameManagedAccountOnly() {
- assertThat(mDevicePolicyManager.getNearbyAppStreamingPolicy())
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
+ assertThat(dpm.getNearbyAppStreamingPolicy())
.isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
}
@PolicyAppliesTest(policy = NearbyAppStreamingPolicy.class)
public void setNearbyAppStreamingPolicy_policyApplied_works() {
- int originalPolicy = mDevicePolicyManager.getNearbyAppStreamingPolicy();
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+ int originalPolicy = dpm.getNearbyAppStreamingPolicy();
- mDevicePolicyManager.setNearbyAppStreamingPolicy(
- DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+ dpm.setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
try {
- assertThat(mDevicePolicyManager.getNearbyAppStreamingPolicy())
+ assertThat(dpm.getNearbyAppStreamingPolicy())
.isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
} finally {
- mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+ dpm.setNearbyAppStreamingPolicy(originalPolicy);
}
}
@CannotSetPolicyTest(policy = NearbyAppStreamingPolicy.class)
public void setNearbyAppStreamingPolicy_policyIsNotAllowedToBeSet_throwsException() {
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
assertThrows(SecurityException.class, () ->
- mDevicePolicyManager.setNearbyAppStreamingPolicy(
- DevicePolicyManager.NEARBY_STREAMING_DISABLED));
+ dpm.setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_DISABLED));
}
@Postsubmit(reason = "new test")
@PolicyDoesNotApplyTest(policy = NearbyAppStreamingPolicy.class)
@EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
public void setNearbyAppStreamingPolicy_setEnabled_doesNotApply() {
- int originalPolicy = mDevicePolicyManager.getNearbyAppStreamingPolicy();
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+ int originalPolicy = dpm.getNearbyAppStreamingPolicy();
- mDevicePolicyManager
- .setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+ dpm.setNearbyAppStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
try {
assertThat(sLocalDevicePolicyManager.getNearbyAppStreamingPolicy()).isNotEqualTo(
DevicePolicyManager.NEARBY_STREAMING_ENABLED);
} finally {
- mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+ dpm.setNearbyAppStreamingPolicy(originalPolicy);
}
}
+
+ @Test
+ @RequireRunOnPrimaryUser
+ @EnsureHasSecondaryUser(installInstrumentedApp = TRUE)
+ @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
+ @EnsureDoesNotHavePermission({INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+ public void getNearbyAppStreamingPolicy_calledAcrossUsers_throwsException() {
+ DevicePolicyManager dpm;
+ try (PermissionContext p = TestApis.permissions()
+ .withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ dpm = TestApis.context()
+ .instrumentedContextAsUser(sDeviceState.secondaryUser())
+ .getSystemService(DevicePolicyManager.class);
+ }
+
+ assertThrows(SecurityException.class, () -> dpm.getNearbyAppStreamingPolicy());
+ }
}
diff --git a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
index 559b79f..bb4113a 100644
--- a/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
+++ b/tests/devicepolicy/src/android/devicepolicy/cts/NearbyNotificationStreamingPolicyTest.java
@@ -18,6 +18,10 @@
import static android.Manifest.permission.READ_NEARBY_STREAMING_POLICY;
+import static com.android.bedstead.harrier.OptionalBoolean.TRUE;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS;
+import static com.android.bedstead.nene.permissions.CommonPermissions.INTERACT_ACROSS_USERS_FULL;
+
import static com.google.common.truth.Truth.assertThat;
import static org.testng.Assert.assertThrows;
@@ -28,17 +32,21 @@
import com.android.bedstead.harrier.BedsteadJUnit4;
import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.annotations.EnsureDoesNotHavePermission;
import com.android.bedstead.harrier.annotations.EnsureHasPermission;
+import com.android.bedstead.harrier.annotations.EnsureHasSecondaryUser;
import com.android.bedstead.harrier.annotations.Postsubmit;
+import com.android.bedstead.harrier.annotations.RequireRunOnPrimaryUser;
import com.android.bedstead.harrier.annotations.enterprise.CannotSetPolicyTest;
import com.android.bedstead.harrier.annotations.enterprise.PolicyAppliesTest;
import com.android.bedstead.harrier.annotations.enterprise.PolicyDoesNotApplyTest;
import com.android.bedstead.harrier.policies.NearbyNotificationStreamingPolicy;
import com.android.bedstead.nene.TestApis;
+import com.android.bedstead.nene.permissions.PermissionContext;
-import org.junit.Before;
import org.junit.ClassRule;
import org.junit.Rule;
+import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(BedsteadJUnit4.class)
@@ -52,38 +60,35 @@
private static final DevicePolicyManager sLocalDevicePolicyManager =
sContext.getSystemService(DevicePolicyManager.class);
- private RemoteDevicePolicyManager mDevicePolicyManager;
-
- @Before
- public void setUp() {
- mDevicePolicyManager = sDeviceState.dpc().devicePolicyManager();
- }
-
@PolicyAppliesTest(policy = NearbyNotificationStreamingPolicy.class)
public void getNearbyNotificationStreamingPolicy_defaultToSameManagedAccountOnly() {
- assertThat(mDevicePolicyManager.getNearbyNotificationStreamingPolicy())
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
+ assertThat(dpm.getNearbyNotificationStreamingPolicy())
.isEqualTo(DevicePolicyManager.NEARBY_STREAMING_SAME_MANAGED_ACCOUNT_ONLY);
}
@PolicyAppliesTest(policy = NearbyNotificationStreamingPolicy.class)
public void setNearbyNotificationStreamingPolicy_policyApplied_works() {
- int originalPolicy = mDevicePolicyManager.getNearbyNotificationStreamingPolicy();
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+ int originalPolicy = dpm.getNearbyNotificationStreamingPolicy();
- mDevicePolicyManager.setNearbyNotificationStreamingPolicy(
- DevicePolicyManager.NEARBY_STREAMING_DISABLED);
+ dpm.setNearbyNotificationStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
try {
- assertThat(mDevicePolicyManager.getNearbyNotificationStreamingPolicy())
+ assertThat(dpm.getNearbyNotificationStreamingPolicy())
.isEqualTo(DevicePolicyManager.NEARBY_STREAMING_DISABLED);
} finally {
- mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+ dpm.setNearbyAppStreamingPolicy(originalPolicy);
}
}
@CannotSetPolicyTest(policy = NearbyNotificationStreamingPolicy.class)
public void setNearbyNotificationStreamingPolicy_policyIsNotAllowedToBeSet_throwsException() {
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+
assertThrows(SecurityException.class, () ->
- mDevicePolicyManager.setNearbyNotificationStreamingPolicy(
+ dpm.setNearbyNotificationStreamingPolicy(
DevicePolicyManager.NEARBY_STREAMING_DISABLED));
}
@@ -91,17 +96,34 @@
@PolicyDoesNotApplyTest(policy = NearbyNotificationStreamingPolicy.class)
@EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
public void setNearbyNotificationStreamingPolicy_setEnabled_doesNotApply() {
- int originalPolicy = mDevicePolicyManager.getNearbyNotificationStreamingPolicy();
+ RemoteDevicePolicyManager dpm = sDeviceState.dpc().devicePolicyManager();
+ int originalPolicy = dpm.getNearbyNotificationStreamingPolicy();
- mDevicePolicyManager
- .setNearbyNotificationStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
+ dpm.setNearbyNotificationStreamingPolicy(DevicePolicyManager.NEARBY_STREAMING_ENABLED);
try {
assertThat(
sLocalDevicePolicyManager.getNearbyNotificationStreamingPolicy()).isNotEqualTo(
DevicePolicyManager.NEARBY_STREAMING_ENABLED);
} finally {
- mDevicePolicyManager.setNearbyAppStreamingPolicy(originalPolicy);
+ dpm.setNearbyAppStreamingPolicy(originalPolicy);
}
}
+
+ @Test
+ @RequireRunOnPrimaryUser
+ @EnsureHasSecondaryUser(installInstrumentedApp = TRUE)
+ @EnsureHasPermission(READ_NEARBY_STREAMING_POLICY)
+ @EnsureDoesNotHavePermission({INTERACT_ACROSS_USERS, INTERACT_ACROSS_USERS_FULL})
+ public void getNearbyNotificationStreamingPolicy_calledAcrossUsers_throwsException() {
+ DevicePolicyManager dpm;
+ try (PermissionContext p = TestApis.permissions()
+ .withPermission(INTERACT_ACROSS_USERS_FULL)) {
+ dpm = TestApis.context()
+ .instrumentedContextAsUser(sDeviceState.secondaryUser())
+ .getSystemService(DevicePolicyManager.class);
+ }
+
+ assertThrows(SecurityException.class, () -> dpm.getNearbyNotificationStreamingPolicy());
+ }
}
diff --git a/tests/devicestate/Android.bp b/tests/devicestate/Android.bp
index a2c2523..c064c29 100644
--- a/tests/devicestate/Android.bp
+++ b/tests/devicestate/Android.bp
@@ -24,7 +24,8 @@
"compatibility-device-util-axt",
"ctstestrunner-axt",
"mockito-target-minus-junit4",
- "cts-wm-util"
+ "cts-wm-util",
+ "cts_window_jetpack_utils",
],
srcs: ["src/**/*.java"],
// Tag this module as a cts test artifact
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java
index c1815b9..050b069 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTestBase.java
@@ -18,12 +18,6 @@
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-import static android.hardware.devicestate.cts.DeviceStateUtils.assertValidState;
-import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
-import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-
-import static org.junit.Assert.assertTrue;
-
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
import android.server.wm.ActivityManagerTestBase;
@@ -31,18 +25,14 @@
import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-
import androidx.test.ext.junit.runners.AndroidJUnit4;
-import com.android.compatibility.common.util.SystemUtil;
-import com.android.compatibility.common.util.ThrowingRunnable;
-
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-
import org.junit.Before;
import org.junit.runner.RunWith;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
import javax.annotation.concurrent.GuardedBy;
/**
@@ -77,12 +67,14 @@
* {@link java.lang.InterruptedException} will be thrown.
*/
protected final void runWithRequestActive(@NonNull DeviceStateRequest request,
+ boolean isBaseStateRequest,
@NonNull Runnable runnable) throws Throwable {
final UncaughtExceptionHandler exceptionHandler = new UncaughtExceptionHandler();
final RequestAwareThread thread = new RequestAwareThread(request, runnable);
thread.setUncaughtExceptionHandler(exceptionHandler);
- try (DeviceStateRequestSession session
- = new DeviceStateRequestSession(mDeviceStateManager, request, thread)) {
+ try (DeviceStateRequestSession session =
+ new DeviceStateRequestSession(mDeviceStateManager, request,
+ isBaseStateRequest, thread)) {
// Set the exception handler to get the exception and rethrow.
thread.start();
// Wait for the request aware thread to finish executing the runnable. If the request
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
index f6ff303..09afde3 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateManagerTests.java
@@ -18,8 +18,8 @@
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
-import static android.hardware.devicestate.cts.DeviceStateUtils.assertValidState;
-import static android.hardware.devicestate.cts.DeviceStateUtils.runWithControlDeviceStatePermission;
+import static android.server.wm.DeviceStateUtils.assertValidState;
+import static android.server.wm.DeviceStateUtils.runWithControlDeviceStatePermission;
import static android.view.Display.DEFAULT_DISPLAY;
import static org.junit.Assert.assertEquals;
@@ -36,6 +36,8 @@
import android.content.res.Resources;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
+import android.server.wm.jetpack.utils.ExtensionUtil;
+import android.server.wm.jetpack.utils.Version;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -58,6 +60,9 @@
private static final int INVALID_DEVICE_STATE = -1;
+ /** Vendor extension version. Some API behaviors are only available in newer version. */
+ private static final Version WM_EXTENSION_VERSION = ExtensionUtil.getExtensionVersion();
+
/**
* Tests that {@link DeviceStateManager#getSupportedStates()} returns at least one state and
* that none of the returned states are in the range
@@ -92,13 +97,30 @@
final DeviceStateRequest request
= DeviceStateRequest.newBuilder(supportedStates[i]).build();
- runWithRequestActive(request, () -> {
+ runWithRequestActive(request, false, () -> {
verify(callback, atLeastOnce()).onStateChanged(intAgumentCaptor.capture());
assertEquals(intAgumentCaptor.getValue().intValue(), request.getState());
});
}
}
+ @Test
+ public void testRequestBaseState() throws Throwable {
+ assumeExtensionVersionAtLeast2();
+ final ArgumentCaptor<Integer> intAgumentCaptor = ArgumentCaptor.forClass(Integer.class);
+ final DeviceStateManager.DeviceStateCallback callback =
+ mock(DeviceStateManager.DeviceStateCallback.class);
+ final DeviceStateManager manager = getDeviceStateManager();
+
+ manager.registerCallback(Runnable::run, callback);
+
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(0).build();
+ runWithRequestActive(request, true, () -> {
+ verify(callback, atLeastOnce()).onStateChanged(intAgumentCaptor.capture());
+ assertEquals(intAgumentCaptor.getValue().intValue(), request.getState());
+ });
+ }
+
/**
* Tests that calling {@link DeviceStateManager#requestState(DeviceStateRequest, Executor,
* DeviceStateRequest.Callback)} throws an {@link java.lang.IllegalArgumentException} if
@@ -463,9 +485,7 @@
final DeviceStateManager manager = getDeviceStateManager();
final int[] states = manager.getSupportedStates();
final DeviceStateRequest request = DeviceStateRequest.newBuilder(states[0]).build();
- runWithRequestActive(request, () -> {
- manager.cancelStateRequest();
- });
+ runWithRequestActive(request, false, manager::cancelStateRequest);
}
/**
@@ -499,6 +519,12 @@
}
}
+ /** For API changes that are introduced together with WM Extensions version 2. */
+ private static void assumeExtensionVersionAtLeast2() {
+ // TODO(b/232476698) Remove in the next Android release.
+ assumeTrue(WM_EXTENSION_VERSION.getMajor() >= 2);
+ }
+
private class StateTrackingCallback implements DeviceStateManager.DeviceStateCallback {
private int mCurrentState = - 1;
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java
index 7b15fd9..3e5ab31 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java
+++ b/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateRequestSession.java
@@ -16,7 +16,7 @@
package android.hardware.devicestate.cts;
-import static android.hardware.devicestate.cts.DeviceStateUtils.runWithControlDeviceStatePermission;
+import static android.server.wm.DeviceStateUtils.runWithControlDeviceStatePermission;
import android.hardware.devicestate.DeviceStateManager;
import android.hardware.devicestate.DeviceStateRequest;
@@ -34,12 +34,15 @@
@NonNull
private final DeviceStateRequest mRequest;
@NonNull
- private DeviceStateRequest.Callback mCallback;
+ private final DeviceStateRequest.Callback mCallback;
+ private final boolean mIsBaseStateRequest;
public DeviceStateRequestSession(@NonNull DeviceStateManager manager,
- @NonNull DeviceStateRequest request, @NonNull DeviceStateRequest.Callback callback) {
+ @NonNull DeviceStateRequest request, boolean isBaseStateRequest,
+ @NonNull DeviceStateRequest.Callback callback) {
mDeviceStateManager = manager;
mRequest = request;
+ mIsBaseStateRequest = isBaseStateRequest;
mCallback = callback;
submitRequest(request);
@@ -47,8 +50,14 @@
private void submitRequest(@NonNull DeviceStateRequest request) {
try {
- runWithControlDeviceStatePermission(() ->
- mDeviceStateManager.requestState(mRequest, Runnable::run, mCallback));
+ if (mIsBaseStateRequest) {
+ runWithControlDeviceStatePermission(() ->
+ mDeviceStateManager.requestBaseStateOverride(mRequest, Runnable::run,
+ mCallback));
+ } else {
+ runWithControlDeviceStatePermission(() ->
+ mDeviceStateManager.requestState(mRequest, Runnable::run, mCallback));
+ }
} catch (Throwable t) {
throw new RuntimeException(t);
}
@@ -57,7 +66,11 @@
@Override
public void close() {
try {
- runWithControlDeviceStatePermission(mDeviceStateManager::cancelStateRequest);
+ if (mIsBaseStateRequest) {
+ runWithControlDeviceStatePermission(mDeviceStateManager::cancelBaseStateOverride);
+ } else {
+ runWithControlDeviceStatePermission(mDeviceStateManager::cancelStateRequest);
+ }
} catch (Throwable t) {
throw new RuntimeException(t);
}
diff --git a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
index fa05e86..a981703 100644
--- a/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
+++ b/tests/framework/base/biometrics/src/android/server/biometrics/BiometricTestBase.java
@@ -529,7 +529,6 @@
//the enrollent
//TODO(b/217275524)
Thread.sleep(200);
-
session.finishEnroll(userId);
mInstrumentation.waitForIdleSync();
Utils.waitForIdleService(this::getSensorStates);
diff --git a/tests/framework/base/windowmanager/AndroidManifest.xml b/tests/framework/base/windowmanager/AndroidManifest.xml
index 32a22a9..cd6aac3 100644
--- a/tests/framework/base/windowmanager/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/AndroidManifest.xml
@@ -434,6 +434,8 @@
<activity android:name="android.server.wm.WindowInsetsAnimationTestBase$TestActivity"
android:theme="@android:style/Theme.Material.NoActionBar"/>
+ <activity android:name="android.server.wm.WindowInsetsAnimationControllerTests$ControllerTestActivity"
+ android:theme="@android:style/Theme.Material.NoActionBar" />
<activity android:name="android.server.wm.ForceRelayoutTestBase$TestActivity"
android:exported="true"/>
@@ -457,6 +459,10 @@
android:colorMode="wideColorGamut"
android:fitsSystemWindows="true" />
+ <activity android:name="android.server.wm.ActivityTransitionTests$CustomWindowAnimationActivity"
+ android:theme="@style/window_task_animation"
+ android:exported="true"/>
+
<activity android:name="android.server.wm.WindowUntrustedTouchTest$TestActivity"
android:exported="true"
android:configChanges="screenSize|screenLayout|orientation"
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
index 712bf6f..a839054 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/Components.java
@@ -537,6 +537,9 @@
// Calls requestAutoEnterPictureInPicture() with the value provided
public static final String EXTRA_ENTER_PIP_ON_PIP_REQUESTED =
"enter_pip_on_pip_requested";
+ // Calls enterPictureInPictureMode when activity receives onBackPressed
+ public static final String EXTRA_ENTER_PIP_ON_BACK_PRESSED =
+ "enter_pip_on_back_pressed";
public static final String EXTRA_EXPANDED_PIP_ASPECT_RATIO_NUMERATOR =
"expanded_pip_numerator";
public static final String EXTRA_EXPANDED_PIP_ASPECT_RATIO_DENOMINATOR =
diff --git a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
index 074ab48..bc606ea 100644
--- a/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
+++ b/tests/framework/base/windowmanager/app/src/android/server/wm/app/PipActivity.java
@@ -32,6 +32,7 @@
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_BACK_PRESSED;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
@@ -82,6 +83,7 @@
public class PipActivity extends AbstractLifecycleLogActivity {
private boolean mEnteredPictureInPicture;
+ private boolean mEnterPipOnBackPressed;
private RemoteCallback mCb;
private Handler mHandler = new Handler();
@@ -91,7 +93,13 @@
if (intent != null) {
switch (intent.getAction()) {
case ACTION_ENTER_PIP:
- enterPictureInPictureMode();
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ if (intent.getExtras() != null) {
+ mCb = (RemoteCallback) intent.getExtras().get(EXTRA_SET_PIP_CALLBACK);
+ if (mCb != null) {
+ mCb.sendResult(new Bundle());
+ }
+ }
break;
case ACTION_MOVE_TO_BACK:
moveTaskToBack(false /* nonRoot */);
@@ -293,6 +301,9 @@
dumpConfiguration(getResources().getConfiguration());
dumpConfigInfo();
}
+
+ mEnterPipOnBackPressed = Boolean.parseBoolean(
+ getIntent().getStringExtra(EXTRA_ENTER_PIP_ON_BACK_PRESSED));
}
@Override
@@ -388,6 +399,15 @@
dumpConfigInfo();
}
+ @Override
+ public void onBackPressed() {
+ if (mEnterPipOnBackPressed) {
+ enterPictureInPictureMode(new PictureInPictureParams.Builder().build());
+ } else {
+ super.onBackPressed();
+ }
+ }
+
/**
* Launches a new instance of the PipActivity in the same task that will automatically enter
* PiP.
diff --git a/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml b/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml
index 5a82c6b..b692bd1 100644
--- a/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml
+++ b/tests/framework/base/windowmanager/jetpack/AndroidManifest.xml
@@ -19,6 +19,8 @@
package="android.server.wm.jetpack"
android:targetSandboxVersion="2">
+ <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
+
<application android:label="CtsWindowManagerJetpackTestCases">
<uses-library android:name="android.test.runner" />
<uses-library android:name="androidx.window.extensions"
@@ -27,6 +29,7 @@
android:required="false" />
<activity android:name="android.server.wm.jetpack.utils.TestActivity" />
<activity android:name="android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity"
+ android:supportsPictureInPicture="true"
android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
/>
<activity android:name="android.server.wm.jetpack.utils.TestGetWindowLayoutInfoActivity" />
@@ -43,7 +46,10 @@
android:knownActivityEmbeddingCerts="6a8b96e278e58f62cfe3584022cec1d0527fcb85a9e5d2e1694eb0405be5b599"
android:exported="true"
/>
-
+ <activity android:name="android.server.wm.jetpack.utils.TestRearDisplayActivity"
+ android:configChanges="orientation|screenSize|smallestScreenSize|screenLayout|colorMode|density|touchscreen"
+ android:exported="true"
+ />
<!-- The provider properties must match the shared one defined in the util module. -->
<provider android:name="android.server.wm.lifecycle.EventLog"
android:authorities="android.server.wm.jetpack.logprovider"
diff --git a/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java b/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java
index b47e065..b9e1a8d 100644
--- a/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java
+++ b/tests/framework/base/windowmanager/jetpack/SignedApp/src/android/server/wm/jetpack/signed/SignedEmbeddingActivity.java
@@ -19,11 +19,11 @@
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.DEFAULT_SPLIT_RATIO;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.EMBEDDED_ACTIVITY_ID;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createSplitPairRuleBuilderWithJava8Predicate;
-import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createWildcardSplitPairRule;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityCrossUidInSplit;
import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.EXTRA_EMBED_ACTIVITY;
+import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.EXTRA_SPLIT_RATIO;
import static org.junit.Assume.assumeNotNull;
@@ -95,8 +95,9 @@
activityActivityPair -> true /* activityActivityPredicate */,
activityIntentPair -> true /* activityIntentPredicate */,
parentWindowMetrics -> true /* parentWindowMetricsPredicate */)
- .setSplitRatio(DEFAULT_SPLIT_RATIO).build();
- embeddingComponent.setEmbeddingRules(Collections.singleton(createWildcardSplitPairRule()));
+ .setSplitRatio(getIntent().getFloatExtra(EXTRA_SPLIT_RATIO, DEFAULT_SPLIT_RATIO))
+ .build();
+ embeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
// Launch an activity from a different UID that recognizes this package's signature and
// verify that it is split with this activity.
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java
index 57a9fd5..2397a37 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingBoundsTests.java
@@ -23,6 +23,7 @@
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifySplit;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertNotVisible;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitForFillsTask;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
import static org.junit.Assert.assertTrue;
@@ -227,6 +228,6 @@
}
return primaryActivityId.equals(((TestActivityWithId) activityIntentPair.first).getId())
&& secondaryActivityId.equals(activityIntentPair.second.getStringExtra(
- ACTIVITY_ID_LABEL));
+ KEY_ACTIVITY_ID));
}
}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java
index f4fa70c..e017d65 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingFinishTests.java
@@ -97,8 +97,7 @@
SplitPairRule splitPairRule = createWildcardSplitPairRule();
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
- TestActivity primaryActivity = (TestActivityWithId)
- startActivityNewTask(TestActivityWithId.class);
+ TestActivity primaryActivity = startActivityNewTask(TestActivityWithId.class);
TestActivity secondaryActivity = (TestActivity) startActivityAndVerifySplit(primaryActivity,
TestActivityWithId.class, splitPairRule, "secondaryActivity", mSplitInfoConsumer);
@@ -359,8 +358,8 @@
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
// Launch the two activities
- TestActivity primaryActivity = (TestActivity)
- startActivityNewTask(TestConfigChangeHandlingActivity.class);
+ TestActivity primaryActivity = startActivityNewTask(
+ TestConfigChangeHandlingActivity.class);
TestActivity secondaryActivity;
if (mShouldPreventSideBySideActivities) {
secondaryActivity = startActivityAndVerifyNotSplit(primaryActivity);
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingIntegrationTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingIntegrationTests.java
new file mode 100644
index 0000000..5751177
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingIntegrationTests.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack;
+
+import static android.server.wm.jetpack.signed.Components.SIGNED_EMBEDDING_ACTIVITY;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.EMBEDDED_ACTIVITY_ID;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createSplitPairRuleBuilderWithJava8Predicate;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifyNoCallback;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.startActivityAndVerifySplit;
+import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumed;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeHasDisplayFeatures;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeVendorApiLevelAtLeast;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutComponent;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutInfo;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeNotNull;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.jetpack.utils.TestActivityWithId;
+import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.window.extensions.embedding.SplitPairRule;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.Collections;
+
+/**
+ * Tests for the {@link androidx.window.extensions} implementation provided on the device (and only
+ * if one is available) for the Activity Embedding functionality. Specifically tests integration
+ * with other features.
+ *
+ * Build/Install/Run:
+ * atest CtsWindowManagerJetpackTestCases:ActivityEmbeddingIntegrationTests
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ActivityEmbeddingIntegrationTests extends ActivityEmbeddingTestBase {
+ private WindowLayoutComponent mWindowLayoutComponent;
+
+ @Before
+ @Override
+ public void setUp() {
+ super.setUp();
+ assumeExtensionSupportedDevice();
+ mWindowLayoutComponent = getExtensionWindowLayoutComponent();
+ assumeNotNull(mWindowLayoutComponent);
+ }
+
+ /**
+ * Tests that display features are still reported when using ActivityEmbedding.
+ */
+ @Test
+ public void testDisplayFeaturesWithEmbedding() throws Exception {
+ TestConfigChangeHandlingActivity primaryActivity = startActivityNewTask(
+ TestConfigChangeHandlingActivity.class);
+ WindowLayoutInfo windowLayoutInfo = getExtensionWindowLayoutInfo(primaryActivity);
+ assumeHasDisplayFeatures(windowLayoutInfo);
+
+ // Launch a second activity in a split. Use a very small split ratio, so that the secondary
+ // activity occupies most of the screen.
+ SplitPairRule splitPairRule = createSplitPairRuleBuilderWithJava8Predicate(
+ activityActivityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> true
+ )
+ .setSplitRatio(0.1f)
+ .build();
+ mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
+
+ Activity secondaryActivity = startActivityAndVerifySplit(primaryActivity,
+ TestActivityWithId.class, splitPairRule,
+ "secondaryActivity" /* secondActivityId */, mSplitInfoConsumer);
+
+ // Verify that an embedded activity still observes the same number of features
+ WindowLayoutInfo newWindowLayoutInfo = getExtensionWindowLayoutInfo(secondaryActivity);
+ assertEquals(windowLayoutInfo.getDisplayFeatures().size(),
+ newWindowLayoutInfo.getDisplayFeatures().size());
+
+ // Need to reset primary activity bounds change counter because entering the split already
+ // triggered a bounds change.
+ primaryActivity.resetBoundsChangeCounter();
+
+ // Finish the secondary activity and verify that the primary activity still receives the
+ // display features
+ secondaryActivity.finish();
+ assertTrue(primaryActivity.waitForBoundsChange());
+ assertEquals(getMaximumActivityBounds(primaryActivity),
+ getActivityBounds(primaryActivity));
+
+ newWindowLayoutInfo = getExtensionWindowLayoutInfo(primaryActivity);
+ assertEquals(windowLayoutInfo.getDisplayFeatures().size(),
+ newWindowLayoutInfo.getDisplayFeatures().size());
+ }
+
+ /**
+ * Tests that clearing the split info consumer stops notifying unregistered consumer.
+ */
+ @Test
+ public void testClearSplitInfoCallback() throws Exception {
+ assumeVendorApiLevelAtLeast(2); // TODO(b/244450254): harden the requirement in U.
+ mActivityEmbeddingComponent.clearSplitInfoCallback();
+ TestConfigChangeHandlingActivity primaryActivity = startActivityNewTask(
+ TestConfigChangeHandlingActivity.class);
+
+ // Launch a second activity in a split. Use a very small split ratio, so that the secondary
+ // activity occupies most of the screen.
+ SplitPairRule splitPairRule = createSplitPairRuleBuilderWithJava8Predicate(
+ activityActivityPair -> true,
+ activityIntentPair -> true,
+ windowMetrics -> true
+ )
+ .setSplitRatio(0.1f)
+ .build();
+ mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
+
+ startActivityAndVerifyNoCallback(primaryActivity,
+ TestActivityWithId.class,
+ "secondaryActivity" /* secondActivityId */,
+ mSplitInfoConsumer);
+ }
+
+ /**
+ * Tests that display features are still reported when using ActivityEmbedding. Same as above,
+ * but using different packages for the host and embedded activities.
+ * Fixed in CL: If2dbc337c4b8cb909914cc28ae4db28a82ff9de3
+ */
+ @Test
+ public void testDisplayFeaturesWithEmbedding_differentPackage() throws Exception {
+ // Start an activity to collect the window layout info.
+ TestConfigChangeHandlingActivity initialActivity = startActivityNewTask(
+ TestConfigChangeHandlingActivity.class);
+ WindowLayoutInfo windowLayoutInfo = getExtensionWindowLayoutInfo(initialActivity);
+ assumeHasDisplayFeatures(windowLayoutInfo);
+
+ // Start an activity that will attempt to embed TestActivityKnownEmbeddingCerts. It will be
+ // put in a new task, since it has a different affinity.
+ Bundle extras = new Bundle();
+ extras.putBoolean(EXTRA_EMBED_ACTIVITY, true);
+ extras.putFloat(EXTRA_SPLIT_RATIO, 0.1f);
+ startActivityNoWait(mContext, SIGNED_EMBEDDING_ACTIVITY, extras);
+
+ waitAndAssertResumed(EMBEDDED_ACTIVITY_ID);
+ TestActivityWithId secondaryActivity = getResumedActivityById(EMBEDDED_ACTIVITY_ID);
+ assertNotNull(secondaryActivity);
+ assertTrue(mActivityEmbeddingComponent.isActivityEmbedded(secondaryActivity));
+
+ // Verify that an embedded activity from a different package observes the same number of
+ // features as the initial one.
+ WindowLayoutInfo newWindowLayoutInfo = getExtensionWindowLayoutInfo(secondaryActivity);
+ assertEquals(windowLayoutInfo.getDisplayFeatures().size(),
+ newWindowLayoutInfo.getDisplayFeatures().size());
+ }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java
index 7e72571..e2fc36b 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingLifecycleTests.java
@@ -140,7 +140,7 @@
TestActivityWithId2.class, splitPairRule,
"secondaryActivity2" /* secondActivityId */, mSplitInfoConsumer);
List<Pair<String, String>> expected2 = List.of(
- transition(TestActivityWithId.class, ON_DESTROY),
+ transition(TestActivityWithId.class, ON_PAUSE),
transition(TestActivityWithId2.class, ON_CREATE),
transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED));
assertTrue("Replace secondary container activity",
@@ -148,6 +148,8 @@
checkOrder(mEventLog, expected2)));
waitAndAssertResumed(primaryActivity);
waitAndAssertResumed(secondaryActivity2);
+ // Destroy may happen after the secondaryActivity2 becomes visible and IDLE.
+ waitAndAssertActivityOnDestroy(TestActivityWithId.class);
}
/**
@@ -251,12 +253,8 @@
// Finish secondary activity
secondaryActivity.finish();
- assertTrue("Split state change must be observed",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
- assertTrue("Secondary activity must be finished",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TestActivityWithId.class, ON_DESTROY))));
+ waitAndAssertSplitStatesUpdated();
+ waitAndAssertActivityOnDestroy(TestActivityWithId.class);
waitAndAssertResumed(primaryActivity);
}
@@ -284,14 +282,16 @@
// Finish secondary activity, should trigger finishing of the primary as well
secondaryActivity.finish();
List<Pair<String, String>> expected = List.of(
- transition(TestActivityWithId.class, ON_DESTROY),
- transition(TestConfigChangeHandlingActivity.class, ON_DESTROY));
+ transition(TestActivityWithId.class, ON_PAUSE),
+ transition(TestConfigChangeHandlingActivity.class, ON_PAUSE));
assertTrue("Finish secondary activity with dependents",
mLifecycleTracker.waitForConditionWithTimeout(() ->
checkOrder(mEventLog, expected)));
- assertTrue("Split state change must be observed",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+ // There is no guarantee on the order, because the removal may be delayed until the next
+ // resumed becomes visible.
+ waitAndAssertActivityOnDestroy(TestConfigChangeHandlingActivity.class);
+ waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+ waitAndAssertSplitStatesUpdated();
}
/**
@@ -317,12 +317,8 @@
// Finish primary activity
primaryActivity.finish();
- assertTrue("Split state change must be observed",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
- assertTrue("Primary activity must be finished",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TestConfigChangeHandlingActivity.class, ON_DESTROY))));
+ waitAndAssertSplitStatesUpdated();
+ waitAndAssertActivityOnDestroy(TestConfigChangeHandlingActivity.class);
waitAndAssertResumed(secondaryActivity);
}
@@ -349,14 +345,16 @@
// Finish primary activity should trigger finishing of the secondary as well.
primaryActivity.finish();
List<Pair<String, String>> expected = List.of(
- transition(TestActivityWithId.class, ON_DESTROY),
- transition(TestConfigChangeHandlingActivity.class, ON_DESTROY));
+ transition(TestConfigChangeHandlingActivity.class, ON_PAUSE),
+ transition(TestActivityWithId.class, ON_PAUSE));
assertTrue("Finish primary activity with dependents",
mLifecycleTracker.waitForConditionWithTimeout(() ->
checkOrder(mEventLog, expected)));
- assertTrue("Split state change must be observed",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+ // There is no guarantee on the order, because the removal may be delayed until the next
+ // resumed becomes visible.
+ waitAndAssertActivityOnDestroy(TestConfigChangeHandlingActivity.class);
+ waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+ waitAndAssertSplitStatesUpdated();
}
/**
@@ -389,12 +387,8 @@
// Finish the last activity
secondaryActivity2.finish();
- assertTrue("Split state change must be observed",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
- assertTrue("Last activity must be finished",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TestActivityWithId2.class, ON_DESTROY))));
+ waitAndAssertSplitStatesUpdated();
+ waitAndAssertActivityOnDestroy(TestActivityWithId2.class);
waitAndAssertResumed(primaryActivity);
}
@@ -432,14 +426,15 @@
waitAndAssertResumed(secondaryActivity2);
waitAndAssertNotVisible(primaryActivity);
List<Pair<String, String>> expected = List.of(
- transition(TestActivityWithId.class, ON_DESTROY),
+ transition(TestActivityWithId.class, ON_PAUSE),
transition(TestConfigChangeHandlingActivity.class, ON_STOP));
assertTrue("Finish middle activity in multi-split",
mLifecycleTracker.waitForConditionWithTimeout(() ->
checkOrder(mEventLog, expected)));
- assertTrue("Split state change must be observed",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+ // There is no guarantee on the order, because the removal may be delayed until the next
+ // resumed becomes visible.
+ waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+ waitAndAssertSplitStatesUpdated();
}
/**
@@ -473,14 +468,15 @@
waitAndAssertResumed(secondaryActivity2);
waitAndAssertNotVisible(primaryActivity);
List<Pair<String, String>> expected = List.of(
- transition(TestActivityWithId.class, ON_DESTROY),
+ transition(TestActivityWithId.class, ON_PAUSE),
transition(TestConfigChangeHandlingActivity.class, ON_STOP));
assertTrue("Finish middle activity in multi-split",
mLifecycleTracker.waitForConditionWithTimeout(() ->
checkOrder(mEventLog, expected)));
- assertTrue("Split state change must be observed",
- mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
- transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
+ // There is no guarantee on the order, because the removal may be delayed until the next
+ // resumed becomes visible.
+ waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+ waitAndAssertSplitStatesUpdated();
}
/**
@@ -515,12 +511,18 @@
// Finish the middle activity
secondaryActivity.finish();
waitAndAssertResumed(primaryActivity);
- List<Pair<String, String>> expected = List.of(
- transition(TestActivityWithId2.class, ON_DESTROY),
- transition(TestActivityWithId.class, ON_DESTROY));
- assertTrue("Finish middle activity in multi-split with dependents",
- mLifecycleTracker.waitForConditionWithTimeout(() ->
- checkOrder(mEventLog, expected)));
+ // There is no guarantee on the order, because the removal may be delayed until the next
+ // resumed becomes visible.
+ waitAndAssertActivityOnDestroy(TestActivityWithId.class);
+ waitAndAssertActivityOnDestroy(TestActivityWithId2.class);
+ waitAndAssertSplitStatesUpdated();
+ }
+
+ private void waitAndAssertActivityOnDestroy(Class<? extends Activity> activityClass) {
+ mLifecycleTracker.waitAndAssertActivityCurrentState(activityClass, ON_DESTROY);
+ }
+
+ private void waitAndAssertSplitStatesUpdated() {
assertTrue("Split state change must be observed",
mLifecycleTracker.waitForConditionWithTimeout(() -> mEventLog.getLog().contains(
transition(TEST_OWNER, ON_SPLIT_STATES_UPDATED))));
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java
index 0a105cf..e367704 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPlaceholderTests.java
@@ -22,6 +22,7 @@
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertFinishing;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertNotResumed;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.waitAndAssertResumed;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
import static androidx.window.extensions.embedding.SplitRule.FINISH_NEVER;
@@ -319,7 +320,7 @@
public SplitPlaceholderRule build() {
// Create placeholder activity intent
Intent placeholderIntent = new Intent(mContext, TestActivityWithId.class);
- placeholderIntent.putExtra(ACTIVITY_ID_LABEL, mPlaceholderActivityId);
+ placeholderIntent.putExtra(KEY_ACTIVITY_ID, mPlaceholderActivityId);
// Create {@link SplitPlaceholderRule} that launches the placeholder in a split with the
// target primary activity.
@@ -329,7 +330,8 @@
&& mPrimaryActivityId.equals(((TestActivityWithId) activity)
.getId()) /* activityPredicate */,
intent -> mPrimaryActivityId.equals(
- intent.getStringExtra(ACTIVITY_ID_LABEL)) /* intentPredicate */,
+ intent.getStringExtra(KEY_ACTIVITY_ID)
+ ) /* intentPredicate */,
mParentWindowMetricsPredicate)
.setSplitRatio(DEFAULT_SPLIT_RATIO);
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java
index a929e86..5956402 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingPolicyTests.java
@@ -16,6 +16,8 @@
package android.server.wm.jetpack;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.jetpack.second.Components.SECOND_UNTRUSTED_EMBEDDING_ACTIVITY;
import static android.server.wm.jetpack.signed.Components.SIGNED_EMBEDDING_ACTIVITY;
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.createWildcardSplitPairRule;
@@ -23,7 +25,6 @@
import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.EXTRA_EMBED_ACTIVITY;
import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityFromActivity;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityNewTask;
import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.startActivityOnDisplaySingleTop;
import static android.view.Display.DEFAULT_DISPLAY;
@@ -37,11 +38,13 @@
import android.app.UiAutomation;
import android.content.ComponentName;
import android.os.Bundle;
+import android.platform.test.annotations.Presubmit;
import android.server.wm.ActivityManagerTestBase;
import android.server.wm.Condition;
import android.server.wm.NestedShellPermission;
import android.server.wm.WindowManagerState;
import android.server.wm.jetpack.utils.TestActivityKnownEmbeddingCerts;
+import android.server.wm.jetpack.utils.TestActivityLauncher;
import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
import androidx.annotation.NonNull;
@@ -50,6 +53,8 @@
import androidx.window.extensions.embedding.ActivityEmbeddingComponent;
import androidx.window.extensions.embedding.SplitPairRule;
+import com.android.compatibility.common.util.ApiTest;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -66,6 +71,7 @@
* Build/Install/Run:
* atest CtsWindowManagerJetpackTestCases:ActivityEmbeddingPolicyTests
*/
+@Presubmit
@RunWith(AndroidJUnit4.class)
public class ActivityEmbeddingPolicyTests extends ActivityManagerTestBase {
protected ActivityEmbeddingComponent mActivityEmbeddingComponent;
@@ -74,8 +80,6 @@
@Before
public void setUp() throws Exception {
super.setUp();
- // TODO(b/207070762): remove the assumption after shell transition enabled.
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
assumeExtensionSupportedDevice();
WindowExtensions windowExtensions = getWindowExtensions();
assumeNotNull(windowExtensions);
@@ -94,10 +98,20 @@
* Verifies that all input is dropped for activities that are embedded and being animated with
* untrusted embedding.
*/
+ @ApiTest(apis = {"com.android.server.wm.ActivityRecord#setDropInputForAnimation",
+ "androidx.window.extensions.embedding.ActivityEmbeddingComponent#setEmbeddingRules"})
@Test
public void testInputDuringAnimationIsNotAllowed_untrustedEmbedding() {
- Activity primaryActivity = startActivityNewTask(mContext, mInstrumentation,
- TestConfigChangeHandlingActivity.class, null /* activityId */);
+ // TODO(b/207070762): remove the test when cleanup legacy app transition
+ // We don't need to disable input with Shell transition, because we won't pass the surface
+ // to app.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
+
+ Activity primaryActivity = new TestActivityLauncher<>(mContext,
+ TestConfigChangeHandlingActivity.class)
+ .addIntentFlag(FLAG_ACTIVITY_NEW_TASK)
+ .setWindowingMode(WINDOWING_MODE_FULLSCREEN)
+ .launch(mInstrumentation);
SplitPairRule splitPairRule = createWildcardSplitPairRule(true /* shouldClearTop */);
mActivityEmbeddingComponent.setEmbeddingRules(Collections.singleton(splitPairRule));
@@ -139,6 +153,11 @@
*/
@Test
public void testInputDuringAnimationIsNotAllowed_trustedEmbedding() {
+ // TODO(b/207070762): remove the test when cleanup legacy app transition
+ // We don't need to disable input with Shell transition, because we won't pass the surface
+ // to app.
+ assumeFalse(ENABLE_SHELL_TRANSITIONS);
+
// Extend the animation scale, so that the test has enough time to catch the state during
// transition.
UiAutomation automation = mInstrumentation.getUiAutomation();
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java
index e50cd40..5f5263f 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ActivityEmbeddingTestBase.java
@@ -19,11 +19,10 @@
import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeNotNull;
-import android.os.SystemProperties;
import android.server.wm.ActivityManagerTestBase.ReportedDisplayMetrics;
+import android.server.wm.UiDeviceUtils;
import android.server.wm.jetpack.utils.JavaConsumerAdapter;
import android.server.wm.jetpack.utils.TestValueCountConsumer;
import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
@@ -48,15 +47,11 @@
protected TestValueCountConsumer<List<SplitInfo>> mSplitInfoConsumer;
protected ReportedDisplayMetrics mReportedDisplayMetrics =
ReportedDisplayMetrics.getDisplayMetrics(Display.DEFAULT_DISPLAY);
- private static final boolean ENABLE_SHELL_TRANSITIONS =
- SystemProperties.getBoolean("persist.wm.debug.shell_transit", false);
@Override
@Before
public void setUp() {
super.setUp();
- // TODO(b/207070762): remove the assumption after shell transition enabled.
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
assumeExtensionSupportedDevice();
WindowExtensions windowExtensions = getWindowExtensions();
assumeNotNull(windowExtensions);
@@ -70,6 +65,9 @@
new JavaConsumerAdapter<>(mSplitInfoConsumer)
);
}
+
+ UiDeviceUtils.pressWakeupButton();
+ UiDeviceUtils.pressUnlockButton();
}
@After
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionRearDisplayTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionRearDisplayTest.java
new file mode 100644
index 0000000..8a6df77
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionRearDisplayTest.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack;
+
+import static android.server.wm.UiDeviceUtils.pressUnlockButton;
+import static android.server.wm.UiDeviceUtils.pressWakeupButton;
+
+import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
+
+import static com.android.compatibility.common.util.PollingCheck.waitFor;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.KeyguardManager;
+import android.content.Context;
+import android.content.res.Resources;
+import android.hardware.devicestate.DeviceStateManager;
+import android.hardware.devicestate.DeviceStateRequest;
+import android.os.PowerManager;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.DeviceStateUtils;
+import android.server.wm.jetpack.utils.TestRearDisplayActivity;
+import android.server.wm.jetpack.utils.WindowExtensionTestRule;
+import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.area.WindowAreaComponent.WindowAreaSessionState;
+import androidx.window.extensions.area.WindowAreaComponent.WindowAreaStatus;
+import androidx.window.extensions.core.util.function.Consumer;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.PollingCheck;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.HashSet;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Tests for the {@link androidx.window.extensions.area.WindowAreaComponent} implementation
+ * of the rear display functionality provided on the device (and only if one is available).
+ *
+ * Build/Install/Run:
+ * atest CtsWindowManagerJetpackTestCases:ExtensionRearDisplayTest
+ */
+@LargeTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ExtensionRearDisplayTest extends WindowManagerJetpackTestBase implements
+ DeviceStateManager.DeviceStateCallback {
+
+ private static final int TIMEOUT = 2000;
+ private static final int INVALID_DEVICE_STATE = -1;
+
+ private TestRearDisplayActivity mActivity;
+ private WindowAreaComponent mWindowAreaComponent;
+ private int mCurrentDeviceState;
+ private int mCurrentDeviceBaseState;
+ private int[] mSupportedDeviceStates;
+ @WindowAreaStatus
+ private Integer mWindowAreaStatus;
+ @WindowAreaSessionState
+ private Integer mWindowAreaSessionState;
+ private int mRearDisplayState;
+
+ private final Context mInstrumentationContext = getInstrumentation().getTargetContext();
+ private final KeyguardManager mKeyguardManager = mInstrumentationContext.getSystemService(
+ KeyguardManager.class);
+ private final DeviceStateManager mDeviceStateManager = mInstrumentationContext
+ .getSystemService(DeviceStateManager.class);
+
+ private final Consumer<Integer> mStatusListener = (status) -> mWindowAreaStatus = status;
+
+ private final Consumer<Integer> mSessionStateListener = (sessionState) -> {
+ mWindowAreaSessionState = sessionState;
+ };
+
+ @Rule
+ public final WindowExtensionTestRule mWindowManagerJetpackTestRule =
+ new WindowExtensionTestRule(WindowAreaComponent.class);
+
+ @Before
+ @Override
+ public void setUp() {
+ super.setUp();
+ mWindowAreaComponent =
+ (WindowAreaComponent) mWindowManagerJetpackTestRule.getExtensionComponent();
+ mSupportedDeviceStates = mDeviceStateManager.getSupportedStates();
+ assumeTrue(mSupportedDeviceStates.length > 1);
+ // TODO(b/236022708) Move rear display state to device state config file
+ mRearDisplayState = getInstrumentation().getTargetContext().getResources()
+ .getInteger(Resources.getSystem()
+ .getIdentifier("config_deviceStateRearDisplay", "integer", "android"));
+ assumeTrue(mRearDisplayState != INVALID_DEVICE_STATE);
+ mDeviceStateManager.registerCallback(Runnable::run, this);
+ mWindowAreaComponent.addRearDisplayStatusListener(mStatusListener);
+ unlockDeviceIfNeeded();
+ mActivity = startActivityNewTask(TestRearDisplayActivity.class);
+ waitAndAssert(() -> mWindowAreaStatus != null);
+ }
+
+ @After
+ @Override
+ public void tearDown() {
+ super.tearDown();
+ if (mWindowAreaComponent != null) {
+ mWindowAreaComponent.removeRearDisplayStatusListener(mStatusListener);
+ try {
+ DeviceStateUtils.runWithControlDeviceStatePermission(
+ () -> mDeviceStateManager.cancelStateRequest());
+ } catch (Throwable t) {
+ throw new RuntimeException(t);
+ }
+ }
+ }
+
+ /**
+ * Tests that the RearDisplay status listeners receive the correct {@link WindowAreaStatus}
+ * values.
+ *
+ * The test goes through all supported device states and verifies that the correct status is
+ * returned. If the state does not place the device in the active RearDisplay configuration
+ * (i.e. the base state of the device is different than the current state, and that current
+ * state is the RearDisplay state), then it should receive the
+ * {@link WindowAreaStatus#STATUS_AVAILABLE} value, otherwise it should receive the
+ * {@link WindowAreaStatus#STATUS_UNAVAILABLE} value.
+ */
+ @ApiTest(apis = {
+ "androidx.window.extensions.area.WindowAreaComponent#addRearDisplayStatusListener",
+ "androidx.window.extensions.area.WindowAreaComponent#removeRearDisplayStatusListener"})
+ @Test
+ public void testRearDisplayStatusListeners() throws Throwable {
+ Set<Integer> requestedStates = new HashSet<>();
+ while (requestedStates.size() != mSupportedDeviceStates.length) {
+ int newState = determineNewState(mCurrentDeviceState, mSupportedDeviceStates,
+ requestedStates);
+ if (newState != INVALID_DEVICE_STATE) {
+ requestedStates.add(newState);
+ DeviceStateRequest request = DeviceStateRequest.newBuilder(newState).build();
+ DeviceStateUtils.runWithControlDeviceStatePermission(() ->
+ mDeviceStateManager.requestState(request, null, null));
+
+ waitAndAssert(() -> mCurrentDeviceState == newState);
+ // If the state does not put the device into the rear display configuration,
+ // then the listener should receive the STATUS_AVAILABLE value.
+ if (!isRearDisplayActive(mCurrentDeviceState, mCurrentDeviceBaseState)) {
+ waitAndAssert(
+ () -> mWindowAreaStatus == WindowAreaComponent.STATUS_AVAILABLE);
+ } else {
+ waitAndAssert(
+ () -> mWindowAreaStatus == WindowAreaComponent.STATUS_UNAVAILABLE);
+ }
+ }
+ }
+ }
+
+ /**
+ * Tests that you can start and end rear display mode. Verifies that the {@link Consumer} that
+ * is provided when calling {@link WindowAreaComponent#startRearDisplaySession} receives
+ * the {@link WindowAreaSessionState#SESSION_STATE_ACTIVE} value when starting the session
+ * and {@link WindowAreaSessionState#SESSION_STATE_INACTIVE} when ending the session.
+ *
+ * This test also verifies that the {@link android.app.Activity} is still visible when rear
+ * display mode is started, and that the activity received a configuration change when enabling
+ * and disabling rear display mode. This is verifiable due to the current generation of
+ * hardware and the fact that there are different screen sizes from the different displays.
+ */
+ @ApiTest(apis = {
+ "androidx.window.extensions.area.WindowAreaComponent#startRearDisplaySession",
+ "androidx.window.extensions.area.WindowAreaComponent#endRearDisplaySession"})
+ @Test
+ public void testStartAndEndRearDisplaySession() throws Throwable {
+ assumeTrue(mWindowAreaStatus == WindowAreaComponent.STATUS_AVAILABLE);
+ assumeTrue(mCurrentDeviceState != mRearDisplayState);
+
+ mActivity.mConfigurationChanged = false;
+ // Running with CONTROL_DEVICE_STATE permission to bypass educational overlay
+ DeviceStateUtils.runWithControlDeviceStatePermission(() ->
+ mWindowAreaComponent.startRearDisplaySession(mActivity, mSessionStateListener));
+ waitAndAssert(() -> mActivity.mConfigurationChanged);
+ assertTrue(mWindowAreaSessionState != null
+ && mWindowAreaSessionState == WindowAreaComponent.SESSION_STATE_ACTIVE);
+ assertEquals(mCurrentDeviceState, mRearDisplayState);
+ assertTrue(isActivityVisible(mActivity));
+
+ mActivity.mConfigurationChanged = false;
+ mWindowAreaComponent.endRearDisplaySession();
+ waitAndAssert(() -> WindowAreaComponent.SESSION_STATE_INACTIVE == mWindowAreaSessionState);
+ waitAndAssert(() -> mActivity.mConfigurationChanged);
+ assertTrue(isActivityVisible(mActivity));
+ // Cancelling rear display mode should cancel the override, so verifying that the
+ // device state is the same as the physical state of the device.
+ assertEquals(mCurrentDeviceState, mCurrentDeviceBaseState);
+ assertEquals(WindowAreaComponent.STATUS_AVAILABLE, (int) mWindowAreaStatus);
+
+ }
+
+ @Override
+ public void onBaseStateChanged(int state) {
+ mCurrentDeviceBaseState = state;
+ }
+
+ @Override
+ public void onStateChanged(int state) {
+ mCurrentDeviceState = state;
+ }
+
+ /**
+ * Returns the next state that we should request that isn't the current state and
+ * has not already been requested.
+ */
+ private int determineNewState(int currentDeviceState, int[] statesToRequest,
+ Set<Integer> requestedStates) {
+ for (int state : statesToRequest) {
+ if (state != currentDeviceState && !requestedStates.contains(state)) {
+ return state;
+ }
+ }
+ return INVALID_DEVICE_STATE;
+ }
+
+ /**
+ * Helper method to determine if a rear display session is currently active by checking
+ * if the current device configuration matches that of rear display. This would be true
+ * if there is a device override currently active (base state != current state) and the current
+ * state is that which corresponds to {@code mRearDisplayState}
+ * @return {@code true} if the device is in rear display mode and {@code false} if not
+ */
+ private boolean isRearDisplayActive(int currentDeviceState, int currentDeviceBaseState) {
+ return (currentDeviceState != currentDeviceBaseState)
+ && (currentDeviceState == mRearDisplayState);
+ }
+
+ private void unlockDeviceIfNeeded() {
+ if (isKeyguardLocked() || !Objects.requireNonNull(
+ mInstrumentationContext.getSystemService(PowerManager.class)).isInteractive()) {
+ pressWakeupButton();
+ pressUnlockButton();
+ }
+ }
+
+ private boolean isKeyguardLocked() {
+ return mKeyguardManager != null && mKeyguardManager.isKeyguardLocked();
+ }
+
+ private void waitAndAssert(PollingCheck.PollingCheckCondition condition) {
+ waitFor(TIMEOUT, condition);
+ }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java
index 4326f7d..95036cb 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/ExtensionWindowLayoutComponentTest.java
@@ -18,13 +18,14 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.server.wm.jetpack.utils.ExtensionUtil.EXTENSION_VERSION_2;
import static android.server.wm.jetpack.utils.ExtensionUtil.assertEqualWindowLayoutInfo;
-import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
import static android.server.wm.jetpack.utils.ExtensionUtil.assumeHasDisplayFeatures;
-import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutComponent;
import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutInfo;
+import static android.server.wm.jetpack.utils.ExtensionUtil.isExtensionVersionAtLeast;
import static android.server.wm.jetpack.utils.SidecarUtil.assumeSidecarSupportedDevice;
import static android.server.wm.jetpack.utils.SidecarUtil.getSidecarInterface;
+import static android.view.Display.DEFAULT_DISPLAY;
import static androidx.window.extensions.layout.FoldingFeature.STATE_FLAT;
import static androidx.window.extensions.layout.FoldingFeature.STATE_HALF_OPENED;
@@ -35,17 +36,28 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assume.assumeNotNull;
+import static org.junit.Assume.assumeTrue;
+import android.content.Context;
import android.graphics.Rect;
+import android.hardware.display.DisplayManager;
+import android.platform.test.annotations.Presubmit;
import android.server.wm.IgnoreOrientationRequestSession;
import android.server.wm.jetpack.utils.TestActivity;
import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
import android.server.wm.jetpack.utils.TestValueCountJavaConsumer;
+import android.server.wm.jetpack.utils.WindowExtensionTestRule;
import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
+import android.view.Display;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.WindowMetrics;
import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
import androidx.window.extensions.layout.DisplayFeature;
import androidx.window.extensions.layout.FoldingFeature;
@@ -54,10 +66,13 @@
import androidx.window.sidecar.SidecarDisplayFeature;
import androidx.window.sidecar.SidecarInterface;
+import com.android.compatibility.common.util.ApiTest;
+
import com.google.common.collect.BoundType;
import com.google.common.collect.Range;
import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -74,6 +89,7 @@
* Build/Install/Run:
* atest CtsWindowManagerJetpackTestCases:ExtensionWindowLayoutComponentTest
*/
+@Presubmit
@LargeTest
@RunWith(AndroidJUnit4.class)
public class ExtensionWindowLayoutComponentTest extends WindowManagerJetpackTestBase {
@@ -82,20 +98,47 @@
private WindowLayoutComponent mWindowLayoutComponent;
private WindowLayoutInfo mWindowLayoutInfo;
+ @Rule
+ public final WindowExtensionTestRule mWindowExtensionTestRule =
+ new WindowExtensionTestRule(WindowLayoutComponent.class);
+
@Before
@Override
public void setUp() {
super.setUp();
- assumeExtensionSupportedDevice();
- mActivity = (TestActivity) startActivityNewTask(TestActivity.class);
- mWindowLayoutComponent = getExtensionWindowLayoutComponent();
+ mWindowLayoutComponent =
+ (WindowLayoutComponent) mWindowExtensionTestRule.getExtensionComponent();
assumeNotNull(mWindowLayoutComponent);
+ mActivity = startActivityNewTask(TestActivity.class);
+ }
+
+ private Context createContextWithNonActivityWindow() {
+ Display defaultDisplay = mContext.getSystemService(DisplayManager.class).getDisplay(
+ DEFAULT_DISPLAY);
+ Context windowContext = mContext.createWindowContext(defaultDisplay,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY, null);
+
+ mInstrumentation.runOnMainSync(() -> {
+ final View view = new View(windowContext);
+ WindowManager wm = windowContext.getSystemService(WindowManager.class);
+ WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
+ wm.addView(view, params);
+ });
+ return windowContext;
+ }
+
+ private void assumeExtensionVersionSupportsWindowContextLayout() {
+ assumeTrue("This test should only be run on devices with version: ",
+ isExtensionVersionAtLeast(EXTENSION_VERSION_2));
}
/**
* Test adding and removing a window layout change listener.
*/
@Test
+ @ApiTest(apis = {
+ "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
public void testWindowLayoutComponent_onWindowLayoutChangeListener() throws Exception {
// Set activity to portrait
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
@@ -106,29 +149,36 @@
// removed.
TestValueCountJavaConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
new TestValueCountJavaConsumer<>();
- windowLayoutInfoConsumer.setCount(2);
+ windowLayoutInfoConsumer.setCount(1);
// Add window layout listener for mWindowToken - onWindowLayoutChanged should be called
mWindowLayoutComponent.addWindowLayoutInfoListener(mActivity, windowLayoutInfoConsumer);
+ // Initial registration invokes a consumer callback synchronously, clear the queue to
+ // make sure there's no residual value or from the first orientation change.
+ windowLayoutInfoConsumer.clearQueue();
// Change the activity orientation - onWindowLayoutChanged should be called
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
ORIENTATION_LANDSCAPE);
+ // Check we have received exactly one layout update.
+ assertNotNull(windowLayoutInfoConsumer.waitAndGet());
+
// Remove the listener
mWindowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
+ windowLayoutInfoConsumer.clearQueue();
// Change the activity orientation - onWindowLayoutChanged should NOT be called
setActivityOrientationActivityDoesNotHandleOrientationChanges(mActivity,
ORIENTATION_PORTRAIT);
-
- // Check that the countdown is zero
WindowLayoutInfo lastValue = windowLayoutInfoConsumer.waitAndGet();
- assertNotNull(lastValue);
+ assertNull(lastValue);
}
@Test
- public void testWindowLayoutComponent_WindowLayoutInfoListener() {
+ @ApiTest(apis = {
+ "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+ public void testWindowLayoutComponent_windowLayoutInfoListener() {
TestValueCountJavaConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
new TestValueCountJavaConsumer<>();
// Test that adding and removing callback succeeds
@@ -136,8 +186,9 @@
mWindowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
}
+ @ApiTest(apis = {"androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
@Test
- public void testDisplayFeatures()
+ public void testWindowLayoutComponent_providesWindowLayoutFromActivity()
throws ExecutionException, InterruptedException, TimeoutException {
mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
assumeHasDisplayFeatures(mWindowLayoutInfo);
@@ -167,44 +218,153 @@
}
@Test
+ @ApiTest(apis = {
+ "androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
+ public void testWindowLayoutComponent_providesWindowLayoutFromWindowContext()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ assumeExtensionVersionSupportsWindowContextLayout();
+ Context windowContext = createContextWithNonActivityWindow();
+
+ mWindowLayoutInfo = getExtensionWindowLayoutInfo(windowContext);
+ assumeHasDisplayFeatures(mWindowLayoutInfo);
+
+ // Verify that window layouts and metrics are reasonable.
+ WindowManager mWm = windowContext.getSystemService(WindowManager.class);
+ final WindowMetrics currentMetrics = mWm.getCurrentWindowMetrics();
+
+ for (DisplayFeature displayFeature : mWindowLayoutInfo.getDisplayFeatures()) {
+ final Rect featureRect = displayFeature.getBounds();
+ assertHasNonNegativeDimensions(featureRect);
+ assertNotBothDimensionsZero(featureRect);
+ assertTrue(currentMetrics.getBounds().contains(featureRect));
+ }
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+ public void testWindowLayoutComponent_windowLayoutMatchesBetweenActivityAndWindowContext()
+ throws ExecutionException, InterruptedException, TimeoutException {
+ assumeExtensionVersionSupportsWindowContextLayout();
+ Context windowContext = createContextWithNonActivityWindow();
+
+ WindowLayoutInfo windowLayoutInfoFromContext = getExtensionWindowLayoutInfo(windowContext);
+
+ TestConfigChangeHandlingActivity configHandlingActivity = startFullScreenActivityNewTask(
+ TestConfigChangeHandlingActivity.class, null);
+ WindowLayoutInfo windowLayoutInfoFromActivity = getExtensionWindowLayoutInfo(
+ configHandlingActivity);
+
+ assertEquals(windowLayoutInfoFromContext, windowLayoutInfoFromActivity);
+ }
+
+ @ApiTest(apis = {"androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
+ @Test
public void testGetWindowLayoutInfo_configChanged_windowLayoutUpdates()
throws InterruptedException {
+ assumeSupportsRotation();
mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
assumeHasDisplayFeatures(mWindowLayoutInfo);
- final TestConfigChangeHandlingActivity configHandlingActivity
- = (TestConfigChangeHandlingActivity) startActivityNewTask(
- TestConfigChangeHandlingActivity.class);
+ final TestConfigChangeHandlingActivity activity = startFullScreenActivityNewTask(
+ TestConfigChangeHandlingActivity.class, null);
try (IgnoreOrientationRequestSession session =
- new IgnoreOrientationRequestSession(false /* enable */)) {
- setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity,
+ new IgnoreOrientationRequestSession(false /* enable */)) {
+ setActivityOrientationActivityHandlesOrientationChanges(activity,
ORIENTATION_PORTRAIT);
final WindowLayoutInfo portraitWindowLayoutInfo = getExtensionWindowLayoutInfo(
- configHandlingActivity);
- final Rect portraitBounds = getActivityBounds(configHandlingActivity);
- final Rect portraitMaximumBounds = getMaximumActivityBounds(configHandlingActivity);
+ activity);
+ final Rect portraitBounds = getActivityBounds(activity);
+ final Rect portraitMaximumBounds = getMaximumActivityBounds(activity);
- setActivityOrientationActivityHandlesOrientationChanges(configHandlingActivity,
+ setActivityOrientationActivityHandlesOrientationChanges(activity,
ORIENTATION_LANDSCAPE);
final WindowLayoutInfo landscapeWindowLayoutInfo = getExtensionWindowLayoutInfo(
- configHandlingActivity);
- final Rect landscapeBounds = getActivityBounds(configHandlingActivity);
- final Rect landscapeMaximumBounds = getMaximumActivityBounds(configHandlingActivity);
+ activity);
+ final Rect landscapeBounds = getActivityBounds(activity);
+ final Rect landscapeMaximumBounds = getMaximumActivityBounds(activity);
final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
portraitMaximumBounds, landscapeMaximumBounds);
- assertTrue(doesDisplayRotateForOrientation);
assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo,
portraitBounds, landscapeBounds, doesDisplayRotateForOrientation);
}
}
+ @ApiTest(apis = {"androidx.window.extensions.layout.WindowLayoutInfo#getDisplayFeatures"})
@Test
- public void testGetWindowLayoutInfo_windowRecreated_windowLayoutUpdates()
+ public void testGetWindowLayoutInfo_enterExitPip_windowLayoutInfoMatches()
throws InterruptedException {
+ mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
+ assumeHasDisplayFeatures(mWindowLayoutInfo);
+
+ TestConfigChangeHandlingActivity configHandlingActivity = startActivityNewTask(
+ TestConfigChangeHandlingActivity.class, null);
+
+ final WindowLayoutInfo initialInfo = getExtensionWindowLayoutInfo(
+ configHandlingActivity);
+
+ enterPipActivityHandlesConfigChanges(configHandlingActivity);
+ exitPipActivityHandlesConfigChanges(configHandlingActivity);
+
+ final WindowLayoutInfo updatedInfo = getExtensionWindowLayoutInfo(
+ configHandlingActivity);
+
+ assertEquals(initialInfo, updatedInfo);
+ }
+
+ /*
+ * Similar to #testGetWindowLayoutInfo_configChanged_windowLayoutUpdates, here we trigger
+ * rotations with a full screen activity on one Display Area, verify that WindowLayoutInfo
+ * are updated with callbacks.
+ */
+ @FlakyTest(bugId = 254056760)
+ @Test
+ @ApiTest(apis = {
+ "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener",
+ "androidx.window.extensions.layout.WindowLayoutComponent#removeWindowLayoutInfoListener"
+ })
+ public void testWindowLayoutComponent_updatesWindowLayoutFromContextAfterRotation()
+ throws InterruptedException {
+ assumeExtensionVersionSupportsWindowContextLayout();
+ assumeSupportsRotation();
+
try (IgnoreOrientationRequestSession session =
- new IgnoreOrientationRequestSession(false /* enable */)) {
+ new IgnoreOrientationRequestSession(false /* enable */)) {
+ final TestConfigChangeHandlingActivity activity = startFullScreenActivityNewTask(
+ TestConfigChangeHandlingActivity.class, null);
+
+ setActivityOrientationActivityHandlesOrientationChanges(activity,
+ ORIENTATION_PORTRAIT);
+
+ WindowLayoutInfo firstWindowLayout = getExtensionWindowLayoutInfo(activity);
+ final Rect firstBounds = getActivityBounds(activity);
+ final Rect firstMaximumBounds = getMaximumActivityBounds(activity);
+
+ setActivityOrientationActivityHandlesOrientationChanges(activity,
+ ORIENTATION_LANDSCAPE);
+
+ WindowLayoutInfo secondWindowLayout = getExtensionWindowLayoutInfo(activity);
+ final Rect secondBounds = getActivityBounds(activity);
+ final Rect secondMaximumBounds = getMaximumActivityBounds(activity);
+
+ final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
+ firstMaximumBounds, secondMaximumBounds);
+ assertEqualWindowLayoutInfo(firstWindowLayout, secondWindowLayout,
+ firstBounds, secondBounds, doesDisplayRotateForOrientation);
+ }
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+ public void testGetWindowLayoutInfo_windowRecreated_windowLayoutUpdates()
+ throws InterruptedException {
+ assumeSupportsRotation();
+
+ try (IgnoreOrientationRequestSession session =
+ new IgnoreOrientationRequestSession(false /* enable */)) {
mWindowLayoutInfo = getExtensionWindowLayoutInfo(mActivity);
assumeHasDisplayFeatures(mWindowLayoutInfo);
@@ -224,7 +384,6 @@
final boolean doesDisplayRotateForOrientation = doesDisplayRotateForOrientation(
portraitMaximumBounds, landscapeMaximumBounds);
- assertTrue(doesDisplayRotateForOrientation);
assertEqualWindowLayoutInfo(portraitWindowLayoutInfo, landscapeWindowLayoutInfo,
portraitBounds, landscapeBounds, doesDisplayRotateForOrientation);
}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java
index f0b5a7c..f30a7d4 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/SidecarTest.java
@@ -18,45 +18,28 @@
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-import static android.server.wm.jetpack.utils.SidecarUtil.MINIMUM_SIDECAR_VERSION;
import static android.server.wm.jetpack.utils.SidecarUtil.assertEqualWindowLayoutInfo;
import static android.server.wm.jetpack.utils.SidecarUtil.assumeHasDisplayFeatures;
import static android.server.wm.jetpack.utils.SidecarUtil.assumeSidecarSupportedDevice;
import static android.server.wm.jetpack.utils.SidecarUtil.getSidecarInterface;
-import static android.server.wm.jetpack.utils.SidecarUtil.isSidecarVersionValid;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.assertNotBothDimensionsZero;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.assertHasNonNegativeDimensions;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.doesDisplayRotateForOrientation;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.getActivityBounds;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.getActivityWindowToken;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.getMaximumActivityBounds;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.setActivityOrientationActivityDoesNotHandleOrientationChanges;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.setActivityOrientationActivityHandlesOrientationChanges;
import static com.google.common.truth.Truth.assertThat;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.junit.Assume.assumeTrue;
-import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.IBinder;
import android.platform.test.annotations.Presubmit;
-import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
import android.server.wm.jetpack.utils.SidecarCallbackCounter;
import android.server.wm.jetpack.utils.TestActivity;
import android.server.wm.jetpack.utils.TestConfigChangeHandlingActivity;
import android.server.wm.jetpack.utils.TestGetWindowLayoutInfoActivity;
+import android.server.wm.jetpack.utils.WindowManagerJetpackTestBase;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.FlakyTest;
import androidx.test.filters.LargeTest;
-
import androidx.window.sidecar.SidecarDeviceState;
import androidx.window.sidecar.SidecarDisplayFeature;
import androidx.window.sidecar.SidecarInterface;
@@ -69,8 +52,6 @@
import org.junit.Test;
import org.junit.runner.RunWith;
-import java.util.List;
-
/**
* Tests for the {@link androidx.window.sidecar} implementation provided on the device (and only
* if one is available).
@@ -78,6 +59,7 @@
* Build/Install/Run:
* atest CtsWindowManagerJetpackTestCases:SidecarTest
*/
+@Presubmit
@LargeTest
@RunWith(AndroidJUnit4.class)
public class SidecarTest extends WindowManagerJetpackTestBase {
@@ -192,8 +174,7 @@
// The value is verified inside TestGetWindowLayoutInfoActivity
TestGetWindowLayoutInfoActivity.resetResumeCounter();
- TestGetWindowLayoutInfoActivity testGetWindowLayoutInfoActivity
- = (TestGetWindowLayoutInfoActivity) startActivityNewTask(
+ TestGetWindowLayoutInfoActivity testGetWindowLayoutInfoActivity = startActivityNewTask(
TestGetWindowLayoutInfoActivity.class);
// Make sure the activity has gone through all states.
@@ -205,8 +186,7 @@
public void testGetWindowLayoutInfo_configChanged_windowLayoutUpdates() {
assumeHasDisplayFeatures(mSidecarInterface, mWindowToken);
- TestConfigChangeHandlingActivity configHandlingActivity
- = (TestConfigChangeHandlingActivity) startActivityNewTask(
+ TestConfigChangeHandlingActivity configHandlingActivity = startActivityNewTask(
TestConfigChangeHandlingActivity.class);
SidecarInterface sidecar = getSidecarInterface(configHandlingActivity);
assertThat(sidecar).isNotNull();
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/WindowExtensionsImplTest.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/WindowExtensionsImplTest.java
new file mode 100644
index 0000000..fd14846
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/WindowExtensionsImplTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack;
+
+import static android.server.wm.jetpack.utils.ExtensionUtil.EXTENSION_VERSION_1;
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionVersion;
+
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for the {@link androidx.window.extensions.WindowExtensionsImpl} implementation.
+ * Verifies that the extensions API level is aligned or higher than the current level.
+ *
+ * Build/Install/Run:
+ * atest CtsWindowManagerJetpackTestCases:WindowExtensionsImplTest
+ */
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class WindowExtensionsImplTest {
+
+ @ApiTest(apis = {"androidx.window.extensions.WindowExtensions#getVendorApiLevel"})
+ @Test
+ public void testVerifiesExtensionVendorApiLevel() {
+ assumeExtensionSupportedDevice();
+ assertTrue(getExtensionVersion().compareTo(EXTENSION_VERSION_1) >= 0);
+ }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
index aeb1cbf..84873b2 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ActivityEmbeddingUtil.java
@@ -27,6 +27,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeTrue;
@@ -178,6 +179,21 @@
return secondaryActivity;
}
+ public static void startActivityAndVerifyNoCallback(@NonNull Activity activityLaunchingFrom,
+ @NonNull Class secondActivityClass, @NonNull String secondaryActivityId,
+ @NonNull TestValueCountConsumer<List<SplitInfo>> splitInfoConsumer) throws Exception {
+ // We expect the actual count to be 0. Set to 1 to trigger the timeout and verify no calls.
+ splitInfoConsumer.setCount(1);
+
+ // Start second activity
+ startActivityFromActivity(activityLaunchingFrom, secondActivityClass, secondaryActivityId);
+
+ // A split info callback should occur after the new activity is launched because the split
+ // states have changed.
+ List<SplitInfo> activeSplitStates = splitInfoConsumer.waitAndGet();
+ assertNull("Received SplitInfo value but did not expect none.", activeSplitStates);
+ }
+
public static Activity startActivityAndVerifySplit(@NonNull Activity primaryActivity,
@NonNull Class secondActivityClass, @NonNull SplitPairRule splitPairRule,
@NonNull String secondActivityId, int expectedCallbackCount,
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java
index 143f7c0..245052e 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/ExtensionUtil.java
@@ -23,21 +23,22 @@
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
+import android.content.Context;
import android.graphics.Rect;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.UiContext;
import androidx.window.extensions.WindowExtensions;
import androidx.window.extensions.WindowExtensionsProvider;
+import androidx.window.extensions.area.WindowAreaComponent;
import androidx.window.extensions.layout.DisplayFeature;
import androidx.window.extensions.layout.FoldingFeature;
import androidx.window.extensions.layout.WindowLayoutComponent;
import androidx.window.extensions.layout.WindowLayoutInfo;
import java.util.List;
-import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
/**
@@ -49,7 +50,9 @@
private static final String EXTENSION_TAG = "Extension";
- public static final Version MINIMUM_EXTENSION_VERSION = new Version(1, 0, 0, "");
+ public static final Version EXTENSION_VERSION_1 = new Version(1, 0, 0, "");
+
+ public static final Version EXTENSION_VERSION_2 = new Version(1, 1, 0, "");
@NonNull
public static Version getExtensionVersion() {
@@ -67,10 +70,29 @@
return Version.UNKNOWN;
}
+ public static boolean isExtensionVersionAtLeast(Version targetVersion) {
+ final Version version = getExtensionVersion();
+ return version.compareTo(targetVersion) >= 0;
+ }
+
+ /**
+ * If called on a device with the vendor api level less than the bound then the test will be
+ * ignored.
+ * @param vendorApiLevel minimum {@link WindowExtensions#getVendorApiLevel()} for a test to
+ * succeed
+ */
+ public static void assumeVendorApiLevelAtLeast(int vendorApiLevel) {
+ final Version version = getExtensionVersion();
+ assumeTrue(
+ "Needs vendorApiLevel " + vendorApiLevel + " but has " + version.getMajor(),
+ version.getMajor() >= vendorApiLevel
+ );
+ }
+
public static boolean isExtensionVersionValid() {
final Version version = getExtensionVersion();
// Check that the extension version on the device is at least the minimum valid version.
- return version.compareTo(MINIMUM_EXTENSION_VERSION) >= 0;
+ return version.compareTo(EXTENSION_VERSION_1) >= 0;
}
@Nullable
@@ -90,7 +112,7 @@
assumeTrue("Device does not support extensions", extensionNotNull);
// If extensions are on the device, make sure that the version is valid.
assertTrue("Extension version is invalid, must be at least "
- + MINIMUM_EXTENSION_VERSION.toString(), isExtensionVersionValid());
+ + EXTENSION_VERSION_1.toString(), isExtensionVersionValid());
}
@Nullable
@@ -102,6 +124,12 @@
return extension.getWindowLayoutComponent();
}
+ /**
+ * Publishes a WindowLayoutInfo update to a test consumer. In EXTENSION_VERSION_1, only type
+ * Activity can be the listener to WindowLayoutInfo changes. This method should be called at
+ * most once for each given Activity because addWindowLayoutInfoListener implementation
+ * assumes a 1-1 mapping between the activity and consumer.
+ */
@Nullable
public static WindowLayoutInfo getExtensionWindowLayoutInfo(Activity activity)
throws InterruptedException {
@@ -122,9 +150,36 @@
return info;
}
+ /**
+ * Publishes a WindowLayoutInfo update to a test consumer. In EXTENSION_VERSION_2 both type
+ * WindowContext and Activity can be listeners. This method should be called at most once for
+ * each given Context because addWindowLayoutInfoListener implementation assumes a 1-1
+ * mapping between the context and consumer.
+ */
+ @Nullable
+ public static WindowLayoutInfo getExtensionWindowLayoutInfo(@UiContext Context context)
+ throws InterruptedException {
+ assertTrue(isExtensionVersionAtLeast(EXTENSION_VERSION_2));
+ WindowLayoutComponent windowLayoutComponent = getExtensionWindowLayoutComponent();
+ if (windowLayoutComponent == null) {
+ return null;
+ }
+ TestValueCountConsumer<WindowLayoutInfo> windowLayoutInfoConsumer =
+ new TestValueCountConsumer<>();
+ windowLayoutComponent.addWindowLayoutInfoListener(context, windowLayoutInfoConsumer);
+ WindowLayoutInfo info = windowLayoutInfoConsumer.waitAndGet();
+
+ // The default implementation only allows a single listener per context. Since we are using
+ // a local windowLayoutInfoConsumer within this function, we must remember to clean up.
+ // Otherwise, subsequent calls to addWindowLayoutInfoListener with the same context will
+ // fail to have its callback registered.
+ windowLayoutComponent.removeWindowLayoutInfoListener(windowLayoutInfoConsumer);
+ return info;
+ }
+
@NonNull
public static int[] getExtensionDisplayFeatureTypes(Activity activity)
- throws ExecutionException, InterruptedException, TimeoutException {
+ throws InterruptedException {
WindowLayoutInfo windowLayoutInfo = getExtensionWindowLayoutInfo(activity);
if (windowLayoutInfo == null) {
return new int[0];
@@ -267,4 +322,17 @@
.filter(d -> otherOrientationBounds.contains(d.getBounds()))
.collect(Collectors.toList());
}
+
+ /**
+ * Returns the {@link WindowAreaComponent} available in {@link WindowExtensions} if available.
+ * If the component is not available, returns null.
+ */
+ @Nullable
+ public static WindowAreaComponent getExtensionWindowAreaComponent() {
+ WindowExtensions extension = getWindowExtensions();
+ if (extension == null || extension.getVendorApiLevel() < 2) {
+ return null;
+ }
+ return extension.getWindowAreaComponent();
+ }
}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityLauncher.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityLauncher.java
new file mode 100644
index 0000000..22b77ed
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityLauncher.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack.utils;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.content.Intent;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class TestActivityLauncher<T extends Activity> {
+
+ /** Key for string extra, ID to track an Activity that is launched. */
+ public static final String KEY_ACTIVITY_ID = "ActivityID";
+
+ /**
+ * Options that will be passed to the instrumentation.
+ * @see TestActivityLauncher#launch(Instrumentation)
+ */
+ private final ActivityOptions mOptions = ActivityOptions.makeBasic();
+
+ /**
+ * The class for the {@link Activity} that you are launching.
+ */
+ private final Class<T> mActivityClass;
+
+ /**
+ * The intent that will be used to launch the {@link Activity}.
+ */
+ private final Intent mIntent;
+
+ public TestActivityLauncher(@NonNull Context context, @NonNull Class<T> activityClass) {
+ mActivityClass = activityClass;
+ mIntent = new Intent(context, activityClass);
+ }
+
+ public TestActivityLauncher<T> addIntentFlag(int flag) {
+ mIntent.addFlags(flag);
+ return this;
+ }
+
+ public TestActivityLauncher<T> setActivityId(@Nullable String id) {
+ mIntent.putExtra(KEY_ACTIVITY_ID, id);
+ return this;
+ }
+
+ public TestActivityLauncher<T> setWindowingMode(int windowingMode) {
+ mOptions.setLaunchWindowingMode(windowingMode);
+ return this;
+ }
+
+ public T launch(@NonNull Instrumentation instrumentation) {
+ return mActivityClass.cast(instrumentation.startActivitySync(mIntent, mOptions.toBundle()));
+ }
+
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java
index 1987020..455db39 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestActivityWithId.java
@@ -16,7 +16,7 @@
package android.server.wm.jetpack.utils;
-import static android.server.wm.jetpack.utils.WindowManagerJetpackTestBase.ACTIVITY_ID_LABEL;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
import android.content.Intent;
import android.os.Bundle;
@@ -37,8 +37,8 @@
// Get ID
Intent intent = getIntent();
- if (intent != null && intent.hasExtra(ACTIVITY_ID_LABEL)) {
- mId = intent.getStringExtra(ACTIVITY_ID_LABEL);
+ if (intent != null && intent.hasExtra(KEY_ACTIVITY_ID)) {
+ mId = intent.getStringExtra(KEY_ACTIVITY_ID);
}
}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestRearDisplayActivity.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestRearDisplayActivity.java
new file mode 100644
index 0000000..5e19aec6
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestRearDisplayActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack.utils;
+
+import android.app.Activity;
+import android.content.res.Configuration;
+
+import androidx.annotation.NonNull;
+
+public class TestRearDisplayActivity extends Activity {
+
+ public boolean mConfigurationChanged;
+
+ @Override
+ public void onConfigurationChanged(@NonNull Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mConfigurationChanged = true;
+ }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java
index 10fe6fc..f221875 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/TestValueCountConsumer.java
@@ -62,6 +62,11 @@
return value;
}
+ // Doesn't change the count.
+ public void clearQueue() {
+ mLinkedBlockingQueue.clear();
+ }
+
@Nullable
public T getLastReportedValue() {
return mLastReportedValue;
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java
index f8be26c..b6e2919 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/Version.java
@@ -81,7 +81,8 @@
return matcher.matches();
}
- int getMajor() {
+ /** Major version of the vendor implementation. */
+ public int getMajor() {
return mMajor;
}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowExtensionTestRule.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowExtensionTestRule.java
new file mode 100644
index 0000000..5ae4b72
--- /dev/null
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowExtensionTestRule.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm.jetpack.utils;
+
+import static android.server.wm.jetpack.utils.ExtensionUtil.assumeExtensionSupportedDevice;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowAreaComponent;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getExtensionWindowLayoutComponent;
+
+import static org.junit.Assume.assumeNotNull;
+
+import androidx.window.extensions.area.WindowAreaComponent;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class WindowExtensionTestRule implements TestRule {
+
+ private final Class<?> mComponentToFetch;
+ private Object mComponent;
+
+ public Object getExtensionComponent() {
+ return mComponent;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ assumeExtensionSupportedDevice();
+ mComponent = getComponent(mComponentToFetch);
+ assumeNotNull(mComponent);
+ base.evaluate();
+ }
+ };
+ }
+
+ public WindowExtensionTestRule(Class<?> componentType) {
+ mComponentToFetch = componentType;
+ }
+
+ public Object getComponent(Class<?> componentToFetch) {
+ if (componentToFetch == WindowAreaComponent.class) {
+ return getExtensionWindowAreaComponent();
+ } else if (componentToFetch == WindowLayoutComponent.class) {
+ return getExtensionWindowLayoutComponent();
+ }
+ return null;
+ }
+}
diff --git a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
index 2c8c843..0495238 100644
--- a/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
+++ b/tests/framework/base/windowmanager/jetpack/src/android/server/wm/jetpack/utils/WindowManagerJetpackTestBase.java
@@ -16,12 +16,17 @@
package android.server.wm.jetpack.utils;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.PackageManager.FEATURE_SCREEN_LANDSCAPE;
+import static android.content.pm.PackageManager.FEATURE_SCREEN_PORTRAIT;
import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+import static android.server.wm.jetpack.utils.TestActivityLauncher.KEY_ACTIVITY_ID;
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -30,11 +35,13 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
import android.app.Activity;
import android.app.ActivityOptions;
import android.app.Application;
import android.app.Instrumentation;
+import android.app.PictureInPictureParams;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
@@ -57,8 +64,8 @@
/** Base class for all tests in the module. */
public class WindowManagerJetpackTestBase {
- public static final String ACTIVITY_ID_LABEL = "ActivityID";
public static final String EXTRA_EMBED_ACTIVITY = "EmbedActivity";
+ public static final String EXTRA_SPLIT_RATIO = "SplitRatio";
public Instrumentation mInstrumentation;
public Context mContext;
@@ -85,24 +92,45 @@
sVisibleActivities.clear();
}
- public Activity startActivityNewTask(@NonNull Class activityClass) {
+ protected boolean hasDeviceFeature(final String requiredFeature) {
+ return mContext.getPackageManager().hasSystemFeature(requiredFeature);
+ }
+
+ /**
+ * Rotation support is indicated by explicitly having both landscape and portrait
+ * features or not listing either at all.
+ */
+ protected void assumeSupportsRotation() {
+ final boolean supportsLandscape = hasDeviceFeature(FEATURE_SCREEN_LANDSCAPE);
+ final boolean supportsPortrait = hasDeviceFeature(FEATURE_SCREEN_PORTRAIT);
+ assumeTrue((supportsLandscape && supportsPortrait)
+ || (!supportsLandscape && !supportsPortrait));
+ }
+
+ public <T extends Activity> T startActivityNewTask(@NonNull Class<T> activityClass) {
return startActivityNewTask(activityClass, null /* activityId */);
}
- public Activity startActivityNewTask(@NonNull Class activityClass,
+ public <T extends Activity> T startActivityNewTask(@NonNull Class<T> activityClass,
@Nullable String activityId) {
- return startActivityNewTask(mContext, mInstrumentation, activityClass, activityId);
+ return launcherForActivityNewTask(activityClass, activityId, false /* isFullScreen */)
+ .launch(mInstrumentation);
}
- public static Activity startActivityNewTask(@NonNull Context context,
- @NonNull Instrumentation instrumentation, @NonNull Class activityClass,
+ public <T extends Activity> T startFullScreenActivityNewTask(@NonNull Class<T> activityClass,
@Nullable String activityId) {
- final Intent intent = new Intent(context, activityClass);
- intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
- if (activityId != null) {
- intent.putExtra(ACTIVITY_ID_LABEL, activityId);
- }
- return instrumentation.startActivitySync(intent);
+ return launcherForActivityNewTask(activityClass, activityId, true/* isFullScreen */)
+ .launch(mInstrumentation);
+ }
+
+ private <T extends Activity> TestActivityLauncher<T> launcherForActivityNewTask(
+ @NonNull Class<T> activityClass, @Nullable String activityId, boolean isFullScreen) {
+ final int windowingMode = isFullScreen ? WINDOWING_MODE_FULLSCREEN :
+ WINDOWING_MODE_UNDEFINED;
+ return new TestActivityLauncher<>(mContext, activityClass)
+ .addIntentFlag(FLAG_ACTIVITY_NEW_TASK)
+ .setActivityId(activityId)
+ .setWindowingMode(windowingMode);
}
/**
@@ -140,7 +168,7 @@
public static <T extends Activity> void startActivityFromActivity(Activity activityToLaunchFrom,
Class<T> activityToLaunchClass, String newActivityId) {
Intent intent = new Intent(activityToLaunchFrom, activityToLaunchClass);
- intent.putExtra(ACTIVITY_ID_LABEL, newActivityId);
+ intent.putExtra(KEY_ACTIVITY_ID, newActivityId);
activityToLaunchFrom.startActivity(intent);
}
@@ -153,7 +181,7 @@
Intent intent = new Intent();
intent.setClassName(activityToLaunchComponent.getPackageName(),
activityToLaunchComponent.getClassName());
- intent.putExtra(ACTIVITY_ID_LABEL, newActivityId);
+ intent.putExtra(KEY_ACTIVITY_ID, newActivityId);
intent.putExtras(extras);
activityToLaunchFrom.startActivity(intent);
}
@@ -209,6 +237,36 @@
assertEquals(orientation, activity.getResources().getConfiguration().orientation);
}
+ public static void enterPipActivityHandlesConfigChanges(TestActivity activity) {
+ if (activity.isInPictureInPictureMode()) {
+ throw new IllegalStateException("Activity must not be in PiP");
+ }
+ activity.resetLayoutCounter();
+ // Enter picture in picture
+ PictureInPictureParams params = (new PictureInPictureParams.Builder()).build();
+ activity.enterPictureInPictureMode(params);
+ // Wait for the activity to layout, which will happen after the Activity has been resized.
+ assertTrue(activity.waitForLayout());
+ // Check that Activity is in PiP.
+ assertTrue(activity.isInPictureInPictureMode());
+ }
+
+ public static void exitPipActivityHandlesConfigChanges(TestActivity activity) {
+ if (!activity.isInPictureInPictureMode()) {
+ throw new IllegalStateException("Activity must be in PiP");
+ }
+ activity.resetLayoutCounter();
+ // Launch the same Activity using the single top flag so that the PiP Activity will be
+ // expanded to full screen.
+ Intent intent = new Intent(activity, activity.getClass());
+ intent.addFlags(FLAG_ACTIVITY_SINGLE_TOP);
+ activity.startActivity(intent);
+ // Wait for the activity to layout, which will happen after the Activity has been resized.
+ assertTrue(activity.waitForLayout());
+ // Check that the Activity is not in PiP.
+ assertFalse(activity.isInPictureInPictureMode());
+ }
+
public static void setActivityOrientationActivityDoesNotHandleOrientationChanges(
TestActivity activity, int orientation) {
// Make sure that the provided orientation is a fixed orientation
@@ -234,6 +292,10 @@
* display rotates for orientation, then the maximum portrait bounds will be a rotated version
* of the maximum landscape bounds.
*/
+ // TODO(b/186631239): ActivityManagerTestBase#ignoresOrientationRequests could disable
+ // activity rotation, as a result the display area would remain in the old orientation while
+ // the activity orientation changes. We should check the existence of this request before
+ // running tests that compare orientation values.
public static boolean doesDisplayRotateForOrientation(@NonNull Rect portraitMaximumBounds,
@NonNull Rect landscapeMaximumBounds) {
return !portraitMaximumBounds.equals(landscapeMaximumBounds);
diff --git a/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar b/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
index 367e3b9..7eee6da 100644
--- a/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
+++ b/tests/framework/base/windowmanager/jetpack/window-extensions-release.aar
Binary files differ
diff --git a/tests/framework/base/windowmanager/res/values/styles.xml b/tests/framework/base/windowmanager/res/values/styles.xml
index 2945940..d9f0c15 100644
--- a/tests/framework/base/windowmanager/res/values/styles.xml
+++ b/tests/framework/base/windowmanager/res/values/styles.xml
@@ -57,4 +57,10 @@
<item name="android:statusBarColor">@android:color/transparent</item>
<item name="android:navigationBarColor">@android:color/transparent</item>
</style>
-</resources>
+ <style name="window_task_animation" parent="@android:style/Theme.DeviceDefault">
+ <item name="android:taskOpenEnterAnimation">@anim/alpha</item>
+ <item name="android:taskOpenExitAnimation">@anim/alpha</item>
+ <item name="android:taskCloseEnterAnimation">@anim/alpha</item>
+ <item name="android:taskCloseExitAnimation">@anim/alpha</item>
+ </style>
+ </resources>
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
index d47cc6d..073e584 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityTransitionTests.java
@@ -142,7 +142,7 @@
waitAndAssertTopResumedActivity(new ComponentName(mContext, TransitionActivity.class),
DEFAULT_DISPLAY, "Activity must be launched");
- latch.await(3, TimeUnit.SECONDS);
+ latch.await(5, TimeUnit.SECONDS);
final long totalTime = transitionEndTime.get() - transitionStartTime.get();
assertTrue("Actual transition duration should be in the range "
+ "<" + CUSTOM_ANIMATION_DURATION_RANGE.getLower() + ", "
@@ -182,6 +182,41 @@
+ "actual=" + totalTime, !CUSTOM_ANIMATION_DURATION_RANGE.contains(totalTime));
}
+ @Test
+ public void testTaskWindowAnimationOverrideDisabled() throws Exception {
+ final CountDownLatch latch = new CountDownLatch(1);
+ AtomicLong transitionStartTime = new AtomicLong();
+ AtomicLong transitionEndTime = new AtomicLong();
+
+ final ActivityOptions.OnAnimationStartedListener startedListener = transitionStartTime::set;
+ final ActivityOptions.OnAnimationFinishedListener finishedListener = (t) -> {
+ transitionEndTime.set(t);
+ latch.countDown();
+ };
+
+ // Overriding task transit animation is disabled, so default wallpaper close animation
+ // is played.
+ final Bundle bundle = ActivityOptions.makeCustomAnimation(mContext,
+ R.anim.alpha, 0 /* exitResId */, 0 /* backgroundColor */,
+ new Handler(Looper.getMainLooper()), startedListener, finishedListener).toBundle();
+
+ final ComponentName customWindowAnimationActivity = new ComponentName(
+ mContext, CustomWindowAnimationActivity.class);
+ final Intent intent = new Intent().setComponent(customWindowAnimationActivity)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ mContext.startActivity(intent, bundle);
+ mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+ waitAndAssertTopResumedActivity(customWindowAnimationActivity, DEFAULT_DISPLAY,
+ "Activity must be launched");
+
+ latch.await(5, TimeUnit.SECONDS);
+ final long totalTime = transitionEndTime.get() - transitionStartTime.get();
+ assertTrue("Actual transition duration should be out of the range "
+ + "<" + CUSTOM_ANIMATION_DURATION_RANGE.getLower() + ", "
+ + CUSTOM_ANIMATION_DURATION_RANGE.getUpper() + "> ms, "
+ + "actual=" + totalTime, !CUSTOM_ANIMATION_DURATION_RANGE.contains(totalTime));
+ }
+
/**
* Checks that the activity's theme's background color is used as the default animation's
* background color when no override is specified.
@@ -734,7 +769,7 @@
Bundle extras) {
final Intent i = new Intent(this, klass);
i.putExtras(extras);
- startActivity(i, activityOptions.toBundle());
+ startActivity(i, activityOptions != null ? activityOptions.toBundle() : null);
}
}
@@ -804,4 +839,6 @@
overridePendingTransition(enterAnim, R.anim.alpha_0);
}
}
+
+ public static class CustomWindowAnimationActivity extends Activity { }
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
index 9762bc4..eda914f 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/ActivityVisibilityTests.java
@@ -198,8 +198,7 @@
assumeTrue(supportsLockScreen());
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
- final boolean notSupportsInsecureLock = !supportsInsecureLock();
- if (notSupportsInsecureLock) {
+ if (!supportsInsecureLock()) {
lockScreenSession.setLockCredential();
}
final ActivitySessionClient activityClient = createManagedActivityClientSession();
@@ -207,11 +206,25 @@
true /* useWindowFlags */);
testTurnScreenOnActivity(lockScreenSession, activityClient,
false /* useWindowFlags */);
- if (notSupportsInsecureLock) {
- // In the platform without InsecureLock, we just test if the display is on with
- // TurnScreenOnActivity.
- mObjectTracker.close(lockScreenSession);
- }
+
+ // Start TURN_SCREEN_ON_ACTIVITY
+ launchActivity(TURN_SCREEN_ON_ACTIVITY, WINDOWING_MODE_FULLSCREEN);
+ mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+ assertTrue("Display turns on", isDisplayOn(DEFAULT_DISPLAY));
+
+ // Start another activity on top and put device to sleep
+ final ActivitySession activity = activityClient.startActivity(
+ getLaunchActivityBuilder().setUseInstrumentation()
+ .setWaitForLaunched(false).setTargetActivity(TOP_ACTIVITY));
+ waitAndAssertActivityState(TOP_ACTIVITY, STATE_STOPPED, "Activity must be stopped.");
+ lockScreenSession.sleepDevice();
+
+ // Finish the top activity and make sure the device still in sleep
+ activity.finish();
+ waitAndAssertActivityState(TURN_SCREEN_ON_ACTIVITY, STATE_STOPPED,
+ "Activity must be stopped");
+ mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, false);
+ assertFalse("Display must be remained OFF", isDisplayOn(DEFAULT_DISPLAY));
}
@Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
index 02457a6..4725b45 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/CompatChangeTests.java
@@ -518,9 +518,6 @@
*/
private void runSizeCompatTest(ComponentName activity, int windowingMode, double resizeRatio,
boolean inSizeCompatModeAfterResize) {
- // TODO(b/208918131): Remove once real cause is found.
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
-
launchActivity(activity, windowingMode);
assertSizeCompatMode(activity, /* expectedInSizeCompatMode= */ false);
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
index b3f2804..4a3503d 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/DreamManagerServiceTests.java
@@ -83,7 +83,7 @@
startFullscreenTestActivity();
mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);
- mDreamCoordinator.startDream(TEST_DREAM_SERVICE);
+ mDreamCoordinator.startDream();
waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
"Dream activity should be the top resumed activity");
mWmState.waitForValidState(mWmState.getHomeActivityName());
@@ -104,7 +104,7 @@
public void testDreamServiceStopsTimely() throws Exception {
mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);
- mDreamCoordinator.startDream(TEST_DREAM_SERVICE);
+ mDreamCoordinator.startDream();
waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
"Dream activity should be the top resumed activity");
mWmState.waitForValidState(mWmState.getHomeActivityName());
@@ -124,7 +124,7 @@
startFullscreenTestActivity();
mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_STUBBORN_DREAM_SERVICE);
- mDreamCoordinator.startDream(TEST_STUBBORN_DREAM_SERVICE);
+ mDreamCoordinator.startDream();
waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
"Dream activity should be the top resumed activity");
mWmState.waitForValidState(mWmState.getHomeActivityName());
@@ -149,7 +149,7 @@
final RotationSession rotationSession = createManagedRotationSession();
rotationSession.set(Surface.ROTATION_0);
mDreamActivityName = mDreamCoordinator.setActiveDream(TEST_DREAM_SERVICE);
- mDreamCoordinator.startDream(TEST_DREAM_SERVICE);
+ mDreamCoordinator.startDream();
rotationSession.set(Surface.ROTATION_90);
waitAndAssertTopResumedActivity(mDreamActivityName, DEFAULT_DISPLAY,
@@ -243,7 +243,7 @@
private class DreamingState implements AutoCloseable {
public DreamingState(ComponentName dream) {
mDreamActivityName = mDreamCoordinator.setActiveDream(dream);
- mDreamCoordinator.startDream(dream);
+ mDreamCoordinator.startDream();
waitAndAssertDreaming();
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeepScreenOnTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeepScreenOnTests.java
new file mode 100644
index 0000000..a97399e
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeepScreenOnTests.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.provider.Settings.System.SCREEN_OFF_TIMEOUT;
+import static android.server.wm.app.Components.TEST_ACTIVITY;
+import static android.server.wm.app.Components.TURN_SCREEN_ON_ACTIVITY;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeTrue;
+
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.provider.Settings;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class KeepScreenOnTests extends MultiDisplayTestBase {
+ private static final String TAG = "KeepScreenOnTests";
+ private String mInitialDisplayTimeout;
+ private PowerManager mPowerManager;
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mInitialDisplayTimeout =
+ Settings.System.getString(mContext.getContentResolver(), SCREEN_OFF_TIMEOUT);
+ mPowerManager = mContext.getSystemService(PowerManager.class);
+ }
+
+ @After
+ public void tearDown() {
+ setScreenOffTimeoutMs(mInitialDisplayTimeout);
+ }
+
+ @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+ @Test
+ public void testKeepScreenOn_activityOnDefaultDisplay_screenStaysOn() {
+ setScreenOffTimeoutMs("500");
+ launchActivity(TURN_SCREEN_ON_ACTIVITY);
+ assertTrue(mPowerManager.isInteractive());
+
+ SystemClock.sleep(getMinimumScreenOffTimeoutMs());
+
+ assertTrue(mPowerManager.isInteractive());
+ mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+ }
+
+ @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+ @Test
+ public void testKeepScreenOn_activityNotForeground_screenTurnsOff() {
+ setScreenOffTimeoutMs("500");
+
+ launchActivity(TURN_SCREEN_ON_ACTIVITY);
+ assertTrue(mPowerManager.isInteractive());
+ try (BlockingBroadcastReceiver r = BlockingBroadcastReceiver.create(mContext,
+ Intent.ACTION_SCREEN_OFF).register()) {
+ launchActivity(TEST_ACTIVITY);
+ }
+ mWmState.waitAndAssertVisibilityGone(TURN_SCREEN_ON_ACTIVITY);
+ assertFalse(mPowerManager.isInteractive());
+ }
+
+ @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+ @Test
+ public void testKeepScreenOn_activityOnVirtualDisplay_screenStaysOn() {
+ assumeTrue(supportsMultiDisplay());
+ setScreenOffTimeoutMs("500");
+
+ final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
+ launchActivityOnDisplay(TURN_SCREEN_ON_ACTIVITY, newDisplay.mId);
+ mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+ assertTrue(mPowerManager.isInteractive());
+
+ SystemClock.sleep(getMinimumScreenOffTimeoutMs());
+
+ assertTrue(mPowerManager.isInteractive());
+ mWmState.assertVisibility(TURN_SCREEN_ON_ACTIVITY, true);
+ }
+
+ @ApiTest(apis = "android.view.WindowManager.LayoutParams#FLAG_KEEP_SCREEN_ON")
+ @Test
+ public void testKeepScreenOn_activityOnVirtualDisplayNotForeground_screenTurnsOff() {
+ assumeTrue(supportsMultiDisplay());
+ setScreenOffTimeoutMs("500");
+
+ final WindowManagerState.DisplayContent newDisplay = createManagedVirtualDisplaySession()
+ .setSimulateDisplay(true).createDisplay();
+ launchActivityOnDisplay(TURN_SCREEN_ON_ACTIVITY, newDisplay.mId);
+ assertTrue(mPowerManager.isInteractive());
+ try (BlockingBroadcastReceiver r = BlockingBroadcastReceiver.create(mContext,
+ Intent.ACTION_SCREEN_OFF).register()) {
+ launchActivityOnDisplay(TEST_ACTIVITY, newDisplay.mId);
+ }
+ mWmState.waitAndAssertVisibilityGone(TURN_SCREEN_ON_ACTIVITY);
+ assertFalse(mPowerManager.isInteractive());
+ }
+
+ private void setScreenOffTimeoutMs(String timeoutMs) {
+ Settings.System.putString(
+ mContext.getContentResolver(), SCREEN_OFF_TIMEOUT, timeoutMs);
+ }
+
+ private int getMinimumScreenOffTimeoutMs() {
+ return mContext.getResources().getInteger(
+ Resources.getSystem().getIdentifier("config_minimumScreenOffTimeout", "integer",
+ "android"));
+ }
+}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
index d425363..47845f5 100755
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTests.java
@@ -45,9 +45,7 @@
import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
-import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.never;
@@ -62,7 +60,6 @@
import android.platform.test.annotations.Presubmit;
import android.server.wm.CommandSession.ActivitySession;
import android.server.wm.CommandSession.ActivitySessionClient;
-import android.server.wm.WindowManagerState.WindowState;
import android.server.wm.app.Components;
import androidx.test.filters.FlakyTest;
@@ -196,16 +193,13 @@
*/
@Test
public void testTranslucentShowWhenLockedActivity() {
- // TODO(b/209906849) remove assumeFalse after issue fix.
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
launchActivity(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
mWmState.computeState(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
mWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY);
- mWmState.computeState();
mWmState.assertVisibility(SHOW_WHEN_LOCKED_TRANSLUCENT_ACTIVITY, true);
- assertWallpaperShowing();
+ mWmState.waitAndAssertWindowShown(TYPE_WALLPAPER, true);
mWmState.assertKeyguardShowingAndOccluded();
}
@@ -230,16 +224,13 @@
@Test
public void testDialogShowWhenLockedActivity() {
- // TODO(b/209906849) remove assumeFalse after issue fix.
- assumeFalse(ENABLE_SHELL_TRANSITIONS);
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
launchActivity(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
mWmState.computeState(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
mWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
- lockScreenSession.gotoKeyguard();
- mWmState.computeState();
+ lockScreenSession.gotoKeyguard(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY);
mWmState.assertVisibility(SHOW_WHEN_LOCKED_DIALOG_ACTIVITY, true);
- assertWallpaperShowing();
+ mWmState.waitAndAssertWindowShown(TYPE_WALLPAPER, true);
mWmState.assertKeyguardShowingAndOccluded();
}
@@ -622,13 +613,6 @@
mWmState.waitAndAssertActivityRemoved(SHOW_WHEN_LOCKED_ACTIVITY);
}
- private void assertWallpaperShowing() {
- WindowState wallpaper =
- mWmState.findFirstWindowWithType(TYPE_WALLPAPER);
- assertNotNull(wallpaper);
- assertTrue(wallpaper.isSurfaceShown());
- }
-
@Test
public void testDismissKeyguardAttrActivity_method_turnScreenOn() {
final LockScreenSession lockScreenSession = createManagedLockScreenSession();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
index ab08a11..0dd0406 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/KeyguardTransitionTests.java
@@ -36,6 +36,8 @@
import android.platform.test.annotations.Presubmit;
+import androidx.test.filters.FlakyTest;
+
import org.junit.Before;
import org.junit.Test;
@@ -105,6 +107,7 @@
mWmState.getDefaultDisplayLastTransition());
}
+ @FlakyTest(bugId = 238776938)
@Test
public void testDismissKeyguard() {
createManagedLockScreenSession().gotoKeyguard();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
index 75aa696..eea2396 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/MultiDisplayTestBase.java
@@ -171,74 +171,6 @@
return result;
}
- public static class DisplayMetricsSession implements AutoCloseable {
- private final ReportedDisplayMetrics mInitialDisplayMetrics;
- private final int mDisplayId;
-
- DisplayMetricsSession(int displayId) {
- mDisplayId = displayId;
- mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
- }
-
- ReportedDisplayMetrics getInitialDisplayMetrics() {
- return mInitialDisplayMetrics;
- }
-
- ReportedDisplayMetrics getDisplayMetrics() {
- return ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
- }
-
- void changeAspectRatio(double aspectRatio, int orientation) {
- final Size originalSize = mInitialDisplayMetrics.physicalSize;
- final int smaller = Math.min(originalSize.getWidth(), originalSize.getHeight());
- final int larger = (int) (smaller * aspectRatio);
- Size overrideSize;
- if (orientation == ORIENTATION_LANDSCAPE) {
- overrideSize = new Size(larger, smaller);
- }
- else {
- overrideSize = new Size(smaller, larger);
- }
- overrideDisplayMetrics(overrideSize, mInitialDisplayMetrics.physicalDensity);
- }
-
- void changeDisplayMetrics(double sizeRatio, double densityRatio) {
- // Given a display may already have an override applied before the test is begun,
- // resize based upon the override.
- final Size originalSize;
- final int density;
- if (mInitialDisplayMetrics.overrideSize != null) {
- originalSize = mInitialDisplayMetrics.overrideSize;
- } else {
- originalSize = mInitialDisplayMetrics.physicalSize;
- }
-
- if (mInitialDisplayMetrics.overrideDensity != null) {
- density = mInitialDisplayMetrics.overrideDensity;
- } else {
- density = mInitialDisplayMetrics.physicalDensity;
- }
-
- final Size overrideSize = new Size((int)(originalSize.getWidth() * sizeRatio),
- (int)(originalSize.getHeight() * sizeRatio));
- final int overrideDensity = (int)(density * densityRatio);
- overrideDisplayMetrics(overrideSize, overrideDensity);
- }
-
- void overrideDisplayMetrics(final Size size, final int density) {
- mInitialDisplayMetrics.setDisplayMetrics(size, density);
- }
-
- void restoreDisplayMetrics() {
- mInitialDisplayMetrics.restoreDisplayMetrics();
- }
-
- @Override
- public void close() {
- restoreDisplayMetrics();
- }
- }
-
/** @see ObjectTracker#manage(AutoCloseable) */
protected DisplayMetricsSession createManagedDisplayMetricsSession(int displayId) {
return mObjectTracker.manage(new DisplayMetricsSession(displayId));
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 cd1fcda..5171898 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/PinnedStackTests.java
@@ -29,6 +29,7 @@
import static android.server.wm.CliIntentExtra.extraString;
import static android.server.wm.ComponentNameUtils.getActivityName;
import static android.server.wm.ComponentNameUtils.getWindowName;
+import static android.server.wm.UiDeviceUtils.pressBackButton;
import static android.server.wm.UiDeviceUtils.pressWindowButton;
import static android.server.wm.WindowManagerState.STATE_PAUSED;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
@@ -60,6 +61,7 @@
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_DENOMINATOR;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ASPECT_RATIO_NUMERATOR;
+import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_BACK_PRESSED;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PAUSE;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_PIP_REQUESTED;
import static android.server.wm.app.Components.PipActivity.EXTRA_ENTER_PIP_ON_USER_LEAVE_HINT;
@@ -328,6 +330,34 @@
}
@Test
+ public void testEnterPipOnBackPressed() {
+ // Launch a PiP activity that calls enterPictureInPictureMode when it receives
+ // onBackPressed callback.
+ launchActivity(PIP_ACTIVITY, extraString(EXTRA_ENTER_PIP_ON_BACK_PRESSED, "true"));
+
+ assertEnterPipOnBackPressed(PIP_ACTIVITY);
+ }
+
+ @Test
+ public void testEnterPipOnBackPressedWithAutoPipEnabled() {
+ // Launch the PIP activity that calls enterPictureInPictureMode when it receives
+ // onBackPressed callback and set its pip params to allow auto-pip.
+ launchActivity(PIP_ACTIVITY,
+ extraString(EXTRA_ALLOW_AUTO_PIP, "true"),
+ extraString(EXTRA_ENTER_PIP_ON_BACK_PRESSED, "true"));
+
+ assertEnterPipOnBackPressed(PIP_ACTIVITY);
+ }
+
+ private void assertEnterPipOnBackPressed(ComponentName componentName) {
+ // Press the back button.
+ pressBackButton();
+ // Assert that we have entered PiP.
+ waitForEnterPipAnimationComplete(componentName);
+ assertPinnedStackExists();
+ }
+
+ @Test
public void testEnterExpandedPipAspectRatio() {
assumeTrue(supportsExpandedPip());
launchActivity(PIP_ACTIVITY,
@@ -565,7 +595,7 @@
public void testDisallowPipLaunchFromStoppedActivity() {
// Launch the bottom pip activity which will launch a new activity on top and attempt to
// enter pip when it is stopped
- launchActivity(PIP_ON_STOP_ACTIVITY);
+ launchActivityNoWait(PIP_ON_STOP_ACTIVITY);
// Wait for the bottom pip activity to be stopped
mWmState.waitForActivityState(PIP_ON_STOP_ACTIVITY, STATE_STOPPED);
@@ -694,7 +724,7 @@
// Launch the PIP activity on pause, and have it start another activity on
// top of itself. Wait for the new activity to be visible and ensure that the pinned stack
// was not created in the process
- launchActivity(PIP_ACTIVITY,
+ launchActivityNoWait(PIP_ACTIVITY,
extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
extraString(EXTRA_START_ACTIVITY, getActivityName(NON_RESIZEABLE_ACTIVITY)));
mWmState.computeState(
@@ -714,9 +744,11 @@
// Launch the PIP activity on pause, and set it to finish itself after
// some period. Wait for the previous activity to be visible, and ensure that the pinned
// stack was not created in the process
- launchActivity(PIP_ACTIVITY,
+ launchActivityNoWait(PIP_ACTIVITY,
extraString(EXTRA_ENTER_PIP_ON_PAUSE, "true"),
extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
+ mWmState.computeState(
+ new WaitForValidActivityState(TEST_ACTIVITY));
assertPinnedStackDoesNotExist();
}
@@ -1016,7 +1048,7 @@
*/
@Test
public void testPipFromTaskWithAnotherFinishingActivity() {
- launchActivity(LAUNCH_ENTER_PIP_ACTIVITY,
+ launchActivityNoWait(LAUNCH_ENTER_PIP_ACTIVITY,
extraString(EXTRA_FINISH_SELF_ON_RESUME, "true"));
waitForEnterPip(PIP_ACTIVITY);
@@ -1113,8 +1145,7 @@
waitForOrFail("Task in lock mode", () -> {
return mAm.getLockTaskModeState() != LOCK_TASK_MODE_NONE;
});
- mBroadcastActionTrigger.doAction(ACTION_ENTER_PIP);
- waitForEnterPip(PIP_ACTIVITY);
+ mBroadcastActionTrigger.enterPipAndWait();
assertPinnedStackDoesNotExist();
launchHomeActivityNoWait();
mWmState.computeState();
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
index 4b7242e..7b75861b 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/SplitActivityLifecycleTest.java
@@ -18,6 +18,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.SplitActivityLifecycleTest.ActivityB.EXTRA_SHOW_WHEN_LOCKED;
@@ -494,6 +495,39 @@
}
/**
+ * Verifies the Activity in primary TaskFragment is no longer focused after clear adjacent
+ * TaskFragments.
+ */
+ @Test
+ public void testResetFocusedAppAfterClearAdjacentTaskFragment() {
+ // TODO(b/232476698) Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+
+ // Initialize test environment by launching Activity A and B side-by-side.
+ initializeSplitActivities(false /* verifyEmbeddedTask */);
+
+ // Request the focus on the primary TaskFragment
+ WindowContainerTransaction wct = new WindowContainerTransaction()
+ .requestFocusOnTaskFragment(mTaskFragA.getTaskFragToken());
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ waitForActivityFocused(5000, mActivityA);
+ assertThat(mWmState.getFocusedApp()).isEqualTo(mActivityA.flattenToShortString());
+
+ // Expand top TaskFragment and clear the adjacent TaskFragments to have the two
+ // TaskFragment stacked.
+ wct = new WindowContainerTransaction()
+ .setBounds(mTaskFragB.getToken(), new Rect())
+ .setWindowingMode(mTaskFragB.getToken(), WINDOWING_MODE_UNDEFINED)
+ .setAdjacentTaskFragments(mTaskFragA.getTaskFragToken(), null, null);
+ mTaskFragmentOrganizer.applyTransaction(wct);
+
+ // Ensure the Activity on primary TaskFragment is stopped and no longer focused.
+ waitAndAssertActivityState(mActivityA, STATE_STOPPED, "Activity A must be stopped");
+ assertThat(mWmState.getFocusedApp()).isNotEqualTo(mActivityA.flattenToShortString());
+ assertThat(mWmState.getFocusedWindow()).isEqualTo(mActivityB.flattenToShortString());
+ }
+
+ /**
* Verifies an Activity below adjacent translucent TaskFragments is visible.
*/
@Test
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java
index d49948f..4484568 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerPolicyTest.java
@@ -16,10 +16,13 @@
package android.server.wm;
+import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.TaskFragmentOrganizerTestBase.assertEmptyTaskFragment;
+import static android.server.wm.TaskFragmentOrganizerTestBase.assumeExtensionVersionAtLeast2;
import static android.server.wm.TaskFragmentOrganizerTestBase.getActivityToken;
+import static android.server.wm.TaskFragmentOrganizerTestBase.startNewActivity;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app30.Components.SDK_30_TEST_ACTIVITY;
@@ -30,9 +33,9 @@
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
import android.app.Activity;
-import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Intent;
import android.graphics.Rect;
@@ -50,9 +53,10 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
-import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ApiTest;
+
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@@ -70,8 +74,10 @@
@RunWith(AndroidJUnit4.class)
@Presubmit
public class TaskFragmentOrganizerPolicyTest extends ActivityManagerTestBase {
+
private TaskOrganizer mTaskOrganizer;
private BasicTaskFragmentOrganizer mTaskFragmentOrganizer;
+ private final ArrayList<BasicTaskFragmentOrganizer> mOrganizers = new ArrayList<>();
@Before
@Override
@@ -79,102 +85,592 @@
super.setUp();
mTaskFragmentOrganizer = new BasicTaskFragmentOrganizer();
mTaskFragmentOrganizer.registerOrganizer();
+ mOrganizers.add(mTaskFragmentOrganizer);
}
@After
public void tearDown() {
- if (mTaskFragmentOrganizer != null) {
- mTaskFragmentOrganizer.unregisterOrganizer();
+ for (TaskFragmentOrganizer organizer : mOrganizers) {
+ organizer.unregisterOrganizer();
+ }
+ mOrganizers.clear();
+ if (mTaskOrganizer != null) {
+ NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer());
}
}
/**
- * Verifies whether performing non-TaskFragment
- * {@link android.window.WindowContainerTransaction.HierarchyOp operations} on
- * {@link TaskFragmentOrganizer} without permission throws {@link SecurityException}.
+ * Verifies that performing {@link WindowContainerTransaction#createTaskFragment} will fail if
+ * the fragment token is not unique.
*/
- @Test(expected = SecurityException.class)
- public void testPerformNonTaskFragmentHierarchyOperation_ThrowException() {
- final List<TaskAppearedInfo> taskInfos = new ArrayList<>();
- try {
- // Register TaskOrganizer to obtain Task information.
- NestedShellPermission.run(() -> {
- mTaskOrganizer = new TaskOrganizer();
- taskInfos.addAll(mTaskOrganizer.registerOrganizer());
- });
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#createTaskFragment"})
+ public void testCreateTaskFragment_duplicatedFragmentToken_reportError() {
+ // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder existingFragmentToken = taskFragmentInfo.getFragmentToken();
+ final IBinder errorCallbackToken = new Binder();
- // It is expected to throw Security exception when TaskFragmentOrganizer performs a
- // non-TaskFragment hierarchy operation.
- final WindowContainerToken taskToken = taskInfos.get(0).getTaskInfo().getToken();
- final WindowContainerTransaction wct = new WindowContainerTransaction()
- .reorder(taskToken, true /* opTop */);
- mTaskFragmentOrganizer.applyTransaction(wct);
- } finally {
- if (mTaskOrganizer != null) {
- NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer());
- }
- }
+ // Request to create another TaskFragment using the existing fragment token.
+ final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(
+ existingFragmentToken, getActivityToken(activity), new Rect(),
+ WINDOWING_MODE_UNDEFINED);
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setErrorCallbackToken(errorCallbackToken)
+ .createTaskFragment(params);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ mTaskFragmentOrganizer.waitForTaskFragmentError();
+
+ assertThat(mTaskFragmentOrganizer.getThrowable()).isInstanceOf(
+ IllegalArgumentException.class);
+ assertThat(mTaskFragmentOrganizer.getErrorCallbackToken()).isEqualTo(errorCallbackToken);
}
/**
- * Verifies whether changing property on non-TaskFragment window container without permission
- * throws {@link SecurityException}.
+ * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on
+ * non-TaskFragment window will throw {@link SecurityException}.
*/
- @Test(expected = SecurityException.class)
- public void testSetPropertyOnNonTaskFragment_ThrowException() {
- final List<TaskAppearedInfo> taskInfos = new ArrayList<>();
- try {
- // Register TaskOrganizer to obtain Task information.
- NestedShellPermission.run(() -> {
- mTaskOrganizer = new TaskOrganizer();
- taskInfos.addAll(mTaskOrganizer.registerOrganizer());
- });
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#deleteTaskFragment"})
+ public void testDeleteTaskFragment_nonTaskFragmentWindow_throwException() {
+ final WindowContainerToken taskToken = getFirstTaskToken();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .deleteTaskFragment(taskToken);
- // It is expected to throw SecurityException when TaskFragmentOrganizer attempts to
- // change the property on non-TaskFragment container.
- final WindowContainerToken taskToken = taskInfos.get(0).getTaskInfo().getToken();
- final WindowContainerTransaction wct = new WindowContainerTransaction()
- .setBounds(taskToken, new Rect());
- mTaskFragmentOrganizer.applyTransaction(wct);
- } finally {
- if (mTaskOrganizer != null) {
- NestedShellPermission.run(() -> mTaskOrganizer.unregisterOrganizer());
- }
- }
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
}
/**
- * Verifies whether performing TaskFragment
- * {@link android.window.WindowContainerTransaction.HierarchyOp operations} on the TaskFragment
- * which is not organized by given {@link TaskFragmentOrganizer} throws
+ * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on
+ * non-organized TaskFragment will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#deleteTaskFragment"})
+ public void testDeleteTaskFragment_nonOrganizedTaskFragment_throwException() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .deleteTaskFragment(taskFragmentToken);
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#deleteTaskFragment} on organized
+ * TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#deleteTaskFragment"})
+ public void testDeleteTaskFragment_organizedTaskFragment() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .deleteTaskFragment(taskFragmentToken);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#setAdjacentRoots} on
+ * non-TaskFragment window will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setAdjacentRoots"})
+ public void testSetAdjacentRoots_nonTaskFragmentWindow_throwException() {
+ // TODO(b/232476698) The TestApi is changed. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final WindowContainerToken taskToken = getFirstTaskToken();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setAdjacentRoots(taskToken, taskToken);
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#setAdjacentRoots} on
+ * non-organized TaskFragment will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setAdjacentRoots"})
+ public void testSetAdjacentRoots_nonOrganizedTaskFragment_throwException() {
+ // TODO(b/232476698) The TestApi is changed. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken0 = taskFragmentInfo0.getToken();
+ final WindowContainerToken taskFragmentToken1 = taskFragmentInfo1.getToken();
+
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setAdjacentRoots(taskFragmentToken0, taskFragmentToken1);
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#setAdjacentRoots} on organized
+ * TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setAdjacentRoots"})
+ public void testSetAdjacentRoots_organizedTaskFragment() {
+ // TODO(b/232476698) The TestApi is changed. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken0 = taskFragmentInfo0.getToken();
+ final WindowContainerToken taskFragmentToken1 = taskFragmentInfo1.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setAdjacentRoots(taskFragmentToken0, taskFragmentToken1);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#reparentChildren} on
+ * non-TaskFragment window will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#reparentChildren"})
+ public void testReparentChildren_nonTaskFragmentWindow_throwException() {
+ final WindowContainerToken taskToken = getFirstTaskToken();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .reparentChildren(taskToken, null /* newParent */);
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#reparentChildren} on
+ * non-organized TaskFragment will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#reparentChildren"})
+ public void testReparentChildren_nonOrganizedTaskFragment_throwException() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .reparentChildren(taskFragmentToken, null /* newParent */);
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#reparentChildren} on organized
+ * TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#reparentChildren"})
+ public void testReparent_organizedTaskFragment() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .reparentChildren(taskFragmentToken, null /* newParent */);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#startActivityInTaskFragment} on
+ * non-organized TaskFragment will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#startActivityInTaskFragment"})
+ public void testStartActivityInTaskFragment_nonOrganizedTaskFragment_throwException() {
+ // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ final IBinder callerToken = getActivityToken(activity);
+ final Intent intent = new Intent(mContext, TestActivity.class);
+
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .startActivityInTaskFragment(fragmentToken, callerToken, intent,
+ null /* activityOptions */);
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#startActivityInTaskFragment} on
+ * organized TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#startActivityInTaskFragment"})
+ public void testStartActivityInTaskFragment_organizedTaskFragment() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ final IBinder callerToken = getActivityToken(activity);
+ final Intent intent = new Intent(mContext, TestActivity.class);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .startActivityInTaskFragment(fragmentToken, callerToken, intent,
+ null /* activityOptions */);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#requestFocusOnTaskFragment} on
+ * non-organized TaskFragment will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#requestFocusOnTaskFragment"})
+ public void testRequestFocusOnTaskFragment_nonOrganizedTaskFragment_throwException() {
+ // TODO(b/232476698) The TestApi is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .requestFocusOnTaskFragment(fragmentToken);
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#requestFocusOnTaskFragment} on
+ * organized TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#requestFocusOnTaskFragment"})
+ public void testRequestFocusOnTaskFragment_organizedTaskFragment() {
+ // TODO(b/232476698) The TestApi is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .requestFocusOnTaskFragment(fragmentToken);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#reparentActivityToTaskFragment} on
+ * non-organized TaskFragment will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#reparentActivityToTaskFragment"})
+ public void testReparentActivityToTaskFragment_nonOrganizedTaskFragment_throwException() {
+ // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ final IBinder activityToken = getActivityToken(activity);
+
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .reparentActivityToTaskFragment(fragmentToken, activityToken);
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#reparentActivityToTaskFragment} on
+ * organized TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#startActivityInTaskFragment"})
+ public void testReparentActivityToTaskFragment_organizedTaskFragment() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken = taskFragmentInfo.getFragmentToken();
+ final IBinder activityToken = getActivityToken(activity);
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .reparentActivityToTaskFragment(fragmentToken, activityToken);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#setAdjacentTaskFragments} on
+ * non-organized TaskFragment will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setAdjacentTaskFragments"})
+ public void testSetAdjacentTaskFragments_nonOrganizedTaskFragment_throwException() {
+ // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken0 = taskFragmentInfo0.getFragmentToken();
+ final IBinder fragmentToken1 = taskFragmentInfo1.getFragmentToken();
+
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setAdjacentTaskFragments(fragmentToken0, fragmentToken1, null /* params */);
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#setAdjacentTaskFragments} on
+ * organized TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setAdjacentTaskFragments"})
+ public void testSetAdjacentTaskFragments_organizedTaskFragment() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final IBinder fragmentToken0 = taskFragmentInfo0.getFragmentToken();
+ final IBinder fragmentToken1 = taskFragmentInfo1.getFragmentToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setAdjacentTaskFragments(fragmentToken0, fragmentToken1, null /* params */);
+
+ mTaskFragmentOrganizer.applyTransaction(wct);
+ }
+
+ /**
+ * Verifies that changing property on non-TaskFragment window will throw
* {@link SecurityException}.
*/
- @Test(expected = SecurityException.class)
- public void testPerformOperationsOnNonOrganizedTaskFragment_ThrowException() {
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setBounds"})
+ public void testSetProperty_nonTaskFragmentWindow_throwException() {
+ final WindowContainerToken taskToken = getFirstTaskToken();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setBounds(taskToken, new Rect());
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that changing property on non-organized TaskFragment will throw
+ * {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setBounds"})
+ public void testSetProperty_nonOrganizedTaskFragment_throwException() {
final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
- // Create a TaskFragment with a TaskFragmentOrganizer.
- final TaskFragmentCreationParams params = mTaskFragmentOrganizer.generateTaskFragParams(
- getActivityToken(activity));
- final IBinder taskFragToken = params.getFragmentToken();
- WindowContainerTransaction wct = new WindowContainerTransaction()
- .createTaskFragment(params);
+ // Create another TaskFragmentOrganizer to request operation.
+ final TaskFragmentOrganizer anotherOrganizer = registerNewOrganizer();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setBounds(taskFragmentToken, new Rect());
+
+ assertThrows(SecurityException.class, () -> anotherOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that changing property on organized TaskFragment is allowed.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setBounds"})
+ public void testSetProperty_organizedTaskFragment() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setBounds(taskFragmentToken, new Rect());
+
mTaskFragmentOrganizer.applyTransaction(wct);
- // Wait for TaskFragment's creation to obtain its WindowContainerToken.
- mTaskFragmentOrganizer.waitForTaskFragmentCreated();
+ }
- // Create another TaskFragmentOrganizer
- final TaskFragmentOrganizer anotherOrganizer = new TaskFragmentOrganizer(Runnable::run);
- anotherOrganizer.registerOrganizer();
- // Try to perform an operation on the TaskFragment when is organized by the previous
- // TaskFragmentOrganizer.
- wct = new WindowContainerTransaction()
- .deleteTaskFragment(mTaskFragmentOrganizer.getTaskFragmentInfo(taskFragToken)
- .getToken());
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#reparent} from
+ * TaskFragmentOrganizer will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#reparent"})
+ public void testDisallowOperation_reparent() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo0 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final TaskFragmentInfo taskFragmentInfo1 = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken0 = taskFragmentInfo0.getToken();
+ final WindowContainerToken taskFragmentToken1 = taskFragmentInfo1.getToken();
- // It is expected to throw SecurityException when performing operations on the TaskFragment
- // which is not organized by the same TaskFragmentOrganizer.
- anotherOrganizer.applyTransaction(wct);
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .reparent(taskFragmentToken0, taskFragmentToken1, true /* onTop */);
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#reorder} from
+ * TaskFragmentOrganizer will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#reorder"})
+ public void testDisallowOperation_reorder() {
+ // TODO(b/232476698) The enforcement is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .reorder(taskFragmentToken, true /* onTop */);
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#setLaunchRoot} from
+ * TaskFragmentOrganizer will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setLaunchRoot"})
+ public void testDisallowOperation_setLaunchRoot() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setLaunchRoot(taskFragmentToken, null /* windowingModes */,
+ null /* activityTypes */);
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#setLaunchAdjacentFlagRoot} from
+ * TaskFragmentOrganizer will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#setLaunchAdjacentFlagRoot"})
+ public void testDisallowOperation_setLaunchAdjacentFlagRoot() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .setLaunchAdjacentFlagRoot(taskFragmentToken);
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
+ }
+
+ /**
+ * Verifies that performing {@link WindowContainerTransaction#clearLaunchAdjacentFlagRoot} from
+ * TaskFragmentOrganizer will throw {@link SecurityException}.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#clearLaunchAdjacentFlagRoot"})
+ public void testDisallowOperation_clearLaunchAdjacentFlagRoot() {
+ final Activity activity = startNewActivity();
+ final TaskFragmentInfo taskFragmentInfo = createOrganizedTaskFragment(
+ mTaskFragmentOrganizer, activity);
+ final WindowContainerToken taskFragmentToken = taskFragmentInfo.getToken();
+
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .clearLaunchAdjacentFlagRoot(taskFragmentToken);
+
+ assertThrows(SecurityException.class, () -> mTaskFragmentOrganizer.applyTransaction(wct));
}
/**
@@ -349,15 +845,47 @@
});
}
}
-
- private static Activity startNewActivity() {
- return startNewActivity(TestActivity.class);
+ /**
+ * Creates and registers a {@link TaskFragmentOrganizer} that will be unregistered in
+ * {@link #tearDown()}.
+ */
+ private BasicTaskFragmentOrganizer registerNewOrganizer() {
+ final BasicTaskFragmentOrganizer organizer = new BasicTaskFragmentOrganizer();
+ organizer.registerOrganizer();
+ mOrganizers.add(organizer);
+ return organizer;
}
- private static Activity startNewActivity(Class<?> className) {
- final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
- final Intent intent = new Intent(instrumentation.getTargetContext(), className)
- .addFlags(FLAG_ACTIVITY_NEW_TASK);
- return instrumentation.startActivitySync(intent);
+ /**
+ * Registers a {@link TaskOrganizer} to get the {@link WindowContainerToken} of a Task. The
+ * organizer will be unregistered in {@link #tearDown()}.
+ */
+ private WindowContainerToken getFirstTaskToken() {
+ final List<TaskAppearedInfo> taskInfos = new ArrayList<>();
+ // Register TaskOrganizer to obtain Task information.
+ NestedShellPermission.run(() -> {
+ mTaskOrganizer = new TaskOrganizer();
+ taskInfos.addAll(mTaskOrganizer.registerOrganizer());
+ });
+ return taskInfos.get(0).getTaskInfo().getToken();
+ }
+
+ /**
+ * Creates a TaskFragment organized by the given organizer. The TaskFragment will be removed
+ * when the organizer is unregistered.
+ */
+ private static TaskFragmentInfo createOrganizedTaskFragment(
+ BasicTaskFragmentOrganizer organizer, Activity ownerActivity) {
+ // Create a TaskFragment with a TaskFragmentOrganizer.
+ final TaskFragmentCreationParams params = organizer.generateTaskFragParams(
+ getActivityToken(ownerActivity));
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .createTaskFragment(params);
+ organizer.applyTransaction(wct);
+
+ // Wait for TaskFragment's creation to obtain its info.
+ organizer.waitForTaskFragmentCreated();
+ organizer.resetLatch();
+ return organizer.getTaskFragmentInfo(params.getFragmentToken());
}
}
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java
index 2621667..c9b7191 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTest.java
@@ -19,6 +19,7 @@
import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static android.server.wm.WindowManagerState.STATE_STOPPED;
+import static android.view.Display.DEFAULT_DISPLAY;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -44,6 +45,8 @@
import android.window.WindowContainerToken;
import android.window.WindowContainerTransaction;
+import com.android.compatibility.common.util.ApiTest;
+
import org.junit.Test;
/**
@@ -207,6 +210,28 @@
}
/**
+ * Verifies the behavior of {@link WindowContainerTransaction#finishActivity(IBinder)} to finish
+ * an Activity.
+ */
+ @Test
+ @ApiTest(apis = {
+ "android.window.TaskFragmentOrganizer#applyTransaction",
+ "android.window.WindowContainerTransaction#finishActivity"})
+ public void testFinishActivity() {
+ // TODO(b/232476698) The TestApi is new. Remove the assume in the next release.
+ assumeExtensionVersionAtLeast2();
+
+ final Activity activity = startNewActivity();
+ final WindowContainerTransaction wct = new WindowContainerTransaction()
+ .finishActivity(getActivityToken(activity));
+ mTaskFragmentOrganizer.applyTransaction(wct);
+
+ mWmState.waitForAppTransitionIdleOnDisplay(DEFAULT_DISPLAY);
+
+ assertTrue(activity.isDestroyed());
+ }
+
+ /**
* Verifies the visibility of an activity behind a TaskFragment that has the same
* bounds of the host Task.
*/
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
index 1ec0a11..93b9aff 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentOrganizerTestBase.java
@@ -17,14 +17,17 @@
package android.server.wm;
import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.server.wm.WindowManagerState.STATE_RESUMED;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeTrue;
import android.app.Activity;
+import android.app.Instrumentation;
import android.content.ComponentName;
import android.content.Intent;
import android.content.res.Configuration;
@@ -33,6 +36,7 @@
import android.os.IBinder;
import android.server.wm.WindowContextTests.TestActivity;
import android.server.wm.WindowManagerState.WindowContainer;
+import android.server.wm.jetpack.utils.ExtensionUtil;
import android.util.ArrayMap;
import android.window.TaskFragmentCreationParams;
import android.window.TaskFragmentInfo;
@@ -41,6 +45,7 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.test.InstrumentationRegistry;
import org.junit.After;
import org.junit.Before;
@@ -198,6 +203,23 @@
WINDOWING_MODE_UNDEFINED);
}
+ static Activity startNewActivity() {
+ return startNewActivity(TestActivity.class);
+ }
+
+ static Activity startNewActivity(Class<?> className) {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ final Intent intent = new Intent(instrumentation.getTargetContext(), className)
+ .addFlags(FLAG_ACTIVITY_NEW_TASK);
+ return instrumentation.startActivitySync(intent);
+ }
+
+ /** For API changes that are introduced together with WM Extensions version 2. */
+ static void assumeExtensionVersionAtLeast2() {
+ // TODO(b/232476698) Remove in the next Android release.
+ assumeTrue(ExtensionUtil.getExtensionVersion().getMajor() >= 2);
+ }
+
public static class BasicTaskFragmentOrganizer extends TaskFragmentOrganizer {
private final static int WAIT_TIMEOUT_IN_SECOND = 10;
@@ -257,7 +279,13 @@
@NonNull
public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder ownerToken,
@NonNull Rect bounds, int windowingMode) {
- return new TaskFragmentCreationParams.Builder(getOrganizerToken(), new Binder(),
+ return generateTaskFragParams(new Binder(), ownerToken, bounds, windowingMode);
+ }
+
+ @NonNull
+ public TaskFragmentCreationParams generateTaskFragParams(@NonNull IBinder fragmentToken,
+ @NonNull IBinder ownerToken, @NonNull Rect bounds, int windowingMode) {
+ return new TaskFragmentCreationParams.Builder(getOrganizerToken(), fragmentToken,
ownerToken)
.setInitialBounds(bounds)
.setWindowingMode(windowingMode)
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java
index 9fd1a41..d3bd635 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/TaskFragmentTrustedModeTest.java
@@ -21,7 +21,6 @@
import static android.server.wm.jetpack.utils.ActivityEmbeddingUtil.assumeActivityEmbeddingSupportedDevice;
import static com.google.common.truth.Truth.assertThat;
-import static com.google.common.truth.Truth.assertWithMessage;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -251,7 +250,7 @@
*/
@Test
public void testUntrustedModeTaskFragment_startActivityInTaskFragmentOutsideOfParentBounds() {
- Task parentTask = mWmState.getRootTask(mOwnerTaskId);
+ final Task parentTask = mWmState.getRootTask(mOwnerTaskId);
final Rect parentBounds = new Rect(parentTask.getBounds());
final IBinder errorCallbackToken = new Binder();
final WindowContainerTransaction wct = new WindowContainerTransaction()
@@ -266,11 +265,6 @@
// It is disallowed to start activity to TaskFragment with bounds outside of its parent
// in untrusted mode.
assertTaskFragmentError(errorCallbackToken, SecurityException.class);
-
- parentTask = mWmState.getRootTask(mOwnerTaskId);
- assertWithMessage("Activity must be started in parent Task because it's not"
- + " allowed to be embedded").that(parentTask.mActivities).contains(
- mWmState.getActivity(SECOND_UNTRUSTED_EMBEDDING_ACTIVITY));
}
/**
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
index 0281f55..cfa91f5 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationControllerTests.java
@@ -33,7 +33,6 @@
import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.hasItem;
-import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
@@ -51,6 +50,7 @@
import android.animation.ValueAnimator;
import android.app.Instrumentation;
import android.graphics.Insets;
+import android.os.Bundle;
import android.os.CancellationSignal;
import android.platform.test.annotations.Presubmit;
import android.server.wm.WindowInsetsAnimationTestBase.TestActivity;
@@ -105,7 +105,7 @@
@RunWith(Parameterized.class)
public class WindowInsetsAnimationControllerTests extends WindowManagerTestBase {
- TestActivity mActivity;
+ ControllerTestActivity mActivity;
View mRootView;
ControlListener mListener;
CancellationSignal mCancellationSignal = new CancellationSignal();
@@ -139,6 +139,16 @@
};
}
+ public static class ControllerTestActivity extends TestActivity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ // Ensure to set animation callback to null before starting a test. Otherwise, launching
+ // this activity might trigger some inset animation accidentally.
+ mView.setWindowInsetsAnimationCallback(null);
+ }
+ }
+
@Before
public void setUpWindowInsetsAnimationControllerTests() throws Throwable {
assumeFalse(
@@ -152,7 +162,8 @@
assumeThat(MockImeSession.getUnavailabilityReason(instrumentation.getContext()),
nullValue());
- // For the best test stability MockIme should be selected before launching TestActivity.
+ // For the best test stability MockIme should be selected before launching
+ // ControllerTestActivity.
mMockImeSession = MockImeSession.create(
instrumentation.getContext(), instrumentation.getUiAutomation(),
new ImeSettings.Builder());
@@ -161,14 +172,15 @@
mockImeEventStream = null;
}
- mActivity = startActivityInWindowingMode(TestActivity.class, WINDOWING_MODE_FULLSCREEN);
+ mActivity = startActivityInWindowingMode(ControllerTestActivity.class,
+ WINDOWING_MODE_FULLSCREEN);
mRootView = mActivity.getWindow().getDecorView();
mListener = new ControlListener(mErrorCollector);
assumeTestCompatibility();
if (mockImeEventStream != null) {
- // TestActivity has a focused EditText. Hence MockIme should receive onStartInput() for
- // that EditText within a reasonable time.
+ // ControllerTestActivity has a focused EditText. Hence MockIme should receive
+ // onStartInput() for that EditText within a reasonable time.
expectEvent(mockImeEventStream,
editorMatcher("onStartInput", mActivity.getEditTextMarker()),
TimeUnit.SECONDS.toMillis(10));
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
index 87c0fc8..2fcf5ce 100644
--- a/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowInsetsAnimationTests.java
@@ -258,7 +258,8 @@
waitForOrFail("Waiting until animation done", () -> mActivity.mCallback.animationDone);
- assertFalse(getWmState().isWindowVisible("StatusBar"));
+ mWmState.computeState();
+ assertFalse(mWmState.isWindowVisible("StatusBar"));
verify(mActivity.mCallback).onPrepare(any());
verify(mActivity.mCallback).onStart(any(), any());
verify(mActivity.mCallback, atLeastOnce()).onProgress(any(), any());
diff --git a/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerReflectionTests.java b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerReflectionTests.java
new file mode 100644
index 0000000..63bbcda
--- /dev/null
+++ b/tests/framework/base/windowmanager/src/android/server/wm/WindowManagerReflectionTests.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.server.wm;
+
+
+import static org.junit.Assert.assertThrows;
+
+import android.platform.test.annotations.Presubmit;
+
+import org.junit.Test;
+import org.junit.function.ThrowingRunnable;
+
+import java.lang.reflect.InvocationTargetException;
+
+@Presubmit
+public class WindowManagerReflectionTests {
+
+ /** Regression test for b/273906410. */
+ @Test
+ public void requestAppKeyboardShortcuts_requiresPermission_b273906410() throws Throwable {
+ Object wms = Class.forName("android.view.WindowManagerGlobal")
+ .getMethod("getWindowManagerService")
+ .invoke(null);
+
+ assertThrows(SecurityException.class, () -> {
+ runAndUnwrapTargetException(() -> {
+ Class.forName("android.view.IWindowManager")
+ .getMethod("requestAppKeyboardShortcuts",
+ Class.forName("com.android.internal.os.IResultReceiver"),
+ Integer.TYPE)
+ .invoke(wms, null, 0);
+ });
+ });
+ }
+
+ private void runAndUnwrapTargetException(ThrowingRunnable r) throws Throwable {
+ try {
+ r.run();
+ } catch (InvocationTargetException e) {
+ throw e.getTargetException();
+ }
+ }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
index 2c6a21f..a5f0beb 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/ActivityManagerTestBase.java
@@ -91,6 +91,7 @@
import static android.server.wm.app.Components.LAUNCHING_ACTIVITY;
import static android.server.wm.app.Components.LaunchingActivity.KEY_FINISH_BEFORE_LAUNCH;
import static android.server.wm.app.Components.PipActivity.ACTION_CHANGE_ASPECT_RATIO;
+import static android.server.wm.app.Components.PipActivity.ACTION_ENTER_PIP;
import static android.server.wm.app.Components.PipActivity.ACTION_EXPAND_PIP;
import static android.server.wm.app.Components.PipActivity.ACTION_SET_REQUESTED_ORIENTATION;
import static android.server.wm.app.Components.PipActivity.ACTION_UPDATE_PIP_STATE;
@@ -191,6 +192,8 @@
import java.util.List;
import java.util.Objects;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.BooleanSupplier;
import java.util.function.Consumer;
@@ -433,6 +436,19 @@
.putExtra(EXTRA_DISMISS_KEYGUARD_METHOD, true));
}
+ void enterPipAndWait() {
+ try {
+ final CompletableFuture<Boolean> future = new CompletableFuture<>();
+ final RemoteCallback remoteCallback = new RemoteCallback(
+ (Bundle result) -> future.complete(true));
+ mContext.sendBroadcast(createIntentWithAction(ACTION_ENTER_PIP)
+ .putExtra(EXTRA_SET_PIP_CALLBACK, remoteCallback));
+ assertTrue(future.get(5000, TimeUnit.MILLISECONDS));
+ } catch (Exception e) {
+ logE("enterPipAndWait failed", e);
+ }
+ }
+
void expandPip() {
mContext.sendBroadcast(createIntentWithAction(ACTION_EXPAND_PIP));
}
@@ -628,6 +644,8 @@
pressUnlockButton();
}
launchHomeActivityNoWait();
+ // TODO(b/242933292): Consider removing all the tasks belonging to android.server.wm
+ // instead of removing all and then waiting for allActivitiesResumed.
removeRootTasksWithActivityTypes(ALL_ACTIVITY_TYPE_BUT_HOME);
runWithShellPermission(() -> {
@@ -637,6 +655,14 @@
// state.
mAtm.clearLaunchParamsForPackages(TEST_PACKAGES);
});
+
+ // removeRootTaskWithActivityTypes() removes all the tasks apart from home. In a few cases,
+ // the systemUI might have a few tasks that need to be displayed all the time.
+ // For such tasks, systemUI might have a restart-logic that restarts those tasks. Those
+ // restarts can interfere with the test state. To avoid that, its better to wait for all
+ // the activities to come in the resumed state.
+ mWmState.waitForWithAmState(WindowManagerState::allActivitiesResumed, "Root Tasks should "
+ + "be either empty or resumed");
}
/** It always executes after {@link org.junit.After}. */
diff --git a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateUtils.java b/tests/framework/base/windowmanager/util/src/android/server/wm/DeviceStateUtils.java
similarity index 92%
rename from tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateUtils.java
rename to tests/framework/base/windowmanager/util/src/android/server/wm/DeviceStateUtils.java
index df97832..55d0926 100644
--- a/tests/devicestate/src/android/hardware/devicestate/cts/DeviceStateUtils.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/DeviceStateUtils.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-package android.hardware.devicestate.cts;
+package android.server.wm;
import static android.hardware.devicestate.DeviceStateManager.MAXIMUM_DEVICE_STATE;
import static android.hardware.devicestate.DeviceStateManager.MINIMUM_DEVICE_STATE;
@@ -26,7 +26,7 @@
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.ThrowingRunnable;
-/** Utility methods for {@DeviceStateManager} CTS tests. */
+/** Utility methods for CTS tests requiring the use of {@DeviceStateManager}. */
public final class DeviceStateUtils {
/**
* Runs the supplied {@code runnable} with the
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/DisplayMetricsSession.java b/tests/framework/base/windowmanager/util/src/android/server/wm/DisplayMetricsSession.java
new file mode 100644
index 0000000..86eea03
--- /dev/null
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/DisplayMetricsSession.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.server.wm;
+
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
+import static android.server.wm.ActivityManagerTestBase.ReportedDisplayMetrics;
+
+import android.util.Size;
+
+/**
+ * A test helper class that queries or changes the latest DisplayMetrics override in WM by
+ * sending Shell wm commands. Provides handling for errors, orientation types and fallback
+ * behaviors.
+ * Example usage: This can be used to resize a Display in CTS test and trigger configuration
+ * changes.
+ */
+public class DisplayMetricsSession implements AutoCloseable {
+ private final ReportedDisplayMetrics mInitialDisplayMetrics;
+ private final int mDisplayId;
+
+ public DisplayMetricsSession(int displayId) {
+ mDisplayId = displayId;
+ mInitialDisplayMetrics = ReportedDisplayMetrics.getDisplayMetrics(
+ mDisplayId);
+ }
+
+ ReportedDisplayMetrics getInitialDisplayMetrics() {
+ return mInitialDisplayMetrics;
+ }
+
+ ReportedDisplayMetrics getDisplayMetrics() {
+ return ReportedDisplayMetrics.getDisplayMetrics(mDisplayId);
+ }
+
+ void changeAspectRatio(double aspectRatio, int orientation) {
+ final Size originalSize = mInitialDisplayMetrics.physicalSize;
+ final int smaller = Math.min(originalSize.getWidth(), originalSize.getHeight());
+ final int larger = (int) (smaller * aspectRatio);
+ Size overrideSize;
+ if (orientation == ORIENTATION_LANDSCAPE) {
+ overrideSize = new Size(larger, smaller);
+ } else {
+ overrideSize = new Size(smaller, larger);
+ }
+ overrideDisplayMetrics(overrideSize, mInitialDisplayMetrics.physicalDensity);
+ }
+
+ public void changeDisplayMetrics(double sizeRatio, double densityRatio) {
+ // Given a display may already have an override applied before the test is begun,
+ // resize based upon the override.
+ final Size originalSize;
+ final int density;
+ if (mInitialDisplayMetrics.overrideSize != null) {
+ originalSize = mInitialDisplayMetrics.overrideSize;
+ } else {
+ originalSize = mInitialDisplayMetrics.physicalSize;
+ }
+
+ if (mInitialDisplayMetrics.overrideDensity != null) {
+ density = mInitialDisplayMetrics.overrideDensity;
+ } else {
+ density = mInitialDisplayMetrics.physicalDensity;
+ }
+
+ final Size overrideSize = new Size((int)(originalSize.getWidth() * sizeRatio),
+ (int)(originalSize.getHeight() * sizeRatio));
+ final int overrideDensity = (int)(density * densityRatio);
+ overrideDisplayMetrics(overrideSize, overrideDensity);
+ }
+
+ void overrideDisplayMetrics(final Size size, final int density) {
+ mInitialDisplayMetrics.setDisplayMetrics(size, density);
+ }
+
+ public void restoreDisplayMetrics() {
+ mInitialDisplayMetrics.restoreDisplayMetrics();
+ }
+
+ @Override
+ public void close() {
+ restoreDisplayMetrics();
+ }
+}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java b/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
index 3cdd57a..ced0497 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/DreamCoordinator.java
@@ -25,14 +25,13 @@
import com.android.compatibility.common.util.SystemUtil;
public class DreamCoordinator {
- private Context mContext;
+ private final DreamManager mDreamManager;
+
private boolean mSetup;
private boolean mDefaultDreamServiceEnabled;
- private DreamManager mDreamManager;
public DreamCoordinator(Context context) {
- mContext = context;
- mDreamManager = mContext.getSystemService(DreamManager.class);
+ mDreamManager = context.getSystemService(DreamManager.class);
}
public final ComponentName getDreamActivityName(ComponentName dream) {
@@ -60,7 +59,7 @@
* Restores any settings changed by {@link #setup()}.
*/
public void restoreDefaults() {
- // If we have not setup the coordinator, do not do anything.
+ // If we have not set up the coordinator, do not do anything.
if (!mSetup) {
return;
}
@@ -76,24 +75,30 @@
() -> mDreamManager.setScreensaverEnabled(false));
}
- public void startDream(ComponentName name) {
- SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.startDream(name));
+ public void startDream() {
+ SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.startDream());
}
public void stopDream() {
- SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.stopDream());
+ SystemUtil.runWithShellPermissionIdentity(mDreamManager::stopDream);
}
- public ComponentName setActiveDream(ComponentName dream) {
+ public ComponentName setActiveDream(ComponentName dream) {
SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.setActiveDream(dream));
return getDreamActivityName(dream);
}
+ public ComponentName setSystemDream(ComponentName dream) {
+ SystemUtil.runWithShellPermissionIdentity(() ->
+ mDreamManager.setSystemDreamComponent(dream));
+ return dream == null ? null : getDreamActivityName(dream);
+ }
+
public void setDreamOverlay(ComponentName overlay) {
SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.setDreamOverlay(overlay));
}
public boolean isDreaming() {
- return SystemUtil.runWithShellPermissionIdentity(() -> mDreamManager.isDreaming());
+ return SystemUtil.runWithShellPermissionIdentity(mDreamManager::isDreaming);
}
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
index 6f6edf9..7ee2b81 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerState.java
@@ -976,6 +976,15 @@
.collect(Collectors.toList());
}
+ public boolean allActivitiesResumed() {
+ for (Task rootTask : mRootTasks) {
+ final Activity nonResumedActivity =
+ rootTask.getActivity((a) -> !a.state.equals(STATE_RESUMED));
+ if (nonResumedActivity != null) return false;
+ }
+ return true;
+ }
+
public boolean hasNotificationShade() {
computeState();
return !getMatchingWindowType(TYPE_NOTIFICATION_SHADE).isEmpty();
@@ -1225,27 +1234,23 @@
// don't treat them as regular root tasks
collectDescendantsOfTypeIf(Task.class, t -> t.isRootTask(), this,
mRootTasks);
- ArrayList<Task> rootOrganizedTasks = new ArrayList<>();
- for (int i = mRootTasks.size() -1; i >= 0; --i) {
+
+ ArrayList<Task> nonOrganizedRootTasks = new ArrayList<>();
+ for (int i = 0; i < mRootTasks.size(); i++) {
final Task task = mRootTasks.get(i);
- // Skip tasks created by an organizer
if (task.mCreatedByOrganizer) {
- mRootTasks.remove(task);
- rootOrganizedTasks.add(task);
+ // Get all tasks inside the root-task created by an organizer
+ List<Task> nonOrganizedDescendants = new ArrayList<>();
+ collectDescendantsOfTypeIf(Task.class, t -> !t.mCreatedByOrganizer, task,
+ nonOrganizedDescendants);
+ nonOrganizedRootTasks.addAll(nonOrganizedDescendants);
+ } else {
+ nonOrganizedRootTasks.add(task);
}
}
- // Add root tasks controlled by an organizer
- while (rootOrganizedTasks.size() > 0) {
- final Task task = rootOrganizedTasks.remove(0);
- for (int i = task.mChildren.size() - 1; i >= 0; i--) {
- final Task child = (Task) task.mChildren.get(i);
- if (!child.mCreatedByOrganizer) {
- mRootTasks.add(child);
- } else {
- rootOrganizedTasks.add(child);
- }
- }
- }
+
+ mRootTasks.clear();
+ mRootTasks.addAll(nonOrganizedRootTasks);
}
boolean containsActivity(ComponentName activityName) {
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
index 0913831..a6d5f76 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/WindowManagerStateHelper.java
@@ -190,6 +190,13 @@
"Keyguard showing and occluded");
}
+ void waitAndAssertWindowShown(int windowType, boolean show) {
+ assertTrue(waitFor(state -> {
+ WindowState w = state.findFirstWindowWithType(windowType);
+ return w != null && w.isSurfaceShown() == show;
+ }, "wait for window surface " + (show ? "show" : "hide")));
+ }
+
public void waitForAodShowing() {
waitForWithAmState(state -> state.getKeyguardControllerState().aodShowing, "AOD showing");
}
diff --git a/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java b/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java
index fae5657..d60fc0d 100644
--- a/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java
+++ b/tests/framework/base/windowmanager/util/src/android/server/wm/lifecycle/EventTracker.java
@@ -48,7 +48,11 @@
}
}
- void waitAndAssertActivityCurrentState(Class<? extends Activity> activityClass,
+ /**
+ * Blocking call that will wait and verify that the activity transition settles with the
+ * expected state.
+ */
+ public void waitAndAssertActivityCurrentState(Class<? extends Activity> activityClass,
String expectedState) {
final boolean waitResult = waitForConditionWithTimeout(() -> {
List<String> activityLog = mEventLog.getActivityLog(activityClass);
diff --git a/tests/inputmethod/Android.bp b/tests/inputmethod/Android.bp
index 63f826f..5d00e88 100644
--- a/tests/inputmethod/Android.bp
+++ b/tests/inputmethod/Android.bp
@@ -31,7 +31,6 @@
"androidx.test.rules",
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
- "cts-wm-util",
"cts-inputmethod-util",
"cts-mock-a11y-ime-client",
"ctstestrunner-axt",
@@ -41,6 +40,7 @@
"testng",
"kotlin-test",
"cts-wm-util",
+ "cts_window-extensions",
],
srcs: [
"src/**/*.java",
diff --git a/tests/inputmethod/AndroidManifest.xml b/tests/inputmethod/AndroidManifest.xml
index 0211434..79203ee 100644
--- a/tests/inputmethod/AndroidManifest.xml
+++ b/tests/inputmethod/AndroidManifest.xml
@@ -29,8 +29,8 @@
<uses-library android:name="android.test.runner"/>
- <!-- TestActivity etc are merged from util/AndroidManifest.xml -->
-
+ <uses-library android:name="androidx.window.extensions"
+ android:required="false" />
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/inputmethod/mockime/Android.bp b/tests/inputmethod/mockime/Android.bp
index 5ee0505..ba03f4f 100644
--- a/tests/inputmethod/mockime/Android.bp
+++ b/tests/inputmethod/mockime/Android.bp
@@ -30,6 +30,8 @@
"androidx.annotation_annotation",
"androidx.autofill_autofill",
"compatibility-device-util-axt",
+ "cts_window_jetpack_utils",
+ "cts_window-extensions",
],
}
diff --git a/tests/inputmethod/mockime/AndroidManifest.xml b/tests/inputmethod/mockime/AndroidManifest.xml
index e695412..cab88f8 100644
--- a/tests/inputmethod/mockime/AndroidManifest.xml
+++ b/tests/inputmethod/mockime/AndroidManifest.xml
@@ -18,6 +18,10 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.cts.mockime">
+ <queries>
+ <package android:name="com.android.cts.spellcheckingime" />
+ </queries>
+
<application android:multiArch="true"
android:supportsRtl="true"
android:debuggable="true">
@@ -41,6 +45,7 @@
android:exported="true"
android:visibleToInstantApps="true">
</provider>
-
+ <uses-library android:name="androidx.window.extensions"
+ android:required="false" />
</application>
</manifest>
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
index cb49460..cb0dd06 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeEventStreamTestUtils.java
@@ -16,6 +16,9 @@
package com.android.cts.mockime;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.SystemClock;
import android.text.TextUtils;
import android.util.Pair;
@@ -23,7 +26,13 @@
import android.view.inputmethod.InputBinding;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.FoldingFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeoutException;
import java.util.function.Predicate;
@@ -443,4 +452,84 @@
}
return stream.copy();
}
+
+ /**
+ * A copy of {@link WindowLayoutInfo} class just for the purpose of testing with MockIME
+ * test setup.
+ * This is because only in this setup we will pass {@link WindowLayoutInfo} through
+ * different processes.
+ */
+ public static class WindowLayoutInfoParcelable implements Parcelable {
+ private List<DisplayFeature> mDisplayFeatures = new ArrayList<DisplayFeature>();
+
+ public WindowLayoutInfoParcelable(WindowLayoutInfo windowLayoutInfo) {
+ this.mDisplayFeatures = windowLayoutInfo.getDisplayFeatures();
+ }
+ public WindowLayoutInfoParcelable(Parcel in) {
+ while (in.dataAvail() > 0) {
+ Rect bounds;
+ int type = -1, state = -1;
+ bounds = in.readParcelable(Rect.class.getClassLoader(), Rect.class);
+ type = in.readInt();
+ state = in.readInt();
+ mDisplayFeatures.add(new FoldingFeature(bounds, type, state));
+ }
+ }
+
+ @Override
+ public boolean equals(@Nullable Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof WindowLayoutInfoParcelable)) {
+ return false;
+ }
+
+ List<androidx.window.extensions.layout.DisplayFeature> listA =
+ this.getDisplayFeatures();
+ List<DisplayFeature> listB = ((WindowLayoutInfoParcelable) o).getDisplayFeatures();
+ if (listA.size() != listB.size()) return false;
+ for (int i = 0; i < listA.size(); ++i) {
+ if (!listA.get(i).equals(listB.get(i))) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ // The actual implementation is FoldingFeature, DisplayFeature is an abstract class.
+ mDisplayFeatures.forEach(feature -> {
+ dest.writeParcelable(feature.getBounds(), flags);
+ dest.writeInt(((FoldingFeature) feature).getType());
+ dest.writeInt(((FoldingFeature) feature).getState());
+ }
+ );
+ }
+
+ public List<DisplayFeature> getDisplayFeatures() {
+ return mDisplayFeatures;
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ public static final Parcelable.Creator<WindowLayoutInfoParcelable> CREATOR =
+ new Parcelable.Creator<WindowLayoutInfoParcelable>() {
+
+ @Override
+ public WindowLayoutInfoParcelable createFromParcel(Parcel in) {
+ return new WindowLayoutInfoParcelable(in);
+ }
+
+ @Override
+ public WindowLayoutInfoParcelable[] newArray(int size) {
+ return new WindowLayoutInfoParcelable[size];
+ }
+ };
+ }
}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
index cfc30ec..eea1727 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/ImeSettings.java
@@ -25,6 +25,7 @@
import androidx.annotation.IntDef;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.window.extensions.layout.WindowLayoutInfo;
import java.lang.annotation.Retention;
@@ -60,6 +61,8 @@
"InlineSuggestionViewContentDesc";
private static final String STRICT_MODE_ENABLED = "StrictModeEnabled";
private static final String VERIFY_CONTEXT_APIS_IN_ON_CREATE = "VerifyContextApisInOnCreate";
+ private static final String WINDOW_LAYOUT_INFO_CALLBACK_ENABLED =
+ "WindowLayoutInfoCallbackEnabled";
/**
* Simulate the manifest flag enableOnBackInvokedCallback being true for the IME.
@@ -187,6 +190,10 @@
return mBundle.getBoolean(VERIFY_CONTEXT_APIS_IN_ON_CREATE, false);
}
+ public boolean isWindowLayoutInfoCallbackEnabled() {
+ return mBundle.getBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, false);
+ }
+
public boolean isOnBackCallbackEnabled() {
return mBundle.getBoolean(ON_BACK_CALLBACK_ENABLED, false);
}
@@ -374,6 +381,14 @@
}
/**
+ * Sets whether to enable {@link WindowLayoutInfo} callbacks for {@link MockIme}.
+ */
+ public Builder setWindowLayoutInfoCallbackEnabled(boolean enabled) {
+ mBundle.putBoolean(WINDOW_LAYOUT_INFO_CALLBACK_ENABLED, enabled);
+ return this;
+ }
+
+ /**
* Sets whether the IME's
* {@link android.content.pm.ApplicationInfo#isOnBackInvokedCallbackEnabled()}
* should be set to {@code true}.
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
index 528da58..ea168f4 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockIme.java
@@ -16,6 +16,7 @@
package com.android.cts.mockime;
+import static android.server.wm.jetpack.utils.ExtensionUtil.getWindowExtensions;
import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
import static android.view.WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS;
@@ -65,6 +66,7 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethod;
+import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.TextAttribute;
import android.widget.FrameLayout;
import android.widget.HorizontalScrollView;
@@ -82,6 +84,9 @@
import androidx.autofill.inline.UiVersions;
import androidx.autofill.inline.UiVersions.StylesBuilder;
import androidx.autofill.inline.v1.InlineSuggestionUi;
+import androidx.window.extensions.WindowExtensions;
+import androidx.window.extensions.layout.WindowLayoutComponent;
+import androidx.window.extensions.layout.WindowLayoutInfo;
import java.util.ArrayList;
import java.util.concurrent.ExecutorService;
@@ -117,6 +122,14 @@
return eventActionName + ".command";
}
+ @Nullable
+ private final WindowExtensions mWindowExtensions = getWindowExtensions();
+ @Nullable
+ private final WindowLayoutComponent mWindowLayoutComponent =
+ mWindowExtensions != null ? mWindowExtensions.getWindowLayoutComponent() : null;
+
+ private Consumer<WindowLayoutInfo> mWindowLayoutInfoConsumer;
+
private final HandlerThread mHandlerThread = new HandlerThread("CommandReceiver");
private final Handler mMainHandler = new Handler();
@@ -396,6 +409,26 @@
return getMemorizedOrCurrentInputConnection().setImeConsumesInput(
imeConsumesInput);
}
+ case "switchInputMethod": {
+ final String id = command.getExtras().getString("id");
+ try {
+ switchInputMethod(id);
+ } catch (Exception e) {
+ return e;
+ }
+ return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+ }
+ case "switchInputMethod(String,InputMethodSubtype)": {
+ final String id = command.getExtras().getString("id");
+ final InputMethodSubtype subtype = command.getExtras().getParcelable(
+ "subtype", InputMethodSubtype.class);
+ try {
+ switchInputMethod(id, subtype);
+ } catch (Exception e) {
+ return e;
+ }
+ return ImeEvent.RETURN_VALUE_UNAVAILABLE;
+ }
case "setBackDisposition": {
final int backDisposition =
command.getExtras().getInt("backDisposition");
@@ -692,6 +725,11 @@
} else {
registerReceiver(mCommandReceiver, filter, null /* broadcastPermission */, handler);
}
+ // If Extension components are not loaded successfully, notify Test app.
+ if (mSettings.isWindowLayoutInfoCallbackEnabled()) {
+ getTracer().onVerify("windowLayoutComponentLoaded",
+ () -> mWindowLayoutComponent != null);
+ }
if (mSettings.isVerifyContextApisInOnCreate()) {
getTracer().onVerify("isUiContext", this::verifyIsUiContext);
getTracer().onVerify("getDisplay", this::verifyGetDisplay);
@@ -726,6 +764,12 @@
// in IME event.
mLastDispatchedConfiguration.setTo(getResources().getConfiguration());
});
+
+ if (mSettings.isWindowLayoutInfoCallbackEnabled() && mWindowLayoutComponent != null) {
+ mWindowLayoutInfoConsumer = (windowLayoutInfo) -> getTracer().getWindowLayoutInfo(
+ windowLayoutInfo, () -> {});
+ mWindowLayoutComponent.addWindowLayoutInfoListener(this, mWindowLayoutInfoConsumer);
+ }
}
@Override
@@ -1060,6 +1104,9 @@
getTracer().onDestroy(() -> {
mDestroying = true;
super.onDestroy();
+ if (mSettings.isWindowLayoutInfoCallbackEnabled() && mWindowLayoutComponent != null) {
+ mWindowLayoutComponent.removeWindowLayoutInfoListener(mWindowLayoutInfoConsumer);
+ }
unregisterReceiver(mCommandReceiver);
mHandlerThread.quitSafely();
});
@@ -1202,6 +1249,8 @@
@Override
public void onConfigurationChanged(Configuration configuration) {
+ // Broadcasting configuration change is implemented at WindowProviderService level.
+ super.onConfigurationChanged(configuration);
getTracer().onConfigurationChanged(() -> {}, configuration);
mLastDispatchedConfiguration.setTo(configuration);
}
@@ -1517,5 +1566,14 @@
mIme.mLastDispatchedConfiguration));
recordEventInternal("onConfigurationChanged", runnable, arguments);
}
+
+ void getWindowLayoutInfo(@NonNull WindowLayoutInfo windowLayoutInfo,
+ @NonNull Runnable runnable) {
+ final Bundle arguments = new Bundle();
+ ImeEventStreamTestUtils.WindowLayoutInfoParcelable parcel =
+ new ImeEventStreamTestUtils.WindowLayoutInfoParcelable(windowLayoutInfo);
+ arguments.putParcelable("WindowLayoutInfo", parcel);
+ recordEventInternal("getWindowLayoutInfo", runnable, arguments);
+ }
}
}
diff --git a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
index 56795ad..8a61977 100644
--- a/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
+++ b/tests/inputmethod/mockime/src/com/android/cts/mockime/MockImeSession.java
@@ -46,6 +46,7 @@
import android.view.inputmethod.InputConnection;
import android.view.inputmethod.InputContentInfo;
import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodSubtype;
import android.view.inputmethod.TextAttribute;
import androidx.annotation.AnyThread;
@@ -1311,6 +1312,44 @@
}
/**
+ * Makes {@link MockIme} call {@link
+ * android.inputmethodservice.InputMethodService#switchInputMethod(String)}
+ * with the given parameters.
+ *
+ * @param id the IME ID.
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callSwitchInputMethod(String id) {
+ final Bundle params = new Bundle();
+ params.putString("id", id);
+ return callCommandInternal("switchInputMethod", params);
+ }
+
+ /**
+ * Lets {@link MockIme} to call {@link
+ * android.inputmethodservice.InputMethodService#switchInputMethod(String, InputMethodSubtype)}
+ * with the given parameters.
+ *
+ * <p>This triggers {@code switchInputMethod(id, subtype)}.</p>
+ *
+ * @param id the IME ID.
+ * @param subtype {@link InputMethodSubtype} to be switched to. Ignored if {@code null}.
+ * @return {@link ImeCommand} object that can be passed to
+ * {@link ImeEventStreamTestUtils#expectCommand(ImeEventStream, ImeCommand, long)} to
+ * wait until this event is handled by {@link MockIme}
+ */
+ @NonNull
+ public ImeCommand callSwitchInputMethod(String id, @Nullable InputMethodSubtype subtype) {
+ final Bundle params = new Bundle();
+ params.putString("id", id);
+ params.putParcelable("subtype", subtype);
+ return callCommandInternal("switchInputMethod(String,InputMethodSubtype)", params);
+ }
+
+ /**
* Lets {@link MockIme} to call
* {@link android.inputmethodservice.InputMethodService#setBackDisposition(int)} with the given
* parameters.
@@ -1496,6 +1535,11 @@
}
@NonNull
+ public ImeCommand callGetWindowLayoutInfo() {
+ return callCommandInternal("getWindowLayoutInfo", new Bundle());
+ }
+
+ @NonNull
public ImeCommand callSetStylusHandwritingInkView() {
return callCommandInternal("setStylusHandwritingInkView", new Bundle());
}
diff --git a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
index a9cec51..680aded 100644
--- a/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
+++ b/tests/inputmethod/src/android/view/inputmethod/cts/InputMethodServiceTest.java
@@ -18,6 +18,8 @@
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.server.wm.jetpack.utils.ExtensionUtil.EXTENSION_VERSION_2;
+import static android.server.wm.jetpack.utils.ExtensionUtil.isExtensionVersionAtLeast;
import static android.view.Display.DEFAULT_DISPLAY;
import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
@@ -29,6 +31,7 @@
import static android.view.inputmethod.cts.util.TestUtils.waitOnMainUntil;
import static com.android.cts.mockime.ImeEventStreamTestUtils.EventFilterMode.CHECK_EXIT_EVENT_ONLY;
+import static com.android.cts.mockime.ImeEventStreamTestUtils.WindowLayoutInfoParcelable;
import static com.android.cts.mockime.ImeEventStreamTestUtils.editorMatcher;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectCommand;
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
@@ -41,6 +44,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
+import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;
import android.app.Activity;
@@ -48,10 +52,12 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.inputmethodservice.InputMethodService;
import android.os.Bundle;
import android.os.SystemClock;
+import android.server.wm.DisplayMetricsSession;
import android.support.test.uiautomator.UiObject2;
import android.text.TextUtils;
import android.view.KeyCharacterMap;
@@ -79,7 +85,10 @@
import androidx.test.filters.MediumTest;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import androidx.window.extensions.layout.DisplayFeature;
+import androidx.window.extensions.layout.WindowLayoutInfo;
+import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.SystemUtil;
import com.android.cts.mockime.ImeCommand;
import com.android.cts.mockime.ImeEvent;
@@ -88,6 +97,7 @@
import com.android.cts.mockime.MockImeSession;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -111,6 +121,7 @@
private static final long EXPECTED_TIMEOUT = TimeUnit.SECONDS.toMillis(2);
private static final long ACTIVITY_LAUNCH_INTERVAL = 500; // msec
+ private static final String OTHER_IME_ID = "com.android.cts.spellcheckingime/.SpellCheckingIme";
private static final String ERASE_FONT_SCALE_CMD = "settings delete system font_scale";
// 1.2 is an arbitrary value.
@@ -183,6 +194,53 @@
}
}
+ @Test
+ public void testSwitchInputMethod_verifiesEnabledState() throws Exception {
+ SystemUtil.runShellCommand("ime disable " + OTHER_IME_ID);
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+ expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+ final ImeCommand cmd = imeSession.callSwitchInputMethod(OTHER_IME_ID);
+ final ImeEvent event = expectCommand(stream, cmd, TIMEOUT);
+ assertTrue("should be exception result, but wasn't" + event,
+ event.isExceptionReturnValue());
+ // Should be IllegalStateException, but CompletableFuture converts to RuntimeException
+ assertTrue("should be RuntimeException, but wasn't: "
+ + event.getReturnExceptionValue(),
+ event.getReturnExceptionValue() instanceof RuntimeException);
+ assertTrue(
+ "should contain 'not enabled' but didn't: " + event.getReturnExceptionValue(),
+ event.getReturnExceptionValue().getMessage().contains("not enabled"));
+ }
+ }
+ @Test
+ public void testSwitchInputMethodWithSubtype_verifiesEnabledState() throws Exception {
+ SystemUtil.runShellCommand("ime disable " + OTHER_IME_ID);
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder())) {
+ final ImeEventStream stream = imeSession.openEventStream();
+ expectEvent(stream, event -> "onStartInput".equals(event.getEventName()), TIMEOUT);
+
+ final ImeCommand cmd = imeSession.callSwitchInputMethod(OTHER_IME_ID, null);
+ final ImeEvent event = expectCommand(stream, cmd, TIMEOUT);
+ assertTrue("should be exception result, but wasn't" + event,
+ event.isExceptionReturnValue());
+ // Should be IllegalStateException, but CompletableFuture converts to RuntimeException
+ assertTrue("should be RuntimeException, but wasn't: "
+ + event.getReturnExceptionValue(),
+ event.getReturnExceptionValue() instanceof RuntimeException);
+ assertTrue(
+ "should contain 'not enabled' but didn't: " + event.getReturnExceptionValue(),
+ event.getReturnExceptionValue().getMessage().contains("not enabled"));
+ }
+ }
+
private void verifyImeConsumesBackButton(int backDisposition) throws Exception {
try (MockImeSession imeSession = MockImeSession.create(
InstrumentationRegistry.getInstrumentation().getContext(),
@@ -794,6 +852,121 @@
}
}
+ /**
+ * Starts a {@link MockImeSession} and verifies MockIme receives {@link WindowLayoutInfo}
+ * updates. Trigger Configuration changes by modifying the DisplaySession where MockIME window
+ * is located, then verify Bounds from MockIME window and {@link DisplayFeature} from
+ * WindowLayoutInfo updates observe the same changes to the hinge location.
+ * Here we use {@link WindowLayoutInfoParcelable} to pass {@link WindowLayoutInfo} values
+ * between this test process and the MockIME process.
+ */
+ @Ignore("b/264026686")
+ @Test
+ @ApiTest(apis = {
+ "androidx.window.extensions.layout.WindowLayoutComponent#addWindowLayoutInfoListener"})
+ public void testImeListensToWindowLayoutInfo() throws Exception {
+ assumeTrue(
+ "This test should only be run on devices with extension version that supports IME"
+ + " as WindowLayoutInfo listener ",
+ isExtensionVersionAtLeast(EXTENSION_VERSION_2));
+
+ try (MockImeSession imeSession = MockImeSession.create(
+ InstrumentationRegistry.getInstrumentation().getContext(),
+ InstrumentationRegistry.getInstrumentation().getUiAutomation(),
+ new ImeSettings.Builder().setWindowLayoutInfoCallbackEnabled(true))) {
+
+ final ImeEventStream stream = imeSession.openEventStream();
+ TestActivity activity = createTestActivity(SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+
+ assertTrue(expectEvent(stream, verificationMatcher("windowLayoutComponentLoaded"),
+ CHECK_EXIT_EVENT_ONLY, TIMEOUT).getReturnBooleanValue());
+
+ try (DisplayMetricsSession displaySession = new DisplayMetricsSession(
+ activity.getDisplay().getDisplayId())) {
+
+ final double displayResizeRatio = 0.8;
+
+ // MockIME has registered addWindowLayoutInfo, it should be emitting the
+ // current location of hinge now.
+ WindowLayoutInfoParcelable windowLayoutInit = verifyReceivedWindowLayout(
+ stream);
+ // Skip the test if the device doesn't support hinges.
+ assertNotNull(windowLayoutInit);
+ assertNotNull(windowLayoutInit.getDisplayFeatures());
+ assumeFalse(windowLayoutInit.getDisplayFeatures().isEmpty());
+
+ final Rect windowLayoutInitBounds = windowLayoutInit.getDisplayFeatures().get(0)
+ .getBounds();
+
+ expectEvent(stream, event -> "onStartInput".equals(event.getEventName()),
+ TIMEOUT);
+ expectEvent(stream, event -> "showSoftInput".equals(event.getEventName()),
+ TIMEOUT);
+
+ // After IME is shown, get the bounds of IME.
+ final Rect imeBoundsInit = expectCommand(stream,
+ imeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+ .getReturnParcelableValue();
+ // Contain first part of the test in a try-block so that the display session
+ // could be restored for the remaining testsuite even if something fails.
+ try {
+ // Shrink the entire display 20% smaller.
+ displaySession.changeDisplayMetrics(displayResizeRatio /* sizeRatio */,
+ 1.0 /* densityRatio */);
+
+ // onConfigurationChanged on WM side triggers a new calculation for
+ // hinge location.
+ WindowLayoutInfoParcelable windowLayoutSizeChange = verifyReceivedWindowLayout(
+ stream);
+
+ // Expect to receive same number of display features in WindowLayoutInfo.
+ assertEquals(windowLayoutInit.getDisplayFeatures().size(),
+ windowLayoutSizeChange.getDisplayFeatures().size());
+
+ Rect windowLayoutSizeChangeBounds =
+ windowLayoutSizeChange.getDisplayFeatures().get(
+ 0).getBounds();
+ Rect imeBoundsShrunk = expectCommand(stream,
+ imeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+ .getReturnParcelableValue();
+
+ final Boolean widthsChangedInSameRatio =
+ (windowLayoutInitBounds.width() * displayResizeRatio
+ == windowLayoutSizeChangeBounds.width() && (
+ imeBoundsInit.width() * displayResizeRatio
+ == imeBoundsShrunk.width()));
+ final Boolean heightsChangedInSameRatio =
+ (windowLayoutInitBounds.height() * displayResizeRatio
+ == windowLayoutSizeChangeBounds.height() && (
+ imeBoundsInit.height() * displayResizeRatio
+ == imeBoundsShrunk.height()));
+ // Expect the hinge dimension to shrink in exactly one direction, the actual
+ // dimension depends on device implementation. Observe hinge dimensions from
+ // IME configuration bounds and from WindowLayoutInfo.
+ assertTrue(widthsChangedInSameRatio || heightsChangedInSameRatio);
+ } finally {
+ // Restore Display to original size.
+ displaySession.restoreDisplayMetrics();
+ // Advance stream to ignore unrelated side effect from WM configuration changes.
+ // TODO(b/257990185): Add filtering in WM Extensions to remove this.
+ stream.skipAll();
+
+ WindowLayoutInfoParcelable windowLayoutRestored = verifyReceivedWindowLayout(
+ stream);
+
+ assertEquals(windowLayoutInitBounds,
+ windowLayoutRestored.getDisplayFeatures().get(0).getBounds());
+
+ final Rect imeBoundsRestored = expectCommand(stream,
+ imeSession.callGetCurrentWindowMetricsBounds(), TIMEOUT)
+ .getReturnParcelableValue();
+
+ assertEquals(imeBoundsRestored, imeBoundsInit);
+ }
+ }
+ }
+ }
+
/** Verify if {@link InputMethodService#isUiContext()} returns {@code true}. */
@Test
public void testIsUiContext() throws Exception {
@@ -1131,4 +1304,13 @@
return this;
}
}
+
+ private static WindowLayoutInfoParcelable verifyReceivedWindowLayout(ImeEventStream stream)
+ throws TimeoutException {
+ WindowLayoutInfoParcelable received = expectEvent(stream,
+ event -> "getWindowLayoutInfo".equals(event.getEventName()),
+ TIMEOUT).getArguments().getParcelable("WindowLayoutInfo",
+ WindowLayoutInfoParcelable.class);
+ return received;
+ }
}
diff --git a/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java b/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java
index b4c5c83..48d5946 100644
--- a/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java
+++ b/tests/location/location_none/src/android/location/cts/none/LocationDisabledAppOpsTest.java
@@ -94,24 +94,22 @@
mode[0] = mAom.noteOpNoThrow(
OPSTR_FINE_LOCATION, ai.uid, ai.packageName);
isProvider[0] = mLm.isProviderPackage(null, pi.packageName, null);
- if (mode[0] == MODE_ALLOWED && !ignoreList.containsAll(pi.packageName)
- && !isProvider[0]) {
- bypassedNoteOps.add(pi.packageName);
- }
});
+ if (mode[0] == MODE_ALLOWED && !ignoreList.containsAll(pi.packageName)
+ && !isProvider[0]) {
+ bypassedNoteOps.add(pi.packageName);
+ }
mode[0] = MODE_ALLOWED;
runWithShellPermissionIdentity(() -> {
mode[0] = mAom
.checkOpNoThrow(OPSTR_FINE_LOCATION, ai.uid, ai.packageName);
- isProvider[0] = mLm.isProviderPackage(null, pi.packageName, null);
- if (mode[0] == MODE_ALLOWED && !ignoreList.includes(pi.packageName)
- && !isProvider[0]) {
- bypassedCheckOps.add(pi.packageName);
- }
});
-
+ if (mode[0] == MODE_ALLOWED && !ignoreList.includes(pi.packageName)
+ && !isProvider[0]) {
+ bypassedCheckOps.add(pi.packageName);
+ }
}
}
diff --git a/tests/quicksettings/Android.bp b/tests/quicksettings/Android.bp
index 7246788..df7b7dc 100644
--- a/tests/quicksettings/Android.bp
+++ b/tests/quicksettings/Android.bp
@@ -30,6 +30,7 @@
"androidx.test.ext.junit",
"androidx.test.uiautomator_uiautomator",
"compatibility-device-util-axt",
+ "SystemUI-proto",
],
libs: [
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
index 8ef5807..3030546 100644
--- a/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
+++ b/tests/quicksettings/src/android/quicksettings/cts/BaseTileServiceTest.java
@@ -22,9 +22,11 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assume.assumeTrue;
+import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
+import android.os.ParcelFileDescriptor;
import android.service.quicksettings.TileService;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
@@ -35,11 +37,18 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
+import com.android.systemui.dump.nano.SystemUIProtoDump;
+import com.android.systemui.qs.nano.QsTileState;
+import com.android.systemui.util.nano.ComponentNameProto;
+
+import com.google.protobuf.nano.InvalidProtocolBufferNanoException;
import org.junit.After;
import org.junit.Before;
import org.junit.runner.RunWith;
+import java.io.ByteArrayOutputStream;
+import java.io.FileInputStream;
import java.io.IOException;
@RunWith(AndroidJUnit4.class)
@@ -52,8 +61,8 @@
protected abstract void waitForListening(boolean state) throws InterruptedException;
protected Context mContext;
- final static String DUMP_COMMAND =
- "dumpsys activity service com.android.systemui/.SystemUIService QSTileHost";
+ static final String PROTO_DUMP_COMMAND =
+ "dumpsys statusbar QSTileHost --proto";
// Time between checks for state we expect.
protected static final long CHECK_DELAY = 250;
@@ -138,4 +147,46 @@
}
return null;
}
+
+ protected final QsTileState findTileState() {
+ QsTileState[] tiles = getTilesStates();
+ for (int i = 0; i < tiles.length; i++) {
+ QsTileState tile = tiles[i];
+ if (tile.hasComponentName()) {
+ ComponentNameProto componentName = tile.getComponentName();
+ ComponentName c = ComponentName.createRelative(
+ componentName.packageName, componentName.className);
+ if (c.flattenToString().equals(getComponentName())) {
+ return tile;
+ }
+ }
+ }
+ return null;
+ }
+
+ private QsTileState[] getTilesStates() {
+ try {
+ return SystemUIProtoDump.parseFrom(getProtoDump()).tiles;
+ } catch (InvalidProtocolBufferNanoException e) {
+ throw new IllegalStateException("Couldn't parse proto dump", e);
+ }
+ }
+
+ private byte[] getProtoDump() {
+ try {
+ ParcelFileDescriptor pfd = InstrumentationRegistry.getInstrumentation()
+ .getUiAutomation().executeShellCommand(PROTO_DUMP_COMMAND);
+ byte[] buf = new byte[512];
+ int bytesRead;
+ FileInputStream fis = new ParcelFileDescriptor.AutoCloseInputStream(pfd);
+ ByteArrayOutputStream stdout = new ByteArrayOutputStream();
+ while ((bytesRead = fis.read(buf)) != -1) {
+ stdout.write(buf, 0, bytesRead);
+ }
+ fis.close();
+ return stdout.toByteArray();
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
}
diff --git a/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
index 0f6d56e..67a1f40 100644
--- a/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
+++ b/tests/quicksettings/src/android/quicksettings/cts/BooleanTileServiceTest.java
@@ -17,12 +17,15 @@
package android.quicksettings.cts;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
+import com.android.systemui.qs.nano.QsTileState;
+
import org.junit.Test;
public class BooleanTileServiceTest extends BaseTileServiceTest {
@@ -39,12 +42,9 @@
public void testTileInDumpAndHasBooleanState() throws Exception {
initializeAndListen();
- final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
- final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- final String line = findLine(dumpLines, tileLabel);
- assertNotNull(line);
- assertTrue(line.trim().startsWith("BooleanState"));
+ final QsTileState tileState = findTileState();
+ assertNotNull(tileState);
+ assertTrue(tileState.hasBooleanState());
}
@Test
@@ -58,13 +58,10 @@
public void testValueTracksState() throws Exception {
initializeAndListen();
- final CharSequence tileLabel = mTileService.getQsTile().getLabel();
-
- String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- String line = findLine(dumpLines, tileLabel);
+ QsTileState tileState = findTileState();
// Tile starts inactive
- assertTrue(line.contains("value=false"));
+ assertFalse(tileState.getBooleanState());
((ToggleableTestTileService) mTileService).toggleState();
@@ -76,10 +73,9 @@
assertEquals(Tile.STATE_ACTIVE, mTileService.getQsTile().getState());
- dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- line = findLine(dumpLines, tileLabel);
+ tileState = findTileState();
- assertTrue(line.contains("value=true"));
+ assertTrue(tileState.getBooleanState());
}
@Override
diff --git a/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
index 2ccab0c..64b6ce4 100644
--- a/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
+++ b/tests/quicksettings/src/android/quicksettings/cts/TileServiceTest.java
@@ -27,6 +27,8 @@
import android.service.quicksettings.Tile;
import android.service.quicksettings.TileService;
+import com.android.systemui.qs.nano.QsTileState;
+
import org.junit.Test;
public class TileServiceTest extends BaseTileServiceTest {
@@ -115,15 +117,32 @@
}
@Test
- public void testTileInDumpAndHasState() throws Exception {
+ public void testTileInDumpAndHasNonBooleanState() throws Exception {
initializeAndListen();
+ final QsTileState tileState = findTileState();
+ assertNotNull(tileState);
+ assertFalse(tileState.hasBooleanState());
+ }
- final CharSequence tileLabel = mTileService.getQsTile().getLabel();
+ @Test
+ public void testTileInDumpAndHasCorrectState() throws Exception {
+ initializeAndListen();
+ CharSequence label = "test_label";
+ CharSequence subtitle = "test_subtitle";
- final String[] dumpLines = executeShellCommand(DUMP_COMMAND).split("\n");
- final String line = findLine(dumpLines, tileLabel);
- assertNotNull(line);
- assertTrue(line.trim().startsWith("State")); // Not BooleanState
+ Tile tile = mTileService.getQsTile();
+ tile.setState(Tile.STATE_ACTIVE);
+ tile.setLabel(label);
+ tile.setSubtitle(subtitle);
+ tile.updateTile();
+
+ Thread.sleep(200);
+
+ final QsTileState tileState = findTileState();
+ assertNotNull(tileState);
+ assertEquals(Tile.STATE_ACTIVE, tileState.state);
+ assertEquals(label, tileState.getLabel());
+ assertEquals(subtitle, tileState.getSecondaryLabel());
}
private void clickTile(String componentName) throws Exception {
diff --git a/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
index 291d9cc..ecc0940 100644
--- a/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
+++ b/tests/suspendapps/tests/src/android/suspendapps/cts/SuspendPackagesTest.java
@@ -277,6 +277,13 @@
assertOpDisallowed(cameraOp);
}
+ @Test
+ public void testOpVibrateOnSuspend() throws Exception {
+ final int vibrateOp = AppOpsManager.strOpToOp(AppOpsManager.OPSTR_VIBRATE);
+ SuspendTestUtils.suspend(null, null, null);
+ assertOpDisallowed(vibrateOp);
+ }
+
@After
public void tearDown() throws Exception {
if (mTestAppInterface != null) {
diff --git a/tests/tests/app.usage/AndroidTest.xml b/tests/tests/app.usage/AndroidTest.xml
index 5d4e7f5..431e0c9 100644
--- a/tests/tests/app.usage/AndroidTest.xml
+++ b/tests/tests/app.usage/AndroidTest.xml
@@ -33,6 +33,9 @@
<option name="test-file-name" value="CtsUsageStatsTestAssistApp.apk" />
<option name="test-file-name" value="CtsUsageStatsTestExactAlarmApp.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="settings put global device_config_sync_disabled 0" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.app.usage.cts" />
<option name="runtime-hint" value="1m47s" />
diff --git a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
index e906720..d33758f 100644
--- a/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
+++ b/tests/tests/app.usage/src/android/app/usage/cts/UsageStatsTest.java
@@ -550,6 +550,10 @@
Map<String,UsageStats> events = mUsageStatsManager.queryAndAggregateUsageStats(
startTime, endTime);
UsageStats stats = events.get(mTargetPackage);
+ if (stats == null) {
+ fail("Querying UsageStats for " + mTargetPackage + " returned empty; list of packages "
+ + "with events: " + Arrays.toString(events.keySet().toArray(new String[0])));
+ }
int startingCount = stats.getAppLaunchCount();
// Launch count is updated by UsageStatsService depending on last background package.
// When running this test on single screen device (where tasks are launched in the same
diff --git a/tests/tests/bluetooth/AndroidTest.xml b/tests/tests/bluetooth/AndroidTest.xml
index 9a3075b..7b42874 100644
--- a/tests/tests/bluetooth/AndroidTest.xml
+++ b/tests/tests/bluetooth/AndroidTest.xml
@@ -25,6 +25,12 @@
<option name="cleanup-apks" value="true" />
<option name="test-file-name" value="CtsBluetoothTestCases.apk" />
</target_preparer>
+ <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
+ <option name="run-command" value="cmd bluetooth_manager enable" />
+ <option name="run-command" value="cmd bluetooth_manager wait-for-state:STATE_ON" />
+ <option name="teardown-command" value="cmd bluetooth_manager disable" />
+ <option name="teardown-command" value="cmd bluetooth_manager wait-for-state:STATE_OFF" />
+ </target_preparer>
<test class="com.android.tradefed.testtype.AndroidJUnitTest" >
<option name="package" value="android.bluetooth.cts" />
<option name="runtime-hint" value="1m11s" />
@@ -33,6 +39,7 @@
<!-- Only run Cts Tests in MTS if the Bluetooth Mainline module is installed. -->
<object type="module_controller"
class="com.android.tradefed.testtype.suite.module.MainlineTestModuleController">
- <option name="mainline-module-package-name" value="com.google.android.bluetooth" />
+ <option name="mainline-module-package-name" value="com.android.btservices" />
+ <option name="mainline-module-package-name" value="com.google.android.btservices" />
</object>
</configuration>
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
index f9531fb..22d3447 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BasicBluetoothGattTest.java
@@ -16,6 +16,8 @@
package android.bluetooth.cts;
+import static org.junit.Assert.assertThrows;
+
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
@@ -23,6 +25,7 @@
import android.bluetooth.BluetoothGattService;
import android.bluetooth.BluetoothManager;
import android.test.AndroidTestCase;
+import android.util.Log;
import androidx.test.InstrumentationRegistry;
@@ -33,6 +36,7 @@
* Other tests that run with real bluetooth connections are located in CtsVerifier.
*/
public class BasicBluetoothGattTest extends AndroidTestCase {
+ private static final String TAG = BasicBluetoothGattTest.class.getSimpleName();
private BluetoothAdapter mBluetoothAdapter;
private BluetoothDevice mBluetoothDevice;
@@ -53,6 +57,17 @@
mBluetoothDevice = mBluetoothAdapter.getRemoteDevice("00:11:22:AA:BB:CC");
mBluetoothGatt = mBluetoothDevice.connectGatt(
mContext, /*autoConnect=*/ true, new BluetoothGattCallback() {});
+ if (mBluetoothGatt == null) {
+ try {
+ Thread.sleep(500); // Bt is not binded yet. Wait and retry
+ } catch (InterruptedException e) {
+ Log.e(TAG, "delay connectGatt interrupted");
+ }
+ mBluetoothGatt = mBluetoothDevice.connectGatt(
+ mContext, /*autoConnect=*/ true, new BluetoothGattCallback() {});
+ }
+ assertNotNull(mBluetoothGatt);
+
}
@Override
@@ -61,8 +76,9 @@
// mBluetoothAdapter == null.
return;
}
- mBluetoothGatt.disconnect();
- assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
+ if (mBluetoothGatt != null) {
+ mBluetoothGatt.disconnect();
+ }
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
@@ -83,11 +99,7 @@
return;
}
- try {
- mBluetoothGatt.connect();
- } catch (Exception e) {
- fail("Exception caught from connect(): " + e.toString());
- }
+ mBluetoothGatt.connect();
}
public void testSetPreferredPhy() throws Exception {
@@ -95,47 +107,34 @@
return;
}
- try {
- mBluetoothGatt.setPreferredPhy(BluetoothDevice.PHY_LE_1M, BluetoothDevice.PHY_LE_1M,
- BluetoothDevice.PHY_OPTION_NO_PREFERRED);
- } catch (Exception e) {
- fail("Exception caught from setPreferredPhy(): " + e.toString());
- }
+ mBluetoothGatt.setPreferredPhy(BluetoothDevice.PHY_LE_1M, BluetoothDevice.PHY_LE_1M,
+ BluetoothDevice.PHY_OPTION_NO_PREFERRED);
}
public void testGetConnectedDevices() {
if (!TestUtils.isBleSupported(getContext())) {
return;
}
- try {
- mBluetoothGatt.getConnectedDevices();
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException ex) {
- // Expected
- }
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mBluetoothGatt.getConnectedDevices());
}
public void testGetConnectionState() {
if (!TestUtils.isBleSupported(getContext())) {
return;
}
- try {
- mBluetoothGatt.getConnectionState(null);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException ex) {
- // Expected
- }
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mBluetoothGatt.getConnectionState(null));
}
public void testGetDevicesMatchingConnectionStates() {
if (!TestUtils.isBleSupported(getContext())) {
return;
}
- try {
- mBluetoothGatt.getDevicesMatchingConnectionStates(null);
- fail("Should throw UnsupportedOperationException!");
- } catch (UnsupportedOperationException ex) {
- // Expected
- }
+
+ assertThrows(UnsupportedOperationException.class,
+ () -> mBluetoothGatt.getDevicesMatchingConnectionStates(null));
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
index 7a20e9b..a653bc5 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpSinkTest.java
@@ -48,8 +48,8 @@
private BluetoothA2dpSink mBluetoothA2dpSink;
private boolean mIsA2dpSinkSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -68,8 +68,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothA2dpSink = null;
@@ -88,14 +88,22 @@
mBluetoothA2dpSink = null;
mIsProfileReady = false;
}
- mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mUiAutomation.dropShellPermissionIdentity();
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsA2dpSinkSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothA2dpSink);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.A2DP_SINK, mBluetoothA2dpSink);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsA2dpSinkSupported)) return;
@@ -193,11 +201,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -207,28 +215,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothA2dpSinkServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothA2dpSink = (BluetoothA2dpSink) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
index 515f0ef..1d3c00a 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothA2dpTest.java
@@ -49,8 +49,8 @@
private BluetoothA2dp mBluetoothA2dp;
private boolean mIsA2dpSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private ReentrantLock mProfileConnectionlock;
+ private Condition mConditionProfileConnection;
@Override
public void setUp() throws Exception {
@@ -69,8 +69,9 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+
mIsProfileReady = false;
mBluetoothA2dp = null;
@@ -89,13 +90,22 @@
mBluetoothA2dp = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
mUiAutomation.dropShellPermissionIdentity();
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsA2dpSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothA2dp);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.A2DP, mBluetoothA2dp);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsA2dpSupported)) return;
@@ -332,11 +342,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -346,28 +356,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothA2dpServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothA2dp = (BluetoothA2dp) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
index 44f34f6..395505f 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothAdapterTest.java
@@ -89,8 +89,6 @@
@Override
public void tearDown() throws Exception {
if (mHasBluetooth) {
- mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
mUiAutomation.dropShellPermissionIdentity();
}
}
@@ -366,20 +364,20 @@
return;
}
- Duration minute = Duration.ofMinutes(1);
+ Duration minutes = Duration.ofMinutes(2);
assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
assertEquals(null, mAdapter.getDiscoverableTimeout());
assertEquals(BluetoothStatusCodes.ERROR_BLUETOOTH_NOT_ENABLED,
- mAdapter.setDiscoverableTimeout(minute));
+ mAdapter.setDiscoverableTimeout(minutes));
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
TestUtils.adoptPermissionAsShellUid(BLUETOOTH_CONNECT, BLUETOOTH_PRIVILEGED);
assertThrows(IllegalArgumentException.class, () -> mAdapter.setDiscoverableTimeout(
Duration.ofDays(25000)));
assertEquals(BluetoothStatusCodes.SUCCESS,
- mAdapter.setDiscoverableTimeout(minute));
- assertEquals(minute, mAdapter.getDiscoverableTimeout());
+ mAdapter.setDiscoverableTimeout(minutes));
+ assertEquals(minutes, mAdapter.getDiscoverableTimeout());
}
public void test_getConnectionState() {
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
index f6336bf..4c641ac 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothConfigTest.java
@@ -60,7 +60,6 @@
super.tearDown();
if (!mHasBluetooth) return;
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
mAdapter = null;
mUiAutomation.dropShellPermissionIdentity();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
index 50a8c46..bb4c2c1 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothCsipSetCoordinatorTest.java
@@ -21,23 +21,19 @@
import static org.junit.Assert.assertThrows;
-import android.app.UiAutomation;
import android.bluetooth.BluetoothAdapter;
-import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothCsipSetCoordinator;
+import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.BluetoothStatusCodes;
import android.bluetooth.BluetoothUuid;
import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.os.Build;
import android.os.ParcelUuid;
import android.test.AndroidTestCase;
import android.util.Log;
-import androidx.test.InstrumentationRegistry;
-
import com.android.compatibility.common.util.ApiLevelUtil;
import java.util.List;
@@ -59,8 +55,8 @@
private BluetoothCsipSetCoordinator mBluetoothCsipSetCoordinator;
private boolean mIsCsipSetCoordinatorSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private boolean mGroupLockCallbackCalled;
private TestCallback mTestCallback;
private Executor mTestExecutor;
@@ -94,8 +90,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothCsipSetCoordinator = null;
@@ -134,14 +130,24 @@
mTestCallback = null;
mTestExecutor = null;
}
- if (mAdapter != null ) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- mAdapter = null;
- }
+ mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
}
+ public void testCloseProfileProxy() {
+ if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothCsipSetCoordinator);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(
+ BluetoothProfile.CSIP_SET_COORDINATOR, mBluetoothCsipSetCoordinator);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void testGetConnectedDevices() {
if (!(mHasBluetooth && mIsCsipSetCoordinatorSupported)) return;
@@ -265,11 +271,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -279,28 +285,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothCsipServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothCsipSetCoordinator = (BluetoothCsipSetCoordinator) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
index a3ea455..4459cbc 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothDeviceTest.java
@@ -76,7 +76,6 @@
public void tearDown() throws Exception {
super.tearDown();
if (mHasBluetooth && mHasCompanionDevice) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
mAdapter = null;
mUiAutomation.dropShellPermissionIdentity();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
index f247d5e..4ee4038 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerCallbackTest.java
@@ -122,8 +122,6 @@
public void tearDown() throws Exception {
super.tearDown();
if (mHasBluetooth) {
- mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
mAdapter = null;
mBluetoothDevice = null;
mBluetoothGattService = null;
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
index 6c9959a..a859e73 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothGattServerTest.java
@@ -70,7 +70,6 @@
mBluetoothGattServer.close();
mBluetoothGattServer = null;
}
- assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
mBluetoothAdapter = null;
mUIAutomation.dropShellPermissionIdentity();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
index fb5f241..adebccb 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapClientTest.java
@@ -20,6 +20,7 @@
import static android.Manifest.permission.BLUETOOTH_PRIVILEGED;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThrows;
@@ -69,8 +70,8 @@
private BluetoothHapClient mBluetoothHapClient;
private boolean mIsHapClientSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private boolean mOnPresetSelected = false;
private boolean mOnPresetSelectionFailed = false;
@@ -107,8 +108,8 @@
mAdapter = TestUtils.getBluetoothAdapterOrDie();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothHapClient = null;
@@ -126,14 +127,26 @@
mBluetoothHapClient = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- mAdapter = null;
- }
+ mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
@Test
+ public void test_closeProfileProxy() {
+ if (shouldSkipTest()) {
+ return;
+ }
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothHapClient);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mBluetoothHapClient);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
+ @Test
public void testGetConnectedDevices() {
if (shouldSkipTest()) {
return;
@@ -466,11 +479,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -480,28 +493,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothHapClientServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothHapClient = (BluetoothHapClient) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
index 80f4bbd..2f65320 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHapPresetInfoTest.java
@@ -77,9 +77,6 @@
if (!(mHasBluetooth && mIsHapSupported)) {
return;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java
index 69b5747..bac21ae 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetClientTest.java
@@ -49,8 +49,8 @@
private BluetoothHeadsetClient mBluetoothHeadsetClient;
private boolean mIsHeadsetClientSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -69,8 +69,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothHeadsetClient = null;
@@ -90,12 +90,21 @@
mBluetoothHeadsetClient = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsHeadsetClientSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothHeadsetClient);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.HEADSET_CLIENT, mBluetoothHeadsetClient);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsHeadsetClientSupported)) return;
@@ -219,11 +228,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -233,28 +242,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothHeadsetClientServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothHeadsetClient = (BluetoothHeadsetClient) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
index cb2c2b4..cf683b4 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHeadsetTest.java
@@ -49,8 +49,8 @@
private BluetoothHeadset mBluetoothHeadset;
private boolean mIsHeadsetSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -69,8 +69,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothHeadset = null;
@@ -89,13 +89,22 @@
mBluetoothHeadset = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
mUiAutomation.dropShellPermissionIdentity();
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsHeadsetSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothHeadset);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsHeadsetSupported)) return;
@@ -376,11 +385,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -390,28 +399,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothHeadsetServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothHeadset = (BluetoothHeadset) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
index d94deab..030ee8d 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidDeviceTest.java
@@ -59,8 +59,8 @@
private boolean mIsProfileReady;
private BluetoothAdapter mAdapter;
private UiAutomation mUiAutomation;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private BluetoothHidDevice mBluetoothHidDevice;
@@ -81,8 +81,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothHidDevice = null;
@@ -101,14 +101,22 @@
mBluetoothHidDevice = null;
mIsProfileReady = false;
}
- mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
mUiAutomation.dropShellPermissionIdentity();
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsHidSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothHidDevice);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.HID_DEVICE, mBluetoothHidDevice);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getDevicesMatchingConnectionStates() {
if (!(mHasBluetooth && mIsHidSupported)) return;
@@ -206,13 +214,12 @@
testDevice, BluetoothProfile.CONNECTION_POLICY_FORBIDDEN));
}
-
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -222,28 +229,54 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothHidServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothHidDevice = (BluetoothHidDevice) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
-
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java
index 5c2e86e..c4fe6b6 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothHidHostTest.java
@@ -50,8 +50,8 @@
private UiAutomation mUiAutomation;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -71,8 +71,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mHidHost = null;
@@ -90,12 +90,21 @@
mHidHost = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsHidHostSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mHidHost);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.HID_HOST, mHidHost);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsHidHostSupported)) return;
@@ -202,11 +211,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -216,28 +225,54 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothHidHostListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
mHidHost = (BluetoothHidHost) proxy;
mIsProfileReady = true;
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
- // Do nothing
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
index e7f59b8..6cf572b 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAdvertiserTest.java
@@ -73,7 +73,6 @@
mAdvertiser.stopAdvertisingSet(mCallback);
assertTrue(mCallback.mAdvertisingSetStoppedLatch.await(TIMEOUT_MS,
TimeUnit.MILLISECONDS));
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
mAdvertiser = null;
mAdapter = null;
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
index b40b52d..21ad04f 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioCodecConfigMetadataTest.java
@@ -93,9 +93,6 @@
@After
public void tearDown() {
if (mHasBluetooth) {
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
index ddb6bca..0e156cf 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioContentMetadataTest.java
@@ -101,9 +101,6 @@
@After
public void tearDown() {
if (mHasBluetooth) {
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
index 3df5b1f..8e1905c 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeAudioTest.java
@@ -54,8 +54,8 @@
private BluetoothLeAudio mBluetoothLeAudio;
private boolean mIsLeAudioSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private Executor mTestExecutor;
private TestCallback mTestCallback;
private boolean mCodecConfigChangedCalled;
@@ -124,8 +124,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothLeAudio = null;
@@ -152,13 +152,22 @@
mBluetoothLeAudio = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
TestUtils.dropPermissionAsShellUid();
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsLeAudioSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothLeAudio);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO, mBluetoothLeAudio);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsLeAudioSupported)) return;
@@ -360,11 +369,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -374,28 +383,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothLeAudioServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothLeAudio = (BluetoothLeAudio) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
index 6138f38..09df2bb 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastAssistantTest.java
@@ -98,8 +98,8 @@
private BluetoothLeBroadcastAssistant mBluetoothLeBroadcastAssistant;
private boolean mIsBroadcastAssistantSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedLock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Mock
BluetoothLeBroadcastAssistant.Callback mCallbacks;
@@ -120,8 +120,8 @@
mAdapter = TestUtils.getBluetoothAdapterOrDie();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedLock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedLock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothLeBroadcastAssistant = null;
@@ -147,15 +147,28 @@
mBluetoothLeBroadcastAssistant = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
}
@Test
+ public void testCloseProfileProxy() {
+ if (shouldSkipTest()) {
+ return;
+ }
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothLeBroadcastAssistant);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(
+ BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mBluetoothLeBroadcastAssistant);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
+ @Test
public void testAddSource() {
if (shouldSkipTest()) {
return;
@@ -541,11 +554,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedLock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -555,27 +568,54 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedLock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class ServiceListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedLock.lock();
+ mProfileConnectionlock.lock();
mBluetoothLeBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedLock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
index e4d0577..424a64e 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastChannelTest.java
@@ -90,9 +90,6 @@
@After
public void tearDown() {
if (mHasBluetooth) {
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
index 7ff121a..5edeb38 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastMetadataTest.java
@@ -114,9 +114,6 @@
@After
public void tearDown() {
if (mHasBluetooth) {
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
index 8f9b0f7..fad215d 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastReceiveStateTest.java
@@ -105,9 +105,6 @@
@After
public void tearDown() {
if (mHasBluetooth) {
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
index 7279ef6..c87a95e 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastSubgroupTest.java
@@ -104,9 +104,6 @@
@After
public void tearDown() {
if (mHasBluetooth) {
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
index e70d6ba..7b72649 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeBroadcastTest.java
@@ -93,8 +93,8 @@
private BluetoothLeBroadcast mBluetoothLeBroadcast;
private boolean mIsLeBroadcastSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private boolean mOnBroadcastStartedCalled = false;
private boolean mOnBroadcastStartFailedCalled = false;
@@ -167,8 +167,8 @@
mAdapter = TestUtils.getBluetoothAdapterOrDie();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothLeBroadcast = null;
@@ -198,15 +198,27 @@
mBluetoothLeBroadcast = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
}
@Test
+ public void testCloseProfileProxy() {
+ if (shouldSkipTest()) {
+ return;
+ }
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothLeBroadcast);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST, mBluetoothLeBroadcast);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
+ @Test
public void testGetConnectedDevices() {
if (shouldSkipTest()) {
return;
@@ -552,11 +564,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -566,28 +578,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class ServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothLeBroadcast = (BluetoothLeBroadcast) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
index 2278e86..f63e02e 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothLeScanTest.java
@@ -102,7 +102,6 @@
if (!mLocationOn) {
TestUtils.disableLocation(getContext());
}
- assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java
index 4a6cd06..34f0f52 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapClientTest.java
@@ -62,8 +62,8 @@
private BluetoothMapClient mBluetoothMapClient;
private boolean mIsMapClientSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -83,8 +83,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothMapClient = null;
@@ -105,12 +105,21 @@
mBluetoothMapClient = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsMapClientSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothMapClient);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.MAP_CLIENT, mBluetoothMapClient);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsMapClientSupported)) return;
@@ -225,11 +234,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -239,28 +248,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothMapClientServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothMapClient = (BluetoothMapClient) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java
index 6a7fe34..0e5da68 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothMapTest.java
@@ -51,8 +51,8 @@
private BluetoothMap mBluetoothMap;
private boolean mIsMapSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -72,8 +72,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothMap = null;
@@ -93,12 +93,21 @@
mBluetoothMap = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsMapSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothMap);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.MAP, mBluetoothMap);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsMapSupported)) return;
@@ -186,11 +195,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -200,28 +209,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothMapServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
mBluetoothMap = (BluetoothMap) proxy;
mIsProfileReady = true;
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java
index 733d9f3..aa4ca29 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPanTest.java
@@ -46,8 +46,8 @@
private BluetoothPan mBluetoothPan;
private boolean mIsPanSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -66,8 +66,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothPan = null;
@@ -86,13 +86,22 @@
mBluetoothPan = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsPanSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothPan);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothPan);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsPanSupported)) return;
@@ -176,11 +185,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -190,27 +199,54 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothPanServiceListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothPan = (BluetoothPan) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java
index 7b3df4e..dcdd677 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapClientTest.java
@@ -48,8 +48,8 @@
private BluetoothPbapClient mBluetoothPbapClient;
private boolean mIsPbapClientSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -68,8 +68,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothPbapClient = null;
@@ -89,12 +89,21 @@
mBluetoothPbapClient = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsPbapClientSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothPbapClient);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.PBAP_CLIENT, mBluetoothPbapClient);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsPbapClientSupported)) return;
@@ -182,11 +191,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -196,28 +205,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothPbapClientServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothPbapClient = (BluetoothPbapClient) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java
index 382a292..05b6e91 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothPbapTest.java
@@ -49,8 +49,8 @@
private BluetoothPbap mBluetoothPbap;
private boolean mIsPbapSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
@Override
public void setUp() throws Exception {
@@ -70,8 +70,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothPbap = null;
@@ -91,12 +91,21 @@
mBluetoothPbap = null;
mIsProfileReady = false;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsPbapSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothPbap);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.PBAP, mBluetoothPbap);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void test_getConnectedDevices() {
if (!(mHasBluetooth && mIsPbapSupported)) return;
@@ -165,11 +174,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -179,28 +188,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothPbapServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
mBluetoothPbap = (BluetoothPbap) proxy;
mIsProfileReady = true;
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
index ad5cfff..6da4b9a 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothSapTest.java
@@ -48,8 +48,8 @@
private BluetoothSap mBluetoothSap;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private boolean mIsSapSupported;
@@ -70,8 +70,8 @@
mAdapter = getContext().getSystemService(BluetoothManager.class).getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothSap = null;
@@ -88,15 +88,23 @@
mBluetoothSap = null;
mIsProfileReady = false;
}
- mUiAutomation.adoptShellPermissionIdentity(BLUETOOTH_CONNECT);
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mUiAutomation.dropShellPermissionIdentity();
mAdapter = null;
}
}
+ public void test_closeProfileProxy() {
+ if (!(mHasBluetooth && mIsSapSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothSap);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.SAP, mBluetoothSap);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
@MediumTest
public void test_getConnectedDevices() {
if (!mHasBluetooth || !mIsSapSupported) return;
@@ -161,11 +169,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -175,27 +183,54 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothSapServiceListener implements BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothSap = (BluetoothSap) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
index 134e4bd..ba3cc71 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothServerSocketTest.java
@@ -60,7 +60,6 @@
if (mHasBluetooth && mBluetoothServerSocket != null) {
mBluetoothServerSocket.close();
}
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
mAdapter = null;
mBluetoothServerSocket = null;
mUiAutomation.dropShellPermissionIdentity();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
index 26237c1..09c4308 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/BluetoothVolumeControlTest.java
@@ -47,8 +47,8 @@
private BluetoothVolumeControl mBluetoothVolumeControl;
private boolean mIsVolumeControlSupported;
private boolean mIsProfileReady;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private boolean mVolumeOffsetChangedCallbackCalled;
private TestCallback mTestCallback;
private Executor mTestExecutor;
@@ -78,8 +78,8 @@
mAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mBluetoothVolumeControl = null;
@@ -117,14 +117,23 @@
mTestCallback = null;
mTestExecutor = null;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- mAdapter = null;
- }
+ mAdapter = null;
TestUtils.dropPermissionAsShellUid();
}
}
+ public void testCloseProfileProxy() {
+ if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mBluetoothVolumeControl);
+ assertTrue(mIsProfileReady);
+
+ mAdapter.closeProfileProxy(BluetoothProfile.VOLUME_CONTROL, mBluetoothVolumeControl);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
public void testGetConnectedDevices() {
if (!(mHasBluetooth && mIsVolumeControlSupported)) return;
@@ -290,11 +299,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -304,28 +313,55 @@
} catch (InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class BluetoothVolumeControlServiceListener implements
BluetoothProfile.ServiceListener {
@Override
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mBluetoothVolumeControl = (BluetoothVolumeControl) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
@Override
public void onServiceDisconnected(int profile) {
+ mProfileConnectionlock.lock();
+ mIsProfileReady = false;
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
index 2ed393f..536d32b 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/HearingAidProfileTest.java
@@ -69,8 +69,8 @@
private BroadcastReceiver mIntentReceiver;
private UiAutomation mUiAutomation;;
- private Condition mConditionProfileIsConnected;
- private ReentrantLock mProfileConnectedlock;
+ private Condition mConditionProfileConnection;
+ private ReentrantLock mProfileConnectionlock;
private boolean mIsProfileReady;
private static List<Integer> mValidConnectionStates = new ArrayList<Integer>(
@@ -94,8 +94,8 @@
mBluetoothAdapter = manager.getAdapter();
assertTrue(BTAdapterUtils.enableAdapter(mBluetoothAdapter, mContext));
- mProfileConnectedlock = new ReentrantLock();
- mConditionProfileIsConnected = mProfileConnectedlock.newCondition();
+ mProfileConnectionlock = new ReentrantLock();
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
mIsProfileReady = false;
mService = null;
mBluetoothAdapter.getProfileProxy(getContext(), new HearingAidsServiceListener(),
@@ -107,12 +107,21 @@
if (!(mIsBleSupported && mIsHearingAidSupported)) {
return;
}
- if (mBluetoothAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mBluetoothAdapter, mContext));
- }
mUiAutomation.dropShellPermissionIdentity();
}
+ public void test_closeProfileProxy() {
+ if (!(mIsBleSupported && mIsHearingAidSupported)) return;
+
+ assertTrue(waitForProfileConnect());
+ assertNotNull(mService);
+ assertTrue(mIsProfileReady);
+
+ mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEARING_AID, mService);
+ assertTrue(waitForProfileDisconnect());
+ assertFalse(mIsProfileReady);
+ }
+
/**
* Basic test case to make sure that Hearing Aid Profile Proxy can connect.
*/
@@ -275,11 +284,11 @@
}
private boolean waitForProfileConnect() {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
try {
// Wait for the Adapter to be disabled
while (!mIsProfileReady) {
- if (!mConditionProfileIsConnected.await(
+ if (!mConditionProfileConnection.await(
PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
// Timeout
Log.e(TAG, "Timeout while waiting for Profile Connect");
@@ -289,30 +298,54 @@
} catch(InterruptedException e) {
Log.e(TAG, "waitForProfileConnect: interrrupted");
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
return mIsProfileReady;
}
+ private boolean waitForProfileDisconnect() {
+ mConditionProfileConnection = mProfileConnectionlock.newCondition();
+ mProfileConnectionlock.lock();
+ try {
+ while (mIsProfileReady) {
+ if (!mConditionProfileConnection.await(
+ PROXY_CONNECTION_TIMEOUT_MS, TimeUnit.MILLISECONDS)) {
+ // Timeout
+ Log.e(TAG, "Timeout while waiting for Profile Disconnect");
+ break;
+ } // else spurious wakeups
+ }
+ } catch (InterruptedException e) {
+ Log.e(TAG, "waitForProfileDisconnect: interrrupted");
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
+ return !mIsProfileReady;
+ }
+
private final class HearingAidsServiceListener
implements BluetoothProfile.ServiceListener {
public void onServiceConnected(int profile, BluetoothProfile proxy) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mService = (BluetoothHearingAid) proxy;
mIsProfileReady = true;
try {
- mConditionProfileIsConnected.signal();
+ mConditionProfileConnection.signal();
} finally {
- mProfileConnectedlock.unlock();
+ mProfileConnectionlock.unlock();
}
}
public void onServiceDisconnected(int profile) {
- mProfileConnectedlock.lock();
+ mProfileConnectionlock.lock();
mIsProfileReady = false;
mService = null;
- mProfileConnectedlock.unlock();
+ try {
+ mConditionProfileConnection.signal();
+ } finally {
+ mProfileConnectionlock.unlock();
+ }
}
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
index 62d1c6a..7973808 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/LeL2capSocketTest.java
@@ -51,9 +51,6 @@
if (!TestUtils.isBleSupported(getContext())) {
return;
}
- if (mAdapter != null) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
mAdapter = null;
InstrumentationRegistry.getInstrumentation().getUiAutomation()
.dropShellPermissionIdentity();
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java
index 40d5c3f..88b5fb3 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/OobDataTest.java
@@ -18,11 +18,12 @@
import static android.bluetooth.cts.TestUtils.assertArrayEquals;
+import static com.google.common.truth.Truth.assertThat;
+
import android.bluetooth.OobData;
import android.content.pm.PackageManager;
import android.test.AndroidTestCase;
-import java.util.Arrays;
public class OobDataTest extends AndroidTestCase {
@@ -177,4 +178,59 @@
assertArrayEquals(leTemporaryKey, leData.getLeTemporaryKey());
assertEquals(OobData.LE_FLAG_BREDR_NOT_SUPPORTED, leData.getLeFlags());
}
+
+ public void testToString() {
+ byte[] confirmationHash = new byte[]{0x52, 0x70, 0x49, 0x41, 0x1A, (byte) 0xB3, 0x3F, 0x5C,
+ (byte) 0xE0, (byte) 0x99, 0x37, 0x29, 0x21, 0x52, 0x65, 0x49};
+ byte[] address = new byte[]{0x12, 0x34, 0x56, 0x78, (byte) 0x8A, (byte) 0xBC, 0x0};
+
+ OobData.LeBuilder leBuilder = new OobData.LeBuilder(confirmationHash, address,
+ OobData.LE_DEVICE_ROLE_PERIPHERAL_ONLY);
+
+ String deviceNameString = "Test Device Name";
+ byte[] deviceName = deviceNameString.getBytes();
+ byte[] randomizerHash = new byte[]{(byte) 0x9E, 0x43, 0x51, 0x10, 0x70, 0x33, 0x01,
+ (byte) 0xDE, 0x00, 0x02, 0x03, 0x05, 0x09, 0x10, 0x40, 0x07};
+ byte[] leTemporaryKey = new byte[]{0x01, 0x12, 0x34, 0x56, 0x78, (byte) 0x9A, (byte) 0xBC,
+ (byte) 0xDE, (byte) 0xF0, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77};
+
+ leBuilder
+ .setDeviceName(deviceName)
+ .setRandomizerHash(randomizerHash)
+ .setLeTemporaryKey(leTemporaryKey)
+ .setLeFlags(OobData.LE_FLAG_BREDR_NOT_SUPPORTED);
+ OobData leData = leBuilder.build();
+
+ String expected = "OobData: \n\t"
+ + "Device Address With Type: "
+ + toHexString(leData.getDeviceAddressWithType()) + "\n\t"
+ + "Confirmation: " + toHexString(leData.getConfirmationHash()) + "\n\t"
+ + "Randomizer: " + toHexString(leData.getRandomizerHash()) + "\n\t"
+ + "Device Name: " + toHexString(leData.getDeviceName()) + "\n\t"
+ + "OobData Length: " + toHexString(leData.getClassicLength()) + "\n\t"
+ + "Class of Device: " + toHexString(leData.getClassOfDevice()) + "\n\t"
+ + "LE Device Role: " + toHexString(leData.getLeDeviceRole()) + "\n\t"
+ + "LE Temporary Key: " + toHexString(leData.getLeTemporaryKey()) + "\n\t"
+ + "LE Appearance: " + toHexString(leData.getLeAppearance()) + "\n\t"
+ + "LE Flags: " + toHexString(leData.getLeFlags()) + "\n\t";
+ String toString = leData.toString();
+
+ assertThat(toString).isEqualTo(expected);
+
+ int describeContents = 0;
+ assertThat(leData.describeContents()).isEqualTo(describeContents);
+ }
+
+ private String toHexString(int b) {
+ return toHexString(new byte[]{(byte) b});
+ }
+
+ private String toHexString(byte[] array) {
+ if (array == null) return "null";
+ StringBuilder builder = new StringBuilder(array.length * 2);
+ for (byte b : array) {
+ builder.append(String.format("%02x", b));
+ }
+ return builder.toString();
+ }
}
diff --git a/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java b/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java
index e9a9314..bfaf557 100644
--- a/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java
+++ b/tests/tests/bluetooth/src/android/bluetooth/cts/SystemBluetoothTest.java
@@ -82,9 +82,6 @@
@Override
public void tearDown() throws Exception {
super.tearDown();
- if (mHasBluetooth) {
- assertTrue(BTAdapterUtils.disableAdapter(mAdapter, mContext));
- }
}
/**
diff --git a/tests/tests/car/Android.bp b/tests/tests/car/Android.bp
index 6140f7b..92351cb 100644
--- a/tests/tests/car/Android.bp
+++ b/tests/tests/car/Android.bp
@@ -32,7 +32,9 @@
name: "CtsCarTestCases",
defaults: ["cts_defaults"],
static_libs: [
+ "android.car.test.utils",
"androidx.test.rules",
+ "android-support-v4",
"compatibility-device-util-axt",
"truth-prebuilt",
"ctstestrunner-axt",
diff --git a/tests/tests/car/AndroidManifest.xml b/tests/tests/car/AndroidManifest.xml
index a0326fc..89a270d 100644
--- a/tests/tests/car/AndroidManifest.xml
+++ b/tests/tests/car/AndroidManifest.xml
@@ -25,6 +25,7 @@
<uses-permission android:name="android.car.permission.CAR_SPEED" />
<uses-permission android:name="android.car.permission.CONTROL_CAR_DISPLAY_UNITS" />
<uses-permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS" />
+ <uses-permission android:name="android.car.permission.CAR_TEST_SERVICE" />
<uses-permission android:name="android.car.permission.READ_CAR_POWER_POLICY" />
<uses-permission android:name="android.car.permission.USE_CAR_TELEMETRY_SERVICE" />
<uses-permission android:name="android.permission.BLUETOOTH" />
@@ -45,6 +46,12 @@
<activity android:name=".drivingstate.NonDistractionOptimizedActivity">
<meta-data android:name="distractionOptimized" android:value="false"/>
</activity>
+
+ <!-- Setting the car target version to TIRAMISU_1 to allow tests checking car target version
+ to pass, e.g.
+ CarServiceHelperServiceUpdatableTest#testSendUserLifecycleEventAndOnUserCreated,
+ CarServiceHelperServiceUpdatableTest#testSendUserLifecycleEventAndOnUserRemoved -->
+ <meta-data android:name="android.car.targetCarVersion" android:value="33:1"/>
</application>
<instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
diff --git a/tests/tests/car/src/android/car/cts/AbstractCarLessTestCase.java b/tests/tests/car/src/android/car/cts/AbstractCarLessTestCase.java
new file mode 100644
index 0000000..3e9632f
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/AbstractCarLessTestCase.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cts;
+
+import android.car.test.AbstractExpectableTestCase;
+import android.car.test.ApiCheckerRule;
+
+import org.junit.Rule;
+
+/**
+ * Base class for tests that don't need to connect to a {@code android.car.Car} object.
+ *
+ * <p>Typically used to test POJO-like (Plain-Old Java Objects) classes.
+ */
+abstract class AbstractCarLessTestCase extends AbstractExpectableTestCase {
+
+ @Rule
+ public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+}
diff --git a/tests/tests/car/src/android/car/cts/ApiVersionTest.java b/tests/tests/car/src/android/car/cts/ApiVersionTest.java
new file mode 100644
index 0000000..603ad27
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/ApiVersionTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.car.ApiVersion;
+import android.car.CarVersion;
+import android.car.PlatformVersion;
+
+//TODO(b/236153976): add when supported
+//import com.google.common.testing.EqualsTester;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.Collection;
+
+@RunWith(Parameterized.class)
+public final class ApiVersionTest {
+
+ private final ApiVersionFactory<?> mFactory;
+
+ public ApiVersionTest(ApiVersionFactory<?> factory) {
+ mFactory = factory;
+ }
+
+ @Test
+ public void testGetters() {
+ ApiVersion<?> version = version(42, 108);
+
+ assertWithMessage("%s.getMajorVersion()", version)
+ .that(version.getMajorVersion()).isEqualTo(42);
+ assertWithMessage("%s.getMinorVersion()", version)
+ .that(version.getMinorVersion()).isEqualTo(108);
+ }
+
+ @Test
+ public void testGetters_majorOnlyConstructor() {
+ ApiVersion<?> version = version(42);
+
+ assertWithMessage("%s.getMajorVersion()", version)
+ .that(version.getMajorVersion()).isEqualTo(42);
+ assertWithMessage("%s.getMinorVersion()", version)
+ .that(version.getMinorVersion()).isEqualTo(0);
+ }
+
+ @Test
+ public void testToString() {
+ String string = version(42, 108).toString();
+
+ assertWithMessage("version(42, 108).toString()").that(string).contains("major=42");
+ assertWithMessage("version(42, 108).toString()").that(string).contains("minor=108");
+ assertWithMessage("version(42, 108).toString()").that(string).doesNotContain("name=");
+ }
+
+ @Test
+ public void testAtLeast_null() {
+ assertThrows(NullPointerException.class, () -> version(42, 108).isAtLeast(null));
+ }
+
+ @Test
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public void testAtLeast_major() {
+ ApiVersion version = version(42, 108);
+
+ assertWithMessage("%s.atLeast(41)", version)
+ .that(version.isAtLeast(version(41))).isTrue();
+ assertWithMessage("%s.atLeast(42)", version)
+ .that(version.isAtLeast(version(42))).isTrue();
+ assertWithMessage("%s.atLeast(43)", version)
+ .that(version.isAtLeast(version(43))).isFalse();
+ }
+
+ @Test
+ @SuppressWarnings({"rawtypes", "unchecked"})
+ public void testAtLeast_majorAndMinor() {
+ ApiVersion version = version(42, 108);
+
+ assertWithMessage("%s.atLeast(41, 109)", version)
+ .that(version.isAtLeast(version(41, 109))).isTrue();
+ assertWithMessage("%s.atLeast(42, 107)", version)
+ .that(version.isAtLeast(version(42, 107))).isTrue();
+ assertWithMessage("%s.atLeast(42, 108)", version)
+ .that(version.isAtLeast(version(42, 108))).isTrue();
+
+ assertWithMessage("%s.atLeast(42, 109)", version)
+ .that(version.isAtLeast(version(42, 109))).isFalse();
+ assertWithMessage("%s.atLeast(43, 0)", version)
+ .that(version.isAtLeast(version(43, 0))).isFalse();
+ }
+
+ // TODO(b/236153976): comment back once guava is supported
+ // (then also add check for different string but same versions)
+// @Test
+// public void testEqualsAndHashcode() {
+// new EqualsTester()
+// .addEqualityGroup(version(4, 8), version(4, 8))
+// .addEqualityGroup(version(15), version(15))
+// .addEqualityGroup(version(16), version(16, 0))
+// .addEqualityGroup(version(23, 0), version(23))
+//
+// // Make sure different subclasses are different
+// .addEqualityGroup(CarApiVersion.forMajorVersion(42),
+// CarApiVersion.forMajorVersion(42))
+// .addEqualityGroup(PlatformApiVersion.forMajorVersion(42),
+// PlatformApiVersion.forMajorVersion(42))
+//
+// .testEquals();
+// }
+
+ @Test
+ public void testAtLeast_wrongTypes() {
+ @SuppressWarnings("rawtypes")
+ ApiVersion myObject = version(42);
+ @SuppressWarnings("rawtypes")
+ ApiVersion otherObject = mFactory.otherType(42);
+
+ assertThrows(IllegalArgumentException.class, () -> myObject.isAtLeast(otherObject));
+ assertThrows(IllegalArgumentException.class, () -> otherObject.isAtLeast(myObject));
+ }
+
+ private ApiVersion<?> version(int major, int minor) {
+ return mFactory.newApiVersion(major, minor);
+ }
+
+ private ApiVersion<?> version(int major) {
+ return mFactory.newApiVersion(major);
+ }
+
+ @Parameterized.Parameters
+ public static Collection<?> parameters() {
+ return Arrays.asList(
+ new Object[][] {
+ { new CarApiVersionFactory() },
+ { new PlatformApiVersionFactory() },
+ });
+ }
+
+ private interface ApiVersionFactory<T extends ApiVersion<T>> {
+ T newApiVersion(int majorVersion, int minorVersion);
+ T newApiVersion(int majorVersion);
+ ApiVersion<?> otherType(int majorVersion);
+ }
+
+ private static final class CarApiVersionFactory implements ApiVersionFactory<CarVersion> {
+
+ @Override
+ public CarVersion newApiVersion(int majorVersion, int minorVersion) {
+ return CarVersion.forMajorAndMinorVersions(majorVersion, minorVersion);
+ }
+
+ @Override
+ public CarVersion newApiVersion(int majorVersion) {
+ return CarVersion.forMajorVersion(majorVersion);
+ }
+
+ @Override
+ public ApiVersion<?> otherType(int majorVersion) {
+ return PlatformVersion.forMajorVersion(majorVersion);
+ }
+ }
+
+ private static final class PlatformApiVersionFactory
+ implements ApiVersionFactory<PlatformVersion> {
+
+ @Override
+ public PlatformVersion newApiVersion(int majorVersion, int minorVersion) {
+ return PlatformVersion.forMajorAndMinorVersions(majorVersion, minorVersion);
+ }
+
+ @Override
+ public PlatformVersion newApiVersion(int majorVersion) {
+ return PlatformVersion.forMajorVersion(majorVersion);
+ }
+
+ @Override
+ public ApiVersion<?> otherType(int majorVersion) {
+ return CarVersion.forMajorVersion(majorVersion);
+ }
+ }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarPerformanceManagerTest.java b/tests/tests/car/src/android/car/cts/CarPerformanceManagerTest.java
new file mode 100644
index 0000000..6a11670
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarPerformanceManagerTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cts;
+
+import static android.os.Process.getThreadPriority;
+import static android.os.Process.setThreadPriority;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.UiAutomation;
+import android.car.Car;
+import android.car.os.CarPerformanceManager;
+import android.car.os.ThreadPolicyWithPriority;
+import android.car.test.ApiCheckerRule;
+import android.platform.test.annotations.AppModeFull;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+
+@SmallTest
+@AppModeFull(reason = "Instant Apps cannot get car related permissions")
+public class CarPerformanceManagerTest extends CarApiTestBase {
+
+ private UiAutomation mUiAutomation;
+ private CarPerformanceManager mCarPerformanceManager;
+ private ThreadPolicyWithPriority mOriginalPolicyWithPriority;
+
+ // TODO(b/242350638): move to super class (although it would need to call
+ // disableAnnotationsCheck()
+ @Rule
+ public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+
+ private void setThreadPriorityGotThreadPriorityVerify(ThreadPolicyWithPriority p)
+ throws Exception {
+ mCarPerformanceManager.setThreadPriority(p);
+
+ ThreadPolicyWithPriority gotP = mCarPerformanceManager.getThreadPriority();
+
+ assertThat(gotP.getPolicy()).isEqualTo(p.getPolicy());
+ assertThat(gotP.getPriority()).isEqualTo(p.getPriority());
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ mUiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ mUiAutomation.adoptShellPermissionIdentity(Car.PERMISSION_MANAGE_THREAD_PRIORITY);
+
+ mCarPerformanceManager = (CarPerformanceManager) getCar().getCarManager(
+ Car.CAR_PERFORMANCE_SERVICE);
+ assertThat(mCarPerformanceManager).isNotNull();
+
+ // TODO(b/237015981): it would be cleaner to split this logic into a separate @Before method
+ // which would be annotated with:
+ // @TestApiRequirements(requiresApi="...", onApiViolation=IGNORE)
+ // But that would require a new rule to wrap the whole test class with
+ // adoptShellPermissionIdentity (otherwise there would be no guarantee that the new method
+ // would be called before the call to adoptShellPermissionIdentity)
+ if (mApiCheckerRule.isApiSupported("android.car.os.CarPerformanceManager#"
+ + "getThreadPriority")) {
+ mOriginalPolicyWithPriority = mCarPerformanceManager.getThreadPriority();
+ }
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ if (mOriginalPolicyWithPriority != null) {
+ mCarPerformanceManager.setThreadPriority(mOriginalPolicyWithPriority);
+ }
+
+ mUiAutomation.dropShellPermissionIdentity();
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+ "android.car.os.CarPerformanceManager#getThreadPriority"})
+ public void testSetThreadPriorityDefault() throws Exception {
+ setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_DEFAULT, /* priority= */ 0));
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+ "android.car.os.CarPerformanceManager#getThreadPriority"})
+ public void testSetThreadPriorityFIFOMinPriority() throws Exception {
+ setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_FIFO,
+ ThreadPolicyWithPriority.PRIORITY_MIN));
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+ "android.car.os.CarPerformanceManager#getThreadPriority"})
+ public void testSetThreadPriorityFIFOMaxPriority() throws Exception {
+ setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_FIFO,
+ ThreadPolicyWithPriority.PRIORITY_MAX));
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+ "android.car.os.CarPerformanceManager#getThreadPriority"})
+ public void testSetThreadPriorityRRMinPriority() throws Exception {
+ setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_RR,
+ ThreadPolicyWithPriority.PRIORITY_MIN));
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+ "android.car.os.CarPerformanceManager#getThreadPriority"})
+ public void testSetThreadPriorityRRMaxPriority() throws Exception {
+ setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_RR,
+ ThreadPolicyWithPriority.PRIORITY_MAX));
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "android.car.os.CarPerformanceManager#setThreadPriority(ThreadPolicyWithPriority)",
+ "android.car.os.CarPerformanceManager#getThreadPriority"})
+ public void testSetThreadPriorityDefaultKeepNiceValue() throws Exception {
+ int expectedNiceValue = 10;
+
+ // Resume the test scheduling policy to default policy.
+ mCarPerformanceManager.setThreadPriority(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_DEFAULT, /* priority= */ 0));
+ // Set a nice value for regular scheduling policy.
+ setThreadPriority(expectedNiceValue);
+
+ // Change the scheduling policy.
+ setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_FIFO,
+ ThreadPolicyWithPriority.PRIORITY_MIN));
+
+ // Change it back, the nice value should be resumed.
+ setThreadPriorityGotThreadPriorityVerify(new ThreadPolicyWithPriority(
+ ThreadPolicyWithPriority.SCHED_DEFAULT, /* priority= */ 0));
+
+ assertThat(getThreadPriority(/* tid= */ 0)).isEqualTo(expectedNiceValue);
+ }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
index 0c9bcc5..a1707c2 100644
--- a/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
+++ b/tests/tests/car/src/android/car/cts/CarPropertyManagerTest.java
@@ -19,6 +19,7 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import static org.junit.Assume.assumeTrue;
import static org.testng.Assert.assertThrows;
import android.app.UiAutomation;
@@ -40,29 +41,34 @@
import android.car.hardware.property.CarPropertyManager.CarPropertyEventCallback;
import android.car.hardware.property.VehicleElectronicTollCollectionCardStatus;
import android.car.hardware.property.VehicleElectronicTollCollectionCardType;
+import android.content.pm.PackageManager;
import android.platform.test.annotations.AppModeFull;
import android.platform.test.annotations.RequiresDevice;
+import android.support.v4.content.ContextCompat;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.ArraySet;
import android.util.SparseArray;
import androidx.annotation.GuardedBy;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.CddTest;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import org.junit.Assert;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
@SmallTest
@RequiresDevice
@@ -104,10 +110,47 @@
private static final ImmutableSet<Integer> SPEED_DISPLAY_UNITS =
ImmutableSet.<Integer>builder().add(VehicleUnit.METER_PER_SEC,
VehicleUnit.MILES_PER_HOUR, VehicleUnit.KILOMETERS_PER_HOUR).build();
+ private static final ImmutableSet<Integer> TURN_SIGNAL_STATES =
+ ImmutableSet.<Integer>builder().add(/*TurnSignalState.NONE=*/0,
+ /*TurnSignalState.RIGHT=*/1, /*TurnSignalState.LEFT=*/2).build();
+ private static final ImmutableSet<Integer> VEHICLE_LIGHT_STATES =
+ ImmutableSet.<Integer>builder().add(/*VehicleLightState.OFF=*/0,
+ /*VehicleLightState.ON=*/1, /*VehicleLightState.DAYTIME_RUNNING=*/2).build();
+ private static final ImmutableSet<Integer> VEHICLE_LIGHT_SWITCHES =
+ ImmutableSet.<Integer>builder().add(/*VehicleLightSwitch.OFF=*/0,
+ /*VehicleLightSwitch.ON=*/1, /*VehicleLightSwitch.DAYTIME_RUNNING=*/2,
+ /*VehicleLightSwitch.AUTOMATIC=*/256).build();
+ private static final ImmutableSet<Integer> HVAC_TEMPERATURE_DISPLAY_UNITS =
+ ImmutableSet.<Integer>builder().add(VehicleUnit.CELSIUS,
+ VehicleUnit.FAHRENHEIT).build();
+ private static final ImmutableSet<Integer> SINGLE_HVAC_FAN_DIRECTIONS = ImmutableSet.of(
+ /*VehicleHvacFanDirection.FACE=*/0x1, /*VehicleHvacFanDirection.FLOOR=*/0x2,
+ /*VehicleHvacFanDirection.DEFROST=*/0x4);
+ private static final ImmutableSet<Integer> ALL_POSSIBLE_HVAC_FAN_DIRECTIONS =
+ generateAllPossibleHvacFanDirections();
+ private static final ImmutableSet<Integer> VEHICLE_SEAT_OCCUPANCY_STATES = ImmutableSet.of(
+ /*VehicleSeatOccupancyState.UNKNOWN=*/0, /*VehicleSeatOccupancyState.VACANT=*/1,
+ /*VehicleSeatOccupancyState.OCCUPIED=*/2);
+
/** contains property Ids for the properties required by CDD */
private final ArraySet<Integer> mPropertyIds = new ArraySet<>();
private CarPropertyManager mCarPropertyManager;
+ private static ImmutableSet<Integer> generateAllPossibleHvacFanDirections() {
+ ImmutableSet.Builder<Integer> allPossibleFanDirectionsBuilder = ImmutableSet.builder();
+ for (int i = 1; i <= SINGLE_HVAC_FAN_DIRECTIONS.size(); i++) {
+ allPossibleFanDirectionsBuilder.addAll(Sets.combinations(SINGLE_HVAC_FAN_DIRECTIONS,
+ i).stream().map(hvacFanDirectionCombo -> {
+ Integer possibleHvacFanDirection = 0;
+ for (Integer hvacFanDirection : hvacFanDirectionCombo) {
+ possibleHvacFanDirection |= hvacFanDirection;
+ }
+ return possibleHvacFanDirection;
+ }).collect(Collectors.toList()));
+ }
+ return allPossibleFanDirectionsBuilder.build();
+ }
+
private static void verifyWheelTickConfigArray(int supportedWheels, int wheelToVerify,
int configArrayIndex, int wheelTicksToUm) {
if ((supportedWheels & wheelToVerify) != 0) {
@@ -154,6 +197,20 @@
}
}
+ private static void adoptSystemLevelPermission(String permission, Runnable verifierRunnable) {
+ UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation.adoptShellPermissionIdentity(permission);
+ try {
+ assumeTrue("Unable to adopt Car Shell permission: " + permission,
+ ContextCompat.checkSelfPermission(
+ InstrumentationRegistry.getInstrumentation().getTargetContext(),
+ permission) == PackageManager.PERMISSION_GRANTED);
+ verifierRunnable.run();
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ }
+ }
+
@Before
public void setUp() throws Exception {
super.setUp();
@@ -332,6 +389,29 @@
}
@Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testInfoVinIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_IDENTIFICATION, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.INFO_VIN,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+ String.class).setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+ "INFO_VIN must be 17 characters").that(
+ (String) carPropertyValue.getValue()).hasLength(17))
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
public void testInfoMakeIfSupported() {
VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.INFO_MAKE,
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
@@ -443,14 +523,8 @@
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
- Integer.class).setCarPropertyValueVerifier(
- (carPropertyConfig, carPropertyValue) -> {
- Integer fuelDoorLocation = (Integer) carPropertyValue.getValue();
- assertWithMessage(
- "INFO_FUEL_DOOR_LOCATION must be a defined port location: "
- + fuelDoorLocation).that(
- fuelDoorLocation).isIn(PORT_LOCATION_TYPES);
- }).build().verify(mCarPropertyManager);
+ Integer.class).setPossibleCarPropertyValues(PORT_LOCATION_TYPES).build()
+ .verify(mCarPropertyManager);
}
@Test
@@ -459,14 +533,8 @@
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
- Integer.class).setCarPropertyValueVerifier(
- (carPropertyConfig, carPropertyValue) -> {
- Integer evPortLocation = (Integer) carPropertyValue.getValue();
- assertWithMessage(
- "INFO_EV_PORT_LOCATION must be a defined port location: "
- + evPortLocation).that(
- evPortLocation).isIn(PORT_LOCATION_TYPES);
- }).build().verify(mCarPropertyManager);
+ Integer.class).setPossibleCarPropertyValues(PORT_LOCATION_TYPES).build()
+ .verify(mCarPropertyManager);
}
@Test
@@ -496,21 +564,16 @@
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
- Integer.class).setCarPropertyValueVerifier(
- (carPropertyConfig, carPropertyValue) -> {
- Integer driverSeat = (Integer) carPropertyValue.getValue();
- assertWithMessage("INFO_DRIVER_SEAT must be a defined front seat location: "
- + driverSeat).that(driverSeat).isIn(
- ImmutableSet.builder().add(VehicleAreaSeat.SEAT_UNKNOWN,
- VehicleAreaSeat.SEAT_ROW_1_LEFT,
- VehicleAreaSeat.SEAT_ROW_1_CENTER,
- VehicleAreaSeat.SEAT_ROW_1_RIGHT).build());
- }).setAreaIdsVerifier(areaIds -> assertWithMessage(
+ Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+ VehicleAreaSeat.SEAT_UNKNOWN,
+ VehicleAreaSeat.SEAT_ROW_1_LEFT,
+ VehicleAreaSeat.SEAT_ROW_1_CENTER,
+ VehicleAreaSeat.SEAT_ROW_1_RIGHT))
+ .setAreaIdsVerifier(areaIds -> assertWithMessage(
"Even though INFO_DRIVER_SEAT is VEHICLE_AREA_TYPE_SEAT, it is meant to be "
+ "VEHICLE_AREA_TYPE_GLOBAL, so its AreaIds must contain a single 0").that(
- areaIds).isEqualTo(
- new int[]{VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL})).build().verify(
- mCarPropertyManager);
+ areaIds).isEqualTo(new int[]{VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL}))
+ .build().verify(mCarPropertyManager);
}
@Test
@@ -540,20 +603,11 @@
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setCarPropertyValueVerifier(
- (carPropertyConfig, carPropertyValue) -> {
- Integer electronicTollCollectionCardType =
- (Integer) carPropertyValue.getValue();
- assertWithMessage(
- "ELECTRONIC_TOLL_COLLECTION_CARD_TYPE value must be a valid "
- + "VehicleElectronicTollCollectionCardType").that(
- electronicTollCollectionCardType).isIn(ImmutableSet.builder().add(
- VehicleElectronicTollCollectionCardType.UNKNOWN,
- VehicleElectronicTollCollectionCardType.
- JP_ELECTRONIC_TOLL_COLLECTION_CARD,
- VehicleElectronicTollCollectionCardType.
- JP_ELECTRONIC_TOLL_COLLECTION_CARD_V2).build());
- }).build().verify(mCarPropertyManager);
+ Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+ VehicleElectronicTollCollectionCardType.UNKNOWN,
+ VehicleElectronicTollCollectionCardType.JP_ELECTRONIC_TOLL_COLLECTION_CARD,
+ VehicleElectronicTollCollectionCardType.JP_ELECTRONIC_TOLL_COLLECTION_CARD_V2)
+ ).build().verify(mCarPropertyManager);
}
@Test
@@ -563,22 +617,15 @@
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setCarPropertyValueVerifier(
- (carPropertyConfig, carPropertyValue) -> {
- Integer electronicTollCollectionCardStatus =
- (Integer) carPropertyValue.getValue();
- assertWithMessage(
- "ELECTRONIC_TOLL_COLLECTION_CARD_STATUS value must be a valid "
- + "VehicleElectronicTollCollectionCardStatus").that(
- electronicTollCollectionCardStatus).isIn(ImmutableSet.builder().add(
- VehicleElectronicTollCollectionCardStatus.UNKNOWN,
- VehicleElectronicTollCollectionCardStatus.
- ELECTRONIC_TOLL_COLLECTION_CARD_VALID,
- VehicleElectronicTollCollectionCardStatus.
- ELECTRONIC_TOLL_COLLECTION_CARD_INVALID,
- VehicleElectronicTollCollectionCardStatus.
- ELECTRONIC_TOLL_COLLECTION_CARD_NOT_INSERTED).build());
- }).build().verify(mCarPropertyManager);
+ Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+ VehicleElectronicTollCollectionCardStatus.UNKNOWN,
+ VehicleElectronicTollCollectionCardStatus
+ .ELECTRONIC_TOLL_COLLECTION_CARD_VALID,
+ VehicleElectronicTollCollectionCardStatus
+ .ELECTRONIC_TOLL_COLLECTION_CARD_INVALID,
+ VehicleElectronicTollCollectionCardStatus
+ .ELECTRONIC_TOLL_COLLECTION_CARD_NOT_INSERTED)
+ ).build().verify(mCarPropertyManager);
}
@Test
@@ -616,82 +663,381 @@
CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setCarPropertyValueVerifier(
- (carPropertyConfig, carPropertyValue) -> {
- Integer ignitionState = (Integer) carPropertyValue.getValue();
- assertWithMessage(
- "IGNITION_STATE must be a defined ignition state: "
- + ignitionState).that(
- ignitionState).isIn(ImmutableSet.of(VehicleIgnitionState.UNDEFINED,
- VehicleIgnitionState.LOCK, VehicleIgnitionState.OFF,
- VehicleIgnitionState.ACC, VehicleIgnitionState.ON,
- VehicleIgnitionState.START));
- }).build().verify(mCarPropertyManager);
+ Integer.class).setPossibleCarPropertyValues(ImmutableSet.of(
+ VehicleIgnitionState.UNDEFINED, VehicleIgnitionState.LOCK,
+ VehicleIgnitionState.OFF, VehicleIgnitionState.ACC, VehicleIgnitionState.ON,
+ VehicleIgnitionState.START)).build().verify(mCarPropertyManager);
+ }
+
+ @Test
+ public void testAbsActiveIfSupported() {
+ adoptSystemLevelPermission(/* Car.PERMISSION_CAR_DYNAMICS_STATE = */
+ "android.car.permission.CAR_DYNAMICS_STATE", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ABS_ACTIVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testTractionControlActiveIfSupported() {
+ adoptSystemLevelPermission(/* Car.PERMISSION_CAR_DYNAMICS_STATE = */
+ "android.car.permission.CAR_DYNAMICS_STATE", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TRACTION_CONTROL_ACTIVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testDoorPosIfSupported() {
+ adoptSystemLevelPermission(/* Car.PERMISSION_CONTROL_CAR_DOORS = */
+ "android.car.permission.CONTROL_CAR_DOORS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DOOR_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_DOOR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().requireMinValuesToBeZero().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testDoorMoveIfSupported() {
+ adoptSystemLevelPermission(/* Car.PERMISSION_CONTROL_CAR_DOORS = */
+ "android.car.permission.CONTROL_CAR_DOORS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DOOR_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_DOOR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testDoorLockIfSupported() {
+ adoptSystemLevelPermission(/* Car.PERMISSION_CONTROL_CAR_DOORS = */
+ "android.car.permission.CONTROL_CAR_DOORS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DOOR_LOCK,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_DOOR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testMirrorZPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Z_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testMirrorZMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Z_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testMirrorYPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Y_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testMirrorYMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_Y_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testMirrorLockIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_LOCK,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE, Boolean.class)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testMirrorFoldIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_MIRRORS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.MIRROR_FOLD,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE, Boolean.class)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testWindowPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.WINDOW_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testWindowMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.WINDOW_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testWindowLockIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_WINDOWS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.WINDOW_LOCK,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
}
@Test
public void testDistanceDisplayUnitsIfSupported() {
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DISTANCE_DISPLAY_UNITS,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setPossibleConfigArrayValues(
- DISTANCE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
- mCarPropertyManager);
+ adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+ "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.DISTANCE_DISPLAY_UNITS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleConfigArrayValues(
+ DISTANCE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+ .verifySetterWithConfigArrayValues().build().verify(
+ mCarPropertyManager);
+ });
}
@Test
public void testFuelVolumeDisplayUnitsIfSupported() {
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setPossibleConfigArrayValues(
- VOLUME_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
- mCarPropertyManager);
+ adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+ "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FUEL_VOLUME_DISPLAY_UNITS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleConfigArrayValues(
+ VOLUME_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+ .verifySetterWithConfigArrayValues().build().verify(
+ mCarPropertyManager);
+
+ });
+ }
+
+ @Test
+ public void testTirePressureIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_TIRES=*/
+ "android.car.permission.CAR_TIRES", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TIRE_PRESSURE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Float.class).requireMinMaxValues().setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+ "TIRE_PRESSURE Float value"
+ + " at Area ID equals to "
+ + carPropertyValue.getAreaId()
+ + " must be greater than or equal 0").that(
+ (Float) carPropertyValue.getValue()).isAtLeast(
+ 0)).build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testCriticallyLowTirePressureIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_TIRES=*/
+ "android.car.permission.CAR_TIRES", () -> {
+ VehiclePropertyVerifier.newBuilder(
+ VehiclePropertyIds.CRITICALLY_LOW_TIRE_PRESSURE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+ Float.class).setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> {
+ int areaId = carPropertyValue.getAreaId();
+
+ assertWithMessage(
+ "CRITICALLY_LOW_TIRE_PRESSURE Float value"
+ + "at Area ID equals to" + areaId
+ + " must be greater than or equal 0")
+ .that((Float) carPropertyValue.getValue()).isAtLeast(0);
+
+ CarPropertyConfig<?> tirePressureConfig =
+ mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.TIRE_PRESSURE);
+
+ if (tirePressureConfig == null
+ || tirePressureConfig.getMinValue(areaId) == null) {
+ return;
+ }
+
+ assertWithMessage(
+ "CRITICALLY_LOW_TIRE_PRESSURE Float value"
+ + "at Area ID equals to" + areaId
+ + " must not exceed"
+ + " minFloatValue in TIRE_PRESSURE")
+ .that((Float) carPropertyValue.getValue()).isAtMost(
+ (Float) tirePressureConfig
+ .getMinValue(areaId));
+ }).build().verify(mCarPropertyManager);
+ });
}
@Test
public void testTirePressureDisplayUnitsIfSupported() {
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setPossibleConfigArrayValues(
- PRESSURE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
- mCarPropertyManager);
+ adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+ "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+ VehiclePropertyVerifier.newBuilder(
+ VehiclePropertyIds.TIRE_PRESSURE_DISPLAY_UNITS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleConfigArrayValues(
+ PRESSURE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+ .verifySetterWithConfigArrayValues().build().verify(
+ mCarPropertyManager);
+
+ });
}
@Test
public void testEvBatteryDisplayUnitsIfSupported() {
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setPossibleConfigArrayValues(
- BATTERY_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
- mCarPropertyManager);
+ adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+ "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_BATTERY_DISPLAY_UNITS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleConfigArrayValues(
+ BATTERY_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+ .verifySetterWithConfigArrayValues().build().verify(
+ mCarPropertyManager);
+
+ });
}
@Test
public void testVehicleSpeedDisplayUnitsIfSupported() {
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Integer.class).setPossibleConfigArrayValues(
- SPEED_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray().build().verify(
- mCarPropertyManager);
+ adoptSystemLevelPermission(/*Car.PERMISSION_VENDOR_EXTENSION=*/
+ "android.car.permission.CAR_VENDOR_EXTENSION", () -> {
+ VehiclePropertyVerifier.newBuilder(
+ VehiclePropertyIds.VEHICLE_SPEED_DISPLAY_UNITS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleConfigArrayValues(
+ SPEED_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+ .verifySetterWithConfigArrayValues().build().verify(
+ mCarPropertyManager);
+ });
}
@Test
public void testFuelConsumptionUnitsDistanceOverTimeIfSupported() {
- VehiclePropertyVerifier.newBuilder(
- VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Boolean.class).build().verify(mCarPropertyManager);
+ adoptSystemLevelPermission(Car.PERMISSION_VENDOR_EXTENSION, () -> {
+ VehiclePropertyVerifier.newBuilder(
+ VehiclePropertyIds.FUEL_CONSUMPTION_UNITS_DISTANCE_OVER_VOLUME,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
}
@Test
@@ -793,22 +1139,24 @@
@Test
public void testFuelDoorOpenIfSupported() {
- VehiclePropertyVerifier.newBuilder(
- VehiclePropertyIds.FUEL_DOOR_OPEN,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Boolean.class).build().verify(mCarPropertyManager);
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FUEL_DOOR_OPEN,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
}
@Test
public void testEvChargePortOpenIfSupported() {
- VehiclePropertyVerifier.newBuilder(
- VehiclePropertyIds.EV_CHARGE_PORT_OPEN,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Boolean.class).build().verify(mCarPropertyManager);
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_ENERGY_PORTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_PORT_OPEN,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
}
@Test
@@ -900,11 +1248,13 @@
@Test
public void testEvChargeSwitchIfSupported() {
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_SWITCH,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
- Boolean.class).build().verify(mCarPropertyManager);
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_ENERGY, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.EV_CHARGE_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
}
@Test
@@ -942,28 +1292,1435 @@
@Test
public void testPerfSteeringAngleIfSupported() {
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- uiAutomation.adoptShellPermissionIdentity(Car.PERMISSION_READ_STEERING_STATE);
-
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_STEERING_ANGLE,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
- Float.class).build().verify(mCarPropertyManager);
+ adoptSystemLevelPermission(Car.PERMISSION_READ_STEERING_STATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_STEERING_ANGLE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Float.class).build().verify(mCarPropertyManager);
+ });
}
@Test
public void testPerfRearSteeringAngleIfSupported() {
- UiAutomation uiAutomation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
- uiAutomation.adoptShellPermissionIdentity(Car.PERMISSION_READ_STEERING_STATE);
-
- VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_REAR_STEERING_ANGLE,
- CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
- VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
- CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
- Float.class).build().verify(mCarPropertyManager);
+ adoptSystemLevelPermission(Car.PERMISSION_READ_STEERING_STATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_REAR_STEERING_ANGLE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Float.class).build().verify(mCarPropertyManager);
+ });
}
+ @Test
+ public void testEngineCoolantTempIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+ "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_COOLANT_TEMP,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Float.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testEngineOilLevelIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+ "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_OIL_LEVEL,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+ "ENGINE_OIL_LEVEL Integer value must be greater than or equal"
+ + " 0").that(
+ (Integer) carPropertyValue.getValue()).isAtLeast(
+ 0)).build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testEngineOilTempIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+ "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_OIL_TEMP,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Float.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testEngineRpmIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_CAR_ENGINE_DETAILED=*/
+ "android.car.permission.CAR_ENGINE_DETAILED", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.ENGINE_RPM,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Float.class).setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+ "ENGINE_RPM Float value must be greater than or equal 0").that(
+ (Float) carPropertyValue.getValue()).isAtLeast(
+ 0)).build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testPerfOdometerIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_MILEAGE=*/"android.car.permission.CAR_MILEAGE",
+ () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.PERF_ODOMETER,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS,
+ Float.class).setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> assertWithMessage(
+ "PERF_ODOMETER Float value must be greater than or equal 0")
+ .that((Float) carPropertyValue.getValue()).isAtLeast(0))
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testTurnSignalStateIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+ "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TURN_SIGNAL_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(TURN_SIGNAL_STATES).build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHeadlightsStateIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+ "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HEADLIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHighBeamLightsStateIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+ "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HIGH_BEAM_LIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testFogLightsStateIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+ "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FOG_LIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> {
+ assertWithMessage(
+ "FRONT_FOG_LIGHTS_STATE must not be implemented"
+ + "when FOG_LIGHTS_STATE is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.FRONT_FOG_LIGHTS_STATE))
+ .isNull();
+
+ assertWithMessage(
+ "REAR_FOG_LIGHTS_STATE must not be implemented"
+ + "when FOG_LIGHTS_STATE is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.REAR_FOG_LIGHTS_STATE))
+ .isNull();
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHazardLightsStateIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+ "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HAZARD_LIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testFrontFogLightsStateIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+ "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FRONT_FOG_LIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> {
+ assertWithMessage(
+ "FOG_LIGHTS_STATE must not be implemented"
+ + "when FRONT_FOG_LIGHTS_STATE is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.FOG_LIGHTS_STATE))
+ .isNull();
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testRearFogLightsStateIfSupported() {
+ adoptSystemLevelPermission(/*Car.PERMISSION_EXTERIOR_LIGHTS=*/
+ "android.car.permission.CAR_EXTERIOR_LIGHTS", () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.REAR_FOG_LIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .setCarPropertyValueVerifier(
+ (carPropertyConfig, carPropertyValue) -> {
+ assertWithMessage(
+ "FOG_LIGHTS_STATE must not be implemented"
+ + "when REAR_FOG_LIGHTS_STATE is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.FOG_LIGHTS_STATE))
+ .isNull();
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testCabinLightsStateIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_READ_INTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.CABIN_LIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testReadingLightsStateIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_READ_INTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.READING_LIGHTS_STATE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_STATES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testVehicleCurbWeightIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_PRIVILEGED_CAR_INFO, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.VEHICLE_CURB_WEIGHT,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+ Integer.class).setConfigArrayVerifier(configArray -> {
+ assertWithMessage(
+ "VEHICLE_CURB_WEIGHT configArray must contain the gross weight in "
+ + "kilograms").that(configArray).hasSize(1);
+ assertWithMessage(
+ "VEHICLE_CURB_WEIGHT configArray[0] must contain the gross weight in "
+ + "kilograms and be greater than zero").that(
+ configArray.get(0)).isGreaterThan(0);
+ }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+ Integer curbWeightKg = (Integer) carPropertyValue.getValue();
+ Integer grossWeightKg = carPropertyConfig.getConfigArray().get(0);
+
+ assertWithMessage("VEHICLE_CURB_WEIGHT must be greater than zero").that(
+ curbWeightKg).isGreaterThan(0);
+ assertWithMessage("VEHICLE_CURB_WEIGHT must be less than the gross weight").that(
+ curbWeightKg).isLessThan(grossWeightKg);
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHeadlightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HEADLIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testTrailerPresentIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_PRIVILEGED_CAR_INFO, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.TRAILER_PRESENT,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(
+ ImmutableSet.of(/*TrailerState.UNKNOWN=*/
+ 0, /*TrailerState.NOT_PRESENT*/
+ 1, /*TrailerState.PRESENT=*/2, /*TrailerState.ERROR=*/
+ 3)).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHighBeamLightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HIGH_BEAM_LIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testFogLightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FOG_LIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+ assertWithMessage("FRONT_FOG_LIGHTS_SWITCH must not be implemented"
+ + "when FOG_LIGHTS_SWITCH is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.FRONT_FOG_LIGHTS_SWITCH)).isNull();
+
+ assertWithMessage("REAR_FOG_LIGHTS_SWITCH must not be implemented"
+ + "when FOG_LIGHTS_SWITCH is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.REAR_FOG_LIGHTS_SWITCH)).isNull();
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHazardLightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HAZARD_LIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testFrontFogLightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.FRONT_FOG_LIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+ assertWithMessage("FOG_LIGHTS_SWITCH must not be implemented"
+ + "when FRONT_FOG_LIGHTS_SWITCH is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.FOG_LIGHTS_SWITCH)).isNull();
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testRearFogLightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_EXTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.REAR_FOG_LIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+ assertWithMessage("FOG_LIGHTS_SWITCH must not be implemented"
+ + "when REAR_FOG_LIGHTS_SWITCH is implemented")
+ .that(mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.FOG_LIGHTS_SWITCH)).isNull();
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testCabinLightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_INTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.CABIN_LIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testReadingLightsSwitchIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_INTERIOR_LIGHTS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.READING_LIGHTS_SWITCH,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleCarPropertyValues(VEHICLE_LIGHT_SWITCHES)
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig"})
+ public void testSeatMemorySelectIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_MEMORY_SELECT,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().requireMinValuesToBeZero()
+ .setCarPropertyConfigVerifier(carPropertyConfig -> {
+ int[] areaIds = carPropertyConfig.getAreaIds();
+ CarPropertyConfig<?> seatMemorySetCarPropertyConfig =
+ mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.SEAT_MEMORY_SET);
+
+ assertWithMessage("SEAT_MEMORY_SET must be implemented if "
+ + "SEAT_MEMORY_SELECT is implemented").that(
+ seatMemorySetCarPropertyConfig).isNotNull();
+
+ assertWithMessage("SEAT_MEMORY_SELECT area IDs must match the area IDs of "
+ + "SEAT_MEMORY_SET").that(Arrays.stream(areaIds).boxed().collect(
+ Collectors.toList()))
+ .containsExactlyElementsIn(Arrays.stream(
+ seatMemorySetCarPropertyConfig.getAreaIds()).boxed()
+ .collect(Collectors.toList()));
+
+ for (int areaId : areaIds) {
+ Integer seatMemorySetAreaIdMaxValue =
+ (Integer) seatMemorySetCarPropertyConfig.getMaxValue(areaId);
+ assertWithMessage("SEAT_MEMORY_SET - area ID: " + areaId
+ + " must have max value defined")
+ .that(seatMemorySetAreaIdMaxValue).isNotNull();
+ assertWithMessage("SEAT_MEMORY_SELECT - area ID: " + areaId
+ + "'s max value must be equal to SEAT_MEMORY_SET's max value"
+ + " under the same area ID")
+ .that(seatMemorySetAreaIdMaxValue)
+ .isEqualTo(carPropertyConfig.getMaxValue(areaId));
+ }
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig"})
+ public void testSeatMemorySetIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_MEMORY_SET,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().requireMinValuesToBeZero()
+ .setCarPropertyConfigVerifier(carPropertyConfig -> {
+ int[] areaIds = carPropertyConfig.getAreaIds();
+ CarPropertyConfig<?> seatMemorySelectCarPropertyConfig =
+ mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.SEAT_MEMORY_SELECT);
+
+ assertWithMessage("SEAT_MEMORY_SELECT must be implemented if "
+ + "SEAT_MEMORY_SET is implemented").that(
+ seatMemorySelectCarPropertyConfig).isNotNull();
+
+ assertWithMessage("SEAT_MEMORY_SET area IDs must match the area IDs of "
+ + "SEAT_MEMORY_SELECT").that(Arrays.stream(areaIds).boxed().collect(
+ Collectors.toList()))
+ .containsExactlyElementsIn(Arrays.stream(
+ seatMemorySelectCarPropertyConfig.getAreaIds()).boxed()
+ .collect(Collectors.toList()));
+
+ for (int areaId : areaIds) {
+ Integer seatMemorySelectAreaIdMaxValue =
+ (Integer) seatMemorySelectCarPropertyConfig.getMaxValue(areaId);
+ assertWithMessage("SEAT_MEMORY_SELECT - area ID: " + areaId
+ + " must have max value defined")
+ .that(seatMemorySelectAreaIdMaxValue).isNotNull();
+ assertWithMessage("SEAT_MEMORY_SET - area ID: " + areaId
+ + "'s max value must be equal to SEAT_MEMORY_SELECT's max value"
+ + " under the same area ID")
+ .that(seatMemorySelectAreaIdMaxValue)
+ .isEqualTo(carPropertyConfig.getMaxValue(areaId));
+ }
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatBeltBuckledIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BELT_BUCKLED,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatBeltHeightPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BELT_HEIGHT_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatBeltHeightMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BELT_HEIGHT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatForeAftPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_FORE_AFT_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatForeAftMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_FORE_AFT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatBackrestAngle1PosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatBackrestAngle1MoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_1_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatBackrestAngle2PosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testSeatBackrestAngle2MoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_BACKREST_ANGLE_2_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatHeightPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEIGHT_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatHeightMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEIGHT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatDepthPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_DEPTH_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatDepthMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_DEPTH_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatTiltPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_TILT_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatTiltMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_TILT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatLumbarForeAftPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatLumbarForeAftMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_FORE_AFT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatLumbarSideSupportPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatLumbarSideSupportMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_LUMBAR_SIDE_SUPPORT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatHeadrestHeightMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_HEIGHT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatHeadrestAnglePosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_ANGLE_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatHeadrestAngleMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_ANGLE_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatHeadrestForeAftPosIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_POS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatHeadrestForeAftMoveIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_HEADREST_FORE_AFT_MOVE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testSeatOccupancyIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_SEATS, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.SEAT_OCCUPANCY,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class)
+ .setPossibleCarPropertyValues(VEHICLE_SEAT_OCCUPANCY_STATES).build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacDefrosterIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_DEFROSTER,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHvacElectricDefrosterOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_ELECTRIC_DEFROSTER_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHvacSideMirrorHeatIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_SIDE_MIRROR_HEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().requireMinValuesToBeZero().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHvacSteeringWheelHeatIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_STEERING_WHEEL_HEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().requireZeroToBeContainedInMinMaxRanges()
+ .build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHvacTemperatureDisplayUnitsIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_DISPLAY_UNITS,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossibleConfigArrayValues(
+ HVAC_TEMPERATURE_DISPLAY_UNITS).requirePropertyValueTobeInConfigArray()
+ .verifySetterWithConfigArrayValues().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ public void testHvacTemperatureValueSuggestionIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_VALUE_SUGGESTION,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Float[].class).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacPowerOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_POWER_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setConfigArrayVerifier(configArray -> {
+ CarPropertyConfig<?> hvacPowerOnCarPropertyConfig =
+ mCarPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON);
+ for (int powerDependentProperty : configArray) {
+ CarPropertyConfig<?> powerDependentCarPropertyConfig =
+ mCarPropertyManager.getCarPropertyConfig(powerDependentProperty);
+ if (powerDependentCarPropertyConfig == null) {
+ continue;
+ }
+ assertWithMessage(
+ "HVAC_POWER_ON configArray must only contain VehicleAreaSeat type "
+ + "properties: " + VehiclePropertyIds.toString(
+ powerDependentProperty)).that(
+ powerDependentCarPropertyConfig.getAreaType()).isEqualTo(
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT);
+ for (int powerDependentAreaId : powerDependentCarPropertyConfig.getAreaIds()) {
+ boolean powerDependentAreaIdIsContained = false;
+ for (int hvacPowerOnAreaId : hvacPowerOnCarPropertyConfig.getAreaIds()) {
+ if ((powerDependentAreaId & hvacPowerOnAreaId)
+ == powerDependentAreaId) {
+ powerDependentAreaIdIsContained = true;
+ break;
+ }
+ }
+ assertWithMessage(
+ "HVAC_POWER_ON's area IDs must contain the area IDs"
+ + " of power dependent property: "
+ + VehiclePropertyIds.toString(
+ powerDependentProperty)).that(
+ powerDependentAreaIdIsContained).isTrue();
+ }
+ }
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacFanSpeedIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_FAN_SPEED,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).requireMinMaxValues().setPossiblyDependentOnHvacPowerOn().build()
+ .verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacFanDirectionAvailableIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC,
+ Integer[].class).setPossiblyDependentOnHvacPowerOn().setAreaIdsVerifier(
+ areaIds -> {
+ CarPropertyConfig<?> hvacFanDirectionCarPropertyConfig =
+ mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.HVAC_FAN_DIRECTION);
+ assertWithMessage("HVAC_FAN_DIRECTION must be implemented if "
+ + "HVAC_FAN_DIRECTION_AVAILABLE is implemented").that(
+ hvacFanDirectionCarPropertyConfig).isNotNull();
+
+ assertWithMessage(
+ "HVAC_FAN_DIRECTION_AVAILABLE area IDs must match the area IDs of "
+ + "HVAC_FAN_DIRECTION").that(Arrays.stream(
+ areaIds).boxed().collect(
+ Collectors.toList())).containsExactlyElementsIn(Arrays.stream(
+ hvacFanDirectionCarPropertyConfig.getAreaIds()).boxed().collect(
+ Collectors.toList()));
+
+ }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+ Integer[] fanDirectionValues = (Integer[]) carPropertyValue.getValue();
+ assertWithMessage(
+ "HVAC_FAN_DIRECTION_AVAILABLE area ID: " + carPropertyValue.getAreaId()
+ + " must have at least 1 direction defined").that(
+ fanDirectionValues.length).isAtLeast(1);
+ assertWithMessage(
+ "HVAC_FAN_DIRECTION_AVAILABLE area ID: " + carPropertyValue.getAreaId()
+ + " values all must all be unique: " + Arrays.toString(
+ fanDirectionValues)).that(fanDirectionValues.length).isEqualTo(
+ ImmutableSet.copyOf(fanDirectionValues).size());
+ for (Integer fanDirection : fanDirectionValues) {
+ assertWithMessage("HVAC_FAN_DIRECTION_AVAILABLE's area ID: "
+ + carPropertyValue.getAreaId()
+ + " must be a valid combination of fan directions").that(
+ fanDirection).isIn(ALL_POSSIBLE_HVAC_FAN_DIRECTIONS);
+ }
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacFanDirectionIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_FAN_DIRECTION,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossiblyDependentOnHvacPowerOn().setAreaIdsVerifier(
+ areaIds -> {
+ CarPropertyConfig<?> hvacFanDirectionAvailableCarPropertyConfig =
+ mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE);
+ assertWithMessage("HVAC_FAN_DIRECTION_AVAILABLE must be implemented if "
+ + "HVAC_FAN_DIRECTION is implemented").that(
+ hvacFanDirectionAvailableCarPropertyConfig).isNotNull();
+
+ assertWithMessage("HVAC_FAN_DIRECTION area IDs must match the area IDs of "
+ + "HVAC_FAN_DIRECTION_AVAILABLE").that(Arrays.stream(
+ areaIds).boxed().collect(
+ Collectors.toList())).containsExactlyElementsIn(Arrays.stream(
+ hvacFanDirectionAvailableCarPropertyConfig.getAreaIds()).boxed()
+ .collect(Collectors.toList()));
+
+ }).setCarPropertyValueVerifier((carPropertyConfig, carPropertyValue) -> {
+ CarPropertyValue<Integer[]> hvacFanDirectionAvailableCarPropertyValue =
+ mCarPropertyManager.getProperty(
+ VehiclePropertyIds.HVAC_FAN_DIRECTION_AVAILABLE,
+ carPropertyValue.getAreaId());
+ assertWithMessage("HVAC_FAN_DIRECTION_AVAILABLE value must be available").that(
+ hvacFanDirectionAvailableCarPropertyValue).isNotNull();
+
+ assertWithMessage("HVAC_FAN_DIRECTION area ID " + carPropertyValue.getAreaId()
+ + " value must be in list for HVAC_FAN_DIRECTION_AVAILABLE").that(
+ carPropertyValue.getValue()).isIn(
+ Arrays.asList(hvacFanDirectionAvailableCarPropertyValue.getValue()));
+ }).build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacTemperatureCurrentIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_CURRENT,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Float.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacTemperatureSetIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.Builder<Float> hvacTempSetVerifierBuilder =
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_TEMPERATURE_SET,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Float.class).setPossiblyDependentOnHvacPowerOn().setConfigArrayVerifier(
+ configArray -> {
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET config array must be size 6").that(
+ configArray.size()).isEqualTo(6);
+
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET lower bound must be less than the "
+ + "upper bound for "
+ + "the supported temperatures in Celsius").that(
+ configArray.get(0)).isLessThan(configArray.get(1));
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET increment in Celsius must be "
+ + "greater than 0").that(
+ configArray.get(2)).isGreaterThan(0);
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET increment in Celsius must be less "
+ + "than the "
+ + "difference between the upper and lower bound "
+ + "supported "
+ + "temperatures").that(
+ configArray.get(2)).isLessThan(
+ configArray.get(1) - configArray.get(0));
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET increment in Celsius must evenly "
+ + "space the gap "
+ + "between upper and lower bound").that(
+ (configArray.get(1) - configArray.get(0)) % configArray.get(
+ 2)).isEqualTo(0);
+
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET lower bound must be less than the "
+ + "upper bound for "
+ + "the supported temperatures in Fahrenheit").that(
+ configArray.get(3)).isLessThan(configArray.get(4));
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET increment in Fahrenheit must be "
+ + "greater than 0").that(
+ configArray.get(5)).isGreaterThan(0);
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET increment in Fahrenheit must be "
+ + "less than the "
+ + "difference between the upper and lower bound "
+ + "supported "
+ + "temperatures").that(
+ configArray.get(5)).isLessThan(
+ configArray.get(4) - configArray.get(3));
+ assertWithMessage(
+ "HVAC_TEMPERATURE_SET increment in Fahrenheit must evenly"
+ + " space the gap "
+ + "between upper and lower bound").that(
+ (configArray.get(4) - configArray.get(3)) % configArray.get(
+ 5)).isEqualTo(0);
+
+ });
+
+ CarPropertyConfig<?> hvacTempSetConfig = mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.HVAC_TEMPERATURE_SET);
+ if (hvacTempSetConfig != null) {
+ ImmutableSet.Builder<Float> possibleHvacTempSetValuesBuilder =
+ ImmutableSet.builder();
+ for (int possibleHvacTempSetValue = hvacTempSetConfig.getConfigArray().get(0);
+ possibleHvacTempSetValue <= hvacTempSetConfig.getConfigArray().get(1);
+ possibleHvacTempSetValue += hvacTempSetConfig.getConfigArray().get(2)) {
+ possibleHvacTempSetValuesBuilder.add((float) possibleHvacTempSetValue / 10.0f);
+ }
+ hvacTempSetVerifierBuilder.setPossibleCarPropertyValues(
+ possibleHvacTempSetValuesBuilder.build());
+ }
+
+ hvacTempSetVerifierBuilder.build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacAcOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_AC_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacMaxAcOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_MAX_AC_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacMaxDefrostOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_MAX_DEFROST_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacRecircOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_RECIRC_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacAutoOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_AUTO_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacSeatTemperatureIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_SEAT_TEMPERATURE,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossiblyDependentOnHvacPowerOn().requireMinMaxValues()
+ .requireZeroToBeContainedInMinMaxRanges().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacActualFanSpeedRpmIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_ACTUAL_FAN_SPEED_RPM,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacAutoRecircOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_AUTO_RECIRC_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setPossiblyDependentOnHvacPowerOn().build().verify(
+ mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacSeatVentilationIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_SEAT_VENTILATION,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Integer.class).setPossiblyDependentOnHvacPowerOn().requireMinMaxValues()
+ .requireMinValuesToBeZero().build().verify(mCarPropertyManager);
+ });
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.hardware.property.CarPropertyManager#getCarPropertyConfig",
+ "android.car.hardware.property.CarPropertyManager#getProperty",
+ "android.car.hardware.property.CarPropertyManager#setProperty",
+ "android.car.hardware.property.CarPropertyManager#registerCallback",
+ "android.car.hardware.property.CarPropertyManager#unregisterCallback"})
+ public void testHvacDualOnIfSupported() {
+ adoptSystemLevelPermission(Car.PERMISSION_CONTROL_CAR_CLIMATE, () -> {
+ VehiclePropertyVerifier.newBuilder(VehiclePropertyIds.HVAC_DUAL_ON,
+ CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE,
+ VehicleAreaType.VEHICLE_AREA_TYPE_SEAT,
+ CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE,
+ Boolean.class).setPossiblyDependentOnHvacPowerOn().setAreaIdsVerifier(
+ areaIds -> {
+ CarPropertyConfig<?> hvacTempSetCarPropertyConfig =
+ mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.HVAC_TEMPERATURE_SET);
+ if (hvacTempSetCarPropertyConfig == null) {
+ return;
+ }
+ ImmutableSet<Integer> hvacTempSetAreaIds = ImmutableSet.copyOf(
+ Arrays.stream(
+ hvacTempSetCarPropertyConfig.getAreaIds()).boxed().collect(
+ Collectors.toList()));
+ ImmutableSet.Builder<Integer> allPossibleHvacDualOnAreaIdsBuilder =
+ ImmutableSet.builder();
+ for (int i = 2; i <= hvacTempSetAreaIds.size(); i++) {
+ allPossibleHvacDualOnAreaIdsBuilder.addAll(Sets.combinations(
+ hvacTempSetAreaIds, i).stream().map(areaIdCombo -> {
+ Integer possibleHvacDualOnAreaId = 0;
+ for (Integer areaId : areaIdCombo) {
+ possibleHvacDualOnAreaId |= areaId;
+ }
+ return possibleHvacDualOnAreaId;
+ }).collect(Collectors.toList()));
+ }
+ ImmutableSet<Integer> allPossibleHvacDualOnAreaIds =
+ allPossibleHvacDualOnAreaIdsBuilder.build();
+ for (int areaId : areaIds) {
+ assertWithMessage("HVAC_DUAL_ON area ID: " + areaId
+ + " must be a combination of HVAC_TEMPERATURE_SET area IDs: "
+ + Arrays.toString(
+ hvacTempSetCarPropertyConfig.getAreaIds())).that(areaId).isIn(
+ allPossibleHvacDualOnAreaIds);
+
+ }
+ }).build().verify(mCarPropertyManager);
+ });
+ }
@SuppressWarnings("unchecked")
@Test
@@ -1080,7 +2837,17 @@
// Test for continuous properties
int vehicleSpeed = VehiclePropertyIds.PERF_VEHICLE_SPEED;
- CarPropertyEventCounter speedListenerUI = new CarPropertyEventCounter();
+ CarPropertyConfig<?> carPropertyConfig = mCarPropertyManager.getCarPropertyConfig(
+ VehiclePropertyIds.PERF_VEHICLE_SPEED);
+ float secondsToMillis = 1_000;
+ long bufferMillis = 1_000; // 1 second
+ // timeoutMillis is set to the maximum expected time needed to receive the required
+ // number of PERF_VEHICLE_SPEED events for test. If the test does not receive the
+ // required number of events before the timeout expires, it fails.
+ long timeoutMillis =
+ ((long) ((1.0f / carPropertyConfig.getMinSampleRate()) * secondsToMillis
+ * UI_RATE_EVENT_COUNTER)) + bufferMillis;
+ CarPropertyEventCounter speedListenerUI = new CarPropertyEventCounter(timeoutMillis);
CarPropertyEventCounter speedListenerFast = new CarPropertyEventCounter();
assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isEqualTo(NO_EVENTS);
@@ -1090,22 +2857,22 @@
assertThat(speedListenerFast.receivedError(vehicleSpeed)).isEqualTo(NO_EVENTS);
assertThat(speedListenerFast.receivedErrorWithErrorCode(vehicleSpeed)).isEqualTo(NO_EVENTS);
+ speedListenerUI.resetCountDownLatch(UI_RATE_EVENT_COUNTER);
mCarPropertyManager.registerCallback(speedListenerUI, vehicleSpeed,
CarPropertyManager.SENSOR_RATE_UI);
mCarPropertyManager.registerCallback(speedListenerFast, vehicleSpeed,
CarPropertyManager.SENSOR_RATE_FASTEST);
- speedListenerUI.resetCountDownLatch(UI_RATE_EVENT_COUNTER);
speedListenerUI.assertOnChangeEventCalled();
+ mCarPropertyManager.unregisterCallback(speedListenerUI);
+ mCarPropertyManager.unregisterCallback(speedListenerFast);
+
assertThat(speedListenerUI.receivedEvent(vehicleSpeed)).isGreaterThan(NO_EVENTS);
- assertThat(speedListenerFast.receivedEvent(vehicleSpeed)).isGreaterThan(
+ assertThat(speedListenerFast.receivedEvent(vehicleSpeed)).isAtLeast(
speedListenerUI.receivedEvent(vehicleSpeed));
// The test did not change property values, it should not get error with error codes.
assertThat(speedListenerUI.receivedErrorWithErrorCode(vehicleSpeed)).isEqualTo(NO_EVENTS);
assertThat(speedListenerFast.receivedErrorWithErrorCode(vehicleSpeed)).isEqualTo(NO_EVENTS);
- mCarPropertyManager.unregisterCallback(speedListenerFast);
- mCarPropertyManager.unregisterCallback(speedListenerUI);
-
// Test for on_change properties
int nightMode = VehiclePropertyIds.NIGHT_MODE;
CarPropertyEventCounter nightModeListener = new CarPropertyEventCounter();
@@ -1114,7 +2881,6 @@
nightModeListener.assertOnChangeEventCalled();
assertThat(nightModeListener.receivedEvent(nightMode)).isEqualTo(1);
mCarPropertyManager.unregisterCallback(nightModeListener);
-
}
@Test
@@ -1157,7 +2923,7 @@
@Test
public void testUnregisterWithPropertyId() throws Exception {
// Ignores the test if wheel_tick property does not exist in the car.
- Assume.assumeTrue("WheelTick is not available, skip unregisterCallback test",
+ assumeTrue("WheelTick is not available, skip unregisterCallback test",
mCarPropertyManager.isPropertyAvailable(
VehiclePropertyIds.WHEEL_TICK, VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL));
@@ -1170,7 +2936,7 @@
int eventCounter = getCounterBySampleRate(maxSampleRateHz);
// Ignores the test if sampleRates for properties are too low.
- Assume.assumeTrue("The SampleRates for properties are too low, "
+ assumeTrue("The SampleRates for properties are too low, "
+ "skip testUnregisterWithPropertyId test", eventCounter != 0);
CarPropertyEventCounter speedAndWheelTicksListener = new CarPropertyEventCounter();
@@ -1239,6 +3005,15 @@
private final SparseArray<Integer> mErrorWithErrorCodeCounter = new SparseArray<>();
private int mCounter = FAST_OR_FASTEST_EVENT_COUNTER;
private CountDownLatch mCountDownLatch = new CountDownLatch(mCounter);
+ private final long mTimeoutMillis;
+
+ CarPropertyEventCounter(long timeoutMillis) {
+ mTimeoutMillis = timeoutMillis;
+ }
+
+ CarPropertyEventCounter() {
+ this(WAIT_CALLBACK);
+ }
public int receivedEvent(int propId) {
int val;
@@ -1295,18 +3070,19 @@
}
public void assertOnChangeEventCalled() throws InterruptedException {
- if (!mCountDownLatch.await(WAIT_CALLBACK, TimeUnit.MILLISECONDS)) {
- throw new IllegalStateException("Callback is not called:" + mCounter + "times in "
- + WAIT_CALLBACK + " ms.");
+ if (!mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException(
+ "Callback is not called " + mCounter + "times in " + mTimeoutMillis
+ + " ms. It was only called " + (mCounter
+ - mCountDownLatch.getCount()) + " times.");
}
}
public void assertOnChangeEventNotCalled() throws InterruptedException {
// Once get an event, fail the test.
mCountDownLatch = new CountDownLatch(1);
- if (mCountDownLatch.await(WAIT_CALLBACK, TimeUnit.MILLISECONDS)) {
- throw new IllegalStateException("Callback is called in "
- + WAIT_CALLBACK + " ms.");
+ if (mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS)) {
+ throw new IllegalStateException("Callback is called in " + mTimeoutMillis + " ms.");
}
}
diff --git a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
index 80229ec..3948687 100644
--- a/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
+++ b/tests/tests/car/src/android/car/cts/CarServiceHelperServiceUpdatableTest.java
@@ -16,8 +16,6 @@
package android.car.cts;
-import static com.android.compatibility.common.util.SystemUtil.eventually;
-
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
@@ -27,6 +25,12 @@
import android.app.UiAutomation;
import android.car.Car;
+import android.car.annotation.ApiRequirements;
+import android.car.test.ApiCheckerRule;
+import android.car.test.ApiCheckerRule.IgnoreInvalidApi;
+import android.car.test.ApiCheckerRule.SupportedVersionTest;
+import android.car.test.ApiCheckerRule.UnsupportedVersionTest;
+import android.car.test.ApiCheckerRule.UnsupportedVersionTest.Behavior;
import android.car.user.CarUserManager;
import android.car.user.CarUserManager.UserLifecycleEvent;
import android.car.user.CarUserManager.UserLifecycleListener;
@@ -34,16 +38,18 @@
import android.os.NewUserResponse;
import android.os.ParcelFileDescriptor;
import android.os.SystemClock;
+import android.os.UserHandle;
import android.os.UserManager;
import android.util.Log;
import androidx.test.InstrumentationRegistry;
import androidx.test.filters.FlakyTest;
+import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.SystemUtil;
import org.junit.Before;
-import org.junit.Ignore;
+import org.junit.Rule;
import org.junit.Test;
import java.io.BufferedReader;
@@ -60,6 +66,11 @@
private static final int TIMEOUT_MS = 60_000;
private static final int WAIT_TIME_MS = 1_000;
+ // TODO(b/242350638): move to super class (although it would need to call
+ // disableAnnotationsCheck()
+ @Rule
+ public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+
@Before
public void setUp() throws Exception {
super.setUp();
@@ -67,46 +78,131 @@
}
@Test
- @Ignore("b/234674080")
+ @ApiTest(apis = {"com.android.internal.car.CarServiceHelperService#dump(PrintWriter,String[])"})
+ @IgnoreInvalidApi(reason = "Class not in classpath as it's indirectly tested using dumpsys")
+ @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_0,
+ minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
public void testCarServiceHelperServiceDump() throws Exception {
- assumeThat("System_server_dumper not implemented.",
- executeShellCommand("service check system_server_dumper"),
- containsStringIgnoringCase("system_server_dumper: found"));
+ assumeSystemServerDumpSupported();
assertWithMessage("System server dumper")
.that(executeShellCommand("dumpsys system_server_dumper --list"))
.contains("CarServiceHelper");
+ }
+
+ @Test
+ @ApiTest(apis = {
+ "com.android.internal.car.CarServiceHelperServiceUpdatable#dump(PrintWriter,String[])"
+ })
+ @IgnoreInvalidApi(reason = "Class not in classpath as it's indirectly tested using dumpsys")
+ @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_0,
+ minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
+ public void testCarServiceHelperServiceDump_carServiceProxy() throws Exception {
+ assumeSystemServerDumpSupported();
assertWithMessage("CarServiceHelperService dump")
.that(executeShellCommand("dumpsys system_server_dumper --name CarServiceHelper"))
.contains("CarServiceProxy");
+ }
- // Test setSafeMode
- try {
- executeShellCommand("cmd car_service emulate-driving-state drive");
+ @Test
+ @ApiTest(apis = {
+ "com.android.internal.car.CarServiceHelperServiceUpdatable#dump(PrintWriter,String[])"
+ })
+ @IgnoreInvalidApi(reason = "Class not in classpath as it's indirectly tested using dumpsys")
+ @ApiRequirements(minCarVersion = ApiRequirements.CarVersion.TIRAMISU_0,
+ minPlatformVersion = ApiRequirements.PlatformVersion.TIRAMISU_0)
+ public void testCarServiceHelperServiceDump_serviceStacks() throws Exception {
+ assumeSystemServerDumpSupported();
- eventually(()-> assertWithMessage("CarServiceHelperService dump")
- .that(executeShellCommand(
- "dumpsys system_server_dumper --name CarServiceHelper"))
- .contains("Safe to run device policy operations: false"));
- } finally {
- executeShellCommand("cmd car_service emulate-driving-state park");
- }
-
- eventually(()-> assertWithMessage("CarServiceHelperService dump")
- .that(executeShellCommand("dumpsys system_server_dumper --name CarServiceHelper"))
- .contains("Safe to run device policy operations: true"));
-
- // Test dumpServiceStacks
assertWithMessage("CarServiceHelperService dump")
- .that(executeShellCommand("dumpsys system_server_dumper --name CarServiceHelper"
- + " --dump-service-stacks"))
+ .that(dumpCarServiceHelper("--dump-service-stacks"))
.contains("dumpServiceStacks ANR file path=/data/anr/anr_");
}
+ @Test
+ @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_CREATED"})
+ @SupportedVersionTest(unsupportedVersionTest =
+ "testSendUserLifecycleEventAndOnUserCreated_unsupportedVersion")
+ public void testSendUserLifecycleEventAndOnUserCreated_supportedVersion() throws Exception {
+ testSendUserLifecycleEventAndOnUserCreated(/*onSupportedVersion=*/ true);
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_CREATED"})
+ @UnsupportedVersionTest(behavior = Behavior.EXPECT_PASS,
+ supportedVersionTest = "testSendUserLifecycleEventAndOnUserCreated_supportedVersion")
+ public void testSendUserLifecycleEventAndOnUserCreated_unsupportedVersion() throws Exception {
+ testSendUserLifecycleEventAndOnUserCreated(/*onSupportedVersion=*/ false);
+ }
+
+ private void testSendUserLifecycleEventAndOnUserCreated(boolean onSupportedVersion)
+ throws Exception {
+ // Add listener to check if user started
+ CarUserManager carUserManager = (CarUserManager) getCar()
+ .getCarManager(Car.CAR_USER_SERVICE);
+ LifecycleListener listener = new LifecycleListener();
+ carUserManager.addListener(Runnable::run, listener);
+
+ NewUserResponse response = null;
+ UserManager userManager = null;
+ try {
+ // get create User permissions
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .adoptShellPermissionIdentity(android.Manifest.permission.CREATE_USERS);
+
+ // CreateUser
+ userManager = mContext.getSystemService(UserManager.class);
+ response = userManager.createUser(new NewUserRequest.Builder().build());
+ assertThat(response.isSuccessful()).isTrue();
+
+ int userId = response.getUser().getIdentifier();
+
+ if (onSupportedVersion) {
+ listener.assertEventReceived(
+ userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED);
+ // check the dump stack
+ assertUserLifecycleEventLogged(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED, userId);
+ } else {
+ listener.assertEventNotReceived(
+ userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_CREATED);
+ }
+ } finally {
+ // Clean up the user that was previously created.
+ userManager.removeUser(response.getUser());
+ carUserManager.removeListener(listener);
+ InstrumentationRegistry.getInstrumentation().getUiAutomation()
+ .dropShellPermissionIdentity();
+ }
+ }
+
@FlakyTest(bugId = 222167696)
@Test
- public void testSendUserLifecycleEventAndOnUserRemoved() throws Exception {
+ @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_REMOVED"})
+ @SupportedVersionTest(unsupportedVersionTest =
+ "testSendUserLifecycleEventAndOnUserRemoved_unsupportedVersion")
+ public void testSendUserLifecycleEventAndOnUserRemoved_supportedVersion() throws Exception {
+ testSendUserLifecycleEventAndOnUserRemoved(/*onSupportedVersion=*/ true);
+ }
+
+ @FlakyTest(bugId = 222167696)
+ @Test
+ @ApiTest(apis = {"android.car.user.CarUserManager#USER_LIFECYCLE_EVENT_TYPE_REMOVED"})
+ @UnsupportedVersionTest(behavior = Behavior.EXPECT_PASS,
+ supportedVersionTest = "testSendUserLifecycleEventAndOnUserRemoved_supportedVersion")
+ public void testSendUserLifecycleEventAndOnUserRemoved_unsupportedVersion() throws Exception {
+ testSendUserLifecycleEventAndOnUserRemoved(/*onSupportedVersion=*/ false);
+ }
+
+ private static void assumeSystemServerDumpSupported() throws IOException {
+ assumeThat("System_server_dumper not implemented.",
+ executeShellCommand("service check system_server_dumper"),
+ containsStringIgnoringCase("system_server_dumper: found"));
+ }
+
+ private void testSendUserLifecycleEventAndOnUserRemoved(boolean onSupportedVersion)
+ throws Exception {
// Add listener to check if user started
CarUserManager carUserManager = (CarUserManager) getCar()
.getCarManager(Car.CAR_USER_SERVICE);
@@ -129,11 +225,23 @@
int userId = response.getUser().getIdentifier();
startUser(userId);
listener.assertEventReceived(userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING);
+ // check the dump stack
+ assertUserLifecycleEventLogged(CarUserManager.USER_LIFECYCLE_EVENT_TYPE_STARTING,
+ userId);
// TestOnUserRemoved call
userRemoved = userManager.removeUser(response.getUser());
- // check the dump stack
- assertLastUserRemoved(userId);
+
+ if (onSupportedVersion) {
+ listener.assertEventReceived(
+ userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_REMOVED);
+ // check the dump stack
+ assertUserLifecycleEventLogged(
+ CarUserManager.USER_LIFECYCLE_EVENT_TYPE_REMOVED, userId);
+ } else {
+ listener.assertEventNotReceived(
+ userId, CarUserManager.USER_LIFECYCLE_EVENT_TYPE_REMOVED);
+ }
} finally {
if (!userRemoved && response != null && response.isSuccessful()) {
userManager.removeUser(response.getUser());
@@ -144,12 +252,17 @@
}
}
- private void assertLastUserRemoved(int userId) throws Exception {
+ private void assertUserLifecycleEventLogged(int eventType, int userId) throws Exception {
+ assertUserLifecycleEventLogged(eventType, UserHandle.USER_NULL, userId);
+ }
+
+ private void assertUserLifecycleEventLogged(int eventType, int fromUserId, int toUserId)
+ throws Exception {
// check for the logcat
// TODO(b/210874444): Use logcat helper from
// cts/tests/tests/car_builtin/src/android/car/cts/builtin/util/LogcatHelper.java
- String match = String.format("car_service_on_user_lifecycle: [%d,%d,%d]", 9,
- -10000, userId);
+ String match = String.format("car_service_on_user_lifecycle: [%d,%d,%d]", eventType,
+ fromUserId, toUserId);
long timeout = 60_000;
long startTime = SystemClock.elapsedRealtime();
UiAutomation automation = InstrumentationRegistry.getInstrumentation().getUiAutomation();
@@ -174,6 +287,15 @@
}
+ private String dumpCarServiceHelper(String...args) throws IOException {
+ StringBuilder cmd = new StringBuilder(
+ "dumpsys system_server_dumper --name CarServiceHelper");
+ for (String arg : args) {
+ cmd.append(' ').append(arg);
+ }
+ return executeShellCommand(cmd.toString());
+ }
+
// TODO(214100537): Improve listener by removing sleep.
private final class LifecycleListener implements UserLifecycleListener {
@@ -202,6 +324,20 @@
fail("Event" + eventType + " was not received within timeoutMs: " + TIMEOUT_MS);
}
+
+ public void assertEventNotReceived(int userId, int eventType)
+ throws InterruptedException {
+ long startTime = SystemClock.elapsedRealtime();
+ while (SystemClock.elapsedRealtime() - startTime < TIMEOUT_MS) {
+ boolean result = checkEvent(userId, eventType);
+ if (result) {
+ fail("Event" + eventType
+ + " was not expected but was received within timeoutMs: " + TIMEOUT_MS);
+ }
+ Thread.sleep(WAIT_TIME_MS);
+ }
+ }
+
private boolean checkEvent(int userId, int eventType) {
synchronized (mLock) {
for (int i = 0; i < mEvents.size(); i++) {
diff --git a/tests/tests/car/src/android/car/cts/CarTest.java b/tests/tests/car/src/android/car/cts/CarTest.java
index 3c9fff5..b75217d 100644
--- a/tests/tests/car/src/android/car/cts/CarTest.java
+++ b/tests/tests/car/src/android/car/cts/CarTest.java
@@ -17,26 +17,34 @@
package android.car.cts;
import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
import android.car.Car;
+import android.car.CarVersion;
+import android.car.PlatformVersion;
+import android.car.test.ApiCheckerRule;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.IBinder;
+import android.os.SystemProperties;
import android.platform.test.annotations.AppModeFull;
import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.test.InstrumentationRegistry;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.RequiredFeatureRule;
import org.junit.After;
import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,6 +60,9 @@
public static final RequiredFeatureRule sRequiredFeatureRule = new RequiredFeatureRule(
PackageManager.FEATURE_AUTOMOTIVE);
+ @Rule
+ public final ApiCheckerRule mApiCheckerRule = new ApiCheckerRule.Builder().build();
+
private static final long DEFAULT_WAIT_TIMEOUT_MS = 2000;
private Context mContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
@@ -66,6 +77,9 @@
}
}
+ @ApiTest(apis = {
+ "android.car.Car#isConnected", "android.car.Car#isConnecting", "android.car.Car#connect"
+ })
@Test
public void testConnection() throws Exception {
mServiceConnectionListener = new DefaultServiceConnectionListener();
@@ -81,24 +95,28 @@
assertThat(mCar.isConnecting()).isFalse();
}
+ @ApiTest(apis = {"android.car.Car#createCar(Context)"})
@Test
public void testBlockingCreateCar() throws Exception {
mCar = Car.createCar(mContext);
assertConnectedCar(mCar);
}
+ @ApiTest(apis = {"android.car.Car#getCarConnectionType"})
@Test
public void testConnectionType() throws Exception {
createCarAndRunOnReady((car) -> assertThat(car.getCarConnectionType()).isEqualTo(
Car.CONNECTION_TYPE_EMBEDDED));
}
+ @ApiTest(apis = {"android.car.Car#isFeatureEnabled(String)"})
@Test
public void testIsFeatureEnabled() throws Exception {
createCarAndRunOnReady(
(car) -> assertThat(car.isFeatureEnabled(Car.AUDIO_SERVICE)).isTrue());
}
+ @ApiTest(apis = {"android.car.Car#createCar(Context,Handler,long,CarServiceLifecycleListener)"})
@Test
public void testCreateCarWaitForever() throws Exception {
CarServiceLifecycleListenerImpl listenerImpl = new CarServiceLifecycleListenerImpl(null);
@@ -108,6 +126,7 @@
listenerImpl.waitForReady(DEFAULT_WAIT_TIMEOUT_MS);
}
+ @ApiTest(apis = {"android.car.Car#createCar(Context,Handler,long,CarServiceLifecycleListener)"})
@Test
public void testCreateCarNoWait() throws Exception {
CarServiceLifecycleListenerImpl listenerImpl = new CarServiceLifecycleListenerImpl(null);
@@ -120,6 +139,8 @@
}
@Test
+ @ApiTest(apis = {"android.car.Car#isApiVersionAtLeast(int)",
+ "android.car.Car#isApiAndPlatformVersionAtLeast(int,int)"})
public void testApiVersion() throws Exception {
int ApiVersionTooHigh = 1000000;
int MinorApiVersionTooHigh = 1000000;
@@ -144,6 +165,40 @@
Car.API_VERSION_MINOR_INT, Build.VERSION.SDK_INT + 1)).isFalse();
}
+ @ApiTest(apis = {"android.car.Car#getCarVersion"})
+ @Test
+ public void testGetCarVersion() {
+ CarVersion version = Car.getCarVersion();
+
+ assertWithMessage("Car.getCarVersion()").that(version).isNotNull();
+ assertWithMessage("Car.getCarVersion().toString()").that(version.toString())
+ .contains("name=Car.CAR_VERSION");
+ }
+
+ @ApiTest(apis = {"android.car.Car#getPlatformVersion"})
+ @Test
+ public void testGetPlatformVersion() {
+ PlatformVersion version = Car.getPlatformVersion();
+
+ assertWithMessage("Car.getPlatformVersion()").that(version).isNotNull();
+ assertWithMessage("Car.getPlatformVersion().toString()").that(version.toString())
+ .contains("name=Car.PLATFORM_VERSION");
+ }
+
+ @ApiTest(apis = {"android.car.Car#getPlatformVersion"})
+ @Test
+ public void testPlatformVersionIsNotEmulated() {
+ assertSystemPropertyNotSet(Car.PROPERTY_EMULATED_PLATFORM_VERSION_MAJOR);
+ assertSystemPropertyNotSet(Car.PROPERTY_EMULATED_PLATFORM_VERSION_MINOR);
+ }
+
+ private void assertSystemPropertyNotSet(String property) {
+ String value = SystemProperties.get(property);
+ if (!TextUtils.isEmpty(value)) {
+ throw new AssertionError("Property '" + property + "' is set; value is " + value);
+ }
+ }
+
private static void assertConnectedCar(Car car) {
assertThat(car).isNotNull();
assertThat(car.isConnected()).isTrue();
diff --git a/tests/tests/car/src/android/car/cts/CarVersionTest.java b/tests/tests/car/src/android/car/cts/CarVersionTest.java
new file mode 100644
index 0000000..8ce6681
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/CarVersionTest.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.car.cts;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.car.CarVersion;
+import android.car.annotation.ApiRequirements;
+import android.os.Parcel;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+
+public final class CarVersionTest extends AbstractCarLessTestCase {
+
+ @Test
+ @ApiTest(apis = {"android.car.CarVersion.VERSION_CODES#TIRAMISU_0"})
+ public void testTiramisu_0() {
+ CarVersion version = CarVersion.VERSION_CODES.TIRAMISU_0;
+
+ assertWithMessage("TIRAMISU_0").that(version).isNotNull();
+ expectWithMessage("TIRAMISU_0.major").that(version.getMajorVersion())
+ .isEqualTo(TIRAMISU);
+ expectWithMessage("TIRAMISU_0.minor").that(version.getMinorVersion())
+ .isEqualTo(0);
+
+ CarVersion fromEnum = ApiRequirements.CarVersion.TIRAMISU_0.get();
+ assertWithMessage("TIRAMISU_0 from enum").that(fromEnum).isNotNull();
+ expectWithMessage("TIRAMISU_0 from enum").that(fromEnum).isSameInstanceAs(version);
+
+ String toString = version.toString();
+ expectWithMessage("TIRAMISU_0.toString()").that(toString)
+ .matches(".*CarVersion.*name=TIRAMISU_0.*major=" + TIRAMISU + ".*minor=0.*");
+ CarVersion clone = clone(version);
+ expectWithMessage("TIRAMISU_0.toString() from parcel").that(clone.toString())
+ .isEqualTo(toString);
+
+ CarVersion anonymous = CarVersion.forMajorAndMinorVersions(version.getMajorVersion(),
+ version.getMinorVersion());
+ expectWithMessage("TIRAMISU_0").that(version).isEqualTo(anonymous);
+ expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+ expectWithMessage("TIRAMISU_0's hashcode").that(version.hashCode())
+ .isEqualTo(anonymous.hashCode());
+ expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+ .isEqualTo(version.hashCode());
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.CarVersion.VERSION_CODES#TIRAMISU_1"})
+ public void testTiramisu_1() {
+ CarVersion version = CarVersion.VERSION_CODES.TIRAMISU_1;
+
+ assertWithMessage("TIRAMISU_1").that(version).isNotNull();
+ expectWithMessage("TIRAMISU_1.major").that(version.getMajorVersion())
+ .isEqualTo(TIRAMISU);
+ expectWithMessage("TIRAMISU_1.minor").that(version.getMinorVersion())
+ .isEqualTo(1);
+
+ CarVersion fromEnum = ApiRequirements.CarVersion.TIRAMISU_1.get();
+ assertWithMessage("TIRAMISU_1 from enum").that(fromEnum).isNotNull();
+ expectWithMessage("TIRAMISU_1 from enum").that(fromEnum).isSameInstanceAs(version);
+
+ String toString = version.toString();
+ expectWithMessage("TIRAMISU_1.toString()").that(toString)
+ .matches(".*CarVersion.*name=TIRAMISU_1.*major=" + TIRAMISU + ".*minor=1.*");
+ CarVersion clone = clone(version);
+ expectWithMessage("TIRAMISU_1.toString() from parcel").that(clone.toString())
+ .isEqualTo(toString);
+
+ CarVersion anonymous = CarVersion.forMajorAndMinorVersions(version.getMajorVersion(),
+ version.getMinorVersion());
+ expectWithMessage("TIRAMISU_1").that(version).isEqualTo(anonymous);
+ expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+ expectWithMessage("TIRAMISU_1's hashcode").that(version.hashCode())
+ .isEqualTo(anonymous.hashCode());
+ expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+ .isEqualTo(version.hashCode());
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.CarVersion.VERSION_CODES#TIRAMISU_2"})
+ public void testTiramisu_2() {
+ CarVersion version = CarVersion.VERSION_CODES.TIRAMISU_2;
+
+ assertWithMessage("TIRAMISU_2").that(version).isNotNull();
+ expectWithMessage("TIRAMISU_2.major").that(version.getMajorVersion())
+ .isEqualTo(TIRAMISU);
+ expectWithMessage("TIRAMISU_2.minor").that(version.getMinorVersion())
+ .isEqualTo(2);
+
+ CarVersion fromEnum = ApiRequirements.CarVersion.TIRAMISU_2.get();
+ assertWithMessage("TIRAMISU_2 from enum").that(fromEnum).isNotNull();
+ expectWithMessage("TIRAMISU_2 from enum").that(fromEnum).isSameInstanceAs(version);
+
+ String toString = version.toString();
+ expectWithMessage("TIRAMISU_2.toString()").that(toString)
+ .matches(".*CarVersion.*name=TIRAMISU_2.*major=" + TIRAMISU + ".*minor=2.*");
+ CarVersion clone = clone(version);
+ expectWithMessage("TIRAMISU_2.toString() from parcel").that(clone.toString())
+ .isEqualTo(toString);
+
+ CarVersion anonymous = CarVersion.forMajorAndMinorVersions(version.getMajorVersion(),
+ version.getMinorVersion());
+ expectWithMessage("TIRAMISU_2").that(version).isEqualTo(anonymous);
+ expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+ expectWithMessage("TIRAMISU_2's hashcode").that(version.hashCode())
+ .isEqualTo(anonymous.hashCode());
+ expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+ .isEqualTo(version.hashCode());
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.CarVersion#CREATOR"})
+ public void testMarshalling() {
+ CarVersion original = CarVersion.forMajorAndMinorVersions(66, 6);
+ expectWithMessage("original.describeContents()").that(original.describeContents())
+ .isEqualTo(0);
+
+ CarVersion clone = clone(original);
+ assertWithMessage("CREATOR.createFromParcel()").that(clone).isNotNull();
+ expectWithMessage("clone.major").that(clone.getMajorVersion()).isEqualTo(66);
+ expectWithMessage("clone.minor").that(clone.getMinorVersion()).isEqualTo(6);
+ expectWithMessage("clone.describeContents()").that(clone.describeContents()).isEqualTo(0);
+ expectWithMessage("clone.equals()").that(clone).isEqualTo(original);
+ expectWithMessage("clone.hashCode()").that(clone.hashCode()).isEqualTo(original.hashCode());
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.CarVersion#CREATOR"})
+ public void testNewArray() {
+ CarVersion[] array = CarVersion.CREATOR.newArray(42);
+
+ assertWithMessage("CREATOR.newArray()").that(array).isNotNull();
+ expectWithMessage("CREATOR.newArray()").that(array).hasLength(42);
+ }
+
+ private CarVersion clone(CarVersion original) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ original.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ return CarVersion.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+}
diff --git a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
index 7447cb8..f3c8fc5 100644
--- a/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
+++ b/tests/tests/car/src/android/car/cts/CarWatchdogDaemonTest.java
@@ -16,11 +16,14 @@
package android.car.cts;
+import static android.car.PlatformVersion.VERSION_CODES;
+
import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
+import android.car.Car;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Process;
@@ -162,6 +165,8 @@
* @return Total written bytes recorded for the current userId and package name.
*/
private static long parseDump(String content, int userId, String packageName) throws Exception {
+ String ioWritesHeader = Car.getPlatformVersion().isAtLeast(VERSION_CODES.TIRAMISU_1)
+ ? "Top N Storage I/O Writes:" : "Top N Writes:";
long writtenBytes = 0;
Section curSection = Section.NONE;
String errorLines = "";
@@ -177,7 +182,7 @@
if (line.contains("collector failed to access")) {
errorLines += "\n" + line;
}
- if (line.matches("Top N Storage I/O Writes:")) {
+ if (line.matches(ioWritesHeader)) {
curSection = Section.WRITTEN_BYTES_HEADER_SECTION;
continue;
}
diff --git a/tests/tests/car/src/android/car/cts/PlatformVersionTest.java b/tests/tests/car/src/android/car/cts/PlatformVersionTest.java
new file mode 100644
index 0000000..652c841
--- /dev/null
+++ b/tests/tests/car/src/android/car/cts/PlatformVersionTest.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.car.cts;
+
+import static android.os.Build.VERSION_CODES.TIRAMISU;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.car.PlatformVersion;
+import android.car.annotation.ApiRequirements;
+import android.os.Parcel;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.Test;
+
+public final class PlatformVersionTest extends AbstractCarLessTestCase {
+
+ @Test
+ @ApiTest(apis = {"android.car.PlatformVersion.VERSION_CODES#TIRAMISU_0"})
+ public void testTiramisu_0() {
+ PlatformVersion version = PlatformVersion.VERSION_CODES.TIRAMISU_0;
+
+ assertWithMessage("TIRAMISU_0").that(version).isNotNull();
+ expectWithMessage("TIRAMISU_0.major").that(version.getMajorVersion())
+ .isEqualTo(TIRAMISU);
+ expectWithMessage("TIRAMISU_0.minor").that(version.getMinorVersion())
+ .isEqualTo(0);
+
+ PlatformVersion fromEnum = ApiRequirements.PlatformVersion.TIRAMISU_0.get();
+ assertWithMessage("TIRAMISU_0 from enum").that(fromEnum).isNotNull();
+ expectWithMessage("TIRAMISU_0 from enum").that(fromEnum).isSameInstanceAs(version);
+
+ String toString = version.toString();
+ expectWithMessage("TIRAMISU_0.toString()").that(toString)
+ .matches(".*PlatformVersion.*name=TIRAMISU_0.*major=" + TIRAMISU + ".*minor=0.*");
+ PlatformVersion clone = clone(version);
+ expectWithMessage("TIRAMISU_0.toString() from parcel").that(clone.toString())
+ .isEqualTo(toString);
+
+ PlatformVersion anonymous = PlatformVersion.forMajorAndMinorVersions(
+ version.getMajorVersion(), version.getMinorVersion());
+ expectWithMessage("TIRAMISU_0").that(version).isEqualTo(anonymous);
+ expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+ expectWithMessage("TIRAMISU_0's hashcode").that(version.hashCode())
+ .isEqualTo(anonymous.hashCode());
+ expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+ .isEqualTo(version.hashCode());
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.PlatformVersion.VERSION_CODES#TIRAMISU_1"})
+ public void testTiramisu_1() {
+ PlatformVersion version = PlatformVersion.VERSION_CODES.TIRAMISU_1;
+
+ assertWithMessage("TIRAMISU_1").that(version).isNotNull();
+ expectWithMessage("TIRAMISU_1.major").that(version.getMajorVersion())
+ .isEqualTo(TIRAMISU);
+ expectWithMessage("TIRAMISU_1.minor").that(version.getMinorVersion())
+ .isEqualTo(1);
+
+ PlatformVersion fromEnum = ApiRequirements.PlatformVersion.TIRAMISU_1.get();
+ assertWithMessage("TIRAMISU_1 from enum").that(fromEnum).isNotNull();
+ expectWithMessage("TIRAMISU_1 from enum").that(fromEnum).isSameInstanceAs(version);
+
+ String toString = version.toString();
+ expectWithMessage("TIRAMISU_1.toString()").that(toString)
+ .matches(".*PlatformVersion.*name=TIRAMISU_1.*major=" + TIRAMISU + ".*minor=1.*");
+ PlatformVersion clone = clone(version);
+ expectWithMessage("TIRAMISU_1.toString() from parcel").that(clone.toString())
+ .isEqualTo(toString);
+
+ PlatformVersion anonymous = PlatformVersion.forMajorAndMinorVersions(
+ version.getMajorVersion(), version.getMinorVersion());
+ expectWithMessage("TIRAMISU_1").that(version).isEqualTo(anonymous);
+ expectWithMessage("anonymous").that(anonymous).isEqualTo(version);
+ expectWithMessage("TIRAMISU_1's hashcode").that(version.hashCode())
+ .isEqualTo(anonymous.hashCode());
+ expectWithMessage("anonymous' hashcode").that(anonymous.hashCode())
+ .isEqualTo(version.hashCode());
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.PlatformVersion#CREATOR"})
+ public void testMarshalling() {
+ PlatformVersion original = PlatformVersion.forMajorAndMinorVersions(66, 6);
+ expectWithMessage("original.describeContents()").that(original.describeContents())
+ .isEqualTo(0);
+
+ PlatformVersion clone = clone(original);
+ assertWithMessage("CREATOR.createFromParcel()").that(clone).isNotNull();
+ expectWithMessage("clone.major").that(clone.getMajorVersion()).isEqualTo(66);
+ expectWithMessage("clone.minor").that(clone.getMinorVersion()).isEqualTo(6);
+ expectWithMessage("clone.describeContents()").that(clone.describeContents())
+ .isEqualTo(0);
+ expectWithMessage("clone.equals()").that(clone).isEqualTo(original);
+ expectWithMessage("clone.hashCode()").that(clone.hashCode()).isEqualTo(original.hashCode());
+ }
+
+ @Test
+ @ApiTest(apis = {"android.car.PlatformVersion#CREATOR"})
+ public void testNewArray() {
+ PlatformVersion[] array = PlatformVersion.CREATOR.newArray(42);
+
+ assertWithMessage("CREATOR.newArray()").that(array).isNotNull();
+ }
+
+ private PlatformVersion clone(PlatformVersion original) {
+ Parcel parcel = Parcel.obtain();
+ try {
+ original.writeToParcel(parcel, /* flags= */ 0);
+ parcel.setDataPosition(0);
+
+ return PlatformVersion.CREATOR.createFromParcel(parcel);
+ } finally {
+ parcel.recycle();
+ }
+ }
+
+}
diff --git a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
index 1e68b01..0001420 100644
--- a/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
+++ b/tests/tests/car/src/android/car/cts/VehiclePropertyIdsTest.java
@@ -173,6 +173,8 @@
.isEqualTo("HVAC_MAX_AC_ON");
assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_MAX_DEFROST_ON))
.isEqualTo("HVAC_MAX_DEFROST_ON");
+ assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_ELECTRIC_DEFROSTER_ON))
+ .isEqualTo("HVAC_ELECTRIC_DEFROSTER_ON");
assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_POWER_ON))
.isEqualTo("HVAC_POWER_ON");
assertThat(VehiclePropertyIds.toString(VehiclePropertyIds.HVAC_RECIRC_ON))
diff --git a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java
index 743c460..6b1869c 100644
--- a/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java
+++ b/tests/tests/car/src/android/car/cts/utils/VehiclePropertyVerifier.java
@@ -20,26 +20,66 @@
import static org.junit.Assume.assumeNotNull;
+import android.car.VehicleAreaMirror;
+import android.car.VehicleAreaSeat;
import android.car.VehicleAreaType;
+import android.car.VehicleAreaWheel;
+import android.car.VehicleAreaWindow;
import android.car.VehiclePropertyIds;
+import android.car.VehiclePropertyType;
import android.car.hardware.CarPropertyConfig;
import android.car.hardware.CarPropertyValue;
import android.car.hardware.property.CarPropertyManager;
import android.os.SystemClock;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Sets;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
+import java.util.stream.IntStream;
public class VehiclePropertyVerifier<T> {
private final static String CAR_PROPERTY_VALUE_SOURCE_GETTER = "Getter";
private final static String CAR_PROPERTY_VALUE_SOURCE_CALLBACK = "Callback";
+ private static final float FLOAT_INEQUALITY_THRESHOLD = 0.00001f;
+ private static final ImmutableSet<Integer> WHEEL_AREAS = ImmutableSet.of(
+ VehicleAreaWheel.WHEEL_LEFT_FRONT, VehicleAreaWheel.WHEEL_LEFT_REAR,
+ VehicleAreaWheel.WHEEL_RIGHT_FRONT, VehicleAreaWheel.WHEEL_RIGHT_REAR);
+ private static final ImmutableSet<Integer> ALL_POSSIBLE_WHEEL_AREA_IDS =
+ generateAllPossibleAreaIds(WHEEL_AREAS);
+ private static final ImmutableSet<Integer> WINDOW_AREAS = ImmutableSet.of(
+ VehicleAreaWindow.WINDOW_FRONT_WINDSHIELD, VehicleAreaWindow.WINDOW_REAR_WINDSHIELD,
+ VehicleAreaWindow.WINDOW_ROW_1_LEFT, VehicleAreaWindow.WINDOW_ROW_1_RIGHT,
+ VehicleAreaWindow.WINDOW_ROW_2_LEFT, VehicleAreaWindow.WINDOW_ROW_2_RIGHT,
+ VehicleAreaWindow.WINDOW_ROW_3_LEFT, VehicleAreaWindow.WINDOW_ROW_3_RIGHT,
+ VehicleAreaWindow.WINDOW_ROOF_TOP_1, VehicleAreaWindow.WINDOW_ROOF_TOP_2);
+ private static final ImmutableSet<Integer> ALL_POSSIBLE_WINDOW_AREA_IDS =
+ generateAllPossibleAreaIds(WINDOW_AREAS);
+ private static final ImmutableSet<Integer> MIRROR_AREAS = ImmutableSet.of(
+ VehicleAreaMirror.MIRROR_DRIVER_LEFT, VehicleAreaMirror.MIRROR_DRIVER_RIGHT,
+ VehicleAreaMirror.MIRROR_DRIVER_CENTER);
+ private static final ImmutableSet<Integer> ALL_POSSIBLE_MIRROR_AREA_IDS =
+ generateAllPossibleAreaIds(MIRROR_AREAS);
+ private static final ImmutableSet<Integer> SEAT_AREAS = ImmutableSet.of(
+ VehicleAreaSeat.SEAT_ROW_1_LEFT, VehicleAreaSeat.SEAT_ROW_1_CENTER,
+ VehicleAreaSeat.SEAT_ROW_1_RIGHT, VehicleAreaSeat.SEAT_ROW_2_LEFT,
+ VehicleAreaSeat.SEAT_ROW_2_CENTER, VehicleAreaSeat.SEAT_ROW_2_RIGHT,
+ VehicleAreaSeat.SEAT_ROW_3_LEFT, VehicleAreaSeat.SEAT_ROW_3_CENTER,
+ VehicleAreaSeat.SEAT_ROW_3_RIGHT);
+ private static final ImmutableSet<Integer> ALL_POSSIBLE_SEAT_AREA_IDS =
+ generateAllPossibleAreaIds(SEAT_AREAS);
+
private final int mPropertyId;
private final String mPropertyName;
@@ -51,16 +91,30 @@
private final Optional<ConfigArrayVerifier> mConfigArrayVerifier;
private final Optional<CarPropertyValueVerifier> mCarPropertyValueVerifier;
private final Optional<AreaIdsVerifier> mAreaIdsVerifier;
+ private final Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier;
private final ImmutableSet<Integer> mPossibleConfigArrayValues;
+ private final ImmutableSet<T> mPossibleCarPropertyValues;
private final boolean mRequirePropertyValueToBeInConfigArray;
+ private final boolean mVerifySetterWithConfigArrayValues;
+ private final boolean mRequireMinMaxValues;
+ private final boolean mRequireMinValuesToBeZero;
+ private final boolean mRequireZeroToBeContainedInMinMaxRanges;
+ private final boolean mPossiblyDependentOnHvacPowerOn;
private VehiclePropertyVerifier(int propertyId, int access, int areaType, int changeMode,
Class<T> propertyType, boolean requiredProperty,
Optional<ConfigArrayVerifier> configArrayVerifier,
Optional<CarPropertyValueVerifier> carPropertyValueVerifier,
Optional<AreaIdsVerifier> areaIdsVerifier,
+ Optional<CarPropertyConfigVerifier> carPropertyConfigVerifier,
ImmutableSet<Integer> possibleConfigArrayValues,
- boolean requirePropertyValueToBeInConfigArray) {
+ ImmutableSet<T> possibleCarPropertyValues,
+ boolean requirePropertyValueToBeInConfigArray,
+ boolean verifySetterWithConfigArrayValues,
+ boolean requireMinMaxValues,
+ boolean requireMinValuesToBeZero,
+ boolean requireZeroToBeContainedInMinMaxRanges,
+ boolean possiblyDependentOnHvacPowerOn) {
mPropertyId = propertyId;
mPropertyName = VehiclePropertyIds.toString(propertyId);
mAccess = access;
@@ -71,8 +125,15 @@
mConfigArrayVerifier = configArrayVerifier;
mCarPropertyValueVerifier = carPropertyValueVerifier;
mAreaIdsVerifier = areaIdsVerifier;
+ mCarPropertyConfigVerifier = carPropertyConfigVerifier;
mPossibleConfigArrayValues = possibleConfigArrayValues;
+ mPossibleCarPropertyValues = possibleCarPropertyValues;
mRequirePropertyValueToBeInConfigArray = requirePropertyValueToBeInConfigArray;
+ mVerifySetterWithConfigArrayValues = verifySetterWithConfigArrayValues;
+ mRequireMinMaxValues = requireMinMaxValues;
+ mRequireMinValuesToBeZero = requireMinValuesToBeZero;
+ mRequireZeroToBeContainedInMinMaxRanges = requireZeroToBeContainedInMinMaxRanges;
+ mPossiblyDependentOnHvacPowerOn = possiblyDependentOnHvacPowerOn;
}
public static <T> Builder<T> newBuilder(int propertyId, int access, int areaType,
@@ -139,46 +200,160 @@
}
verifyCarPropertyConfig(carPropertyConfig);
+
+ if (mPossiblyDependentOnHvacPowerOn) {
+ CarPropertyConfig<?> hvacPowerOnCarPropertyConfig =
+ carPropertyManager.getCarPropertyConfig(VehiclePropertyIds.HVAC_POWER_ON);
+ if (hvacPowerOnCarPropertyConfig != null
+ && hvacPowerOnCarPropertyConfig.getConfigArray().contains(mPropertyId)) {
+ turnOnHvacPower(carPropertyManager,
+ (CarPropertyConfig<Boolean>) hvacPowerOnCarPropertyConfig);
+ }
+ }
+
verifyCarPropertyValueGetter(carPropertyConfig, carPropertyManager);
verifyCarPropertyValueCallback(carPropertyConfig, carPropertyManager);
+ verifyCarPropertyValueSetter(carPropertyConfig, carPropertyManager);
+ }
+
+ private void turnOnHvacPower(CarPropertyManager carPropertyManager,
+ CarPropertyConfig<Boolean> hvacPowerOnCarPropertyConfig) {
+ for (int areaId : hvacPowerOnCarPropertyConfig.getAreaIds()) {
+ if (carPropertyManager.getProperty(VehiclePropertyIds.HVAC_POWER_ON,
+ areaId).getValue().equals(true)) {
+ continue;
+ }
+ setPropertyAndWaitForChange(carPropertyManager, VehiclePropertyIds.HVAC_POWER_ON,
+ hvacPowerOnCarPropertyConfig.getPropertyType(), areaId, Boolean.TRUE);
+ }
+ }
+
+ private void verifyCarPropertyValueSetter(CarPropertyConfig<?> carPropertyConfig,
+ CarPropertyManager carPropertyManager) {
+ if (carPropertyConfig.getAccess() != CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_READ_WRITE) {
+ return;
+ }
+ if (Boolean.class.equals(carPropertyConfig.getPropertyType())) {
+ verifyBooleanPropertySetter(carPropertyConfig, carPropertyManager);
+ } else if (Integer.class.equals(carPropertyConfig.getPropertyType())) {
+ verifyIntegerPropertySetter(carPropertyConfig, carPropertyManager);
+ } else if (Float.class.equals(carPropertyConfig.getPropertyType())) {
+ verifyFloatPropertySetter(carPropertyConfig, carPropertyManager);
+ }
+ }
+
+ private void verifyBooleanPropertySetter(CarPropertyConfig<?> carPropertyConfig,
+ CarPropertyManager carPropertyManager) {
+ for (int areaId : carPropertyConfig.getAreaIds()) {
+ CarPropertyValue<Boolean> currentCarPropertyValue = carPropertyManager.getProperty(
+ mPropertyId, areaId);
+ verifyCarPropertyValue(carPropertyConfig, currentCarPropertyValue, areaId,
+ CAR_PROPERTY_VALUE_SOURCE_GETTER);
+ Boolean valueToSet = !currentCarPropertyValue.getValue();
+ verifySetProperty((CarPropertyConfig<Boolean>) carPropertyConfig, carPropertyManager,
+ areaId, valueToSet);
+ verifySetProperty((CarPropertyConfig<Boolean>) carPropertyConfig, carPropertyManager,
+ areaId, !valueToSet);
+ }
+ }
+
+ private void verifyIntegerPropertySetter(CarPropertyConfig<?> carPropertyConfig,
+ CarPropertyManager carPropertyManager) {
+ if (mVerifySetterWithConfigArrayValues) {
+ verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
+ (Collection<T>) carPropertyConfig.getConfigArray());
+ } else if (!mPossibleCarPropertyValues.isEmpty()) {
+ verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
+ mPossibleCarPropertyValues);
+ } else {
+ verifySetterWithMinMaxValues(carPropertyConfig, carPropertyManager);
+ }
+ }
+
+ private void verifySetterWithValues(CarPropertyConfig<T> carPropertyConfig,
+ CarPropertyManager carPropertyManager, Collection<T> valuesToSet) {
+ for (T valueToSet : valuesToSet) {
+ for (int areaId : carPropertyConfig.getAreaIds()) {
+ verifySetProperty(carPropertyConfig, carPropertyManager, areaId, valueToSet);
+ }
+ }
+ }
+
+ private void verifySetterWithMinMaxValues(CarPropertyConfig<?> carPropertyConfig,
+ CarPropertyManager carPropertyManager) {
+ for (int areaId : carPropertyConfig.getAreaIds()) {
+ if (carPropertyConfig.getMinValue(areaId) == null || carPropertyConfig.getMaxValue(
+ areaId) == null) {
+ continue;
+ }
+ List<Integer> valuesToSet = IntStream.rangeClosed(
+ ((Integer) carPropertyConfig.getMinValue(areaId)).intValue(),
+ ((Integer) carPropertyConfig.getMaxValue(areaId)).intValue()).boxed().collect(
+ Collectors.toList());
+
+ for (Integer valueToSet : valuesToSet) {
+ verifySetProperty((CarPropertyConfig<Integer>) carPropertyConfig,
+ carPropertyManager, areaId, valueToSet);
+ }
+ }
+ }
+
+ private void verifyFloatPropertySetter(CarPropertyConfig<?> carPropertyConfig,
+ CarPropertyManager carPropertyManager) {
+ if (!mPossibleCarPropertyValues.isEmpty()) {
+ verifySetterWithValues((CarPropertyConfig<T>) carPropertyConfig, carPropertyManager,
+ mPossibleCarPropertyValues);
+ }
+ }
+
+ private <T> void verifySetProperty(CarPropertyConfig<T> carPropertyConfig,
+ CarPropertyManager carPropertyManager, int areaId, T valueToSet) {
+ CarPropertyValue<T> currentCarPropertyValue = carPropertyManager.getProperty(mPropertyId,
+ areaId);
+ verifyCarPropertyValue(carPropertyConfig, currentCarPropertyValue, areaId,
+ CAR_PROPERTY_VALUE_SOURCE_GETTER);
+ if (valueEquals(valueToSet, currentCarPropertyValue.getValue())) {
+ return;
+ }
+ CarPropertyValue<T> updatedCarPropertyValue = setPropertyAndWaitForChange(
+ carPropertyManager, mPropertyId, carPropertyConfig.getPropertyType(), areaId,
+ valueToSet);
+ verifyCarPropertyValue(carPropertyConfig, updatedCarPropertyValue, areaId,
+ CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
}
private void verifyCarPropertyValueCallback(CarPropertyConfig<?> carPropertyConfig,
CarPropertyManager carPropertyManager) {
- if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_STATIC
- || mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_ONCHANGE) {
- CarPropertyValueCallback carPropertyValueCallback =
- new CarPropertyValueCallback(mPropertyName,
- carPropertyConfig.getAreaIds().length);
- assertWithMessage("Failed to register callback for " + mPropertyName).that(
- carPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId,
- CarPropertyManager.SENSOR_RATE_ONCHANGE)).isTrue();
- List<CarPropertyValue<?>> carPropertyValues =
- carPropertyValueCallback.getCarPropertyValues();
- carPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId);
+ if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) {
+ return;
+ }
+ int updatesPerAreaId = 1;
+ long timeoutMillis = 1500;
+ if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
+ updatesPerAreaId = 2;
+ float secondsToMillis = 1_000;
+ long bufferMillis = 1_000; // 1 second
+ timeoutMillis = ((long) ((1.0f / carPropertyConfig.getMinSampleRate()) * secondsToMillis
+ * updatesPerAreaId)) + bufferMillis;
+ }
- for (CarPropertyValue<?> carPropertyValue : carPropertyValues) {
- verifyCarPropertyValue(carPropertyConfig, carPropertyValue,
- carPropertyValue.getAreaId(), CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
- }
- assertWithMessage(mPropertyName
- + " callback values did not cover all the property's area IDs").that(
- carPropertyValues.stream().map(CarPropertyValue::getAreaId).collect(
- Collectors.toList())
- ).containsExactlyElementsIn(
- Arrays.stream(carPropertyConfig.getAreaIds()).boxed().collect(
- Collectors.toList()));
+ CarPropertyValueCallback carPropertyValueCallback = new CarPropertyValueCallback(
+ mPropertyName, carPropertyConfig.getAreaIds(), updatesPerAreaId, timeoutMillis);
+ assertWithMessage("Failed to register callback for " + mPropertyName).that(
+ carPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId,
+ carPropertyConfig.getMaxSampleRate())).isTrue();
+ SparseArray<List<CarPropertyValue<?>>> areaIdToCarPropertyValues =
+ carPropertyValueCallback.getAreaIdToCarPropertyValues();
+ carPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId);
- } else if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
- CarPropertyValueCallback carPropertyValueCallback =
- new CarPropertyValueCallback(mPropertyName, 1);
- assertWithMessage("Failed to register callback for " + mPropertyName).that(
- carPropertyManager.registerCallback(carPropertyValueCallback, mPropertyId,
- CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue();
- List<CarPropertyValue<?>> carPropertyValues =
- carPropertyValueCallback.getCarPropertyValues();
- carPropertyManager.unregisterCallback(carPropertyValueCallback, mPropertyId);
-
+ for (int areaId : carPropertyConfig.getAreaIds()) {
+ List<CarPropertyValue<?>> carPropertyValues = areaIdToCarPropertyValues.get(areaId);
+ assertWithMessage(
+ mPropertyName + " callback value list is null for area ID: " + areaId).that(
+ carPropertyValues).isNotNull();
+ assertWithMessage(mPropertyName + " callback values did not receive " + updatesPerAreaId
+ + " updates for area ID: " + areaId).that(carPropertyValues.size()).isAtLeast(
+ updatesPerAreaId);
for (CarPropertyValue<?> carPropertyValue : carPropertyValues) {
verifyCarPropertyValue(carPropertyConfig, carPropertyValue,
carPropertyValue.getAreaId(), CAR_PROPERTY_VALUE_SOURCE_CALLBACK);
@@ -212,13 +387,34 @@
assertWithMessage(mPropertyName + " must be " + mPropertyType + " type property")
.that(carPropertyConfig.getPropertyType()).isEqualTo(mPropertyType);
- if (mAreaIdsVerifier.isPresent()) {
- mAreaIdsVerifier.get().verify(carPropertyConfig.getAreaIds());
- } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
+ assertWithMessage(mPropertyName + "'s must have at least 1 area ID defined").that(
+ carPropertyConfig.getAreaIds().length).isAtLeast(1);
+ assertWithMessage(mPropertyName + "'s area IDs must all be unique: " + Arrays.toString(
+ carPropertyConfig.getAreaIds())).that(ImmutableSet.copyOf(Arrays.stream(
+ carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList())).size()
+ == carPropertyConfig.getAreaIds().length).isTrue();
+
+ if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_GLOBAL) {
assertWithMessage(
mPropertyName + "'s AreaIds must contain a single 0 since it is "
+ areaTypeToString(mAreaType))
.that(carPropertyConfig.getAreaIds()).isEqualTo(new int[]{0});
+ } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WHEEL) {
+ verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_WHEEL_AREA_IDS);
+ verifyNoAreaOverlapInAreaIds(carPropertyConfig, WHEEL_AREAS);
+ } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_WINDOW) {
+ verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_WINDOW_AREA_IDS);
+ verifyNoAreaOverlapInAreaIds(carPropertyConfig, WINDOW_AREAS);
+ } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_MIRROR) {
+ verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_MIRROR_AREA_IDS);
+ verifyNoAreaOverlapInAreaIds(carPropertyConfig, MIRROR_AREAS);
+ } else if (mAreaType == VehicleAreaType.VEHICLE_AREA_TYPE_SEAT
+ && mPropertyId != VehiclePropertyIds.INFO_DRIVER_SEAT) {
+ verifyValidAreaIdsForAreaType(carPropertyConfig, ALL_POSSIBLE_SEAT_AREA_IDS);
+ verifyNoAreaOverlapInAreaIds(carPropertyConfig, SEAT_AREAS);
+ }
+ if (mAreaIdsVerifier.isPresent()) {
+ mAreaIdsVerifier.get().verify(carPropertyConfig.getAreaIds());
}
if (mChangeMode == CarPropertyConfig.VEHICLE_PROPERTY_CHANGE_MODE_CONTINUOUS) {
@@ -227,6 +423,9 @@
verifyNonContinuousCarPropertyConfig(carPropertyConfig);
}
+ mCarPropertyConfigVerifier.ifPresent(carPropertyConfigVerifier ->
+ carPropertyConfigVerifier.verify(carPropertyConfig));
+
if (!mPossibleConfigArrayValues.isEmpty()) {
assertWithMessage(
mPropertyName + " configArray must specify supported values")
@@ -248,6 +447,62 @@
assertWithMessage(mPropertyName + " configArray is undefined, so it must be empty")
.that(carPropertyConfig.getConfigArray().size()).isEqualTo(0);
}
+
+ for (int areaId : carPropertyConfig.getAreaIds()) {
+ T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
+ T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
+ if (mRequireMinMaxValues) {
+ assertWithMessage(mPropertyName + " - area ID: " + areaId
+ + " must have min value defined").that(areaIdMinValue).isNotNull();
+ assertWithMessage(mPropertyName + " - area ID: " + areaId
+ + " must have max value defined").that(areaIdMaxValue).isNotNull();
+ }
+ if (mRequireMinValuesToBeZero) {
+ assertWithMessage(
+ mPropertyName + " - area ID: " + areaId + " min value must be zero").that(
+ areaIdMinValue).isEqualTo(0);
+ }
+ if (mRequireZeroToBeContainedInMinMaxRanges) {
+ assertWithMessage(mPropertyName + " - areaId: " + areaId
+ + "'s max and min range must contain zero").that(
+ verifyMaxAndMinRangeContainsZero(areaIdMinValue, areaIdMaxValue)).isTrue();
+
+ }
+ if (areaIdMinValue == null || areaIdMaxValue == null) {
+ continue;
+ }
+ assertWithMessage(
+ mPropertyName + " - areaId: " + areaId + "'s max value must be >= min value")
+ .that(verifyMaxAndMin(areaIdMinValue, areaIdMaxValue)).isTrue();
+ }
+ }
+
+ private boolean verifyMaxAndMinRangeContainsZero(T min, T max) {
+ int propertyType = mPropertyId & VehiclePropertyType.MASK;
+ switch (propertyType) {
+ case VehiclePropertyType.INT32:
+ return (Integer) max >= 0 && (Integer) min <= 0;
+ case VehiclePropertyType.INT64:
+ return (Long) max >= 0 && (Long) min <= 0;
+ case VehiclePropertyType.FLOAT:
+ return (Float) max >= 0 && (Float) min <= 0;
+ default:
+ return false;
+ }
+ }
+
+ private boolean verifyMaxAndMin(T min, T max) {
+ int propertyType = mPropertyId & VehiclePropertyType.MASK;
+ switch (propertyType) {
+ case VehiclePropertyType.INT32:
+ return (Integer) max >= (Integer) min;
+ case VehiclePropertyType.INT64:
+ return (Long) max >= (Long) min;
+ case VehiclePropertyType.FLOAT:
+ return (Float) max >= (Float) min;
+ default:
+ return false;
+ }
}
private void verifyContinuousCarPropertyConfig(CarPropertyConfig<?> carPropertyConfig) {
@@ -276,6 +531,9 @@
private void verifyCarPropertyValueGetter(CarPropertyConfig<?> carPropertyConfig,
CarPropertyManager carPropertyManager) {
+ if (carPropertyConfig.getAccess() == CarPropertyConfig.VEHICLE_PROPERTY_ACCESS_WRITE) {
+ return;
+ }
for (int areaId : carPropertyConfig.getAreaIds()) {
CarPropertyValue<?> carPropertyValue =
carPropertyManager.getProperty(
@@ -298,6 +556,10 @@
+ areaId)
.that(carPropertyValue.getAreaId())
.isEqualTo(areaId);
+ assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source
+ + " area ID must be in carPropertyConfig#getAreaIds()").that(Arrays.stream(
+ carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()).contains(
+ carPropertyValue.getAreaId())).isTrue();
assertWithMessage(
mPropertyName + " - areaId: " + areaId + " - source: " + source
+ " value's status must be valid")
@@ -326,9 +588,97 @@
carPropertyValue.getValue())).isTrue();
}
+ if (!mPossibleCarPropertyValues.isEmpty()) {
+ if (Float.class.equals(mPropertyType)) {
+ boolean foundInPossibleValues = false;
+ for (Float possibleValue : (Collection<Float>) mPossibleCarPropertyValues) {
+ if (floatEquals(possibleValue, (Float) carPropertyValue.getValue())) {
+ foundInPossibleValues = true;
+ break;
+ }
+ }
+ assertWithMessage(
+ mPropertyName + " - areaId: " + areaId + " - source: " + source + " value: "
+ + carPropertyValue.getValue() + " must be listed in the Float set: "
+ + mPossibleCarPropertyValues).that(foundInPossibleValues).isTrue();
+ } else {
+ assertWithMessage(mPropertyName + " - areaId: " + areaId + " - source: " + source
+ + " value must be listed in the set").that(
+ carPropertyValue.getValue()).isIn(mPossibleCarPropertyValues);
+ }
+ }
+
mCarPropertyValueVerifier.ifPresent(
propertyValueVerifier -> propertyValueVerifier.verify(carPropertyConfig,
carPropertyValue));
+
+ T areaIdMinValue = (T) carPropertyConfig.getMinValue(areaId);
+ T areaIdMaxValue = (T) carPropertyConfig.getMaxValue(areaId);
+ if (areaIdMinValue != null && areaIdMaxValue != null) {
+ assertWithMessage(
+ "carPropertyValue must be between the max and min values")
+ .that(verifyValueInRange(areaIdMinValue, areaIdMaxValue,
+ (T) carPropertyValue.getValue())).isTrue();
+ }
+ }
+
+ private boolean verifyValueInRange(T min, T max, T value) {
+ int propertyType = mPropertyId & VehiclePropertyType.MASK;
+ switch (propertyType) {
+ case VehiclePropertyType.INT32:
+ return ((Integer) value >= (Integer) min && (Integer) value <= (Integer) max);
+ case VehiclePropertyType.INT64:
+ return ((Long) value >= (Long) min && (Long) value <= (Long) max);
+ case VehiclePropertyType.FLOAT:
+ return ((Float) value >= (Float) min && (Float) value <= (Float) max);
+ default:
+ return false;
+ }
+ }
+
+ private static ImmutableSet<Integer> generateAllPossibleAreaIds(ImmutableSet<Integer> areas) {
+ ImmutableSet.Builder<Integer> allPossibleAreaIdsBuilder = ImmutableSet.builder();
+ for (int i = 1; i <= areas.size(); i++) {
+ allPossibleAreaIdsBuilder.addAll(Sets.combinations(areas, i).stream().map(areaCombo -> {
+ Integer possibleAreaId = 0;
+ for (Integer area : areaCombo) {
+ possibleAreaId |= area;
+ }
+ return possibleAreaId;
+ }).collect(Collectors.toList()));
+ }
+ return allPossibleAreaIdsBuilder.build();
+ }
+
+ private void verifyValidAreaIdsForAreaType(CarPropertyConfig<?> carPropertyConfig,
+ ImmutableSet<Integer> allPossibleAreaIds) {
+ for (int areaId : carPropertyConfig.getAreaIds()) {
+ assertWithMessage(
+ mPropertyName + "'s area ID must be a valid " + areaTypeToString(mAreaType)
+ + " area ID").that(areaId).isIn(allPossibleAreaIds);
+ }
+ }
+
+ private void verifyNoAreaOverlapInAreaIds(CarPropertyConfig<?> carPropertyConfig,
+ ImmutableSet<Integer> areas) {
+ if (carPropertyConfig.getAreaIds().length < 2) {
+ return;
+ }
+ ImmutableSet<Integer> areaIds = ImmutableSet.copyOf(Arrays.stream(
+ carPropertyConfig.getAreaIds()).boxed().collect(Collectors.toList()));
+ List<Integer> areaIdOverlapCheckResults = Sets.combinations(areaIds, 2).stream().map(
+ areaIdPair -> {
+ List<Integer> areaIdPairAsList = areaIdPair.stream().collect(
+ Collectors.toList());
+ return areaIdPairAsList.get(0) & areaIdPairAsList.get(1);
+ }).collect(Collectors.toList());
+
+ assertWithMessage(
+ mPropertyName + " area IDs: " + Arrays.toString(carPropertyConfig.getAreaIds())
+ + " must contain each area only once (e.g. no bitwise AND overlap) for "
+ + "the area type: " + areaTypeToString(mAreaType)).that(
+ Collections.frequency(areaIdOverlapCheckResults, 0)
+ == areaIdOverlapCheckResults.size()).isTrue();
}
public interface ConfigArrayVerifier {
@@ -343,6 +693,10 @@
void verify(int[] areaIds);
}
+ public interface CarPropertyConfigVerifier {
+ void verify(CarPropertyConfig<?> carPropertyConfig);
+ }
+
public static class Builder<T> {
private final int mPropertyId;
private final int mAccess;
@@ -353,9 +707,15 @@
private Optional<ConfigArrayVerifier> mConfigArrayVerifier = Optional.empty();
private Optional<CarPropertyValueVerifier> mCarPropertyValueVerifier = Optional.empty();
private Optional<AreaIdsVerifier> mAreaIdsVerifier = Optional.empty();
+ private Optional<CarPropertyConfigVerifier> mCarPropertyConfigVerifier = Optional.empty();
private ImmutableSet<Integer> mPossibleConfigArrayValues = ImmutableSet.of();
+ private ImmutableSet<T> mPossibleCarPropertyValues = ImmutableSet.of();
private boolean mRequirePropertyValueToBeInConfigArray = false;
-
+ private boolean mVerifySetterWithConfigArrayValues = false;
+ private boolean mRequireMinMaxValues = false;
+ private boolean mRequireMinValuesToBeZero = false;
+ private boolean mRequireZeroToBeContainedInMinMaxRanges = false;
+ private boolean mPossiblyDependentOnHvacPowerOn = false;
private Builder(int propertyId, int access, int areaType, int changeMode,
Class<T> propertyType) {
@@ -387,55 +747,131 @@
return this;
}
+ public Builder<T> setCarPropertyConfigVerifier(
+ CarPropertyConfigVerifier carPropertyConfigVerifier) {
+ mCarPropertyConfigVerifier = Optional.of(carPropertyConfigVerifier);
+ return this;
+ }
+
public Builder<T> setPossibleConfigArrayValues(
ImmutableSet<Integer> possibleConfigArrayValues) {
mPossibleConfigArrayValues = possibleConfigArrayValues;
return this;
}
+ public Builder<T> setPossibleCarPropertyValues(
+ ImmutableSet<T> possibleCarPropertyValues) {
+ mPossibleCarPropertyValues = possibleCarPropertyValues;
+ return this;
+ }
+
public Builder<T> requirePropertyValueTobeInConfigArray() {
mRequirePropertyValueToBeInConfigArray = true;
return this;
}
+ public Builder<T> verifySetterWithConfigArrayValues() {
+ mVerifySetterWithConfigArrayValues = true;
+ return this;
+ }
+
+ public Builder<T> requireMinMaxValues() {
+ mRequireMinMaxValues = true;
+ return this;
+ }
+
+ public Builder<T> requireMinValuesToBeZero() {
+ mRequireMinValuesToBeZero = true;
+ return this;
+ }
+
+ public Builder<T> requireZeroToBeContainedInMinMaxRanges() {
+ mRequireZeroToBeContainedInMinMaxRanges = true;
+ return this;
+ }
+
+ public Builder<T> setPossiblyDependentOnHvacPowerOn() {
+ mPossiblyDependentOnHvacPowerOn = true;
+ return this;
+ }
+
public VehiclePropertyVerifier<T> build() {
return new VehiclePropertyVerifier<>(mPropertyId, mAccess, mAreaType, mChangeMode,
mPropertyType, mRequiredProperty, mConfigArrayVerifier,
- mCarPropertyValueVerifier, mAreaIdsVerifier, mPossibleConfigArrayValues,
- mRequirePropertyValueToBeInConfigArray);
+ mCarPropertyValueVerifier, mAreaIdsVerifier, mCarPropertyConfigVerifier,
+ mPossibleConfigArrayValues, mPossibleCarPropertyValues,
+ mRequirePropertyValueToBeInConfigArray, mVerifySetterWithConfigArrayValues,
+ mRequireMinMaxValues, mRequireMinValuesToBeZero,
+ mRequireZeroToBeContainedInMinMaxRanges, mPossiblyDependentOnHvacPowerOn);
}
}
private static class CarPropertyValueCallback implements
CarPropertyManager.CarPropertyEventCallback {
private final String mPropertyName;
- private final int mTotalOnChangeEvents;
- private final CountDownLatch mCountDownLatch;
- private final List<CarPropertyValue<?>> mCarPropertyValues = new ArrayList<>();
+ private final int[] mAreaIds;
+ private final int mTotalCarPropertyValuesPerAreaId;
+ private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+ private final Object mLock = new Object();
+ @GuardedBy("mLock")
+ private final SparseArray<List<CarPropertyValue<?>>> mAreaIdToCarPropertyValues =
+ new SparseArray<>();
+ private final long mTimeoutMillis;
- public CarPropertyValueCallback(String propertyName, int totalOnChangeEvents) {
+ CarPropertyValueCallback(String propertyName, int[] areaIds,
+ int totalCarPropertyValuesPerAreaId, long timeoutMillis) {
mPropertyName = propertyName;
- mTotalOnChangeEvents = totalOnChangeEvents;
- mCountDownLatch = new CountDownLatch(totalOnChangeEvents);
+ mAreaIds = areaIds;
+ mTotalCarPropertyValuesPerAreaId = totalCarPropertyValuesPerAreaId;
+ mTimeoutMillis = timeoutMillis;
+ synchronized (mLock) {
+ for (int areaId : mAreaIds) {
+ mAreaIdToCarPropertyValues.put(areaId, new ArrayList<>());
+ }
+ }
}
- public List<CarPropertyValue<?>> getCarPropertyValues() {
+ public SparseArray<List<CarPropertyValue<?>>> getAreaIdToCarPropertyValues() {
+ boolean awaitSuccess = false;
try {
- assertWithMessage(
- "Never received " + mTotalOnChangeEvents + " onChangeEvent(s) for "
- + mPropertyName + " callback before 1500ms timeout").that(
- mCountDownLatch.await(1500, TimeUnit.MILLISECONDS)).isTrue();
+ awaitSuccess = mCountDownLatch.await(mTimeoutMillis, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
assertWithMessage("Waiting for onChangeEvent callback(s) for " + mPropertyName
+ " threw an exception: " + e).fail();
}
- return mCarPropertyValues;
+ synchronized (mLock) {
+ assertWithMessage("Never received " + mTotalCarPropertyValuesPerAreaId
+ + " CarPropertyValues for all " + mPropertyName + "'s areaIds: "
+ + Arrays.toString(mAreaIds) + " before " + mTimeoutMillis + " ms timeout - "
+ + mAreaIdToCarPropertyValues).that(awaitSuccess).isTrue();
+ return mAreaIdToCarPropertyValues.clone();
+ }
}
@Override
- public void onChangeEvent(CarPropertyValue value) {
- mCarPropertyValues.add(value);
- mCountDownLatch.countDown();
+ public void onChangeEvent(CarPropertyValue carPropertyValue) {
+ synchronized (mLock) {
+ if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
+ return;
+ }
+ mAreaIdToCarPropertyValues.get(carPropertyValue.getAreaId()).add(carPropertyValue);
+ if (hasEnoughCarPropertyValuesForEachAreaIdLocked()) {
+ mCountDownLatch.countDown();
+ }
+ }
+ }
+
+ @GuardedBy("mLock")
+ private boolean hasEnoughCarPropertyValuesForEachAreaIdLocked() {
+ for (int areaId : mAreaIds) {
+ List<CarPropertyValue<?>> carPropertyValues = mAreaIdToCarPropertyValues.get(
+ areaId);
+ if (carPropertyValues == null
+ || carPropertyValues.size() < mTotalCarPropertyValuesPerAreaId) {
+ return false;
+ }
+ }
+ return true;
}
@Override
@@ -446,4 +882,74 @@
public void onErrorEvent(int propId, int areaId, int errorCode) {
}
}
+
+
+ private static class SetterCallback<T> implements CarPropertyManager.CarPropertyEventCallback {
+ private final int mPropertyId;
+ private final String mPropertyName;
+ private final int mAreaId;
+ private final T mExpectedSetValue;
+ private final CountDownLatch mCountDownLatch = new CountDownLatch(1);
+ private final long mCreationTimeNanos = SystemClock.elapsedRealtimeNanos();
+ private CarPropertyValue<?> mUpdatedCarPropertyValue = null;
+
+ SetterCallback(int propertyId, int areaId, T expectedSetValue) {
+ mPropertyId = propertyId;
+ mPropertyName = VehiclePropertyIds.toString(propertyId);
+ mAreaId = areaId;
+ mExpectedSetValue = expectedSetValue;
+ }
+
+ public CarPropertyValue<?> waitForUpdatedCarPropertyValue() {
+ try {
+ assertWithMessage(
+ "Never received onChangeEvent(s) for " + mPropertyName + " new value: "
+ + mExpectedSetValue + " before 5s timeout").that(
+ mCountDownLatch.await(5, TimeUnit.SECONDS)).isTrue();
+ } catch (InterruptedException e) {
+ assertWithMessage("Waiting for onChangeEvent set callback for " + mPropertyName
+ + " threw an exception: " + e).fail();
+ }
+ return mUpdatedCarPropertyValue;
+ }
+
+ @Override
+ public void onChangeEvent(CarPropertyValue carPropertyValue) {
+ if (mUpdatedCarPropertyValue != null || carPropertyValue.getPropertyId() != mPropertyId
+ || carPropertyValue.getAreaId() != mAreaId
+ || carPropertyValue.getStatus() != CarPropertyValue.STATUS_AVAILABLE
+ || carPropertyValue.getTimestamp() <= mCreationTimeNanos
+ || carPropertyValue.getTimestamp() >= SystemClock.elapsedRealtimeNanos()
+ || !valueEquals(mExpectedSetValue, (T) carPropertyValue.getValue())) {
+ return;
+ }
+ mUpdatedCarPropertyValue = carPropertyValue;
+ mCountDownLatch.countDown();
+ }
+
+ @Override
+ public void onErrorEvent(int propId, int zone) {
+ }
+ }
+
+ private static <V> boolean valueEquals(V v1, V v2) {
+ return (v1 instanceof Float && floatEquals((Float) v1, (Float) v2)) || v1.equals(v2);
+ }
+
+ private static boolean floatEquals(float f1, float f2) {
+ return Math.abs(f1 - f2) < FLOAT_INEQUALITY_THRESHOLD;
+ }
+
+ private static <U> CarPropertyValue<U> setPropertyAndWaitForChange(
+ CarPropertyManager carPropertyManager, int propertyId, Class<U> propertyType,
+ int areaId, U valueToSet) {
+ SetterCallback setterCallback = new SetterCallback(propertyId, areaId, valueToSet);
+ assertWithMessage("Failed to register setter callback for " + VehiclePropertyIds.toString(
+ propertyId)).that(carPropertyManager.registerCallback(setterCallback, propertyId,
+ CarPropertyManager.SENSOR_RATE_FASTEST)).isTrue();
+ carPropertyManager.setProperty(propertyType, propertyId, areaId, valueToSet);
+ CarPropertyValue<U> carPropertyValue = setterCallback.waitForUpdatedCarPropertyValue();
+ carPropertyManager.unregisterCallback(setterCallback, propertyId);
+ return carPropertyValue;
+ }
}
diff --git a/tests/tests/car_builtin/OWNERS b/tests/tests/car_builtin/OWNERS
index a8e927d..bda2116 100644
--- a/tests/tests/car_builtin/OWNERS
+++ b/tests/tests/car_builtin/OWNERS
@@ -1,4 +1,2 @@
# Bug component: 526266
-keunyoung@google.com
-felipeal@google.com
-gurunagarajan@google.com
+include platform/packages/services/Car:/OWNERS
diff --git a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
index 95a3d18..582772d 100644
--- a/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
+++ b/tests/tests/car_builtin/src/android/car/cts/builtin/app/ActivityManagerHelperTest.java
@@ -48,7 +48,6 @@
import com.android.compatibility.common.util.SystemUtil;
import org.junit.Before;
-import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -88,6 +87,8 @@
// ActivityManager.getRunningAppProcess called in isAppRunning needs this permission
private static final String PERMISSION_INTERACT_ACROSS_USERS_FULL =
"android.permission.INTERACT_ACROSS_USERS_FULL";
+ private static final String PERMISSION_REAL_GET_TASKS =
+ "android.permission.REAL_GET_TASKS";
private static final String SIMPLE_APP_PACKAGE_NAME = "android.car.cts.builtin.apps.simple";
private static final String SIMPLE_ACTIVITY_RELATIVE_NAME = ".SimpleActivity";
@@ -183,8 +184,7 @@
ComponentName activityName = task1TopActivity.getComponentName();
waitAndAssertTopResumedActivity(activityName, DEFAULT_DISPLAY,
"Activity must be resumed");
- assertWithMessage("task1 top activity has focus")
- .that(task1TopActivity.hasFocus()).isTrue();
+ waitAndAssertFocusStatusChanged(task1TopActivity, true);
assertWithMessage("task1 top activity is visible")
.that(task1TopActivity.isVisible()).isTrue();
@@ -269,7 +269,6 @@
.isEqualTo(expectedLaunchAllowed);
}
- @Ignore("b/232432706")
@Test
public void testStopAllTasksForUser() throws Exception {
int initialCurrentUserId = getCurrentUserId();
@@ -279,6 +278,7 @@
mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(
PERMISSION_MANAGE_ACTIVITY_TASKS,
PERMISSION_REMOVE_TASKS,
+ PERMISSION_REAL_GET_TASKS,
PERMISSION_INTERACT_ACROSS_USERS_FULL);
testUserId = createUser(CTS_CAR_TEST_USER_NAME);
@@ -290,15 +290,13 @@
installPackageForUser(testUserId);
launchSimpleActivityInCurrentUser();
- waitUntilSimpleActivityExistenceStatusIs(true);
- assertThat(isAppRunning(SIMPLE_APP_PACKAGE_NAME)).isTrue();
+ assertIsAppRunning(true, SIMPLE_APP_PACKAGE_NAME);
switchUser(initialCurrentUserId);
waitUntilUserCurrent(initialCurrentUserId);
stopAllTasksForUser(testUserId);
- waitUntilSimpleActivityExistenceStatusIs(false);
- assertThat(isAppRunning(SIMPLE_APP_PACKAGE_NAME)).isFalse();
+ assertIsAppRunning(false, SIMPLE_APP_PACKAGE_NAME);
removeUser(testUserId);
testUserId = INVALID_USER_ID;
@@ -377,14 +375,17 @@
Log.d(TAG, "installPackageForUser return: " + retStr);
}
+ private void assertIsAppRunning(boolean isRunning, String pkgName) {
+ PollingCheck.waitFor(TIMEOUT_MS, () -> isAppRunning(pkgName) == isRunning);
+ }
+
private boolean isAppRunning(String pkgName) {
ActivityManager am = mContext.getSystemService(ActivityManager.class);
- List<ActivityManager.RunningAppProcessInfo> runningAppProcesses =
- am.getRunningAppProcesses();
+ List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(MAX_NUM_TASKS);
- for (ActivityManager.RunningAppProcessInfo procInfo : runningAppProcesses) {
- if (pkgName.equals(procInfo.processName)) {
+ for (ActivityManager.RunningTaskInfo taskInfo : runningTasks) {
+ if (pkgName.equals(taskInfo.baseActivity.getPackageName())) {
return true;
}
}
@@ -444,6 +445,11 @@
public static final class ActivityC extends ActivityManagerTestActivityBase {
}
+ private static void waitAndAssertFocusStatusChanged(ActivityManagerTestActivityBase activity,
+ boolean expectedStatus) throws Exception {
+ PollingCheck.waitFor(TIMEOUT_MS, () -> activity.hasFocus() == expectedStatus);
+ }
+
private static int createUser(String userName) throws Exception {
Log.d(TAG, "createUser: " + userName);
String retStr = SystemUtil.runShellCommand(CREATE_USER_COMMAND + userName);
@@ -496,11 +502,6 @@
ActivityManagerHelper.stopAllTasksForUser(userId);
}
- private static void waitUntilSimpleActivityExistenceStatusIs(boolean expectedStatus) {
- PollingCheck.waitFor(TIMEOUT_MS,
- () -> (checkSimpleActivityExistence() == expectedStatus));
- }
-
private static boolean checkSimpleActivityExistence() {
boolean foundSimpleActivity = false;
diff --git a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
index 36c1217..de74595 100644
--- a/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
+++ b/tests/tests/carrierapi/src/android/carrierapi/cts/NetworkScanApiTest.java
@@ -390,6 +390,34 @@
}
@Test
+ public void testRequestNetworkScanWithRenounceWithoutChannels() {
+ boolean isLocationSwitchOn = getAndSetLocationSwitch(true);
+ try {
+ mNetworkScanRequest = buildNetworkScanRequest(/*includeBandsAndChannels=*/false);
+ mNetworkScanCallback = new NetworkScanCallbackImpl();
+ Message startNetworkScan = mHandler.obtainMessage(EVENT_NETWORK_SCAN_RENOUNCE_START,
+ false);
+ setReady(false);
+ startNetworkScan.sendToTarget();
+ waitUntilReady();
+
+ Log.d(TAG, "mNetworkScanStatus: " + mNetworkScanStatus);
+ assertWithMessage(
+ "The final scan status is "
+ + mNetworkScanStatus
+ + " with error code "
+ + mErrorCode
+ + ", not ScanCompleted"
+ + " or ScanError with an error code ERROR_MODEM_UNAVAILABLE or"
+ + " ERROR_UNSUPPORTED")
+ .that(isScanStatusValid())
+ .isTrue();
+ } finally {
+ getAndSetLocationSwitch(isLocationSwitchOn);
+ }
+ }
+
+ @Test
public void testRequestNetworkScanLocationOffPass() {
requestNetworkScanLocationOffHelper(false, true);
}
diff --git a/tests/tests/content/IntentResolutionTestApp/Android.bp b/tests/tests/content/IntentResolutionTestApp/Android.bp
index 16159cf..79c7538 100644
--- a/tests/tests/content/IntentResolutionTestApp/Android.bp
+++ b/tests/tests/content/IntentResolutionTestApp/Android.bp
@@ -16,9 +16,9 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-android_test_helper_app {
- name: "CtsIntentResolutionTestApp",
- defaults: ["cts_defaults"],
+java_defaults {
+ name: "intent_test_app_defaults",
+ srcs: ["src/**/*.java"],
sdk_version: "current",
// tag this module as a cts test artifact
test_suites: [
@@ -28,17 +28,22 @@
min_sdk_version: "29",
}
+android_test_helper_app {
+ name: "CtsIntentResolutionTestApp",
+ defaults: [
+ "cts_defaults",
+ "intent_test_app_defaults",
+ ],
+ additional_manifests: ["provider.xml"],
+}
+
// Same app but with lower target SDK version and different package_name
android_test_helper_app {
name: "CtsIntentResolutionTestAppApi30",
- package_name: "android.content.cts.IntentResolutionTestApi30",
- defaults: ["cts_defaults"],
- sdk_version: "current",
- target_sdk_version: "30",
- // tag this module as a cts test artifact
- test_suites: [
- "cts",
- "general-tests",
+ defaults: [
+ "cts_defaults",
+ "intent_test_app_defaults",
],
- min_sdk_version: "29",
+ package_name: "android.content.cts.IntentResolutionTestApi30",
+ target_sdk_version: "30",
}
diff --git a/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml b/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
index ac50cb1..1c0aa1b 100644
--- a/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
+++ b/tests/tests/content/IntentResolutionTestApp/AndroidManifest.xml
@@ -16,7 +16,7 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.content.cts.IntentResolutionTest" >
- <application android:hasCode="false">
+ <application>
<activity android:name="android.content.pm.cts.TestPmActivity"
android:exported="true">
<intent-filter>
diff --git a/tests/tests/content/IntentResolutionTestApp/provider.xml b/tests/tests/content/IntentResolutionTestApp/provider.xml
new file mode 100644
index 0000000..a8774b6
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/provider.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2022 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.content.cts.IntentResolutionTest" >
+ <application>
+ <provider android:name="android.content.pm.cts.TestProvider"
+ android:exported="true"
+ android:authorities="${applicationId}.provider" />
+ </application>
+</manifest>
diff --git a/tests/tests/content/IntentResolutionTestApp/src/android/content/pm/cts/TestProvider.java b/tests/tests/content/IntentResolutionTestApp/src/android/content/pm/cts/TestProvider.java
new file mode 100644
index 0000000..ae22d56
--- /dev/null
+++ b/tests/tests/content/IntentResolutionTestApp/src/android/content/pm/cts/TestProvider.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm.cts;
+
+import android.app.PendingIntent;
+import android.content.ContentProvider;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+
+public class TestProvider extends ContentProvider {
+
+ @Override
+ public boolean onCreate() {
+ return false;
+ }
+
+ @Override
+ public Bundle call(String method, String arg, Bundle extras) {
+ var intent = new Intent()
+ .setClassName(getContext(), "android.content.pm.cts.TestPmActivity")
+ .setAction("non.existing.action");
+ var p = PendingIntent.getActivity(getContext(), 0, intent, PendingIntent.FLAG_IMMUTABLE);
+ var b = new Bundle();
+ b.putParcelable("pendingIntent", p);
+ return b;
+ }
+
+ @Override
+ public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
+ String sortOrder) {
+ return null;
+ }
+
+ @Override
+ public String getType(Uri uri) {
+ return null;
+ }
+
+ @Override
+ public Uri insert(Uri uri, ContentValues values) {
+ return null;
+ }
+
+ @Override
+ public int delete(Uri uri, String selection,
+ String[] selectionArgs) {
+ return 0;
+ }
+
+ @Override
+ public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
+ return 0;
+ }
+}
diff --git a/tests/tests/content/src/android/content/cts/ContextTest.java b/tests/tests/content/src/android/content/cts/ContextTest.java
index b742cc9..cd6f3d6 100644
--- a/tests/tests/content/src/android/content/cts/ContextTest.java
+++ b/tests/tests/content/src/android/content/cts/ContextTest.java
@@ -16,6 +16,7 @@
package android.content.cts;
+import static android.Manifest.permission.READ_WALLPAPER_INTERNAL;
import static android.content.pm.PackageManager.PERMISSION_DENIED;
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
@@ -151,7 +152,9 @@
mRegisteredReceiverList = new ArrayList<BroadcastReceiver>();
- mOriginalWallpaper = (BitmapDrawable) mContext.getWallpaper();
+ SystemUtil.runWithShellPermissionIdentity(
+ () -> mOriginalWallpaper = (BitmapDrawable) mContext.getWallpaper(),
+ READ_WALLPAPER_INTERNAL);
}
@Override
diff --git a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
index b429278..8b27e9a 100644
--- a/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
+++ b/tests/tests/content/src/android/content/pm/cts/PackageManagerTest.java
@@ -16,6 +16,7 @@
package android.content.pm.cts;
+import static android.Manifest.permission.GET_INTENT_SENDER_INTENT;
import static android.Manifest.permission.INSTALL_TEST_ONLY_PACKAGE;
import static android.content.pm.ApplicationInfo.FLAG_HAS_CODE;
import static android.content.pm.ApplicationInfo.FLAG_INSTALLED;
@@ -41,8 +42,6 @@
import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;
-import com.google.common.truth.Expect;
-
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -56,6 +55,8 @@
import android.annotation.NonNull;
import android.app.Activity;
import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -99,14 +100,16 @@
import android.util.Log;
import androidx.core.content.FileProvider;
-import androidx.test.InstrumentationRegistry;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.rule.ServiceTestRule;
-import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.SystemUtil;
import com.android.compatibility.common.util.TestUtils;
+import com.google.common.truth.Expect;
+
import junit.framework.AssertionFailedError;
import org.junit.After;
@@ -240,9 +243,9 @@
@Before
public void setup() throws Exception {
- mContext = InstrumentationRegistry.getContext();
- mPackageManager = mContext.getPackageManager();
mInstrumentation = InstrumentationRegistry.getInstrumentation();
+ mContext = mInstrumentation.getContext();
+ mPackageManager = mContext.getPackageManager();
}
@After
@@ -312,8 +315,8 @@
checkProviderInfoName(PROVIDER_NAME, providers);
}
- @Test
- public void testEnforceIntentToMatchIntentFilter() {
+ // Disable the test due to feature revert
+ private void testEnforceIntentToMatchIntentFilter() {
Intent intent = new Intent();
List<ResolveInfo> results;
@@ -505,6 +508,34 @@
results = mPackageManager.queryBroadcastReceivers(intent,
PackageManager.ResolveInfoFlags.of(0));
assertEquals(0, results.size());
+
+ /* Pending Intent tests */
+
+ mInstrumentation.getUiAutomation().adoptShellPermissionIdentity(GET_INTENT_SENDER_INTENT);
+ var authority = INTENT_RESOLUTION_TEST_PKG_NAME + ".provider";
+ Bundle b = mContext.getContentResolver().call(authority, "", null, null);
+ assertNotNull(b);
+ PendingIntent pi = b.getParcelable("pendingIntent", PendingIntent.class);
+ assertNotNull(pi);
+ intent = pi.getIntent();
+ // It should be a non-matching intent, which cannot be resolved in our package
+ results = mPackageManager.queryIntentActivities(intent,
+ PackageManager.ResolveInfoFlags.of(0));
+ assertEquals(0, results.size());
+ // However, querying on behalf of the pending intent creator should work properly
+ results = pi.queryIntentComponents(0);
+ assertEquals(1, results.size());
+ mInstrumentation.getUiAutomation().dropShellPermissionIdentity();
+
+ intent = new Intent();
+ intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.setComponent(
+ new ComponentName("android", "com.android.internal.app.ResolverActivity"));
+ try {
+ mContext.startActivity(intent);
+ } catch (ActivityNotFoundException ignore) {
+
+ }
}
private void checkActivityInfoName(String expectedName, List<ResolveInfo> resolves) {
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
index f5b3cf5..a4ebe91 100644
--- a/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/AndroidManifest.xml
@@ -43,5 +43,14 @@
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
+ <service
+ android:name=".TestSystemDreamService"
+ android:exported="true"
+ android:permission="android.permission.BIND_DREAM_SERVICE">
+ <intent-filter>
+ <action android:name="android.service.dreams.DreamService" />
+ <category android:name="android.intent.category.DEFAULT" />
+ </intent-filter>
+ </service>
</application>
</manifest>
diff --git a/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestSystemDreamService.java b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestSystemDreamService.java
new file mode 100644
index 0000000..57f4cf8
--- /dev/null
+++ b/tests/tests/dreams/CtsDreamOverlayTestApp/src/android/app/dream/cts/app/TestSystemDreamService.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.dream.cts.app;
+
+import android.graphics.Color;
+import android.service.dreams.DreamService;
+import android.widget.FrameLayout;
+
+/**
+ * {@link TestSystemDreamService} is a minimal concrete {@link DreamService} implementation that
+ * sets the entire window to be red.
+ */
+public class TestSystemDreamService extends DreamService {
+ public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ setInteractive(false);
+ setFullscreen(true);
+
+ final FrameLayout frameLayout = new FrameLayout(getApplicationContext());
+ frameLayout.setBackgroundColor(Color.RED);
+ setContentView(frameLayout);
+ }
+}
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
index 8c836fa..88b50de 100644
--- a/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamOverlayTest.java
@@ -99,7 +99,7 @@
ComponentName.unflattenFromString(DREAM_SERVICE_COMPONENT);
final ComponentName dreamActivity = mDreamCoordinator.setActiveDream(dreamService);
- mDreamCoordinator.startDream(dreamService);
+ mDreamCoordinator.startDream();
waitAndAssertTopResumedActivity(dreamActivity, Display.DEFAULT_DISPLAY,
"Dream activity should be the top resumed activity");
// Wait on count down latch.
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java b/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
index 166227a..23831ef 100644
--- a/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
+++ b/tests/tests/dreams/src/android/service/dreams/cts/DreamServiceTest.java
@@ -74,7 +74,7 @@
ComponentName.unflattenFromString(DREAM_SERVICE_COMPONENT);
final ComponentName dreamActivity = mDreamCoordinator.setActiveDream(dreamService);
- mDreamCoordinator.startDream(dreamService);
+ mDreamCoordinator.startDream();
waitAndAssertTopResumedActivity(dreamActivity, Display.DEFAULT_DISPLAY,
"Dream activity should be the top resumed activity");
mDreamCoordinator.stopDream();
diff --git a/tests/tests/dreams/src/android/service/dreams/cts/SystemDreamTest.java b/tests/tests/dreams/src/android/service/dreams/cts/SystemDreamTest.java
new file mode 100644
index 0000000..3a65645
--- /dev/null
+++ b/tests/tests/dreams/src/android/service/dreams/cts/SystemDreamTest.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.service.dreams.cts;
+
+import android.content.ComponentName;
+import android.server.wm.ActivityManagerTestBase;
+import android.server.wm.DreamCoordinator;
+import android.view.Display;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.compatibility.common.util.ApiTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@ApiTest(apis = {"com.android.server.dreams.DreamManagerService#setSystemDreamComponent"})
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class SystemDreamTest extends ActivityManagerTestBase {
+ private static final String USER_DREAM_COMPONENT =
+ "android.app.dream.cts.app/.TestDreamService";
+ private static final String SYSTEM_DREAM_COMPONENT =
+ "android.app.dream.cts.app/.TestSystemDreamService";
+
+ private final DreamCoordinator mDreamCoordinator = new DreamCoordinator(mContext);
+
+ private ComponentName mSystemDream;
+ private ComponentName mUserDreamActivity;
+
+ @Before
+ public void setup() {
+ mDreamCoordinator.setup();
+
+ final ComponentName userDream = ComponentName.unflattenFromString(USER_DREAM_COMPONENT);
+ mSystemDream = ComponentName.unflattenFromString(SYSTEM_DREAM_COMPONENT);
+ mUserDreamActivity = mDreamCoordinator.setActiveDream(userDream);
+ }
+
+ @After
+ public void reset() {
+ mDreamCoordinator.restoreDefaults();
+ }
+
+ @Test
+ public void startDream_systemDreamNotSet_startUserDream() {
+ startAndVerifyDreamActivity(mUserDreamActivity);
+ }
+
+ @Test
+ public void startDream_systemDreamSet_startSystemDream() {
+ final ComponentName systemDreamActivity = mDreamCoordinator.setSystemDream(mSystemDream);
+ startAndVerifyDreamActivity(systemDreamActivity);
+ }
+
+ @Test
+ public void switchDream_systemDreamSet_switchToSystemDream() {
+ mDreamCoordinator.startDream();
+
+ // Sets system dream.
+ final ComponentName systemDreamActivity = mDreamCoordinator.setSystemDream(mSystemDream);
+ try {
+ // Verifies switched to system dream.
+ waitAndAssertTopResumedActivity(systemDreamActivity, Display.DEFAULT_DISPLAY,
+ getDreamActivityVerificationMessage(systemDreamActivity));
+ } finally {
+ mDreamCoordinator.stopDream();
+ }
+ }
+
+ @Test
+ public void switchDream_systemDreamCleared_switchToUserDream() {
+ mDreamCoordinator.setSystemDream(mSystemDream);
+ mDreamCoordinator.startDream();
+
+ // Clears system dream.
+ mDreamCoordinator.setSystemDream(null);
+ try {
+ // Verifies switched back to user dream.
+ waitAndAssertTopResumedActivity(mUserDreamActivity, Display.DEFAULT_DISPLAY,
+ getDreamActivityVerificationMessage(mUserDreamActivity));
+ } finally {
+ mDreamCoordinator.stopDream();
+ }
+ }
+
+ private void startAndVerifyDreamActivity(ComponentName expectedDreamActivity) {
+ try {
+ mDreamCoordinator.startDream();
+ waitAndAssertTopResumedActivity(expectedDreamActivity, Display.DEFAULT_DISPLAY,
+ getDreamActivityVerificationMessage(expectedDreamActivity));
+ } finally {
+ mDreamCoordinator.stopDream();
+ }
+ }
+
+ private String getDreamActivityVerificationMessage(ComponentName activity) {
+ return activity.flattenToString() + " should be displayed";
+ }
+}
diff --git a/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java b/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java
index b21edd5..3b92c16 100644
--- a/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java
+++ b/tests/tests/graphics/src/android/graphics/cts/AnimatorLeakTest.java
@@ -31,6 +31,7 @@
import junit.framework.Assert;
import org.junit.After;
+import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -53,10 +54,17 @@
boolean mFinitePaused = false;
boolean mFinitePausedSet = false;
boolean mResumed = false;
+ long mDefaultAnimatorPauseDelay = 10000L;
+
+ @Before
+ public void setup() {
+ mDefaultAnimatorPauseDelay = Animator.getBackgroundPauseDelay();
+ }
@After
public void cleanup() {
Animator.setAnimatorPausingEnabled(true);
+ Animator.setBackgroundPauseDelay(mDefaultAnimatorPauseDelay);
}
/**
diff --git a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java
index 36d0237..097a9a3 100644
--- a/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java
+++ b/tests/tests/hardware/src/android/hardware/input/cts/tests/VirtualTouchscreenTest.java
@@ -73,6 +73,7 @@
.build());
// Convert the input axis size to its equivalent fraction of the total screen.
final float computedSize = inputSize / (DISPLAY_WIDTH - 1f);
+
// There's an extraneous ACTION_HOVER_ENTER event in builds before T QPR1. We fixed the
// related bug (b/244744917) so the hover event is not generated anymore. In order to make
// the test permissive to existing and new behaviors we only verify the first 2 events here.
diff --git a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
index c9588f4..acfd627 100644
--- a/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
+++ b/tests/tests/media/audio/src/android/media/audio/cts/AudioManagerTest.java
@@ -107,7 +107,7 @@
private final static String TAG = "AudioManagerTest";
private final static long ASYNC_TIMING_TOLERANCE_MS = 50;
- private final static long POLL_TIME_VOLUME_ADJUST = 400;
+ private static final long POLL_TIME_VOLUME_ADJUST = 400;
private final static long POLL_TIME_UPDATE_INTERRUPTION_FILTER = 5000;
private final static int MP3_TO_PLAY = R.raw.testmp3; // ~ 5 second mp3
private final static long POLL_TIME_PLAY_MUSIC = 2000;
diff --git a/tests/tests/media/misc/Android.bp b/tests/tests/media/misc/Android.bp
index feaabe4..87208db 100644
--- a/tests/tests/media/misc/Android.bp
+++ b/tests/tests/media/misc/Android.bp
@@ -66,6 +66,7 @@
"junit-params",
"testng",
"truth-prebuilt",
+ "Harrier",
"mockito-target-minus-junit4",
"androidx.heifwriter_heifwriter",
"CtsCameraUtils",
diff --git a/tests/tests/media/misc/AndroidTest.xml b/tests/tests/media/misc/AndroidTest.xml
index 4a2ffaf..4ecf8fd 100644
--- a/tests/tests/media/misc/AndroidTest.xml
+++ b/tests/tests/media/misc/AndroidTest.xml
@@ -19,6 +19,7 @@
<option name="config-descriptor:metadata" key="parameter" value="instant_app" />
<option name="config-descriptor:metadata" key="parameter" value="multi_abi" />
<option name="config-descriptor:metadata" key="parameter" value="secondary_user" />
+ <option name="config-descriptor:metadata" key="parameter" value="multiuser" />
<target_preparer class="com.android.tradefed.targetprep.DeviceSetup">
<option name="force-skip-system-props" value="true" /> <!-- avoid restarting device -->
<option name="set-test-harness" value="false" />
@@ -57,5 +58,6 @@
<option name="hidden-api-checks" value="false" />
<!-- disable isolated storage so tests can access dynamic config stored in /sdcard. -->
<option name="isolated-storage" value="false" />
+ <option name="exclude-annotation" value="com.android.bedstead.harrier.annotations.RequireRunOnWorkProfile" />
</test>
</configuration>
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
index 89d336c..aae07c7 100644
--- a/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaRouter2Test.java
@@ -56,12 +56,17 @@
import android.text.TextUtils;
import androidx.test.InstrumentationRegistry;
-import androidx.test.runner.AndroidJUnit4;
+import com.android.bedstead.harrier.BedsteadJUnit4;
+import com.android.bedstead.harrier.DeviceState;
+import com.android.bedstead.harrier.UserType;
+import com.android.bedstead.harrier.annotations.UserTest;
import com.android.compatibility.common.util.PollingCheck;
import org.junit.After;
import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -75,12 +80,16 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
-@RunWith(AndroidJUnit4.class)
+@RunWith(BedsteadJUnit4.class)
@AppModeFull(reason = "The system should be able to bind to StubMediaRoute2ProviderService")
@LargeTest
@NonMediaMainlineTest
public class MediaRouter2Test {
private static final String TAG = "MR2Test";
+
+ // Required by Bedstead.
+ @ClassRule @Rule public static final DeviceState sDeviceState = new DeviceState();
+
Context mContext;
private MediaRouter2 mRouter2;
private Executor mExecutor;
@@ -158,8 +167,11 @@
}
/**
- * Tests if we get proper routes for application that has special route type.
+ * Tests if we get proper routes for an application that requests a special route type.
+ *
+ * <p>Runs on both the primary user and a work profile, as per {@link UserTest}.
*/
+ @UserTest({UserType.PRIMARY_USER, UserType.WORK_PROFILE})
@Test
public void testGetRoutes() throws Exception {
Map<String, MediaRoute2Info> routes = waitAndGetRoutes(FEATURES_SPECIAL);
diff --git a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
index 6d4fede..7ab5239 100644
--- a/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
+++ b/tests/tests/media/misc/src/android/media/misc/cts/MediaSessionManagerTest.java
@@ -166,10 +166,33 @@
assertTrue(keyEventSessionListener.mCountDownLatch
.await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
assertNull(keyEventSessionListener.mSessionToken);
+ assertEquals("", keyEventSessionListener.mPackageName);
+
assertNull(mSessionManager.getMediaKeyEventSession());
assertEquals("", mSessionManager.getMediaKeyEventSessionPackageName());
}
+ public void testOnMediaKeyEventSessionChangedListener_noSession_passesEmptyPackageAndNullToken()
+ throws InterruptedException {
+ // The permission can be held only on S+
+ if (!MediaUtils.check(sIsAtLeastS, "test invalid before Android 12")) return;
+
+ getInstrumentation().getUiAutomation().adoptShellPermissionIdentity(
+ Manifest.permission.MEDIA_CONTENT_CONTROL);
+
+ MediaKeyEventSessionListener keyEventSessionListener = new MediaKeyEventSessionListener();
+ mSessionManager.addOnMediaKeyEventSessionChangedListener(
+ Executors.newSingleThreadExecutor(), keyEventSessionListener);
+
+ assertTrue(keyEventSessionListener.mCountDownLatch
+ .await(TIMEOUT_MS, TimeUnit.MILLISECONDS));
+ assertNull(keyEventSessionListener.mSessionToken);
+ assertEquals("", keyEventSessionListener.mPackageName);
+
+ // Clean up listener.
+ mSessionManager.removeOnMediaKeyEventSessionChangedListener(keyEventSessionListener);
+ }
+
private MediaSession createMediaKeySession() {
MediaSession session = new MediaSession(getInstrumentation().getTargetContext(), TAG);
session.setFlags(MediaSession.FLAG_HANDLES_MEDIA_BUTTONS
@@ -749,6 +772,7 @@
implements MediaSessionManager.OnMediaKeyEventSessionChangedListener {
CountDownLatch mCountDownLatch;
MediaSession.Token mSessionToken;
+ String mPackageName;
MediaKeyEventSessionListener() {
mCountDownLatch = new CountDownLatch(1);
@@ -757,12 +781,15 @@
void resetCountDownLatch() {
mCountDownLatch = new CountDownLatch(1);
mSessionToken = null;
+ mPackageName = null;
}
@Override
public void onMediaKeyEventSessionChanged(String packageName,
MediaSession.Token sessionToken) {
+ assertNotNull("The package name cannot be null.", packageName);
mSessionToken = sessionToken;
+ mPackageName = packageName;
mCountDownLatch.countDown();
}
}
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 0a109a4..a2aebb5 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
@@ -27,6 +27,7 @@
import static junit.framework.TestCase.assertNotNull;
import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.junit.Assume.assumeFalse;
@@ -117,9 +118,9 @@
REVOKE_POST_NOTIFICATIONS_WITHOUT_KILL,
REVOKE_RUNTIME_PERMISSIONS);
if (mNotificationListenerService != null) mNotificationListenerService.resetData();
+ if (mNotificationAssistantService != null) mNotificationAssistantService.resetData();
- toggleListenerAccess(false);
- toggleAssistantAccess(false);
+ disconnectListeners();
mUi.dropShellPermissionIdentity();
}
@@ -510,9 +511,10 @@
assertTrue(String.format("snoozed notification <%s> was not removed", sbn.getKey()),
mNotificationListenerService.checkRemovedKey(sbn.getKey()));
- assertEquals(String.format("snoozed notification <%s> was not observed by NAS", sbn.getKey()),
- sbn.getKey(), mNotificationAssistantService.snoozedKey);
+ assertEquals(String.format("snoozed notification <%s> was not observed by NAS",
+ sbn.getKey()), sbn.getKey(), mNotificationAssistantService.snoozedKey);
assertEquals(snoozeContext, mNotificationAssistantService.snoozedUntilContext);
+ mNotificationAssistantService.unsnoozeNotification(sbn.getKey());
}
@Test
@@ -554,7 +556,8 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
Notification.Action action = new Notification.Action.Builder(null, "",
pendingIntent).build();
// This method has to exist and the call cannot fail
@@ -672,7 +675,8 @@
setUpListeners();
turnScreenOn();
- mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+ mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE",
+ "android.permission.EXPAND_STATUS_BAR");
mNotificationAssistantService.resetNotificationClickCount();
@@ -697,7 +701,8 @@
assumeFalse("Status bar service not supported", isWatch());
setUpListeners(); // also enables assistant
- mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE", "android.permission.EXPAND_STATUS_BAR");
+ mUi.adoptShellPermissionIdentity("android.permission.STATUS_BAR_SERVICE",
+ "android.permission.EXPAND_STATUS_BAR");
sendNotification(1, ICON_ID);
StatusBarNotification sbn = getFirstNotificationFromPackage(TestNotificationListener.PKG);
@@ -752,6 +757,18 @@
assertNotNull(mNotificationAssistantService);
}
+ private void disconnectListeners() throws Exception {
+ toggleListenerAccess(false);
+ toggleAssistantAccess(false);
+ Thread.sleep(2 * SLEEP_TIME); // wait for listener and assistant to be disconnected
+
+ mNotificationListenerService = TestNotificationListener.getInstance();
+ mNotificationAssistantService = TestNotificationAssistant.getInstance();
+
+ assertNull(mNotificationListenerService);
+ assertNull(mNotificationAssistantService);
+ }
+
private void sendNotification(final int id, final int icon) throws Exception {
sendNotification(id, null, icon);
}
@@ -763,7 +780,8 @@
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setAction(Intent.ACTION_MAIN);
- final PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, PendingIntent.FLAG_MUTABLE_UNAUDITED);
+ final PendingIntent pendingIntent = PendingIntent.getActivity(
+ mContext, 0, intent, PendingIntent.FLAG_IMMUTABLE);
final Notification notification =
new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(icon)
@@ -780,7 +798,8 @@
Person person = new Person.Builder()
.setName("test")
.build();
- final Notification notification = new Notification.Builder(mContext, NOTIFICATION_CHANNEL_ID)
+ final Notification notification = new Notification.Builder(
+ mContext, NOTIFICATION_CHANNEL_ID)
.setContentTitle("foo")
.setShortcutId("shareShortcut")
.setStyle(new Notification.MessagingStyle(person)
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
index b9f063e..a53c93f 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationAssistant.java
@@ -74,6 +74,18 @@
@Override
public void onListenerDisconnected() {
isConnected = false;
+ sNotificationAssistantInstance = null;
+ }
+
+ public void resetData() {
+ resetNotificationClickCount();
+ resetNotificationVisibilityCounts();
+ isPanelOpen = false;
+ snoozedKey = null;
+ snoozedUntilContext = null;
+ notificationRank = -1;
+ notificationFeedback = 0;
+ mRemoved.clear();
}
public static TestNotificationAssistant getInstance() {
diff --git a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
index a7f068c..9d0337d 100644
--- a/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
+++ b/tests/tests/notificationlegacy/notificationlegacy29/src/android/app/notification/legacy29/cts/TestNotificationListener.java
@@ -70,6 +70,7 @@
@Override
public void onListenerDisconnected() {
isConnected = false;
+ sNotificationListenerInstance = null;
}
public static TestNotificationListener getInstance() {
diff --git a/tests/tests/permission2/res/raw/android_manifest.xml b/tests/tests/permission2/res/raw/android_manifest.xml
index c495afa..01b2580 100644
--- a/tests/tests/permission2/res/raw/android_manifest.xml
+++ b/tests/tests/permission2/res/raw/android_manifest.xml
@@ -3847,7 +3847,7 @@
<p>Should only be requested by the System, should be required by
TileService declarations.-->
<permission android:name="android.permission.BIND_QUICK_SETTINGS_TILE"
- android:protectionLevel="signature" />
+ android:protectionLevel="signature|recents" />
<!-- Allows SystemUI to request third party controls.
<p>Should only be requested by the System and required by
@@ -6038,7 +6038,7 @@
<!-- @SystemApi Allows to access all app shortcuts.
@hide -->
<permission android:name="android.permission.ACCESS_SHORTCUTS"
- android:protectionLevel="signature|role" />
+ android:protectionLevel="signature|role|recents" />
<!-- @SystemApi Allows unlimited calls to shortcut mutation APIs.
@hide -->
diff --git a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
index 576df28..93d9f29 100644
--- a/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
+++ b/tests/tests/permission2/src/android/permission2/cts/PermissionPolicyTest.java
@@ -20,8 +20,6 @@
import static android.content.pm.PermissionInfo.PROTECTION_MASK_BASE;
import static android.os.Build.VERSION.SECURITY_PATCH;
-import static com.android.compatibility.common.util.SystemUtil.runShellCommand;
-
import static com.google.common.truth.Truth.assertWithMessage;
import android.Manifest;
@@ -31,7 +29,6 @@
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PermissionGroupInfo;
import android.content.pm.PermissionInfo;
-import android.os.Build;
import android.os.Process;
import android.os.SystemProperties;
import android.platform.test.annotations.AppModeFull;
@@ -77,16 +74,9 @@
private static final String SET_UNRESTRICTED_GESTURE_EXCLUSION
= "android.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION";
- private static final String BIND_OEM_CAR_SERVICE =
- "android.car.permission.BIND_OEM_CAR_SERVICE";
-
private static final String RECEIVE_KEYCODE_EVENTS_PERMISSION =
"android.permission.RECEIVE_KEYCODE_EVENTS";
- private static final String ACCESS_SHORTCUTS_PERMISSION = "android.permission.ACCESS_SHORTCUTS";
- private static final String BIND_QUICK_SETTINGS_TILE =
- "android.permission.BIND_QUICK_SETTINGS_TILE";
-
private static final String LOG_TAG = "PermissionProtectionTest";
private static final String PLATFORM_PACKAGE_NAME = "android";
@@ -232,11 +222,7 @@
final int expectedProtectionFlags =
expectedPermission.protectionLevel & ~PROTECTION_MASK_BASE;
final int declaredProtectionFlags = declaredPermission.getProtectionFlags();
- if (expectedProtectionFlags != declaredProtectionFlags
- && !shouldAllowProtectionFlagsChange(
- expectedPermissionName,
- expectedProtectionFlags,
- declaredProtectionFlags)) {
+ if (expectedProtectionFlags != declaredProtectionFlags) {
offendingList.add(
String.format(
"Permission %s invalid enforced protection %x, expected %x",
@@ -544,8 +530,6 @@
return parseDate(SECURITY_PATCH).before(MANAGE_COMPANION_DEVICES_PATCH_DATE);
case SET_UNRESTRICTED_GESTURE_EXCLUSION:
return true;
- case BIND_OEM_CAR_SERVICE:
- return shoudldSkipBindOemCarService();
case RECEIVE_KEYCODE_EVENTS_PERMISSION:
return true;
default:
@@ -553,29 +537,6 @@
}
}
- /**
- * check should be skipped only for T and T-QPR1
- */
- private boolean shoudldSkipBindOemCarService() {
- if (Build.VERSION.SDK_INT > 33) {
- return false;
- }
- String output = runShellCommand("dumpsys car_service --version");
- if (output.contains("Car API minor: 0") || output.contains("Car API minor: 1")) {
- return true;
- }
-
- return false;
- }
-
- private static boolean shouldAllowProtectionFlagsChange(
- String permissionName, int expectedFlags, int actualFlags) {
- return (ACCESS_SHORTCUTS_PERMISSION.equals(permissionName)
- || BIND_QUICK_SETTINGS_TILE.equals(permissionName))
- && ((expectedFlags | PermissionInfo.PROTECTION_FLAG_RECENTS)
- == (actualFlags | PermissionInfo.PROTECTION_FLAG_RECENTS));
- }
-
private class ExpectedPermissionInfo {
final @NonNull String name;
final @Nullable String group;
diff --git a/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java b/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java
index eaddc09..ce31901 100644
--- a/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java
+++ b/tests/tests/provider/src/android/provider/cts/MultiAuthorityTest.java
@@ -31,6 +31,7 @@
import org.junit.AfterClass;
import org.junit.Before;
+import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
diff --git a/tests/tests/security/Android.bp b/tests/tests/security/Android.bp
index 6a15b65..989edca 100644
--- a/tests/tests/security/Android.bp
+++ b/tests/tests/security/Android.bp
@@ -33,6 +33,7 @@
"compatibility-common-util-devicesidelib",
"guava",
"platform-test-annotations",
+ "permission-test-util-lib",
"sts-device-util",
"hamcrest-library",
"NeneInternal",
@@ -80,6 +81,16 @@
":CtsDeviceInfo",
":RolePermissionOverrideTestApp",
":SplitBluetoothPermissionTestApp",
+ ":CtsPermissionBackupAppCert1",
+ ":CtsPermissionBackupAppCert1Dup",
+ ":CtsPermissionBackupAppCert2",
+ ":CtsPermissionBackupAppCert3",
+ ":CtsPermissionBackupAppCert4",
+ ":CtsPermissionBackupAppCert12",
+ ":CtsPermissionBackupAppCert12Dup",
+ ":CtsPermissionBackupAppCert34",
+ ":CtsPermissionBackupAppCert123",
+ ":CtsPermissionBackupAppCert4History124",
":CtsAppThatRequestCustomCameraPermission",
],
}
@@ -90,6 +101,115 @@
manifest: "testdata/packageinstallertestapp.xml",
}
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert1",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-1",
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert1Dup",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-1",
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert2",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-2",
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert3",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-3",
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert4",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-4",
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert12",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-1",
+ additional_certificates: [
+ ":permission-test-cert-2",
+ ],
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert12Dup",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-1",
+ additional_certificates: [
+ ":permission-test-cert-2",
+ ],
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert34",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-3",
+ additional_certificates: [
+ ":permission-test-cert-4",
+ ],
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert123",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-1",
+ additional_certificates: [
+ ":permission-test-cert-2",
+ ":permission-test-cert-3",
+ ],
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
+android_test_helper_app {
+ name: "CtsPermissionBackupAppCert4History124",
+ min_sdk_version: "30",
+ resource_dirs: [],
+ asset_dirs: [],
+ certificate: ":permission-test-cert-4",
+ additional_certificates: [
+ ":permission-test-cert-1",
+
+ ],
+ rotationMinSdkVersion: "30",
+ lineage: ":permission-test-cert-with-rotation-history",
+ manifest: "testdata/permissionbackuptestapp/AndroidManifest.xml",
+}
+
android_app_certificate {
name: "security_cts_test_certificate",
certificate: "security_cts_test_cert",
@@ -97,5 +217,34 @@
android_test_helper_app {
name: "RolePermissionOverrideTestApp",
+ resource_dirs: [],
+ asset_dirs: [],
manifest: "testdata/rolepermissionoverridetestapp.xml",
}
+
+android_app_certificate {
+ name: "permission-test-cert-1",
+ certificate: "test-cert-1",
+}
+
+android_app_certificate {
+ name: "permission-test-cert-2",
+ certificate: "test-cert-2",
+}
+
+android_app_certificate {
+ name: "permission-test-cert-3",
+ certificate: "test-cert-3",
+}
+
+android_app_certificate {
+ name: "permission-test-cert-4",
+ certificate: "test-cert-4",
+}
+
+filegroup {
+ name: "permission-test-cert-with-rotation-history",
+ srcs: [
+ "test-cert-with-1-2-4-in-rotation-history",
+ ],
+}
diff --git a/tests/tests/security/AndroidManifest.xml b/tests/tests/security/AndroidManifest.xml
index 4e43d57..f05a623 100644
--- a/tests/tests/security/AndroidManifest.xml
+++ b/tests/tests/security/AndroidManifest.xml
@@ -31,6 +31,9 @@
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+ <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"/>
+ <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS"/>
+
<uses-permission android:name="android.permission.EXPAND_STATUS_BAR"/>
<!-- For FileIntegrityManager -->
diff --git a/tests/tests/security/AndroidTest.xml b/tests/tests/security/AndroidTest.xml
index 57ffd67..6a6dc92 100644
--- a/tests/tests/security/AndroidTest.xml
+++ b/tests/tests/security/AndroidTest.xml
@@ -52,6 +52,16 @@
<option name="cleanup" value="true" />
<option name="push" value="RolePermissionOverrideTestApp.apk->/data/local/tmp/cts/security/RolePermissionOverrideTestApp.apk" />
<option name="push" value="SplitBluetoothPermissionTestApp.apk->/data/local/tmp/cts/security/SplitBluetoothPermissionTestApp.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert1.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert1.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert1Dup.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert1Dup.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert2.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert2.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert3.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert3.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert4.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert4.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert12.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert12.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert12Dup.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert12Dup.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert123.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert123.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert34.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert34.apk" />
+ <option name="push" value="CtsPermissionBackupAppCert4History124.apk->/data/local/tmp/cts/security/CtsPermissionBackupAppCert4History124.apk" />
<option name="push" value="CtsAppThatRequestCustomCameraPermission.apk->/data/local/tmp/cts/permissions/CtsAppThatRequestCustomCameraPermission.apk" />
</target_preparer>
@@ -62,4 +72,9 @@
<option name="test-timeout" value="900000" />
<option name="hidden-api-checks" value="false" />
</test>
+
+ <target_preparer class="android.cts.backup.BackupPreparer">
+ <option name="enable-backup-if-needed" value="true" />
+ <option name="select-local-transport" value="true" />
+ </target_preparer>
</configuration>
diff --git a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
index a27a0b9..116d21e 100644
--- a/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
+++ b/tests/tests/security/src/android/security/cts/ActivityManagerTest.java
@@ -22,15 +22,25 @@
import static org.junit.Assert.*;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.app.Activity;
import android.app.ActivityManager;
import android.app.ActivityOptions;
import android.app.Application;
+
+import android.app.UiAutomation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
+
+import android.os.Process;
+import android.os.UserHandle;
import android.platform.test.annotations.AsbSecurityTest;
import android.util.Log;
import android.view.SurfaceControl;
@@ -43,8 +53,11 @@
import androidx.test.runner.AndroidJUnit4;
import com.android.compatibility.common.util.SystemUtil;
+
+import com.android.compatibility.common.util.ShellUtils;
import com.android.sts.common.util.StsExtraBusinessLogicTestCase;
+
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -56,6 +69,80 @@
@RunWith(AndroidJUnit4.class)
public class ActivityManagerTest extends StsExtraBusinessLogicTestCase {
+ private boolean canSupportMultiuser() {
+ String output = ShellUtils.runShellCommand("pm get-max-users");
+ if (output.contains("Maximum supported users:")) {
+ return Integer.parseInt(output.split(": ", 2)[1].trim()) > 1;
+ }
+ return false;
+ }
+
+ @AsbSecurityTest(cveBugId = 217934898)
+ @Test
+ public void testActivityManager_registerUidChangeObserver_onlyNoInteractAcrossPermission()
+ throws Exception {
+ if (!canSupportMultiuser()) {
+ return;
+ }
+ String out = "";
+ final UidImportanceObserver observer = new UidImportanceObserver();
+ final ActivityManager mActMan = (ActivityManager) getContext()
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ final int currentUser = mActMan.getCurrentUser();
+ try {
+
+ mActMan.addOnUidImportanceListener(observer,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+
+ out = ShellUtils.runShellCommand("pm create-user testUser");
+ out = out.length() > 2 ? out.substring(out.length() - 2) : out;
+
+ ShellUtils.runShellCommand("am switch-user " + out);
+
+ Thread.sleep(5000);
+ assertFalse(observer.didObserverOtherUser());
+ } finally {
+ ShellUtils.runShellCommand("am switch-user " + currentUser);
+ ShellUtils.runShellCommand("pm remove-user " + out);
+ mActMan.removeOnUidImportanceListener(observer);
+ }
+ }
+
+ @AsbSecurityTest(cveBugId = 217934898)
+ @Test
+ public void testActivityManager_registerUidChangeObserver_allPermission()
+ throws Exception {
+ if (!canSupportMultiuser()) {
+ return;
+ }
+ String out = "";
+ final UidImportanceObserver observer = new UidImportanceObserver();
+ final ActivityManager mActMan = (ActivityManager) getContext()
+ .getSystemService(Context.ACTIVITY_SERVICE);
+ final int currentUser = mActMan.getCurrentUser();
+ final UiAutomation uiAutomation =
+ InstrumentationRegistry.getInstrumentation().getUiAutomation();
+ uiAutomation
+ .adoptShellPermissionIdentity("android.permission.INTERACT_ACROSS_USERS_FULL");
+ try {
+ mActMan.addOnUidImportanceListener(observer,
+ ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND);
+
+ out = ShellUtils.runShellCommand("pm create-user testUser");
+ out = out.length() > 2 ? out.substring(out.length() - 2) : out;
+
+ ShellUtils.runShellCommand("am switch-user " + out);
+
+ Thread.sleep(5000);
+ assertTrue(observer.didObserverOtherUser());
+ } finally {
+ uiAutomation.dropShellPermissionIdentity();
+ ShellUtils.runShellCommand("am switch-user " + currentUser);
+ ShellUtils.runShellCommand("pm remove-user " + out);
+ mActMan.removeOnUidImportanceListener(observer);
+ }
+ }
+
@AsbSecurityTest(cveBugId = 19394591)
@Test
public void testActivityManager_injectInputEvents() throws ClassNotFoundException {
@@ -290,4 +377,26 @@
}
}
}
+
+ static final class UidImportanceObserver implements ActivityManager.OnUidImportanceListener {
+
+ private boolean mObservedNonOwned = false;
+ private int mMyUid;
+
+ UidImportanceObserver() {
+ mMyUid = UserHandle.getUserId(Process.myUid());
+ }
+
+ public void onUidImportance(int uid, int importance) {
+ Log.i("ActivityManagerTestObserver", "Observing change for "
+ + uid + " by user " + UserHandle.getUserId(uid));
+ if (UserHandle.getUserId(uid) != mMyUid) {
+ mObservedNonOwned = true;
+ }
+ }
+
+ public boolean didObserverOtherUser() {
+ return this.mObservedNonOwned;
+ }
+ }
}
diff --git a/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java b/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java
index c6b7e35..487c3d9 100644
--- a/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java
+++ b/tests/tests/security/src/android/security/cts/LocationDisabledAppOpsTest.java
@@ -86,7 +86,8 @@
List<String> bypassedCheckOps = new ArrayList<>();
for (PackageInfo pi : pkgs) {
ApplicationInfo ai = pi.applicationInfo;
- if (ai.uid != Process.SYSTEM_UID) {
+ int appId = UserHandle.getAppId(ai.uid);
+ if (appId != Process.SYSTEM_UID) {
final int[] mode = {MODE_ALLOWED};
runWithShellPermissionIdentity(() -> {
mode[0] = mAom.noteOpNoThrow(
diff --git a/tests/tests/security/src/android/security/cts/PermissionBackupCertificateCheckTest.kt b/tests/tests/security/src/android/security/cts/PermissionBackupCertificateCheckTest.kt
new file mode 100644
index 0000000..1170939
--- /dev/null
+++ b/tests/tests/security/src/android/security/cts/PermissionBackupCertificateCheckTest.kt
@@ -0,0 +1,813 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+package android.security.cts
+
+import android.Manifest.permission.*
+import android.app.AppOpsManager
+import android.content.pm.PackageManager.*
+import android.os.ParcelFileDescriptor
+import android.permission.cts.PermissionUtils.grantPermission
+import android.platform.test.annotations.AppModeFull
+import android.platform.test.annotations.AsbSecurityTest
+import androidx.test.InstrumentationRegistry
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.compatibility.common.util.BackupUtils
+import com.android.compatibility.common.util.BackupUtils.LOCAL_TRANSPORT_TOKEN
+import com.android.compatibility.common.util.BusinessLogicTestCase
+import com.android.compatibility.common.util.ShellUtils.runShellCommand
+import com.android.compatibility.common.util.SystemUtil.callWithShellPermissionIdentity
+import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity
+import com.android.sts.common.util.StsExtraBusinessLogicTestCase
+import java.io.InputStream
+import org.junit.After
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Tests that permissions for backed up apps are restored only after checking that their signing
+ * certificates are compared.
+ *
+ * @see [com.android.permissioncontroller.permission.service.BackupHelper]
+ */
+@AppModeFull
+@RunWith(AndroidJUnit4::class)
+class PermissionBackupCertificateCheckTest : StsExtraBusinessLogicTestCase() {
+ private val backupUtils: BackupUtils =
+ object : BackupUtils() {
+ override fun executeShellCommand(command: String): InputStream {
+ val pfd =
+ BusinessLogicTestCase.getInstrumentation()
+ .uiAutomation
+ .executeShellCommand(command)
+ return ParcelFileDescriptor.AutoCloseInputStream(pfd)
+ }
+ }
+
+ private var isBackupSupported = false
+
+ private val targetContext = InstrumentationRegistry.getTargetContext()
+
+ @Before
+ fun setUp() {
+ val packageManager = BusinessLogicTestCase.getInstrumentation().context.packageManager
+ isBackupSupported =
+ (packageManager != null && packageManager.hasSystemFeature(FEATURE_BACKUP))
+
+ if (isBackupSupported) {
+ assertTrue("Backup not enabled", backupUtils.isBackupEnabled)
+ assertTrue("LocalTransport not selected", backupUtils.isLocalTransportSelected)
+ backupUtils.executeShellCommandSync("setprop log.tag.$APP_LOG_TAG VERBOSE")
+ }
+ }
+
+ @After
+ fun tearDown() {
+ uninstallIfInstalled(APP)
+ clearFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET)
+ clearFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has the
+ * same certificate as the backed up app.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_sameCert_restoresRuntimePermissions() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_1_DUP)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has a
+ * different certificate as the backed up app.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_diffCert_doesNotGrantRuntimePermissions() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_3)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has the
+ * backed up app's certificate in its signing history.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_midHistoryToRotated_restoresRuntimePermissions() {
+ install(APP_APK_CERT_2)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has the
+ * backed up app's certificate as the original certificate in its signing history.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_origToRotated_restoresRuntimePermissions() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the backed up app has the
+ * restored app's certificate in its signing history.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_rotatedToMidHistory_restoresRuntimePermissions() {
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_2)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the backed up app has the
+ * restored app's certificate in its signing history as its original certificate.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_rotatedToOrig_restoresRuntimePermissions() {
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_1)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the backed up app has the same
+ * certificate as the restored app, but the restored app additionally has signing certificate
+ * history.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_sameWithHistory_restoresRuntimePermissions() {
+ install(APP_APK_CERT_4)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the backed up app has the same
+ * certificate as the restored app, but the backed up app additionally has signing certificate
+ * history.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_sameWithoutHistory_restoresRuntimePermissions() {
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has
+ * signing history, but the backed up app's certificate is not in this signing history.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_notInBackedUpHistory_doesNotRestoreRuntimePerms() {
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_3)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has
+ * signing history, but the backed up app's certificate is not in this signing history.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_notInRestoredHistory_doesNotRestoreRuntimePerms() {
+ install(APP_APK_CERT_3)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has
+ * multiple certificates, and the backed up app also has identical multiple certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_sameMultCerts_restoresRuntimePermissions() {
+ install(APP_APK_CERT_1_2)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_1_2_DUP)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has
+ * multiple certificates, and the backed up app do not have identical multiple certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_diffMultCerts_doesNotRestoreRuntimePermissions() {
+ install(APP_APK_CERT_1_2)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_3_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the app being restored has
+ * multiple certificates, and the backed up app's certificate is present in th restored app's
+ * certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_singleToMultiCert_restoresRuntimePerms() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_1_2_3)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the backed up app and the app
+ * being restored have multiple certificates, and the backed up app's certificates are a subset
+ * of the restored app's certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_multCertsToSuperset_doesNotRestoreRuntimePerms() {
+ install(APP_APK_CERT_1_2)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_1_2_3)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of regular runtime permissions, when the backed up app and the app
+ * being restored have multiple certificates, and the backed up app's certificates are a
+ * superset of the restored app's certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_multCertsToSubset_doesNotRestoreRuntimePermissions() {
+ install(APP_APK_CERT_1_2_3)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_1_2)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, READ_CONTACTS))
+ }
+ }
+
+ /**
+ * Test backup and restore of tri-state permissions, when both foreground and background runtime
+ * permissions are not granted and the backed up and restored app have compatible certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_fgBgDenied_matchingCerts_restoresFgBgPermissions() {
+ install(APP_APK_CERT_2)
+ if (!isBackupSupported) {
+ return
+ }
+ // Make a token change to permission state, to enable to us to determine when restore is
+ // complete.
+ grantPermission(APP, WRITE_CONTACTS)
+ // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+ // to ensure that permissions are backed up.
+ setFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET)
+ setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+
+ // Wait until restore is complete.
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, WRITE_CONTACTS))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+ assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+ }
+ }
+
+ /**
+ * Test backup and restore of tri-state permissions, when both foreground and background runtime
+ * permissions are not granted and the backed up and restored app don't have compatible
+ * certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_fgBgDenied_notMatchingCerts_doesNotRestorePerms() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ // Make a token change to permission state, to enable to us to determine when restore is
+ // complete.
+ grantPermission(APP, WRITE_CONTACTS)
+ // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+ // to ensure that permissions are backed up.
+ setFlag(APP, ACCESS_FINE_LOCATION, FLAG_PERMISSION_USER_SET)
+ setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_2)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+
+ // Wait until restore is complete.
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, WRITE_CONTACTS))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+ assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+ }
+ }
+
+ /**
+ * Test backup and restore of tri-state permissions, when foreground runtime permission is
+ * granted and the backed up and restored app have compatible certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_fgGranted_matchingCerts_restoresFgBgPermissions() {
+ install(APP_APK_CERT_2)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+ // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+ // to ensure that permissions are backed up.
+ setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+ assertEquals(AppOpsManager.MODE_FOREGROUND, getAppOp(APP, ACCESS_FINE_LOCATION))
+ }
+ }
+
+ /**
+ * Test backup and restore of tri-state permissions, when foreground runtime permission is
+ * granted and the backed up and restored app don't have compatible certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_fgGranted_notMatchingCerts_doesNotRestoreFgBgPerms() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+ // PERMISSION_DENIED is the default state, so we mark the permissions as user set in order
+ // to ensure that permissions are backed up.
+ setFlag(APP, ACCESS_BACKGROUND_LOCATION, FLAG_PERMISSION_USER_SET)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_2)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+ assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+ }
+ }
+
+ /**
+ * Test backup and restore of tri-state permissions, when foreground and background runtime
+ * permissions are granted and the backed up and restored app have compatible certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_fgBgGranted_matchingCerts_restoresFgBgPermissions() {
+ install(APP_APK_CERT_2)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+ grantPermission(APP, ACCESS_BACKGROUND_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+ assertEquals(AppOpsManager.MODE_ALLOWED, getAppOp(APP, ACCESS_FINE_LOCATION))
+ }
+ }
+
+ /**
+ * Test backup and restore of tri-state permissions, when foreground and background runtime
+ * permissions are granted and the backed up and restored app don't have compatible
+ * certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_fgBgGranted_notMatchingCerts_restoresFgBgPerms() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+ grantPermission(APP, ACCESS_BACKGROUND_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_2)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually {
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION))
+ assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_BACKGROUND_LOCATION))
+ assertEquals(AppOpsManager.MODE_IGNORED, getAppOp(APP, ACCESS_FINE_LOCATION))
+ }
+ }
+
+ /**
+ * Test backup and restore of flags when the backed up app and restored app have compatible
+ * certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_matchingCerts_restoresFlags() {
+ install(APP_APK_CERT_2)
+ if (!isBackupSupported) {
+ return
+ }
+ setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually { assertTrue(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)) }
+ }
+
+ /**
+ * Test backup and restore of flags when the backed up app and restored app don't have
+ * compatible certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_notMatchingCerts_doesNotRestoreFlag() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ setFlag(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ install(APP_APK_CERT_2)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+
+ eventually { assertFalse(isFlagSet(APP, WRITE_CONTACTS, FLAG_PERMISSION_USER_SET)) }
+ }
+
+ /**
+ * Test backup and delayed restore of regular runtime permission, i.e. when an app is installed
+ * after restore has run, and the backed up app and restored app have compatible certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_appInstalledLater_matchingCerts_restoresCorrectly() {
+ install(APP_APK_CERT_2)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+
+ eventually { assertEquals(PERMISSION_GRANTED, checkPermission(APP, ACCESS_FINE_LOCATION)) }
+ }
+
+ /**
+ * Test backup and delayed restore of regular runtime permission, i.e. when an app is installed
+ * after restore has run, and the backed up app and restored app don't have compatible
+ * certificates.
+ */
+ @Test
+ @AsbSecurityTest(cveBugId = [184847040])
+ fun testRestore_appInstalledLater_notMatchingCerts_doesNotRestore() {
+ install(APP_APK_CERT_1)
+ if (!isBackupSupported) {
+ return
+ }
+ grantPermission(APP, ACCESS_FINE_LOCATION)
+
+ backupUtils.backupNowAndAssertSuccess(ANDROID_PACKAGE)
+ uninstallIfInstalled(APP)
+ backupUtils.restoreAndAssertSuccess(LOCAL_TRANSPORT_TOKEN, ANDROID_PACKAGE)
+ install(APP_APK_CERT_4_HISTORY_1_2_4)
+
+ eventually { assertEquals(PERMISSION_DENIED, checkPermission(APP, ACCESS_FINE_LOCATION)) }
+ }
+
+ private fun install(apk: String) {
+ val output = runShellCommand("pm install -r $apk")
+ assertEquals("Success", output)
+ }
+
+ private fun uninstallIfInstalled(packageName: String) {
+ runShellCommand("pm uninstall $packageName")
+ }
+
+ private fun setFlag(app: String, permission: String, flag: Int) {
+ runWithShellPermissionIdentity {
+ targetContext.packageManager.updatePermissionFlags(
+ permission, app, flag, flag, targetContext.user)
+ }
+ }
+
+ private fun clearFlag(app: String, permission: String, flag: Int) {
+ runWithShellPermissionIdentity {
+ targetContext.packageManager.updatePermissionFlags(
+ permission, app, flag, 0, targetContext.user)
+ }
+ }
+
+ private fun isFlagSet(app: String, permission: String, flag: Int): Boolean {
+ return try {
+ callWithShellPermissionIdentity<Int> {
+ targetContext.packageManager.getPermissionFlags(permission, app, targetContext.user)
+ } and flag == flag
+ } catch (e: Exception) {
+ throw RuntimeException(e)
+ }
+ }
+
+ private fun checkPermission(app: String, permission: String): Int {
+ return targetContext.packageManager.checkPermission(permission, app)
+ }
+
+ private fun getAppOp(app: String, permission: String): Int {
+ return try {
+ callWithShellPermissionIdentity {
+ targetContext
+ .getSystemService<AppOpsManager>(AppOpsManager::class.java)!!
+ .unsafeCheckOpRaw(
+ AppOpsManager.permissionToOp(permission)!!,
+ targetContext.packageManager.getPackageUid(app, 0),
+ app)
+ }
+ } catch (e: Exception) {
+ throw RuntimeException(e)
+ }
+ }
+
+ companion object {
+ /** The name of the package of the apps under test */
+ private const val APP = "android.security.permissionbackup"
+ /** The apk of the packages */
+ private const val APK_PATH = "/data/local/tmp/cts/security/"
+ private const val APP_APK_CERT_1 = "${APK_PATH}CtsPermissionBackupAppCert1.apk"
+ private const val APP_APK_CERT_1_DUP = "${APK_PATH}CtsPermissionBackupAppCert1Dup.apk"
+ private const val APP_APK_CERT_2 = "${APK_PATH}CtsPermissionBackupAppCert2.apk"
+ private const val APP_APK_CERT_3 = "${APK_PATH}CtsPermissionBackupAppCert3.apk"
+ private const val APP_APK_CERT_4 = "${APK_PATH}CtsPermissionBackupAppCert4.apk"
+ private const val APP_APK_CERT_1_2 = "${APK_PATH}CtsPermissionBackupAppCert12.apk"
+ private const val APP_APK_CERT_1_2_DUP = "${APK_PATH}CtsPermissionBackupAppCert12Dup.apk"
+ private const val APP_APK_CERT_1_2_3 = "${APK_PATH}CtsPermissionBackupAppCert123.apk"
+ private const val APP_APK_CERT_3_4 = "${APK_PATH}CtsPermissionBackupAppCert34.apk"
+ private const val APP_APK_CERT_4_HISTORY_1_2_4 =
+ "${APK_PATH}CtsPermissionBackupAppCert4History124.apk"
+ private const val APP_LOG_TAG = "PermissionBackupApp"
+ /** The name of the package for backup */
+ private const val ANDROID_PACKAGE = "android"
+ private const val TIMEOUT_MILLIS: Long = 10000
+
+ /**
+ * Make sure that a [Runnable] eventually finishes without throwing an [Exception].
+ *
+ * @param r The [Runnable] to run.
+ */
+ fun eventually(r: Runnable) {
+ val start = System.currentTimeMillis()
+ while (true) {
+ try {
+ r.run()
+ return
+ } catch (e: Throwable) {
+ if (System.currentTimeMillis() - start < TIMEOUT_MILLIS) {
+ try {
+ Thread.sleep(100)
+ } catch (ignored: InterruptedException) {
+ throw RuntimeException(e)
+ }
+ } else {
+ throw e
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/tests/tests/security/test-cert-1.pk8 b/tests/tests/security/test-cert-1.pk8
new file mode 100644
index 0000000..f781c30
--- /dev/null
+++ b/tests/tests/security/test-cert-1.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-1.x509.pem b/tests/tests/security/test-cert-1.x509.pem
new file mode 100644
index 0000000..06adcfe
--- /dev/null
+++ b/tests/tests/security/test-cert-1.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbDCCARGgAwIBAgIJAMoPtk37ZudyMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM
+B2VjLXAyNTYwHhcNMTYwMzMxMTQ1ODA2WhcNNDMwODE3MTQ1ODA2WjASMRAwDgYD
+VQQDDAdlYy1wMjU2MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEpl8RPSLLSROQ
+gwesMe4roOkTi3hfrGU20U6izpDStL/hlLUM3I4Wn1SnOpke8Pp2MpglvgeMx4J0
+BwPaRLTX66NQME4wHQYDVR0OBBYEFNQTNWi5WzAVizIgceqMQ/9bBczIMB8GA1Ud
+IwQYMBaAFNQTNWi5WzAVizIgceqMQ/9bBczIMAwGA1UdEwQFMAMBAf8wCgYIKoZI
+zj0EAwIDSQAwRgIhAPUEoIZsrvAp9BcULFy3E1THn/zR1kBhjfyk8Z4W23jWAiEA
++O6kgpeZwGytCMbT0tLsBeBXQVTnR+oP27gELLZVqt0=
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-2.pk8 b/tests/tests/security/test-cert-2.pk8
new file mode 100644
index 0000000..5e73f27
--- /dev/null
+++ b/tests/tests/security/test-cert-2.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-2.x509.pem b/tests/tests/security/test-cert-2.x509.pem
new file mode 100644
index 0000000..f8e5e65
--- /dev/null
+++ b/tests/tests/security/test-cert-2.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbTCCAROgAwIBAgIJAIhVvR3SsrIlMAoGCCqGSM49BAMCMBIxEDAOBgNVBAMM
+B2VjLXAyNTYwHhcNMTgwNzEzMTc0MTUxWhcNMjgwNzEwMTc0MTUxWjAUMRIwEAYD
+VQQDDAllYy1wMjU2XzIwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQdTMoEcq2X
+7jzs7w2pPWK0UMZ4gzOzbnVTzen3SrXfALu6a6lQ5oRh1wu8JxtiFR2tLeK/YgPN
+IHaAHHqdRCLho1AwTjAdBgNVHQ4EFgQUeZHZKwII/ESL9QbU78n/9CjLXl8wHwYD
+VR0jBBgwFoAU1BM1aLlbMBWLMiBx6oxD/1sFzMgwDAYDVR0TBAUwAwEB/zAKBggq
+hkjOPQQDAgNIADBFAiAnaauxtJ/C9TR5xK6SpmMdq/1SLJrLC7orQ+vrmcYwEQIh
+ANJg+x0fF2z5t/pgCYv9JDGfSQWj5f2hAKb+Giqxn/Ce
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-3.pk8 b/tests/tests/security/test-cert-3.pk8
new file mode 100644
index 0000000..d7309dd
--- /dev/null
+++ b/tests/tests/security/test-cert-3.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-3.x509.pem b/tests/tests/security/test-cert-3.x509.pem
new file mode 100644
index 0000000..c028ff7
--- /dev/null
+++ b/tests/tests/security/test-cert-3.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBbjCCARWgAwIBAgIJAIOU9crRaomnMAoGCCqGSM49BAMCMBQxEjAQBgNVBAMM
+CWVjLXAyNTZfMjAeFw0xODA3MTQwMDA1MjZaFw0yODA3MTEwMDA1MjZaMBQxEjAQ
+BgNVBAMMCWVjLXAyNTZfMzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABPMeYkMO
+nbb8WSjZdfxOR0GbrPyy4HyJKZ5s1+NE3SGt/TCNWMtJoaKj/srM7qSGIGnzC+Fk
+O8wlUEDYCJ37N0OjUDBOMB0GA1UdDgQWBBRvjQgosT769Xf8hrDpn6PlS8vP8DAf
+BgNVHSMEGDAWgBR5kdkrAgj8RIv1BtTvyf/0KMteXzAMBgNVHRMEBTADAQH/MAoG
+CCqGSM49BAMCA0cAMEQCICVr2qJ4TCc+TMKRpZWkZ3ne6d6QRNyferggMJVn35/p
+AiAaStjGmJG1qMR0NP6VQO0fSXm1+tNIPz+gTVZ3NVpXng==
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-4.pk8 b/tests/tests/security/test-cert-4.pk8
new file mode 100644
index 0000000..3675d50
--- /dev/null
+++ b/tests/tests/security/test-cert-4.pk8
Binary files differ
diff --git a/tests/tests/security/test-cert-4.x509.pem b/tests/tests/security/test-cert-4.x509.pem
new file mode 100644
index 0000000..4060400
--- /dev/null
+++ b/tests/tests/security/test-cert-4.x509.pem
@@ -0,0 +1,10 @@
+-----BEGIN CERTIFICATE-----
+MIIBezCCASCgAwIBAgIUbIy4qBhDPB5kMfsW+zrg+1rWCqcwCgYIKoZIzj0EAwIw
+FDESMBAGA1UEAwwJZWMtcDI1Nl8zMB4XDTIwMDUxMzE5MTUyOFoXDTMwMDUxMTE5
+MTUyOFowFDESMBAGA1UEAwwJZWMtcDI1Nl80MFkwEwYHKoZIzj0CAQYIKoZIzj0D
+AQcDQgAE20pgAx55rUnLdZAH1oVdRGm5HIurBlQ08vupca3n5NGVmaD2e15wjP2n
+VD5WMMN2nTfgk2QNfHaKFRRM0OXc9KNQME4wHQYDVR0OBBYEFG54lwMyVUM2tu6J
+JOqnAjDjk/Z4MB8GA1UdIwQYMBaAFG+NCCixPvr1d/yGsOmfo+VLy8/wMAwGA1Ud
+EwQFMAMBAf8wCgYIKoZIzj0EAwIDSQAwRgIhAM54bnnsdUdEYILpyvkQYU/4B1j5
+gZ+w8UhpUGer4PzUAiEApIgeMy3ewhFq0rWc+JHQ8zH/fifne3xiBseYjZtTkzA=
+-----END CERTIFICATE-----
diff --git a/tests/tests/security/test-cert-with-1-2-4-in-rotation-history b/tests/tests/security/test-cert-with-1-2-4-in-rotation-history
new file mode 100644
index 0000000..7326e46
--- /dev/null
+++ b/tests/tests/security/test-cert-with-1-2-4-in-rotation-history
Binary files differ
diff --git a/tests/tests/security/testdata/permissionbackuptestapp/AndroidManifest.xml b/tests/tests/security/testdata/permissionbackuptestapp/AndroidManifest.xml
new file mode 100644
index 0000000..2b75d8c
--- /dev/null
+++ b/tests/tests/security/testdata/permissionbackuptestapp/AndroidManifest.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2018 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="android.security.permissionbackup" >
+
+ <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
+ <uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION"/>
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
+
+ <application
+ android:label="Android Permission Backup CTS App">
+ <uses-library android:name="android.test.runner" />
+ </application>
+</manifest>
diff --git a/tests/tests/settings/Android.bp b/tests/tests/settings/Android.bp
index b1a443e..1798e55 100644
--- a/tests/tests/settings/Android.bp
+++ b/tests/tests/settings/Android.bp
@@ -17,20 +17,13 @@
static_libs: [
"androidx.slice_slice-core",
"androidx.slice_slice-view",
- "androidx.test.core",
+ "androidx.window_window",
"ctstestrunner-axt",
"junit",
- "kotlin-stdlib",
"truth-prebuilt",
- "ctsWindowExtLib",
],
srcs: ["src/**/*.java"],
sdk_version: "test_current",
}
-
-android_library_import {
- name: "ctsWindowExtLib",
- aars: ["libs/cts_window_ext_lib.aar"],
-}
diff --git a/tests/tests/settings/AndroidManifest.xml b/tests/tests/settings/AndroidManifest.xml
index 007b36d..631d2b4 100644
--- a/tests/tests/settings/AndroidManifest.xml
+++ b/tests/tests/settings/AndroidManifest.xml
@@ -24,8 +24,10 @@
<application>
<uses-library android:name="android.test.runner"/>
- <uses-library android:name="androidx.window.extensions" android:required="false"/>
- <uses-library android:name="androidx.window.sidecar" android:required="false"/>
+
+ <property
+ android:name="android.window.PROPERTY_ACTIVITY_EMBEDDING_SPLITS_ENABLED"
+ android:value="true" />
<activity android:name=".RightPaneActivity"
android:exported="true">
diff --git a/tests/tests/settings/libs/cts_window_ext_lib.aar b/tests/tests/settings/libs/cts_window_ext_lib.aar
deleted file mode 100644
index ca58b36..0000000
--- a/tests/tests/settings/libs/cts_window_ext_lib.aar
+++ /dev/null
Binary files differ
diff --git a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
index 937141d..48b72d0 100644
--- a/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
+++ b/tests/tests/settings/src/android/settings/cts/SettingsMultiPaneDeepLinkTest.java
@@ -24,6 +24,7 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.app.Instrumentation.ActivityMonitor;
+import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
@@ -55,10 +56,11 @@
@Before
public void setUp() throws Exception {
+ Context targetContext = InstrumentationRegistry.getInstrumentation()
+ .getTargetContext();
boolean isFlagEnabled =
- FeatureFlagUtils.isEnabled(InstrumentationRegistry.getInstrumentation()
- .getTargetContext(), "settings_support_large_screen");
- boolean isSplitSupported = SplitController.getInstance().isSplitSupported();
+ FeatureFlagUtils.isEnabled(targetContext, "settings_support_large_screen");
+ boolean isSplitSupported = SplitController.getInstance(targetContext).isSplitSupported();
mIsSplitSupported = isFlagEnabled && isSplitSupported;
Log.d(TAG, "isFlagEnabled : " + isFlagEnabled);
Log.d(TAG, "isSplitSupported : " + isSplitSupported);
diff --git a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
index edc1cd2..812b1ef 100644
--- a/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
+++ b/tests/tests/shortcutmanager/src/android/content/pm/cts/shortcutmanager/ShortcutManagerLauncherApiTest.java
@@ -421,7 +421,8 @@
mLauncherContext1, mPackageContext1.getPackageName(), "s2", false));
}
- public void testSetDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+ // TODO: b/259468694
+ public void setDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
@@ -474,7 +475,8 @@
});
}
- public void testRemoveAllDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ // TODO: b/259468694
+ public void removeAllDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
@@ -533,7 +535,8 @@
});
}
- public void testAddDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+ // TODO: b/259468694
+ public void addDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
@@ -588,7 +591,8 @@
});
}
- public void testPushDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
+ // TODO: b/259468694
+ public void pushDynamicShortcuts_PersistsShortcutsToDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
@@ -657,7 +661,8 @@
SystemUtil.runShellCommand("cmd shortcut reset-config");
}
- public void testRemoveDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ // TODO: b/259468694
+ public void removeDynamicShortcuts_RemovesShortcutsFromDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
@@ -687,7 +692,8 @@
});
}
- public void testRemoveLongLivedShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ // TODO: b/259468694
+ public void removeLongLivedShortcuts_RemovesShortcutsFromDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
@@ -716,7 +722,8 @@
});
}
- public void testDisableShortcuts_RemovesShortcutsFromDisk() throws Exception {
+ // TODO: b/259468694
+ public void disableShortcuts_RemovesShortcutsFromDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
@@ -746,7 +753,8 @@
});
}
- public void testUpdateShortcuts_UpdateShortcutsOnDisk() throws Exception {
+ // TODO: b/259468694
+ public void updateShortcuts_UpdateShortcutsOnDisk() throws Exception {
if (!isAppSearchEnabled()) {
return;
}
diff --git a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
index be8c089..90e11d1 100644
--- a/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
+++ b/tests/tests/telecom/src/android/telecom/cts/OutgoingCallTest.java
@@ -31,7 +31,6 @@
import android.telecom.Call;
import android.telecom.CallAudioState;
import android.telecom.Connection;
-import android.telecom.PhoneAccountHandle;
import android.telecom.TelecomManager;
import android.telephony.PhoneStateListener;
import android.telephony.TelephonyManager;
@@ -301,23 +300,23 @@
return;
}
+ mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT);
+ TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
+ mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_2);
+ TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2);
+
CountDownLatch latch = new CountDownLatch(1);
mInCallCallbacks = new MockInCallService.InCallServiceCallbacks() {
@Override
public void onCallAdded(Call call, int numCalls) {
if (call.getState() == STATE_SELECT_PHONE_ACCOUNT) {
+ call.phoneAccountSelected(TestUtils.TEST_PHONE_ACCOUNT_HANDLE, true);
latch.countDown();
}
}
};
MockInCallService.setCallbacks(mInCallCallbacks);
- mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT);
- TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
- mTelecomManager.registerPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_2);
- TestUtils.enablePhoneAccount(getInstrumentation(), TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2);
-
- PhoneAccountHandle cachedHandle = mTelecomManager.getUserSelectedOutgoingPhoneAccount();
SystemUtil.runWithShellPermissionIdentity(() -> {
mTelecomManager.setUserSelectedOutgoingPhoneAccount(null);
});
@@ -327,10 +326,11 @@
mTelecomManager.placeCall(testNumber, null);
assertTrue(latch.await(TestUtils.WAIT_FOR_CALL_ADDED_TIMEOUT_S, TimeUnit.SECONDS));
+ assertEquals(TestUtils.TEST_PHONE_ACCOUNT_HANDLE,
+ mTelecomManager.getUserSelectedOutgoingPhoneAccount());
} finally {
- SystemUtil.runWithShellPermissionIdentity(() -> {
- mTelecomManager.setUserSelectedOutgoingPhoneAccount(cachedHandle);
- });
+ mTelecomManager.unregisterPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_HANDLE);
+ mTelecomManager.unregisterPhoneAccount(TestUtils.TEST_PHONE_ACCOUNT_HANDLE_2);
}
}
}
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 ce92c8c..4029c50 100755
--- a/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/SubscriptionManagerTest.java
@@ -33,6 +33,7 @@
import static org.junit.Assume.assumeTrue;
import android.annotation.Nullable;
+import android.app.AppOpsManager;
import android.app.UiAutomation;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -49,6 +50,7 @@
import android.os.Looper;
import android.os.ParcelUuid;
import android.os.PersistableBundle;
+import android.os.Process;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
@@ -63,6 +65,7 @@
import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.CarrierPrivilegeUtils;
import com.android.compatibility.common.util.PropertyUtil;
import com.android.compatibility.common.util.ShellIdentityUtils;
@@ -670,6 +673,7 @@
}
@Test
+ @ApiTest(apis = "android.telephony.SubscriptionManager#getSubscriptionsInGroup")
public void testSubscriptionGroupingWithPermission() throws Exception {
// Set subscription group with current sub Id.
List<Integer> subGroup = new ArrayList();
@@ -680,8 +684,14 @@
// Getting subscriptions in group.
List<SubscriptionInfo> infoList = mSm.getSubscriptionsInGroup(uuid);
assertNotNull(infoList);
+ assertTrue(infoList.isEmpty());
+
+ // has the READ_PRIVILEGED_PHONE_STATE permission
+ infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+ (sm) -> sm.getSubscriptionsInGroup(uuid), READ_PRIVILEGED_PHONE_STATE);
+ assertNotNull(infoList);
assertEquals(1, infoList.size());
- assertNull(infoList.get(0).getGroupUuid());
+ assertEquals(uuid, infoList.get(0).getGroupUuid());
infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getSubscriptionsInGroup(uuid));
@@ -698,33 +708,40 @@
}
availableInfoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
(sm) -> sm.getAvailableSubscriptionInfoList());
- if (availableInfoList.size() > 1) {
- List<Integer> availableSubGroup = availableInfoList.stream()
- .map(info -> info.getSubscriptionId())
- .filter(subId -> subId != mSubId)
- .collect(Collectors.toList());
+ // has the OPSTR_READ_DEVICE_IDENTIFIERS permission
+ try {
+ setIdentifierAccess(true);
+ if (availableInfoList.size() > 1) {
+ List<Integer> availableSubGroup = availableInfoList.stream()
+ .map(info -> info.getSubscriptionId())
+ .filter(subId -> subId != mSubId)
+ .collect(Collectors.toList());
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
+ (sm) -> sm.addSubscriptionsIntoGroup(availableSubGroup, uuid));
+
+ infoList = mSm.getSubscriptionsInGroup(uuid);
+ assertNotNull(infoList);
+ assertEquals(availableInfoList.size(), infoList.size());
+
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
+ (sm) -> sm.removeSubscriptionsFromGroup(availableSubGroup, uuid));
+ }
+
+ // Remove from subscription group with current sub Id.
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
- (sm) -> sm.addSubscriptionsIntoGroup(availableSubGroup, uuid));
+ (sm) -> sm.removeSubscriptionsFromGroup(subGroup, uuid));
infoList = mSm.getSubscriptionsInGroup(uuid);
assertNotNull(infoList);
- assertEquals(availableInfoList.size(), infoList.size());
-
- ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
- (sm) -> sm.removeSubscriptionsFromGroup(availableSubGroup, uuid));
+ assertTrue(infoList.isEmpty());
+ } finally {
+ setIdentifierAccess(false);
}
-
- // Remove from subscription group with current sub Id.
- ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
- (sm) -> sm.removeSubscriptionsFromGroup(subGroup, uuid));
-
- infoList = mSm.getSubscriptionsInGroup(uuid);
- assertNotNull(infoList);
- assertTrue(infoList.isEmpty());
}
@Test
+ @ApiTest(apis = "android.telephony.SubscriptionManager#getSubscriptionsInGroup")
public void testAddSubscriptionIntoNewGroupWithPermission() throws Exception {
// Set subscription group with current sub Id.
List<Integer> subGroup = new ArrayList();
@@ -733,28 +750,33 @@
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
(sm) -> sm.addSubscriptionsIntoGroup(subGroup, uuid));
- // Getting subscriptions in group.
List<SubscriptionInfo> infoList = mSm.getSubscriptionsInGroup(uuid);
assertNotNull(infoList);
- assertEquals(1, infoList.size());
- assertNull(infoList.get(0).getGroupUuid());
+ assertTrue(infoList.isEmpty());
- infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
- (sm) -> sm.getSubscriptionsInGroup(uuid));
- assertNotNull(infoList);
- assertEquals(1, infoList.size());
- assertEquals(uuid, infoList.get(0).getGroupUuid());
+ // Getting subscriptions in group.
+ try {
+ setIdentifierAccess(true);
+ infoList = mSm.getSubscriptionsInGroup(uuid);
+ assertNotNull(infoList);
+ assertEquals(1, infoList.size());
+ assertEquals(uuid, infoList.get(0).getGroupUuid());
+ } finally {
+ setIdentifierAccess(false);
+ }
// Remove from subscription group with current sub Id.
ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(mSm,
(sm) -> sm.removeSubscriptionsFromGroup(subGroup, uuid));
- infoList = mSm.getSubscriptionsInGroup(uuid);
+ infoList = ShellIdentityUtils.invokeMethodWithShellPermissions(mSm,
+ (sm) -> sm.getSubscriptionsInGroup(uuid));
assertNotNull(infoList);
assertTrue(infoList.isEmpty());
}
@Test
+ @ApiTest(apis = "android.telephony.SubscriptionManager#setOpportunistic")
public void testSettingOpportunisticSubscription() throws Exception {
// Set subscription to be opportunistic. This should fail
// because we don't have MODIFY_PHONE_STATE or carrier privilege permission.
@@ -762,6 +784,12 @@
mSm.setOpportunistic(true, mSubId);
fail();
} catch (SecurityException expected) {
+ // Caller permission should not affect accessing SIMINFO table.
+ assertNotEquals(expected.getMessage(),
+ "Access SIMINFO table from not phone/system UID");
+ // Caller does not have permission to manage mSubId.
+ assertEquals(expected.getMessage(),
+ "Caller requires permission on sub " + mSubId);
}
// Shouldn't crash.
@@ -1459,4 +1487,13 @@
return validCarrier && validNetworkType && validCapabilities;
}
+
+ private void setIdentifierAccess(boolean allowed) {
+ String op = AppOpsManager.OPSTR_READ_DEVICE_IDENTIFIERS;
+ AppOpsManager appOpsManager = InstrumentationRegistry.getContext().getSystemService(
+ AppOpsManager.class);
+ int mode = allowed ? AppOpsManager.MODE_ALLOWED : AppOpsManager.opToDefaultMode(op);
+ ShellIdentityUtils.invokeMethodWithShellPermissionsNoReturn(
+ appOpsManager, (appOps) -> appOps.setUidMode(op, Process.myUid(), mode));
+ }
}
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
index 291ce7c..8c39625 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/TelephonyManagerTest.java
@@ -3567,6 +3567,7 @@
ModemActivityInfo diff = activityInfo1.getDelta(activityInfo2);
assertNotNull(diff);
+ assertTrue("two activityInfo are identical", !activityInfo1.equals(activityInfo2));
assertTrue("diff is" + diff, diff.isValid() || diff.isEmpty());
} finally {
InstrumentationRegistry.getInstrumentation().getUiAutomation()
diff --git a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
index 3f6d02f..f969182 100644
--- a/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
+++ b/tests/tests/telephony/current/src/android/telephony/cts/VisualVoicemailServiceTest.java
@@ -18,6 +18,8 @@
import static androidx.test.InstrumentationRegistry.getInstrumentation;
+import static com.android.internal.telephony.SmsConstants.ENCODING_8BIT;
+
import static junit.framework.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
@@ -425,7 +427,7 @@
/**
* Setup the SMS filter with only the {@code clientPrefix}, and sends {@code text} to the
* device. The SMS sent should not be written to the SMS provider. <p> If {@code expectVvmSms}
- * is {@code true}, the SMS should be be caught by the SMS filter. The user should not receive
+ * is {@code true}, the SMS should be caught by the SMS filter. The user should not receive
* the text, and the parsed result will be returned.* <p> If {@code expectVvmSms} is {@code
* false}, the SMS should pass through the SMS filter. The user should receive the text, and
* {@code null} be returned.
@@ -461,7 +463,7 @@
future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
throw new RuntimeException("Unexpected visual voicemail SMS received");
} catch (TimeoutException e) {
- // expected
+ Log.i(TAG, "Expected TimeoutException" + e);
return null;
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
@@ -473,7 +475,6 @@
@Nullable
private VisualVoicemailSms getSmsFromData(VisualVoicemailSmsFilterSettings settings, short port,
String text, boolean expectVvmSms) {
-
mTelephonyManager.setVisualVoicemailSmsFilterSettings(settings);
CompletableFuture<VisualVoicemailSms> future = new CompletableFuture<>();
@@ -497,7 +498,7 @@
future.get(EVENT_NOT_RECEIVED_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS);
throw new RuntimeException("Unexpected visual voicemail SMS received");
} catch (TimeoutException e) {
- // expected
+ Log.i(TAG, "Expected TimeoutException!" + e);
return null;
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
@@ -529,16 +530,28 @@
StringBuilder messageBody = new StringBuilder();
CharsetDecoder decoder = StandardCharsets.UTF_8.newDecoder();
for (SmsMessage message : messages) {
- if (message.getMessageBody() != null) {
- messageBody.append(message.getMessageBody());
- } else if (message.getUserData() != null) {
+ String body = message.getMessageBody();
+
+ if ((body == null || (message.is3gpp()
+ && message.getReceivedEncodingType() == ENCODING_8BIT))
+ && message.getUserData() != null) {
+ Log.d(TAG, "onReceive decode using UTF-8");
+ // Attempt to interpret the user data as UTF-8. UTF-8 string over data SMS using
+ // 8BIT data coding scheme is our recommended way to send VVM SMS and is used in
+ // CTS Tests. The OMTP visual voicemail specification does not specify the SMS
+ // type and encoding.
ByteBuffer byteBuffer = ByteBuffer.wrap(message.getUserData());
try {
- messageBody.append(decoder.decode(byteBuffer).toString());
+ body = decoder.decode(byteBuffer).toString();
} catch (CharacterCodingException e) {
+ Log.e(TAG, "onReceive: got CharacterCodingException"
+ + " when decoding with UTF-8, e = " + e);
return;
}
}
+ if (body != null) {
+ messageBody.append(body);
+ }
}
if (!TextUtils.equals(mText, messageBody.toString())) {
return;
diff --git a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
index dba15dc..87b08b9 100644
--- a/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
+++ b/tests/tests/telephonyprovider/src/android/telephonyprovider/cts/SmsTest.java
@@ -22,16 +22,20 @@
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
+
import android.provider.Telephony;
import androidx.test.filters.SmallTest;
+import com.android.compatibility.common.util.ApiTest;
+
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@@ -327,5 +331,55 @@
DefaultSmsAppHelper.ensureDefaultSmsApp();
}
-}
+ /**
+ * Verifies sql injection is not allowed within a URI.
+ */
+ @Test
+ @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+ public void query_msgParameter_sqlInjection() {
+ Uri uriWithSqlInjection = Uri.parse("content://mms-sms/pending?protocol=sms&message=1 "
+ + "union select type,name,tbl_name,rootpage,sql,1,1,1,1,1 FROM SQLITE_MASTER; --");
+ Cursor uriWithSqlInjectionCur = mContentResolver.query(uriWithSqlInjection, null,
+ null, null, null);
+ assertNull(uriWithSqlInjectionCur);
+ }
+
+ /**
+ * Verifies query() returns non-null cursor when valid URI is passed to it.
+ */
+ @Test
+ @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+ public void query_msgParameter_withoutSqlInjection() {
+ Uri uriWithoutSqlInjection = Uri.parse("content://mms-sms/pending?protocol=sms&message=1");
+ Cursor uriWithoutSqlInjectionCur = mContentResolver.query(uriWithoutSqlInjection,
+ null, null, null, null);
+ assertNotNull(uriWithoutSqlInjectionCur);
+ }
+
+ /**
+ * Verifies sql injection is not allowed within a URI.
+ */
+ @Test
+ @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+ public void query_threadIdParameter_sqlInjection() {
+ Uri uriWithSqlInjection = Uri.parse("content://mms-sms/conversations?simple=true&"
+ + "thread_type=1 union select type,name,tbl_name,rootpage,sql FROM SQLITE_MASTER;; --");
+ Cursor uriWithSqlInjectionCur = mContentResolver.query(uriWithSqlInjection,
+ new String[]{"1","2","3","4","5"}, null, null, null);
+ assertNull(uriWithSqlInjectionCur);
+ }
+
+ /**
+ * Verifies query() returns non-null cursor when valid URI is passed to it.
+ */
+ @Test
+ @ApiTest(apis = "com.android.providers.telephony.MmsSmsProvider#query")
+ public void query_threadIdParameter_withoutSqlInjection() {
+ Uri uriWithoutSqlInjection = Uri.parse(
+ "content://mms-sms/conversations?simple=true&thread_type=1");
+ Cursor uriWithoutSqlInjectionCur = mContentResolver.query(uriWithoutSqlInjection,
+ new String[]{"1","2","3","4","5"}, null, null, null);
+ assertNotNull(uriWithoutSqlInjectionCur);
+ }
+}
\ No newline at end of file
diff --git a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
index 678ce90..9510d93 100644
--- a/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
+++ b/tests/tests/textclassifier/src/android/view/textclassifier/cts/TextViewIntegrationTest.java
@@ -56,7 +56,9 @@
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.uiautomator.By;
import androidx.test.uiautomator.UiDevice;
+import androidx.test.uiautomator.UiObject2;
+import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.ShellUtils;
import com.android.compatibility.common.util.SystemUtil;
@@ -204,6 +206,18 @@
assertThat(mSimpleTextClassifier.getClassifyTextInvocationCount()).isEqualTo(1);
}
+ // TODO: re-use now. Refactor to have a folder/test class for toolbar
+ @Test
+ @ApiTest(apis = "android.view.View#startActionMode")
+ public void smartSelection_toolbarContainerNoContentDescription() throws Exception {
+ smartSelectionInternal();
+
+ UiObject2 toolbarContainer =
+ sDevice.findObject(By.res("android", "floating_popup_container"));
+ assertThat(toolbarContainer).isNotNull();
+ assertThat(toolbarContainer.getContentDescription()).isNull();
+ }
+
private void smartSelectionInternal() {
ActivityScenario<TextViewActivity> scenario = rule.getScenario();
AtomicInteger clickIndex = new AtomicInteger();
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
index f00f0b1..5330b02 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/ExactCanvasTests.java
@@ -20,17 +20,20 @@
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.ColorSpace;
+import android.graphics.LinearGradient;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Picture;
import android.graphics.PorterDuff;
import android.graphics.Rect;
+import android.graphics.Shader;
import android.graphics.drawable.NinePatchDrawable;
import android.uirendering.cts.R;
import android.uirendering.cts.bitmapcomparers.BitmapComparer;
import android.uirendering.cts.bitmapcomparers.ExactComparer;
import android.uirendering.cts.bitmapcomparers.MSSIMComparer;
import android.uirendering.cts.bitmapverifiers.BitmapVerifier;
+import android.uirendering.cts.bitmapverifiers.ColorVerifier;
import android.uirendering.cts.bitmapverifiers.GoldenImageVerifier;
import android.uirendering.cts.bitmapverifiers.PerPixelBitmapVerifier;
import android.uirendering.cts.bitmapverifiers.RectVerifier;
@@ -40,9 +43,13 @@
import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ApiTest;
+
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Arrays;
+
@MediumTest
@RunWith(AndroidJUnit4.class)
public class ExactCanvasTests extends ActivityTestBase {
@@ -352,6 +359,138 @@
}
@Test
+ @ApiTest(apis = {"android.graphics.Canvas#drawVertices"})
+ public void testDrawVertices_doNotBlendColorsWithoutShader() {
+ BitmapVerifier verifier = new ColorVerifier(Color.BLUE);
+
+ createTest()
+ .addCanvasClient(
+ (canvas, width, height) -> {
+ float[] verts =
+ new float[] {
+ width, 0, width, height, 0, 0, 0, height,
+ };
+ int[] vertColors =
+ new int[] {Color.BLUE, Color.BLUE, Color.BLUE, Color.BLUE};
+ // Paint color should be ignored and not blend with vertex color.
+ Paint paint = new Paint();
+ paint.setColor(Color.RED);
+ canvas.drawVertices(
+ Canvas.VertexMode.TRIANGLE_STRIP,
+ verts.length,
+ verts,
+ 0,
+ null,
+ 0,
+ vertColors,
+ 0,
+ null,
+ 0,
+ 0,
+ paint);
+ })
+ .runWithVerifier(verifier);
+ }
+
+ @Test
+ @ApiTest(apis = {"android.graphics.Canvas#drawVertices"})
+ public void testDrawVertices_blendVertexAndShaderColors() {
+ Color vertexColor = Color.valueOf(0.8f, 0.6f, 0.4f);
+ Color shaderColor = Color.valueOf(0.5f, 0.5f, 0.5f);
+ // drawVertices should blend vertex color and shader color with kModulate.
+ Color modulatedColor =
+ Color.valueOf(
+ vertexColor.red() * shaderColor.red(),
+ vertexColor.green() * shaderColor.green(),
+ vertexColor.blue() * shaderColor.blue());
+ BitmapVerifier verifier = new ColorVerifier(modulatedColor.toArgb());
+
+ createTest()
+ .addCanvasClient(
+ (canvas, width, height) -> {
+ float[] verts =
+ new float[] {
+ width, 0, width, height, 0, 0, 0, height,
+ };
+ int[] vertColors = new int[4];
+ Arrays.fill(vertColors, vertexColor.toArgb());
+ int[] shaderColors = new int[2];
+ Arrays.fill(shaderColors, shaderColor.toArgb());
+
+ Paint paint = new Paint();
+ paint.setShader(
+ new LinearGradient(
+ 0,
+ 0,
+ width,
+ height,
+ shaderColors,
+ null,
+ Shader.TileMode.REPEAT));
+ canvas.drawVertices(
+ Canvas.VertexMode.TRIANGLE_STRIP,
+ verts.length,
+ verts,
+ 0,
+ verts,
+ 0,
+ vertColors,
+ 0,
+ null,
+ 0,
+ 0,
+ paint);
+ })
+ .runWithVerifier(verifier);
+ }
+
+ @Test
+ @ApiTest(apis = {"android.graphics.Canvas#drawVertices"})
+ public void testDrawVertices_ignoreShaderIfTexsNotSet() {
+ Color vertexColor = Color.valueOf(0.8f, 0.6f, 0.4f);
+ Color shaderColor = Color.valueOf(0.5f, 0.5f, 0.5f); // Should be ignored.
+ BitmapVerifier verifier = new ColorVerifier(vertexColor.toArgb());
+
+ createTest()
+ .addCanvasClient(
+ (canvas, width, height) -> {
+ float[] verts =
+ new float[] {
+ width, 0, width, height, 0, 0, 0, height,
+ };
+ int[] vertColors = new int[4];
+ Arrays.fill(vertColors, vertexColor.toArgb());
+ int[] shaderColors = new int[2];
+ Arrays.fill(shaderColors, shaderColor.toArgb());
+
+ Paint paint = new Paint();
+ paint.setShader(
+ new LinearGradient(
+ 0,
+ 0,
+ width,
+ height,
+ shaderColors,
+ null,
+ Shader.TileMode.REPEAT));
+ canvas.drawVertices(
+ Canvas.VertexMode.TRIANGLE_STRIP,
+ verts.length,
+ verts,
+ 0,
+ null,
+ 0,
+ vertColors,
+ 0,
+ null,
+ 0,
+ 0,
+ paint);
+ })
+ .runWithVerifier(verifier);
+ }
+
+ @Test
public void testColorLongs() {
createTest()
.addCanvasClient((canvas, width, height) -> {
diff --git a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
index 3148dcc..0674aff 100644
--- a/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
+++ b/tests/tests/uirendering/src/android/uirendering/cts/testclasses/RuntimeShaderTests.kt
@@ -34,6 +34,9 @@
import android.uirendering.cts.testinfrastructure.CanvasClient
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.MediumTest
+import com.android.compatibility.common.util.ApiTest
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
@@ -373,6 +376,37 @@
}
@Test
+ @ApiTest(apis = arrayOf("android.graphics.Shader#setLocalMatrix"))
+ fun testComposeShaderLocalMatrix() {
+ val shaderA = RuntimeShader(simpleRedShader)
+
+ // Create a runtime shader that calls a decal image shader with translation local matrix.
+ val bitmap = Bitmap.createBitmap(20, 20, Bitmap.Config.ARGB_8888, true)
+ bitmap.eraseColor(Color.GREEN)
+ val bitmapShader = BitmapShader(bitmap, Shader.TileMode.DECAL, Shader.TileMode.DECAL)
+ val matrix = Matrix()
+ matrix.setTranslate(100f, 0f)
+ bitmapShader.setLocalMatrix(matrix)
+ val shaderB = RuntimeShader(samplingShader)
+ shaderB.setInputShader("inputShader", bitmapShader)
+
+ // This compose shader will have a local matrix that compensates for the image shader's
+ // translation.
+ val composeShader = ComposeShader(shaderA, shaderB, BlendMode.SRC_OVER)
+ matrix.setTranslate(-100f, 0f)
+ composeShader.setLocalMatrix(matrix)
+
+ val paint = Paint()
+ paint.shader = composeShader
+
+ val rect = Rect(0, 0, 20, 20)
+
+ createTest().addCanvasClient(CanvasClient
+ { canvas: Canvas, width: Int, height: Int -> canvas.drawRect(rect, paint) },
+ true).runWithVerifier(RectVerifier(Color.WHITE, Color.GREEN, rect, 0))
+ }
+
+ @Test
fun testLinearColorIntrinsic() {
val colorA = Color.valueOf(0.75f, 0.25f, 0.0f, 1.0f)
val colorB = Color.valueOf(0.0f, 0.75f, 0.25f, 1.0f)
diff --git a/tests/tests/voiceinteraction/Android.bp b/tests/tests/voiceinteraction/Android.bp
index b847f7f..e534c35 100644
--- a/tests/tests/voiceinteraction/Android.bp
+++ b/tests/tests/voiceinteraction/Android.bp
@@ -20,6 +20,7 @@
name: "CtsVoiceInteractionTestCases",
defaults: ["cts_defaults"],
static_libs: [
+ "CtsAttentionServiceDevice",
"CtsVoiceInteractionCommon",
"ctstestrunner-axt",
"compatibility-device-util-axt",
@@ -35,7 +36,7 @@
"service/src/android/voiceinteraction/service/MainInteractionSessionService.java",
"service/src/android/voiceinteraction/service/MainRecognitionService.java",
"service/src/android/voiceinteraction/service/EventPayloadParcelable.java",
- "service/src/android/voiceinteraction/service/EventPayloadParcelable.aidl"
+ "service/src/android/voiceinteraction/service/EventPayloadParcelable.aidl",
],
// Tag this module as a cts test artifact
test_suites: [
diff --git a/tests/tests/voiceinteraction/AndroidManifest.xml b/tests/tests/voiceinteraction/AndroidManifest.xml
index 1b8b513..6401843 100644
--- a/tests/tests/voiceinteraction/AndroidManifest.xml
+++ b/tests/tests/voiceinteraction/AndroidManifest.xml
@@ -96,6 +96,14 @@
<meta-data android:name="android.speech"
android:resource="@xml/recognition_service" />
</service>
+ <service android:name="android.attentionservice.cts.CtsTestAttentionService"
+ android:label="CtsTestAttentionService"
+ android:permission="android.permission.BIND_ATTENTION_SERVICE"
+ android:exported="true">
+ <intent-filter>
+ <action android:name="android.service.attention.AttentionService"/>
+ </intent-filter>
+ </service>
<receiver android:name="VoiceInteractionTestReceiver"
android:exported="true"/>
</application>
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
index 5c1e926..4a7b5cc 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceBasicTest.java
@@ -17,6 +17,9 @@
package android.voiceinteraction.cts;
import static android.content.pm.PackageManager.FEATURE_MICROPHONE;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.perform;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.performAndGetDetectionResult;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.testHotwordDetection;
import static com.google.common.truth.Truth.assertThat;
@@ -24,7 +27,6 @@
import android.app.Instrumentation;
import android.app.compat.CompatChanges;
-import android.content.Intent;
import android.content.pm.PackageManager;
import android.media.AudioAttributes;
import android.media.AudioFormat;
@@ -44,7 +46,6 @@
import android.voiceinteraction.service.EventPayloadParcelable;
import android.voiceinteraction.service.MainHotwordDetectionService;
-import androidx.annotation.NonNull;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.RequiresDevice;
import androidx.test.platform.app.InstrumentationRegistry;
@@ -139,9 +140,12 @@
@Test
public void testHotwordDetectionService_validHotwordDetectionComponentName_triggerSuccess()
throws Throwable {
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
}
@Test
@@ -157,22 +161,31 @@
receiver.register();
// Create SoftwareHotwordDetector
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
// Destroy detector
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
// Create AlwaysOnHotwordDetector
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
verifyDetectedResult(
- performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST),
+ performAndGetDetectionResult(
+ mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
MainHotwordDetectionService.DETECTED_RESULT);
verifyMicrophoneChip(true);
}
@@ -180,17 +193,21 @@
@Test
public void testVoiceInteractionService_withoutManageHotwordDetectionPermission_triggerFailure()
throws Throwable {
- testHotwordDetection(Utils.VIS_WITHOUT_MANAGE_HOTWORD_DETECTION_PERMISSION_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.VIS_WITHOUT_MANAGE_HOTWORD_DETECTION_PERMISSION_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
}
@Test
public void testVoiceInteractionService_holdBindHotwordDetectionPermission_triggerFailure()
throws Throwable {
- testHotwordDetection(Utils.VIS_HOLD_BIND_HOTWORD_DETECTION_PERMISSION_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.VIS_HOLD_BIND_HOTWORD_DETECTION_PERMISSION_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SECURITY_EXCEPTION,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
}
@Test
@@ -199,12 +216,17 @@
throws Throwable {
Thread.sleep(CLEAR_CHIP_MS);
// Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
verifyDetectedResult(
- performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST),
+ performAndGetDetectionResult(
+ mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
MainHotwordDetectionService.DETECTED_RESULT);
verifyMicrophoneChip(true);
}
@@ -215,11 +237,16 @@
throws Throwable {
Thread.sleep(CLEAR_CHIP_MS);
// Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
- assertThat(performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONREJECT_TEST))
+ assertThat(performAndGetDetectionResult(
+ mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONREJECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC))
.isEqualTo(MainHotwordDetectionService.REJECTED_RESULT);
verifyMicrophoneChip(false);
}
@@ -229,13 +256,17 @@
throws Throwable {
Thread.sleep(CLEAR_CHIP_MS);
// Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
verifyDetectedResult(
performAndGetDetectionResult(
- Utils.HOTWORD_DETECTION_SERVICE_EXTERNAL_SOURCE_ONDETECT_TEST),
+ mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_EXTERNAL_SOURCE_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
MainHotwordDetectionService.DETECTED_RESULT);
verifyMicrophoneChip(true);
}
@@ -246,12 +277,17 @@
throws Throwable {
Thread.sleep(CLEAR_CHIP_MS);
// Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
verifyDetectedResult(
- performAndGetDetectionResult(Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST),
+ performAndGetDetectionResult(
+ mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
MainHotwordDetectionService.DETECTED_RESULT);
verifyMicrophoneChip(true);
}
@@ -261,17 +297,23 @@
public void testHotwordDetectionService_onStopDetection()
throws Throwable {
// Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
// The HotwordDetectionService can't report any result after recognition is stopped. So
// restart it after stopping; then the service can report a special result.
- perform(Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST);
- perform(Utils.HOTWORD_DETECTION_SERVICE_CALL_STOP_RECOGNITION);
+ perform(mActivityTestRule, Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+ perform(mActivityTestRule, Utils.HOTWORD_DETECTION_SERVICE_CALL_STOP_RECOGNITION,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
EventPayloadParcelable result =
(EventPayloadParcelable) performAndGetDetectionResult(
- Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST);
+ mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
verifyDetectedResult(
result, MainHotwordDetectionService.DETECTED_RESULT_AFTER_STOP_DETECTION);
@@ -281,9 +323,11 @@
@RequiresDevice
public void testHotwordDetectionService_concurrentCapture() throws Throwable {
// Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
SystemUtil.runWithShellPermissionIdentity(() -> {
AudioRecord record =
@@ -305,7 +349,9 @@
record.startRecording();
verifyDetectedResult(
performAndGetDetectionResult(
- Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST),
+ mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
MainHotwordDetectionService.DETECTED_RESULT);
// TODO: Test that it still works after restarting the process or killing audio
// server.
@@ -319,14 +365,18 @@
public void testHotwordDetectionService_processDied_triggerOnError()
throws Throwable {
// Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
// Use AlwaysOnHotwordDetector to test process died of HotwordDetectionService
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_PROCESS_DIED_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_PROCESS_DIED_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_GET_ERROR);
+ Utils.HOTWORD_DETECTION_SERVICE_GET_ERROR,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
// ActivityManager will schedule a timer to restart the HotwordDetectionService due to
// we crash the service in this test case. It may impact the other test cases when
@@ -339,68 +389,47 @@
@Test
public void testHotwordDetectionService_destroyDspDetector_activeDetectorRemoved() {
// Create AlwaysOnHotwordDetector
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
// Can no longer use the detector because it is in an invalid state
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
}
@Test
public void testHotwordDetectionService_destroySoftwareDetector_activeDetectorRemoved() {
// Create SoftwareHotwordDetector
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS);
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
// Can no longer use the detector because it is in an invalid state
- testHotwordDetection(Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
- Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION);
- }
-
- private void testHotwordDetection(int testType, String expectedIntent, int expectedResult) {
- final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
- expectedIntent);
- receiver.register();
- perform(testType);
- final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
- receiver.unregisterQuietly();
-
- assertThat(intent).isNotNull();
- assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(expectedResult);
- }
-
- @NonNull
- private Parcelable performAndGetDetectionResult(int testType) {
- final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(mContext,
- Utils.HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT_INTENT);
- receiver.register();
- perform(testType);
- final Intent intent = receiver.awaitForBroadcast(TIMEOUT_MS);
- receiver.unregisterQuietly();
-
- assertThat(intent).isNotNull();
- final Parcelable result = intent.getParcelableExtra(Utils.KEY_TEST_RESULT);
- assertThat(result).isNotNull();
- return result;
- }
-
- private void perform(int testType) {
- mActivityTestRule.getScenario().onActivity(
- activity -> activity.triggerHotwordDetectionServiceTest(
- Utils.HOTWORD_DETECTION_SERVICE_BASIC, testType));
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_ILLEGAL_STATE_EXCEPTION,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
}
// TODO: Implement HotwordDetectedResult#equals to override the Bundle equality check; then
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceProximityTest.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceProximityTest.java
new file mode 100644
index 0000000..a47fb2b
--- /dev/null
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/HotwordDetectionServiceProximityTest.java
@@ -0,0 +1,278 @@
+/*
+ * 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.cts;
+
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.performAndGetDetectionResult;
+import static android.voiceinteraction.cts.testcore.VoiceInteractionDetectionHelper.testHotwordDetection;
+
+import static com.android.compatibility.common.util.ShellUtils.runShellCommand;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.Instrumentation;
+import android.attentionservice.cts.CtsTestAttentionService;
+import android.os.Parcelable;
+import android.platform.test.annotations.AppModeFull;
+import android.provider.DeviceConfig;
+import android.service.voice.HotwordDetectedResult;
+import android.voiceinteraction.common.Utils;
+import android.voiceinteraction.service.EventPayloadParcelable;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.RequiresDevice;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.compatibility.common.util.ApiTest;
+import com.android.compatibility.common.util.DeviceConfigStateChangerRule;
+import com.android.compatibility.common.util.RequiredServiceRule;
+
+import org.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for using the Attention Service inside VoiceInteractionService using
+ * a basic HotwordDetectionService.
+ */
+@ApiTest(apis = {"android.service.voice.HotwordDetectedResult#getExtras"})
+@RunWith(AndroidJUnit4.class)
+@AppModeFull(reason = "No real use case for instant mode hotword detection service")
+public final class HotwordDetectionServiceProximityTest
+ extends AbstractVoiceInteractionBasicTestCase {
+ private static final String ATTENTION_SERVICE = "attention";
+
+ @ClassRule
+ public static final RequiredServiceRule ATTENTION_SERVICE_RULE =
+ new RequiredServiceRule(ATTENTION_SERVICE);
+
+ @Rule
+ public final DeviceConfigStateChangerRule mEnableAttentionManagerServiceRule =
+ new DeviceConfigStateChangerRule(sInstrumentation.getTargetContext(),
+ DeviceConfig.NAMESPACE_ATTENTION_MANAGER_SERVICE,
+ SERVICE_ENABLED,
+ "true");
+
+ private static final String EXTRA_PROXIMITY =
+ "android.service.voice.extra.PROXIMITY";
+
+ private static Instrumentation sInstrumentation = InstrumentationRegistry.getInstrumentation();
+
+ private static final String SERVICE_ENABLED = "service_enabled";
+ private static final String FAKE_SERVICE_PACKAGE =
+ HotwordDetectionServiceProximityTest.class.getPackage().getName();
+ private static final double PROXIMITY_NEAR_METERS = 2.0;
+ private static final double PROXIMITY_FAR_METERS = 6.0;
+ private static final int PROXIMITY_UNKNOWN = -1;
+ private static final int PROXIMITY_NEAR = 1;
+ private static final int PROXIMITY_FAR = 2;
+ private static final boolean ENABLE_PROXIMITY_RESULT = true;
+
+ @BeforeClass
+ public static void enableAttentionService() throws InterruptedException {
+ CtsTestAttentionService.reset();
+ assertThat(setTestableAttentionService(FAKE_SERVICE_PACKAGE)).isTrue();
+ assertThat(getAttentionServiceComponent()).contains(FAKE_SERVICE_PACKAGE);
+ runShellCommand("cmd attention call checkAttention");
+ }
+
+ @AfterClass
+ public static void clearAttentionService() {
+ runShellCommand("cmd attention clearTestableAttentionService");
+ }
+
+ @Test
+ @RequiresDevice
+ public void testAttentionService_onDetectFromDsp() {
+ // Create AlwaysOnHotwordDetector and wait the HotwordDetectionService ready
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+ // by default, proximity should not be returned.
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ null);
+
+ // when proximity is unknown, proximity should not be returned.
+ CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN);
+
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ null);
+
+ CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS);
+
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ PROXIMITY_NEAR);
+
+ CtsTestAttentionService.respondProximity(PROXIMITY_FAR_METERS);
+
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ PROXIMITY_FAR);
+
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+ }
+
+ @Test
+ @RequiresDevice
+ public void testAttentionService_onDetectFromMic_noUpdates() {
+ // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ null);
+
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+ }
+
+ @Test
+ @RequiresDevice
+ public void testAttentionService_onDetectFromMic_unknownProximity() {
+ // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+ CtsTestAttentionService.respondProximity(PROXIMITY_UNKNOWN);
+
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ null);
+
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+ }
+
+ @Test
+ @RequiresDevice
+ public void testAttentionService_onDetectFromMic_updatedProximity() {
+ // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_FROM_SOFTWARE_TRIGGER_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+ CtsTestAttentionService.respondProximity(PROXIMITY_NEAR_METERS);
+
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_MIC_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ PROXIMITY_NEAR);
+
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_DESTROY_DETECTOR,
+ Utils.HOTWORD_DETECTION_SERVICE_SOFTWARE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+ }
+
+ @Test
+ @RequiresDevice
+ public void testAttentionService_onDetectFromExternalSource_doesNotReceiveProximity() {
+ // Create SoftwareHotwordDetector and wait the HotwordDetectionService ready
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+
+ CtsTestAttentionService.respondProximity(PROXIMITY_FAR);
+
+ verifyProximityBundle(
+ performAndGetDetectionResult(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_EXTERNAL_SOURCE_ONDETECT_TEST,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC),
+ null);
+
+ testHotwordDetection(mActivityTestRule, mContext,
+ Utils.HOTWORD_DETECTION_SERVICE_DSP_DESTROY_DETECTOR,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_RESULT_INTENT,
+ Utils.HOTWORD_DETECTION_SERVICE_TRIGGER_SUCCESS,
+ Utils.HOTWORD_DETECTION_SERVICE_BASIC);
+ }
+
+ // simply check that the proximity values are equal.
+ private void verifyProximityBundle(Parcelable result, Integer expected) {
+ assertThat(result).isInstanceOf(EventPayloadParcelable.class);
+ HotwordDetectedResult hotwordDetectedResult =
+ ((EventPayloadParcelable) result).mHotwordDetectedResult;
+ assertThat(hotwordDetectedResult).isNotNull();
+ if (expected == null || !ENABLE_PROXIMITY_RESULT) {
+ assertThat(hotwordDetectedResult.getExtras().containsKey(EXTRA_PROXIMITY)).isFalse();
+ } else {
+ assertThat(hotwordDetectedResult.getExtras().containsKey(EXTRA_PROXIMITY)).isTrue();
+ assertThat(hotwordDetectedResult.getExtras().getInt(EXTRA_PROXIMITY))
+ .isEqualTo(expected);
+ }
+ }
+
+ private static String getAttentionServiceComponent() {
+ return runShellCommand("cmd attention getAttentionServiceComponent");
+ }
+
+ private static boolean setTestableAttentionService(String service) {
+ return runShellCommand("cmd attention setTestableAttentionService " + service)
+ .equals("true");
+ }
+
+ @Override
+ public String getVoiceInteractionService() {
+ return "android.voiceinteraction.cts/"
+ + "android.voiceinteraction.service.BasicVoiceInteractionService";
+ }
+}
+
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
index 70200b8..2792389 100644
--- a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/TestVoiceInteractionServiceActivity.java
@@ -21,13 +21,11 @@
import android.content.Intent;
import android.util.Log;
import android.voiceinteraction.common.Utils;
-import android.voiceinteraction.service.BasicVoiceInteractionService;
-import android.voiceinteraction.service.MainInteractionService;
public class TestVoiceInteractionServiceActivity extends Activity {
static final String TAG = "TestVoiceInteractionServiceActivity";
- void triggerHotwordDetectionServiceTest(int serviceType, int testEvent) {
+ public void triggerHotwordDetectionServiceTest(int serviceType, int testEvent) {
Intent serviceIntent = new Intent();
if (serviceType == Utils.HOTWORD_DETECTION_SERVICE_NONE) {
serviceIntent.setComponent(new ComponentName(this,
diff --git a/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/testcore/VoiceInteractionDetectionHelper.java b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/testcore/VoiceInteractionDetectionHelper.java
new file mode 100644
index 0000000..9e7f573
--- /dev/null
+++ b/tests/tests/voiceinteraction/src/android/voiceinteraction/cts/testcore/VoiceInteractionDetectionHelper.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.voiceinteraction.cts.testcore;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Parcelable;
+import android.voiceinteraction.common.Utils;
+import android.voiceinteraction.cts.TestVoiceInteractionServiceActivity;
+
+import androidx.annotation.NonNull;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+
+import com.android.compatibility.common.util.BlockingBroadcastReceiver;
+
+/**
+ * A helper class to perform an intent to start the Voice Interaction Service and receive Hotword
+ * Detection Result.
+ */
+public class VoiceInteractionDetectionHelper {
+ private static final int TEST_RESULT_AWAIT_TIMEOUT_MS = 10 * 1000;
+
+ public static void perform(
+ ActivityScenarioRule<TestVoiceInteractionServiceActivity> activityTestRule,
+ int testType, int serviceType) {
+ activityTestRule.getScenario().onActivity(
+ activity -> activity.triggerHotwordDetectionServiceTest(
+ serviceType, testType));
+ }
+
+ public static void testHotwordDetection(
+ ActivityScenarioRule<TestVoiceInteractionServiceActivity> activityTestRule,
+ Context context, int testType, String expectedIntent, int expectedResult,
+ int serviceType) {
+ final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context,
+ expectedIntent);
+ receiver.register();
+ perform(activityTestRule, testType, serviceType);
+ final Intent intent = receiver.awaitForBroadcast(TEST_RESULT_AWAIT_TIMEOUT_MS);
+ receiver.unregisterQuietly();
+
+ assertThat(intent).isNotNull();
+ assertThat(intent.getIntExtra(Utils.KEY_TEST_RESULT, -1)).isEqualTo(expectedResult);
+ }
+
+ @NonNull
+ public static Parcelable performAndGetDetectionResult(
+ ActivityScenarioRule<TestVoiceInteractionServiceActivity> activityTestRule,
+ Context context, int testType, int serviceType) {
+ final BlockingBroadcastReceiver receiver = new BlockingBroadcastReceiver(context,
+ Utils.HOTWORD_DETECTION_SERVICE_ONDETECT_RESULT_INTENT);
+ receiver.register();
+ perform(activityTestRule, testType, serviceType);
+ final Intent intent = receiver.awaitForBroadcast(TEST_RESULT_AWAIT_TIMEOUT_MS);
+ receiver.unregisterQuietly();
+
+ assertThat(intent).isNotNull();
+ final Parcelable result = intent.getParcelableExtra(Utils.KEY_TEST_RESULT);
+ assertThat(result).isNotNull();
+ return result;
+ }
+}
diff --git a/tests/tests/widget/AndroidManifest.xml b/tests/tests/widget/AndroidManifest.xml
index 0d562db..5f41171 100644
--- a/tests/tests/widget/AndroidManifest.xml
+++ b/tests/tests/widget/AndroidManifest.xml
@@ -42,6 +42,15 @@
</intent-filter>
</activity>
+ <activity android:name="android.widget.cts.EditTextCursorCtsActivity"
+ android:label="Empty test activity"
+ android:launchMode="singleTop"
+ android:exported="true">
+ <intent-filter>
+ <category android:name="android.intent.category.FRAMEWORK_INSTRUMENTATION_TEST" />
+ </intent-filter>
+ </activity>
+
<activity android:name="android.widget.cts.AbsoluteLayoutCtsActivity"
android:label="AbsoluteLayoutCtsActivity"
android:exported="true">
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextCursorCtsActivity.java b/tests/tests/widget/src/android/widget/cts/EditTextCursorCtsActivity.java
new file mode 100644
index 0000000..a393c58
--- /dev/null
+++ b/tests/tests/widget/src/android/widget/cts/EditTextCursorCtsActivity.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.widget.cts;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.widget.EditText;
+
+
+public class EditTextCursorCtsActivity extends Activity {
+ /**
+ * Called when the activity is first created.
+ */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setContentView(R.layout.edittext_layout);
+
+ EditText et = findViewById(R.id.edittext_simple1);
+ et.setText("test for blinking cursor");
+ }
+}
diff --git a/tests/tests/widget/src/android/widget/cts/EditTextTest.java b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
index 2370562..ca3d7db 100755
--- a/tests/tests/widget/src/android/widget/cts/EditTextTest.java
+++ b/tests/tests/widget/src/android/widget/cts/EditTextTest.java
@@ -19,6 +19,7 @@
import static com.android.cts.mockime.ImeEventStreamTestUtils.expectEvent;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
@@ -27,12 +28,14 @@
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
+import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Point;
import android.os.SystemClock;
import android.support.test.uiautomator.By;
import android.support.test.uiautomator.UiDevice;
import android.support.test.uiautomator.UiObject2;
+import android.support.test.uiautomator.Until;
import android.text.Editable;
import android.text.InputFilter;
import android.text.InputType;
@@ -51,6 +54,7 @@
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
+import android.widget.Editor;
import android.widget.TextView;
import android.widget.TextView.BufferType;
@@ -89,6 +93,8 @@
@Rule
public ActivityTestRule<EditTextCtsActivity> mActivityRule =
new ActivityTestRule<>(EditTextCtsActivity.class);
+ public ActivityTestRule<EditTextCursorCtsActivity> mEmptyActivityRule =
+ new ActivityTestRule<>(EditTextCursorCtsActivity.class, false, false);
@Before
public void setup() {
@@ -760,4 +766,67 @@
object.click();
SystemClock.sleep(ViewConfiguration.getDoubleTapTimeout() + 50);
}
+
+ @Test
+ public void testCursorNotBlinkingOnNewActivity_WithoutFocus() {
+ Activity testActivity = mEmptyActivityRule.launchActivity(null);
+ EditText et = testActivity.findViewById(R.id.edittext_simple1);
+ Editor editor = et.getEditorForTesting();
+ boolean cursorBlinking = editor.isBlinking();
+ assertFalse(cursorBlinking);
+ }
+
+ @Test
+ public void testCursorBlinkingOnNewActivity_WithFocus() {
+ Activity testActivity = mEmptyActivityRule.launchActivity(null);
+ EditText et = testActivity.findViewById(R.id.edittext_simple1);
+ Editor editor = et.getEditorForTesting();
+
+ mInstrumentation.runOnMainSync(() -> {
+ et.requestFocus();
+ });
+
+ boolean cursorBlinking = editor.isBlinking();
+ assertTrue(cursorBlinking);
+ }
+
+ @Test
+ public void testSuspendAndResumeBlinkingCursor() {
+ Activity testActivity = mEmptyActivityRule.launchActivity(null);
+ final EditText et = testActivity.findViewById(R.id.edittext_simple1);
+ Editor editor = et.getEditorForTesting();
+
+ mInstrumentation.runOnMainSync(() -> {
+ et.requestFocus();
+ });
+
+ UiDevice device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation());
+
+ boolean cursorBlinking = editor.isBlinking();
+ assertTrue(cursorBlinking);
+
+ // Send activity to the background.
+ device.pressHome();
+ device.waitForIdle();
+
+ cursorBlinking = editor.isBlinking();
+ assertFalse(cursorBlinking);
+
+ // Bring the activity back into the foreground
+ Intent resumeActivity = new Intent(mInstrumentation.getContext(),
+ EditTextCursorCtsActivity.class);
+ resumeActivity.addFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
+ mActivity.startActivity(resumeActivity);
+
+ // Check if the activity is in the foreground.
+ device.wait(Until.findObject(By.text("test for blinking cursor")), 2000);
+
+ mInstrumentation.runOnMainSync(() -> {
+ et.requestFocus();
+ });
+
+ cursorBlinking = editor.isBlinking();
+ assertTrue(cursorBlinking);
+ }
+
}
diff --git a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
index ac9e140..f421d31 100644
--- a/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
+++ b/tests/tests/widget/src/android/widget/cts/RemoteViewsTest.java
@@ -628,6 +628,8 @@
mRemoteViews.setBitmap(R.id.remoteView_absolute, "setImageBitmap", bitmap);
assertThrowsOnReapply(ActionException.class);
+
+ assertEquals(bitmap.getAllocationByteCount(), mRemoteViews.estimateMemoryUsage());
}
@Test
diff --git a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
index c0e5624..bbd4378 100644
--- a/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
+++ b/tests/tests/wifi/src/android/net/wifi/cts/ConcurrencyTest.java
@@ -292,7 +292,7 @@
state = waitForNextNetworkState();
}
if (ApiLevelUtil.isAtLeast(Build.VERSION_CODES.TIRAMISU)
- && state == NetworkInfo.DetailedState.CONNECTING) {
+ && state == NetworkInfo.DetailedState.CONNECTING) {
state = waitForNextNetworkState();
}
return state == NetworkInfo.DetailedState.CONNECTED;
diff --git a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
index 1937288..4632d03 100644
--- a/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
+++ b/tests/translation/src/android/translation/cts/UiTranslationManagerTest.java
@@ -80,6 +80,7 @@
import androidx.test.filters.FlakyTest;
import androidx.test.runner.AndroidJUnit4;
+import com.android.compatibility.common.util.ApiTest;
import com.android.compatibility.common.util.BlockingBroadcastReceiver;
import com.android.compatibility.common.util.PollingCheck;
import com.android.compatibility.common.util.RequiredServiceRule;
@@ -442,6 +443,38 @@
Mockito.verify(mockCallback, Mockito.times(1)).onHideTranslation(any());
}
+
+ @Test
+ @ApiTest(apis = {"android.view.translation.UiTranslationManagerTest#startUiTranslation",
+ "android.view.View#setViewTranslationCallback"})
+ public void testUiTranslation_CustomViewTranslationCallback_DetachAndReattach()
+ throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ // Set ViewTranslationCallback
+ ViewTranslationCallback mockCallback = Mockito.mock(ViewTranslationCallback.class);
+ mTextView.setViewTranslationCallback(mockCallback);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ mActivityScenario.onActivity(activity -> {
+ // Toggling the visibility will cause detach and reattach. This test ensures the
+ // custom callback is not cleared when this happens.
+ mTextView.setVisibility(View.INVISIBLE);
+ mTextView.setVisibility(View.VISIBLE);
+ });
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+ ArgumentCaptor<View> viewArgumentCaptor = ArgumentCaptor.forClass(View.class);
+ Mockito.verify(mockCallback, Mockito.times(1)).onShowTranslation(
+ viewArgumentCaptor.capture());
+ TextView capturedView = (TextView) viewArgumentCaptor.getValue();
+ assertThat(capturedView.getAutofillId()).isEqualTo(mTextView.getAutofillId());
+ }
+
@Test
@FlakyTest(bugId = 192418800)
public void testUiTranslation_ViewTranslationCallback_paddingText() throws Throwable {
@@ -667,7 +700,50 @@
} finally {
manager.unregisterUiTranslationStateCallback(mockCallback);
}
- // TODO(b/191417938): add a test to verify startUiTranslation + Activity destroyed.
+ }
+
+ @Test
+ public void testActivityDestroyedWithoutFinishTranslation() throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ UiTranslationManager manager =
+ sContext.getSystemService(UiTranslationManager.class);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ // Register callback
+ final Executor executor = Executors.newSingleThreadExecutor();
+ UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+ manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+ try {
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onStarted(any(ULocale.class), any(ULocale.class), any(String.class));
+
+ pauseUiTranslation(contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1)).onPaused(any(String.class));
+
+ resumeUiTranslation(contentCaptureContext);
+
+ Mockito.verify(mockCallback, Mockito.times(1))
+ .onResumed(any(ULocale.class), any(ULocale.class), any(String.class));
+
+ // Make sure onFinished will still be called if Activity is destroyed, even without a
+ // finishTranslation call.
+ mActivityScenario.moveToState(Lifecycle.State.DESTROYED);
+ mActivityScenario = null;
+ SystemClock.sleep(UI_WAIT_TIMEOUT);
+ Mockito.verify(mockCallback, Mockito.times(1)).onFinished(any(String.class));
+ } finally {
+ manager.unregisterUiTranslationStateCallback(mockCallback);
+ }
}
@Test
@@ -868,6 +944,45 @@
}
@Test
+ public void testCallbackRegisteredAfterActivityDestroyedWithoutFinishTranslation()
+ throws Throwable {
+ final Pair<List<AutofillId>, ContentCaptureContext> result =
+ enableServicesAndStartActivityForTranslation();
+
+ final List<AutofillId> views = result.first;
+ final ContentCaptureContext contentCaptureContext = result.second;
+
+ UiTranslationManager manager =
+ sContext.getSystemService(UiTranslationManager.class);
+ // Set response
+ sTranslationReplier.addResponse(createViewsTranslationResponse(views, "success"));
+
+ startUiTranslation(/* shouldPadContent */ false, views, contentCaptureContext);
+
+ pauseUiTranslation(contentCaptureContext);
+
+ resumeUiTranslation(contentCaptureContext);
+
+ // Note: No finishTranslation call; Activity is destroyed.
+ mActivityScenario.moveToState(Lifecycle.State.DESTROYED);
+ mActivityScenario = null;
+ SystemClock.sleep(UI_WAIT_TIMEOUT);
+
+ // Register callback
+ final Executor executor = Executors.newSingleThreadExecutor();
+ UiTranslationStateCallback mockCallback = Mockito.mock(UiTranslationStateCallback.class);
+ manager.registerUiTranslationStateCallback(executor, mockCallback);
+
+ try {
+ // Callback should receive no events.
+ SystemClock.sleep(UI_WAIT_TIMEOUT);
+ Mockito.verifyZeroInteractions(mockCallback);
+ } finally {
+ manager.unregisterUiTranslationStateCallback(mockCallback);
+ }
+ }
+
+ @Test
public void testCallbackCalledOnceAfterDuplicateCalls() throws Throwable {
final Pair<List<AutofillId>, ContentCaptureContext> result =
enableServicesAndStartActivityForTranslation();
diff --git a/tools/cts-tradefed/res/config/cts-slim-stable.xml b/tools/cts-tradefed/res/config/cts-slim-stable.xml
new file mode 100644
index 0000000..d36a7b6
--- /dev/null
+++ b/tools/cts-tradefed/res/config/cts-slim-stable.xml
@@ -0,0 +1,236 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<configuration
+ description="Runs stable set of CTS tests for virtual device slim image">
+
+ <include name="cts-virtual-device-stable"/>
+
+ <option name="plan" value="cts-slim-stable"/>
+
+ <!-- These core test suites are inexplicably not included in cts-virtual-device-stable, so add them here -->
+ <option name="compatibility:include-filter" value="CtsAppTestCases"/>
+ <option name="compatibility:include-filter" value="CtsContentTestCases"/>
+ <option name="compatibility:include-filter" value="CtsDisplayTestCases"/>
+ <option name="compatibility:include-filter" value="CtsGraphicsTestCases"/>
+ <option name="compatibility:include-filter" value="CtsOsTestCases"/>
+ <option name="compatibility:include-filter" value="CtsUtilTestCases"/>
+ <option name="compatibility:include-filter" value="CtsViewTestCases"/>
+ <option name="compatibility:include-filter" value="CtsWidgetTestCases"/>
+
+ <!-- flaky tests -->
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.ActivityManagerFgsBgStartTest#testOverlappedTempAllowList"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testBackgroundCheckActivityService"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testFgsSticky1"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testCantSaveStateLaunchAndSwitch" />
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testFgsSticky1"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.AlertDialog_BuilderTest"/>
+ <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationUriPermissionsGranted"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.cts.ContentProviderClientTest#testBulkInsertTimeout"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.cts.ContentProviderClientTest#testUncanonicalizeTimeout" />
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.cts.ContentResolverSyncTestCase#testCallMultipleAccounts" />
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.cts.ContentQueryMapTest#testSetKeepUpdated" />
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.cts.AnimatorLeakTest#testPauseResume"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsLocationFineTestCases android.location.cts.fine.LocationManagerFineTest#testRegisterGnssMeasurementsCallback" />
+ <option name="compatibility:exclude-filter" value="CtsTransitionTestCases"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsVoiceInteractionTestCases android.voiceinteraction.cts.DirectActionsTest" />
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ZoomControlsTest#testHasFocus" />
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.TextViewTest#testOnBackInvokedCallback"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.TextViewTest#testUndo_imeInsertAndDeleteLatin"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.TimePickerTest#testConstructorNullContext2"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToggleButtonTest#testAttributesFromLayout"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToggleButtonTest#testSetChecked"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testNavigationConfiguration"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testMenuOverflowShowHide"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testTitleAndSubtitleContent"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testCurrentContentInsetsRtl"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToolbarTest#testMenuContent"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.TwoLineListItemTest#testConstructorWithNullContext2"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.TwoLineListItemTest#testConstructor"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ViewAnimatorTest#testAccessDisplayedChild"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ViewAnimatorTest#testGetBaseline"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ZoomButtonTest#testConstructorWithNullContext2"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ZoomControlsTest#testSetZoomSpeed"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.inline.InlineContentViewTest#testSetSurfaceControlCallback"/>
+<option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ViewFlipperTest#testConstructorNullContext"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ViewFlipperTest#testConstructor"/>
+
+ <!-- also fails on sdk_phone -->
+ <!-- causes bluetooth crash b/254094190 -->
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.ActivityManagerProcessStateTest#testFgsSticky3"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.SystemFeaturesTest#testLocationFeatures" />
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.SystemFeaturesTest#testCameraFeatures" />
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.SystemFeaturesTest#testSensorFeatures" />
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.pm.cts.InstallSessionParamsUnitTest#checkSessionParams[11]" />
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.pm.cts.ChecksumsTest#testFixedFSVerityDefaultChecksumsIncremental"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.pm.cts.ChecksumsTest#testFixedFSVerityDefaultChecksums"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.cts.VulkanFeaturesTest#testVulkanHardwareFeatures"/>
+
+ <!-- documentsui dependent tests -->
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.DownloadManagerTest#testDownload_onMediaStoreDownloadsDeleted"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.StrictModeTest#testVmPenaltyListener" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.StrictModeTest#testContentUriWithoutPermission" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.StrictModeTest#testFileUriExposure" />
+
+ <!-- ime dependent tests -->
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.ActivityKeyboardShortcutsTest#testRequestShowKeyboardShortcuts"/>
+ <option name="compatibility:exclude-filter" value="CtsInputMethodTestCases"/>
+
+ <!-- systemui dependent tests -->
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_setSuppressBubble"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_setSuppressBubble_dismissLocusActivity"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_checkActivityFlagsDocumentLaunchMode"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_checkIsBubbled_pendingIntent"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsAppTestCases android.app.cts.NotificationManagerTest#testNotificationManagerBubble_checkIsBubbled_shortcut"/>
+ <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationManagerBubbleTest"/>
+ <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationTemplateTest#testPromoteBigPicture_withLargeIcon"/> |
+ <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.NotificationTemplateTest#testPromoteBigPicture_withBigLargeIcon"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.AppHibernationIntegrationTest#testUnusedApp_getsForceStopped" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.AutoRevokeTest#testInstallGrants_notRevokedImmediately" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.AutoRevokeTest#testAutoRevoke_userAllowlisting" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.AutoRevokeTest#testUnusedApp_getsPermissionRevoked" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.AutoRevokeTest#testUnusedApp_uninstallApp" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.ViewTest#testGetWindowVisibleDisplayFrame" />
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.ToastTest" />
+ <option name="compatibility:exclude-filter" value="CtsSliceTestCases android.slice.cts.SlicePermissionsTest#testPermissionIntent"/>
+
+ <!-- other apps dependent tests -->
+ <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityManagerTest#testHomeVisibilityListener"/>
+ <option name="compatibility:exclude-filter" value="CtsAppTestCases android.app.cts.ActivityManagerFgsBgStartTest#testVisibleActivityGracePeriod"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsContentTestCases android.content.cts.AvailableIntentsTest"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.cts.SystemPaletteTest#testThemeStyles"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.AppHibernationIntegrationTest#testAppInfo_RemovePermissionsAndFreeUpSpaceToggleExists" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.CompanionDeviceManagerTest#testProfiles" />
+ <option name="compatibility:exclude-filter"
+ value="CtsOsTestCases android.os.cts.CompanionDeviceManagerTest#testRequestNotifications" />
+ <option name="compatibility:exclude-filter"
+ value="CtsLocationFineTestCases android.location.cts.fine.ScanningSettingsTest#testWifiScanningSettings" />
+ <option name="compatibility:exclude-filter"
+ value="CtsLocationFineTestCases android.location.cts.fine.ScanningSettingsTest#testBleScanningSettings" />
+ <option name="compatibility:exclude-filter"
+ value="CtsVoiceInteractionTestCases android.voiceinteraction.cts.HotwordDetectionServiceBasicTest#testHotwordDetectionService_onDetectFromExternalSource_success" />
+
+ <!-- hardware renderer dependent tests -->
+ <option name="compatibility:exclude-filter"
+ value="CtsDisplayTestCases android.display.cts.VirtualDisplayTest#testUntrustedSysDecorVirtualDisplay" />
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.cts.BasicVulkanGpuTest" />
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.cts.BitmapTest#testCreateBitmap_Picture_immutable" />
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedImageDrawableTest#testRepeatCounts" />
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedImageDrawableTest#testAddCallbackAfterStart" />
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedImageDrawableTest#testLifeCycle" />
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.cts.HardwareRendererTest#isDrawingEnabled_defaultsTrue"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsNativeHardwareTestCases" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.ASurfaceControlTest" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.ASurfaceControlBackPressureTest" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.FrameMetricsListenerTest" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.PixelCopyTest" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.TextureViewTest#testSamplingWithTransform" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.TextureViewTest#testTransformScale" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.TextureViewTest#testRotateScale" />
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.TextureViewTest#testFirstFrames" />
+ <option name="compatibility:exclude-filter"
+ value="CtsWidgetTestCases android.widget.cts.MagnifierTest" />
+
+ <!-- screenshot reliant tests -->
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedVectorDrawableParameterizedTest#testAnimationOnLayer"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsGraphicsTestCases android.graphics.drawable.cts.AnimatedVectorDrawableParameterizedTest#testInfiniteAVD"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.ViewAnimationMatrixTest#testAnimationMatrixAppliedDuringDrawing"/>
+ <option name="compatibility:exclude-filter"
+ value="CtsViewTestCases android.view.cts.ViewAnimationMatrixTest#testAnimationMatrixClearedWithPassingNull"/>
+ <option name="compatibility:exclude-filter" value="CtsUiRenderingTestCases"/>
+ <option name="compatibility:exclude-filter" value="CtsViewTestCases android.view.cts.TextureViewTest#testCropRect"/>
+
+
+</configuration>