diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-04 22:55:16 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2023-12-04 22:55:16 +0000 |
commit | 68eb83501586d5d0fd38d253232342813dee0a1a (patch) | |
tree | df27762550944b33bbb1eabf6e257d4f6d5e3b1d | |
parent | 4f74fd28431ac1bdc37bd1713f4267f182a00325 (diff) | |
parent | 539a419cf93efa512a20c07c2a0e3fbf2bff8bc9 (diff) | |
download | platform_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
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 |