diff --git a/src/com/android/settings/users/UserSettings.java b/src/com/android/settings/users/UserSettings.java index e95fed784ad..9bebcb96f82 100644 --- a/src/com/android/settings/users/UserSettings.java +++ b/src/com/android/settings/users/UserSettings.java @@ -780,51 +780,62 @@ public class UserSettings extends SettingsPreferenceFragment mUserCreatingDialog = new UserCreatingDialog(getActivity()); mUserCreatingDialog.show(); - ThreadUtils.postOnBackgroundThread(new Runnable() { - @Override - public void run() { - UserInfo user; - String username; + ThreadUtils.postOnBackgroundThread(new AddUserNowImpl(userType, mAddingUserName)); + } - synchronized (mUserLock) { - username = mAddingUserName; - } + @VisibleForTesting + class AddUserNowImpl implements Runnable{ + int mUserType; + String mImplAddUserName; - // Could take a few seconds - if (userType == USER_TYPE_USER) { - user = mUserManager.createUser(username, 0); - } else { - user = mUserManager.createRestrictedProfile(username); - } + AddUserNowImpl(final int userType, final String addUserName) { + mUserType = userType; + mImplAddUserName = addUserName; + } - synchronized (mUserLock) { - if (user == null) { - mAddingUser = false; - mPendingUserIcon = null; - mPendingUserName = null; - ThreadUtils.postOnMainThread(() -> onUserCreationFailed()); - return; - } + @Override + public void run() { + UserInfo user; + String username; - Drawable newUserIcon = mPendingUserIcon; - if (newUserIcon == null) { - newUserIcon = UserIcons.getDefaultUserIcon(getResources(), user.id, false); - } - mUserManager.setUserIcon(user.id, UserIcons.convertToBitmap(newUserIcon)); + synchronized (mUserLock) { + username = mImplAddUserName; + } - if (userType == USER_TYPE_USER) { - mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); - } - - mHandler.sendMessage(mHandler.obtainMessage( - MESSAGE_USER_CREATED, user.id, user.serialNumber)); + // Could take a few seconds + if (mUserType == USER_TYPE_USER) { + user = mUserManager.createUser(username, 0); + } else { + user = mUserManager.createRestrictedProfile(username); + } + synchronized (mUserLock) { + if (user == null) { + mAddingUser = false; mPendingUserIcon = null; mPendingUserName = null; + ThreadUtils.postOnMainThread(() -> onUserCreationFailed()); + return; } + + Drawable newUserIcon = mPendingUserIcon; + if (newUserIcon == null) { + newUserIcon = UserIcons.getDefaultUserIcon(getResources(), user.id, false); + } + mUserManager.setUserIcon(user.id, UserIcons.convertToBitmap(newUserIcon)); + + if (mUserType == USER_TYPE_USER) { + mHandler.sendEmptyMessage(MESSAGE_UPDATE_LIST); + } + + mHandler.sendMessage(mHandler.obtainMessage( + MESSAGE_USER_CREATED, user.id, user.serialNumber)); + + mPendingUserIcon = null; + mPendingUserName = null; } - }); - } + } + }; /** * Erase the current user (guest) and switch to another user. diff --git a/tests/componenttests/src/com/android/settings/testutils/AdbUtils.java b/tests/componenttests/src/com/android/settings/testutils/AdbUtils.java index d2b46f8b203..81f98549e84 100644 --- a/tests/componenttests/src/com/android/settings/testutils/AdbUtils.java +++ b/tests/componenttests/src/com/android/settings/testutils/AdbUtils.java @@ -17,8 +17,6 @@ package com.android.settings.testutils; import android.os.ParcelFileDescriptor; -import android.text.TextUtils; -import android.util.Log; import androidx.test.platform.app.InstrumentationRegistry; @@ -26,36 +24,32 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; -import java.util.Optional; import java.util.stream.Collectors; public class AdbUtils { + public static String getCallerClassName() { + StackTraceElement[] stElements = Thread.currentThread().getStackTrace(); + for (int i = 1; i < stElements.length; i++) { + StackTraceElement ste = stElements[i]; + if (!ste.getClassName().equals(new Object() { + }.getClass().getEnclosingClass().getName()) && ste.getClassName().indexOf( + "java.lang.Thread") != 0) { + return ste.getClassName(); + } + } + return null; + } + public static boolean checkStringInAdbCommandOutput(String logTag, String command, String prefix, String target, int timeoutInMillis) throws Exception { long start = System.nanoTime(); //Sometimes the change do no reflect in adn output immediately, so need a wait and poll here while (System.nanoTime() - start < (timeoutInMillis * 1000000)) { - try (ParcelFileDescriptor.AutoCloseInputStream in = - new ParcelFileDescriptor.AutoCloseInputStream( - InstrumentationRegistry.getInstrumentation() - .getUiAutomation() - .executeShellCommand(command))) { - try (BufferedReader br = - new BufferedReader( - new InputStreamReader(in, StandardCharsets.UTF_8))) { - Optional resultOptional = br.lines().filter(line -> { - Log.d(logTag, line); - return TextUtils.isEmpty(prefix) || line.contains(prefix); - }).findFirst(); - String result = resultOptional.get(); - if (result.contains(target)) { - return true; - } else { - Thread.sleep(100); - } - } - } catch (Exception e) { - throw e; + String result = shell(command); + if (result.contains(prefix) && result.contains(target)) { + return true; + } else { + Thread.sleep(100); } } diff --git a/tests/componenttests/src/com/android/settings/testutils/UiUtils.java b/tests/componenttests/src/com/android/settings/testutils/UiUtils.java index 481a7b2a71e..d58dcedec1e 100644 --- a/tests/componenttests/src/com/android/settings/testutils/UiUtils.java +++ b/tests/componenttests/src/com/android/settings/testutils/UiUtils.java @@ -32,7 +32,7 @@ import java.util.function.Supplier; public class UiUtils { private static final String TAG = "UI_UTILS"; - public static void waitUntilCondition(long timeoutInMillis, Supplier condition) { + public static boolean waitUntilCondition(long timeoutInMillis, Supplier condition) { long start = System.nanoTime(); while (System.nanoTime() - start < (timeoutInMillis * 1000000)) { try { @@ -40,17 +40,14 @@ public class UiUtils { //findViewById when the view hierarchy is still rendering, it sometimes encounter //null views that may exist few milliseconds before, and causes a NPE. if (condition.get()) { - return; + return true; } } catch (NullPointerException e) { e.printStackTrace(); } } - if (System.nanoTime() - start >= (timeoutInMillis * 1000000)) { - Log.w(TAG, "Condition not match and timeout for waiting " + timeoutInMillis + "(ms)."); - } else { - Log.d(TAG, "Condition matched."); - } + Log.w(TAG, "Condition not match and timeout for waiting " + timeoutInMillis + "(ms)."); + return false; } public static boolean waitForActivitiesInStage(long timeoutInMillis, Stage stage) { diff --git a/tests/componenttests/src/com/android/settings/users/UserSettingsComponentTest.java b/tests/componenttests/src/com/android/settings/users/UserSettingsComponentTest.java new file mode 100644 index 00000000000..b0735fb726b --- /dev/null +++ b/tests/componenttests/src/com/android/settings/users/UserSettingsComponentTest.java @@ -0,0 +1,141 @@ +/* + * 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 com.android.settings.users; + +import static com.google.common.truth.Truth.assertThat; + +import android.app.Instrumentation; +import android.content.Intent; +import android.content.pm.UserInfo; +import android.os.UserManager; +import android.util.Log; + +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; +import androidx.test.core.app.ActivityScenario; +import androidx.test.ext.junit.rules.ActivityScenarioRule; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.SmallTest; +import androidx.test.platform.app.InstrumentationRegistry; + +import com.android.settings.Settings; +import com.android.settings.testutils.AdbUtils; +import com.android.settings.testutils.UiUtils; +import com.android.settingslib.utils.ThreadUtils; + +import org.junit.After; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Random; +import java.util.stream.Collectors; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class UserSettingsComponentTest { + public static final int TIMEOUT = 2000; + private static final int USER_TYPE_RESTRICTED_PROFILE = 2; + public final String TAG = this.getClass().getName(); + private final Instrumentation mInstrumentation = InstrumentationRegistry.getInstrumentation(); + private final ArrayList mOriginUserIds = new ArrayList<>(); + private final UserManager mUserManager = + (UserManager) mInstrumentation.getTargetContext().getSystemService("user"); + @Rule + public ActivityScenarioRule + rule = new ActivityScenarioRule<>( + new Intent(android.provider.Settings.ACTION_USER_SETTINGS) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)); + + @Before + public void setUp() { + for (UserInfo info : mUserManager.getUsers()) { + mOriginUserIds.add(info.id); + } + + // Enable multiple user switch. + if (!mUserManager.isUserSwitcherEnabled()) { + android.provider.Settings.Global.putInt( + mInstrumentation.getTargetContext().getContentResolver(), + android.provider.Settings.Global.USER_SWITCHER_ENABLED, 1); + } + } + + @Test + public void test_new_user_on_multiple_setting_page() throws IOException { + String randomUserName = gendrate_random_name(10); + ActivityScenario scenario = rule.getScenario(); + scenario.onActivity(activity -> { + Fragment f = + ((FragmentActivity) activity).getSupportFragmentManager().getFragments().get(0); + UserSettings us = (UserSettings) f; + Log.d(TAG, "Start to add user :" + randomUserName); + ThreadUtils.postOnBackgroundThread( + us.new AddUserNowImpl(USER_TYPE_RESTRICTED_PROFILE, randomUserName)); + }); + + assertThat( + UiUtils.waitUntilCondition(5000, () -> mUserManager.getAliveUsers().stream().filter( + (user) -> user.name.equals( + randomUserName)).findFirst().isPresent())).isTrue(); + } + + @After + public void tearDown() { + int retryNumber = 5; + for (int i = 0; i < retryNumber; ++i) { + int currentUsersCount = mUserManager.getUserCount(); + if (currentUsersCount == mOriginUserIds.size()) { + break; + } else if (i != 0) { + Log.d(TAG, "[tearDown] User not fully removed. Retry #" + (i = 1) + " of total " + + mOriginUserIds.size()); + } + + for (UserInfo info : mUserManager.getUsers()) { + if (mOriginUserIds.contains(info.id)) { + continue; + } + Log.d(TAG, "[tearDown] Clean up user {" + info.id + "}:" + info.name); + try { + AdbUtils.shell("pm remove-user " + info.id); + } catch (Exception e) { + Log.w(TAG, "[tearDown] Error occurs while removing user. " + e.toString()); + } + } + } + } + + private String gendrate_random_name(int length) { + String seed = "abcdefghijklmnopqrstuvwxyABCDEFGHIJKLMNOPQSTUVWXYZ"; + Random r1 = new Random(); + String result = ""; + for (int i = 0; i < length; ++i) { + result = result + seed.charAt(r1.nextInt(seed.length() - 1)); + } + if (mUserManager.getAliveUsers().stream().map(user -> user.name).collect( + Collectors.toList()).contains(result)) { + Log.d(TAG, "Name repeated! add padding 'rpt' in the end of name."); + result += "rpt"; + } + return result; + } + +}