summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-12-04 22:55:16 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2023-12-04 22:55:16 +0000
commit68eb83501586d5d0fd38d253232342813dee0a1a (patch)
treedf27762550944b33bbb1eabf6e257d4f6d5e3b1d
parent4f74fd28431ac1bdc37bd1713f4267f182a00325 (diff)
parent539a419cf93efa512a20c07c2a0e3fbf2bff8bc9 (diff)
downloadplatform_testing-aml_ext_341518010.tar.gz
Snap for 11174750 from 539a419cf93efa512a20c07c2a0e3fbf2bff8bc9 to mainline-extservices-releaseaml_ext_341518010aml_ext_341414010android14-mainline-extservices-release
Change-Id: I96e019aced9be107a4feca2914c51cb35b0d8bfe
-rw-r--r--libraries/car-helpers/multiuser-helper/Android.bp1
-rw-r--r--libraries/car-helpers/multiuser-helper/src/android/platform/helpers/MultiUserHelper.java17
-rw-r--r--libraries/sts-common-util/host-side/src/com/android/sts/common/DumpsysUtils.java184
-rw-r--r--libraries/sts-common-util/host-side/src/com/android/sts/common/UserUtils.java71
-rw-r--r--libraries/sts-common-util/host-side/tests/src/com/android/sts/common/UserUtilsTest.java17
-rw-r--r--tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToExistingSecondaryUser.java6
-rw-r--r--tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewGuest.java6
-rw-r--r--tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewSecondaryUser.java6
8 files changed, 261 insertions, 47 deletions
diff --git a/libraries/car-helpers/multiuser-helper/Android.bp b/libraries/car-helpers/multiuser-helper/Android.bp
index 9fd8040cf..32f4911b9 100644
--- a/libraries/car-helpers/multiuser-helper/Android.bp
+++ b/libraries/car-helpers/multiuser-helper/Android.bp
@@ -25,6 +25,7 @@ java_library_static {
static_libs: [
"android.car-test-stubs",
"androidx.test.runner",
+ "compatibility-device-util-axt",
"ub-uiautomator",
],
}
diff --git a/libraries/car-helpers/multiuser-helper/src/android/platform/helpers/MultiUserHelper.java b/libraries/car-helpers/multiuser-helper/src/android/platform/helpers/MultiUserHelper.java
index bf48642cc..35b75f832 100644
--- a/libraries/car-helpers/multiuser-helper/src/android/platform/helpers/MultiUserHelper.java
+++ b/libraries/car-helpers/multiuser-helper/src/android/platform/helpers/MultiUserHelper.java
@@ -24,12 +24,15 @@ import android.car.user.UserSwitchResult;
import android.car.util.concurrent.AsyncFuture;
import android.content.Context;
import android.content.pm.UserInfo;
+import android.os.Build;
import android.os.SystemClock;
import android.os.UserManager;
import android.support.test.uiautomator.UiDevice;
import androidx.test.InstrumentationRegistry;
+import com.android.compatibility.common.util.SystemUtil;
+
import java.io.IOException;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -42,6 +45,8 @@ public class MultiUserHelper {
/** For testing purpose we allow a wide range of switching time. */
private static final int USER_SWITCH_TIMEOUT_SECOND = 300;
+ private static final String SWITCH_USER_COMMAND = "cmd car_service switch-user ";
+
private static MultiUserHelper sMultiUserHelper;
private CarUserManager mCarUserManager;
private UserManager mUserManager;
@@ -102,6 +107,11 @@ public class MultiUserHelper {
* @param id Id of the user to switch to
*/
public void switchToUserId(int id) throws Exception {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+ switchUserUsingShell(id);
+ return;
+ }
+
final CountDownLatch latch = new CountDownLatch(1);
// A UserLifeCycleListener to wait for user switch event. It is equivalent to
// UserSwitchObserver#onUserSwitchComplete callback
@@ -177,4 +187,11 @@ public class MultiUserHelper {
.findFirst()
.orElse(null);
}
+
+ private void switchUserUsingShell(int userId) throws Exception {
+ String retStr = SystemUtil.runShellCommand(SWITCH_USER_COMMAND + userId);
+ if (!retStr.contains("STATUS_SUCCESSFUL")) {
+ throw new Exception("failed to switch to user: " + userId);
+ }
+ }
}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/DumpsysUtils.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/DumpsysUtils.java
new file mode 100644
index 000000000..e4997d913
--- /dev/null
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/DumpsysUtils.java
@@ -0,0 +1,184 @@
+/*
+ * 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 com.android.sts.common;
+
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.util.CommandResult;
+import com.android.tradefed.util.CommandStatus;
+
+import java.util.Arrays;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+/** Util to parse dumpsys */
+public class DumpsysUtils {
+
+ /**
+ * Fetch dumpsys for the service
+ *
+ * @param device the device {@link ITestDevice} to use.
+ * @param args the arguments {@link String} to filter output
+ * @return the raw output without newline character
+ * @throws Exception
+ */
+ public static String getRawDumpsys(ITestDevice device, String args) throws Exception {
+ CommandResult output = device.executeShellV2Command("dumpsys " + args);
+ if (output.getStatus() != CommandStatus.SUCCESS) {
+ throw new IllegalStateException(
+ String.format(
+ "Failed to get dumpsys for %s details, due to : %s",
+ args, output.toString()));
+ }
+ return output.getStdout();
+ }
+
+ /**
+ * Parse the dumpsys for the service using pattern
+ *
+ * @param device the device {@link ITestDevice} to use.
+ * @param service the service name {@link String} to check status.
+ * @param args the argument {@link Map} to filter output
+ * @param pattern the pattern {@link String} to parse the dumpsys output
+ * @param matcherFlag the flags {@link String} to look while parsing the dumpsys output
+ * @return the required value.
+ * @throws Exception
+ */
+ public static Matcher getParsedDumpsys(
+ ITestDevice device,
+ String service,
+ Map<String, String> args,
+ String pattern,
+ int matcherFlag)
+ throws Exception {
+ String arguments =
+ args == null
+ ? ""
+ : args.entrySet().stream()
+ .map(arg -> String.format("%s %s", arg.getKey(), arg.getValue()))
+ .collect(Collectors.joining(" "));
+ String rawOutput = getRawDumpsys(device, String.format("%s %s", service, arguments));
+ rawOutput =
+ String.join(
+ "",
+ Arrays.stream(rawOutput.split("\n"))
+ .map(e -> e.trim())
+ .toArray(String[]::new));
+ return Pattern.compile(pattern, matcherFlag).matcher(rawOutput);
+ }
+
+ /**
+ * Parse the dumpsys for the service using pattern
+ *
+ * @param device the device {@link ITestDevice} to use.
+ * @param service the service name {@link String} to check status.
+ * @param pattern the pattern {@link String} to parse the dumpsys output
+ * @param matcherFlag the flags {@link String} to look while parsing the dumpsys output
+ * @return the required value.
+ * @throws Exception
+ */
+ public static Matcher getParsedDumpsys(
+ ITestDevice device, String service, String pattern, int matcherFlag) throws Exception {
+ return getParsedDumpsys(device, service, null /* args */, pattern, matcherFlag);
+ }
+
+ /**
+ * Check if output contains mResumed=true for the activity
+ *
+ * @param device the device {@link ITestDevice} to use.
+ * @param activityName the activity name {@link String} to check status.
+ * @return true, if mResumed=true. Else false.
+ * @throws Exception
+ */
+ public static boolean hasActivityResumed(ITestDevice device, String activityName)
+ throws Exception {
+ return getParsedDumpsys(
+ device,
+ "activity" /* service */,
+ Map.of("-a", activityName) /* args */,
+ "mResumed=true" /* pattern */,
+ Pattern.CASE_INSENSITIVE /* matcherFlag */)
+ .find();
+ }
+
+ /**
+ * Check if output contains mVisible=true for the activity
+ *
+ * @param device the device {@link ITestDevice} to use.
+ * @param activityName the activity name {@link String} to check status.
+ * @return true, if mVisible=true. Else false.
+ * @throws Exception
+ */
+ public static boolean isActivityVisible(ITestDevice device, String activityName)
+ throws Exception {
+ return getParsedDumpsys(
+ device,
+ "activity" /* service */,
+ Map.of("-a", activityName) /* args */,
+ "mVisible=true" /* pattern */,
+ Pattern.CASE_INSENSITIVE /* matcherFlag */)
+ .find();
+ }
+
+ /**
+ * Fetch the role-holder-name for the role-name under the userid
+ *
+ * @param device the device {@link ITestDevice} to use.
+ * @param roleName the role name {@link String} to fetch role holder's name.
+ * @param userId the userid {@link int} to fetch role holder's name for the user.
+ * @return holder name, if exits. Else null.
+ * @throws Exception
+ */
+ public static String getRoleHolder(ITestDevice device, String roleName, int userId)
+ throws Exception {
+ // Fetch roles for the user
+ Matcher rolesMatcher =
+ getParsedDumpsys(
+ device,
+ "role" /* service */,
+ String.format("user_id=%d.+?roles=(?<roles>\\[.+?])", userId) /* pattern */,
+ Pattern.CASE_INSENSITIVE);
+ if (!rolesMatcher.find()) {
+ return null;
+ }
+
+ // Fetch the holder's name for the role
+ Matcher holderMatcher =
+ Pattern.compile(
+ String.format("\\{name=%sholders=(?<holders>.+?)}", roleName),
+ Pattern.CASE_INSENSITIVE)
+ .matcher(rolesMatcher.group("roles"));
+ if (!holderMatcher.find()) {
+ return null;
+ }
+ return holderMatcher.group("holders").trim();
+ }
+
+ /**
+ * Fetch the role-holder-name for the role-name
+ *
+ * @param device the device {@link ITestDevice} to use.
+ * @param roleName the role name {@link String} to fetch role holder's name for the current
+ * user.
+ * @return holder name, if exits. Else null.
+ * @throws Exception
+ */
+ public static String getRoleHolder(ITestDevice device, String roleName) throws Exception {
+ return getRoleHolder(device, roleName, device.getCurrentUser());
+ }
+}
diff --git a/libraries/sts-common-util/host-side/src/com/android/sts/common/UserUtils.java b/libraries/sts-common-util/host-side/src/com/android/sts/common/UserUtils.java
index 01d7c964f..7f8d8bc86 100644
--- a/libraries/sts-common-util/host-side/src/com/android/sts/common/UserUtils.java
+++ b/libraries/sts-common-util/host-side/src/com/android/sts/common/UserUtils.java
@@ -20,6 +20,9 @@ import com.android.tradefed.device.ITestDevice;
import com.android.tradefed.util.CommandResult;
import com.android.tradefed.util.CommandStatus;
+import java.util.HashMap;
+import java.util.Map;
+
/** Util to manage secondary user */
public class UserUtils {
@@ -34,28 +37,32 @@ public class UserUtils {
private boolean mIsPreCreateOnly; // User type : --pre-created-only
private boolean mIsRestricted; // User type : --restricted
private boolean mSwitch; // Switch to newly created user
- private boolean
- mDisallowAppInstall; // Disallow app installation in secondary user explicitly
private int mProfileOf; // Userid associated with managed user
private int mTestUserId;
+ private Map<String, String> mUserRestrictions; // Map of user-restrictions for new user
/**
* Create an instance of secondary user.
*
* @param device the device {@link ITestDevice} to use.
- * @throws IllegalArgumentException when {@code device} is null.
+ * @throws Exception
*/
- public SecondaryUser(ITestDevice device) throws IllegalArgumentException {
+ public SecondaryUser(ITestDevice device) throws Exception {
// Device should not be null
if (device == null) {
throw new IllegalArgumentException("Device should not be null");
}
+ // Check if device supports multiple users
+ if (!device.isMultiUserSupported()) {
+ throw new IllegalStateException("Device does not support multiple users");
+ }
+
mDevice = device;
mName = "testUser"; /* Default username */
+ mUserRestrictions = new HashMap<String, String>();
// Set default value for all flags as false
- mDisallowAppInstall = false;
mIsDemo = false;
mIsEphemeral = false;
mIsForTesting = false;
@@ -67,17 +74,6 @@ public class UserUtils {
}
/**
- * Disallow app installation in secondary user explicitly. This requires root UID, system
- * UID, or MANAGE_USERS permission.
- *
- * @return this object for method chaining.
- */
- public SecondaryUser disallowAppInstallation() {
- mDisallowAppInstall = true;
- return this;
- }
-
- /**
* Set the user type as demo.
*
* @return this object for method chaining.
@@ -177,6 +173,17 @@ public class UserUtils {
}
/**
+ * Set user-restrictions on newly created secondary user.
+ * Note: Setting user-restrictions requires enabling root.
+ *
+ * @return this object for method chaining.
+ */
+ public SecondaryUser withUserRestrictions(Map<String, String> restrictions) {
+ mUserRestrictions.putAll(restrictions);
+ return this;
+ }
+
+ /**
* Create a secondary user and if required, switch to it. Returns an Autocloseable that
* removes the secondary user.
*
@@ -233,20 +240,26 @@ public class UserUtils {
String.format("Failed to start the user: %s", mTestUserId));
}
- if (mDisallowAppInstall) {
- // Enable/Disable app installation in secondary user
- final CommandResult userRestrictionCmdOutput =
- mDevice.executeShellV2Command(
+ // Add user-restrictions to newly created secondary user
+ if (!mUserRestrictions.isEmpty()) {
+ if (!mDevice.isAdbRoot()) {
+ throw new IllegalStateException("Setting user-restriction requires root");
+ }
+
+ for (Map.Entry<String, String> entry : mUserRestrictions.entrySet()) {
+ final CommandResult cmdOutput =
+ mDevice.executeShellV2Command(
+ String.format(
+ "pm set-user-restriction --user %d %s %s",
+ mTestUserId, entry.getKey(), entry.getValue()));
+ if (cmdOutput.getStatus() != CommandStatus.SUCCESS) {
+ asSecondaryUser.close();
+ throw new IllegalStateException(
String.format(
- "pm set-user-restriction --user %d no_install_apps %d",
- mTestUserId, 1));
- if (userRestrictionCmdOutput.getStatus() != CommandStatus.SUCCESS) {
- asSecondaryUser.close();
- throw new IllegalStateException(
- String.format(
- "Failed to set user restriction 'no_install_apps' with message:"
- + " %s",
- userRestrictionCmdOutput.toString()));
+ "Failed to set user restriction %s value %s with"
+ + " message %s",
+ entry.getKey(), entry.getValue(), cmdOutput.toString()));
+ }
}
}
diff --git a/libraries/sts-common-util/host-side/tests/src/com/android/sts/common/UserUtilsTest.java b/libraries/sts-common-util/host-side/tests/src/com/android/sts/common/UserUtilsTest.java
index 5da5d301b..aa58f1022 100644
--- a/libraries/sts-common-util/host-side/tests/src/com/android/sts/common/UserUtilsTest.java
+++ b/libraries/sts-common-util/host-side/tests/src/com/android/sts/common/UserUtilsTest.java
@@ -29,6 +29,8 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import java.util.Map;
+
/** Unit tests for {@link UserUtils}. */
@RunWith(DeviceJUnit4ClassRunner.class)
public class UserUtilsTest extends BaseHostJUnit4Test {
@@ -79,4 +81,19 @@ public class UserUtilsTest extends BaseHostJUnit4Test {
"device should still be root after cleanup if started with root",
getDevice().isAdbRoot());
}
+
+ @Test
+ public void testUserUtilsUserRestriction() throws Exception {
+ assertTrue("must test with rootable device", getDevice().enableAdbRoot());
+ try (AutoCloseable user =
+ new UserUtils.SecondaryUser(getDevice())
+ .name(TEST_USER_NAME)
+ .withUserRestrictions(Map.of("test_restriction", "1"))
+ .withUser()) {
+ // Exception is thrown if any error occurs while setting user restriction above
+ }
+ assertTrue(
+ "device should still be root after cleanup if started with root",
+ getDevice().isAdbRoot());
+ }
}
diff --git a/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToExistingSecondaryUser.java b/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToExistingSecondaryUser.java
index 3aa6efd8a..4b6cd79e1 100644
--- a/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToExistingSecondaryUser.java
+++ b/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToExistingSecondaryUser.java
@@ -18,14 +18,12 @@ package android.platform.scenario.multiuser;
import android.app.UiAutomation;
import android.content.pm.UserInfo;
-import android.os.Build;
import android.os.SystemClock;
import android.platform.helpers.MultiUserHelper;
import android.platform.test.scenario.annotation.Scenario;
import androidx.test.platform.app.InstrumentationRegistry;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,10 +49,6 @@ public class SwitchToExistingSecondaryUser {
/*
TODO: Create setup util API
*/
- // Execute these tests only on devices running Android T or higher
- Assume.assumeTrue(
- "Skipping below Android T", Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU);
-
// Execute user manager APIs with elevated permissions
mUiAutomation = getUiAutomation();
// TODO: b/302175460 - update minimum SDK version
diff --git a/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewGuest.java b/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewGuest.java
index 00897dea0..facc1ac6f 100644
--- a/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewGuest.java
+++ b/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewGuest.java
@@ -18,14 +18,12 @@ package android.platform.scenario.multiuser;
import android.app.UiAutomation;
import android.content.pm.UserInfo;
-import android.os.Build;
import android.os.SystemClock;
import android.platform.helpers.MultiUserHelper;
import android.platform.test.scenario.annotation.Scenario;
import androidx.test.platform.app.InstrumentationRegistry;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -51,10 +49,6 @@ public class SwitchToNewGuest {
/*
TODO: Create setup util API
*/
- // Execute these tests only on devices running Android T or higher
- Assume.assumeTrue(
- "Skipping below Android T", Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU);
-
// Execute user manager APIs with elevated permissions
mUiAutomation = getUiAutomation();
// TODO: b/302175460 - update minimum SDK version
diff --git a/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewSecondaryUser.java b/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewSecondaryUser.java
index 4feedeced..7696f6ce6 100644
--- a/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewSecondaryUser.java
+++ b/tests/automotive/health/multiuser/src/android/platform/scenario/multiuser/nonui/SwitchToNewSecondaryUser.java
@@ -18,14 +18,12 @@ package android.platform.scenario.multiuser;
import android.app.UiAutomation;
import android.content.pm.UserInfo;
-import android.os.Build;
import android.os.SystemClock;
import android.platform.helpers.MultiUserHelper;
import android.platform.test.scenario.annotation.Scenario;
import androidx.test.platform.app.InstrumentationRegistry;
-import org.junit.Assume;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -52,10 +50,6 @@ public class SwitchToNewSecondaryUser {
TODO(b/194536236): Refactor setup code in multiuser nonui tests
* and create setup util API instead
*/
- // Execute these tests only on devices running Android T or higher
- Assume.assumeTrue(
- "Skipping below Android T", Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU);
-
// Execute user manager APIs with elevated permissions
mUiAutomation = getUiAutomation();
// TODO: b/302175460 - update minimum SDK version