From f25049378751acd8da848a214b846c6f773e80f3 Mon Sep 17 00:00:00 2001 From: Chaohui Wang Date: Tue, 12 Apr 2022 20:31:10 +0800 Subject: [PATCH] Update Personal / work selection UI. Implement the new-look by using AlertDialog's custom title and custom view. Using the RecyclerView so we can display profile horizontally. Bug: 174626616 Test: manual & robolectric Change-Id: I9f5a7685d9217fc62e01799ad73f9b9a3ddbf19a --- res/drawable/user_select_background.xml | 32 +++ res/layout/user_select.xml | 29 +++ res/layout/user_select_item.xml | 47 ++++ res/layout/user_select_title.xml | 27 ++ .../profileselector/ProfileSelectDialog.java | 69 +++-- .../profileselector/UserAdapter.java | 243 ++++++++++-------- ...thServiceSettingsPreferenceController.java | 62 ++--- .../ProfileSelectDialogTest.java | 27 +- .../profileselector/UserAdapterTest.java | 122 +++++++++ 9 files changed, 486 insertions(+), 172 deletions(-) create mode 100644 res/drawable/user_select_background.xml create mode 100644 res/layout/user_select.xml create mode 100644 res/layout/user_select_item.xml create mode 100644 res/layout/user_select_title.xml create mode 100644 tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java diff --git a/res/drawable/user_select_background.xml b/res/drawable/user_select_background.xml new file mode 100644 index 00000000000..777bff95db7 --- /dev/null +++ b/res/drawable/user_select_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + diff --git a/res/layout/user_select.xml b/res/layout/user_select.xml new file mode 100644 index 00000000000..8c8c37a64fe --- /dev/null +++ b/res/layout/user_select.xml @@ -0,0 +1,29 @@ + + + + + + diff --git a/res/layout/user_select_item.xml b/res/layout/user_select_item.xml new file mode 100644 index 00000000000..fa0c91a4ab5 --- /dev/null +++ b/res/layout/user_select_item.xml @@ -0,0 +1,47 @@ + + + + + + + + + + + diff --git a/res/layout/user_select_title.xml b/res/layout/user_select_title.xml new file mode 100644 index 00000000000..e01ca22554f --- /dev/null +++ b/res/layout/user_select_title.xml @@ -0,0 +1,27 @@ + + + diff --git a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java index d3234dd1151..f82694c5841 100644 --- a/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java +++ b/src/com/android/settings/dashboard/profileselector/ProfileSelectDialog.java @@ -20,7 +20,6 @@ import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnDismissListener; import android.content.DialogInterface.OnShowListener; import android.content.Intent; @@ -28,17 +27,27 @@ import android.os.Bundle; import android.os.UserHandle; import android.os.UserManager; import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.FragmentManager; +import com.android.internal.widget.DialogTitle; +import com.android.internal.widget.LinearLayoutManager; +import com.android.internal.widget.RecyclerView; +import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.drawer.Tile; import java.util.List; -public class ProfileSelectDialog extends DialogFragment implements OnClickListener { +/** + * A {@link DialogFragment} that can select one of the different profiles. + */ +public class ProfileSelectDialog extends DialogFragment implements UserAdapter.OnClickListener { private static final String TAG = "ProfileSelectDialog"; private static final String ARG_SELECTED_TILE = "selectedTile"; @@ -53,12 +62,13 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen /** * Display the profile select dialog, adding the fragment to the given FragmentManager. - * @param manager The FragmentManager this fragment will be added to. - * @param tile The tile for this fragment. + * + * @param manager The FragmentManager this fragment will be added to. + * @param tile The tile for this fragment. * @param sourceMetricCategory The source metric category. - * @param onShowListener The listener listens to the dialog showing event. - * @param onDismissListener The listener listens to the dialog dismissing event. - * @param onCancelListener The listener listens to the dialog cancelling event. + * @param onShowListener The listener listens to the dialog showing event. + * @param onDismissListener The listener listens to the dialog dismissing event. + * @param onCancelListener The listener listens to the dialog cancelling event. */ public static void show(FragmentManager manager, Tile tile, int sourceMetricCategory, OnShowListener onShowListener, OnDismissListener onDismissListener, @@ -77,32 +87,53 @@ public class ProfileSelectDialog extends DialogFragment implements OnClickListen @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - mSelectedTile = getArguments().getParcelable(ARG_SELECTED_TILE); - mSourceMetricCategory = getArguments().getInt(ARG_SOURCE_METRIC_CATEGORY); + Bundle arguments = requireArguments(); + mSelectedTile = arguments.getParcelable(ARG_SELECTED_TILE, Tile.class); + mSourceMetricCategory = arguments.getInt(ARG_SOURCE_METRIC_CATEGORY); } + @NonNull @Override public Dialog onCreateDialog(Bundle savedInstanceState) { - final Context context = getActivity(); - final AlertDialog.Builder builder = new AlertDialog.Builder(context); - final UserAdapter adapter = UserAdapter.createUserAdapter(UserManager.get(context), context, - mSelectedTile.userHandle); - builder.setTitle(com.android.settingslib.R.string.choose_profile) - .setAdapter(adapter, this); + return createDialog(getContext(), mSelectedTile.userHandle, this); + } - return builder.create(); + /** + * Creates the profile select dialog. + */ + public static Dialog createDialog(Context context, List userProfiles, + UserAdapter.OnClickListener onClickListener) { + LayoutInflater layoutInflater = context.getSystemService(LayoutInflater.class); + + DialogTitle titleView = + (DialogTitle) layoutInflater.inflate(R.layout.user_select_title, null); + titleView.setText(com.android.settingslib.R.string.choose_profile); + + View contentView = layoutInflater.inflate(R.layout.user_select, null); + + RecyclerView listView = contentView.findViewById(R.id.list); + listView.setAdapter( + UserAdapter.createUserRecycleViewAdapter(context, userProfiles, onClickListener)); + listView.setLayoutManager( + new LinearLayoutManager(context, LinearLayoutManager.HORIZONTAL, false)); + + return new AlertDialog.Builder(context) + .setCustomTitle(titleView) + .setView(contentView) + .create(); } @Override - public void onClick(DialogInterface dialog, int which) { - final UserHandle user = mSelectedTile.userHandle.get(which); + public void onClick(int position) { + final UserHandle user = mSelectedTile.userHandle.get(position); // Show menu on top level items. final Intent intent = new Intent(mSelectedTile.getIntent()); FeatureFactory.getFactory(getContext()).getMetricsFeatureProvider() .logStartedIntentWithProfile(intent, mSourceMetricCategory, - which == 1 /* isWorkProfile */); + position == 1 /* isWorkProfile */); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); getActivity().startActivityAsUser(intent, user); + dismiss(); } @Override diff --git a/src/com/android/settings/dashboard/profileselector/UserAdapter.java b/src/com/android/settings/dashboard/profileselector/UserAdapter.java index ae5351e1390..2573d11ce19 100644 --- a/src/com/android/settings/dashboard/profileselector/UserAdapter.java +++ b/src/com/android/settings/dashboard/profileselector/UserAdapter.java @@ -18,188 +18,156 @@ package com.android.settings.dashboard.profileselector; import static android.app.admin.DevicePolicyResources.Strings.Settings.PERSONAL_CATEGORY_HEADER; import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_CATEGORY_HEADER; -import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL; import android.app.ActivityManager; import android.app.admin.DevicePolicyManager; +import android.app.admin.DevicePolicyResourcesManager; import android.content.Context; import android.content.pm.UserInfo; -import android.database.DataSetObserver; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.os.UserHandle; import android.os.UserManager; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.BaseAdapter; import android.widget.ImageView; -import android.widget.ListAdapter; -import android.widget.SpinnerAdapter; import android.widget.TextView; import com.android.internal.util.UserIcons; +import com.android.internal.widget.RecyclerView; import com.android.settingslib.R; -import com.android.settingslib.drawable.UserIconDrawable; +import com.android.settingslib.Utils; import java.util.ArrayList; import java.util.List; +import java.util.Objects; /** * Adapter for a spinner that shows a list of users. */ -public class UserAdapter implements SpinnerAdapter, ListAdapter { +public class UserAdapter extends BaseAdapter { + /** Holder for user details */ public static class UserDetails { private final UserHandle mUserHandle; - private final String mName; private final Drawable mIcon; + private final String mTitle; public UserDetails(UserHandle userHandle, UserManager um, Context context) { mUserHandle = userHandle; UserInfo userInfo = um.getUserInfo(mUserHandle.getIdentifier()); - Drawable icon; + int tintColor = Utils.getColorAttrDefaultColor(context, + com.android.internal.R.attr.colorAccentPrimaryVariant); if (userInfo.isManagedProfile()) { - mName = context.getSystemService(DevicePolicyManager.class).getResources() - .getString(WORK_PROFILE_USER_LABEL, - () -> context.getString(R.string.managed_user_title)); - icon = context.getPackageManager().getUserBadgeForDensityNoBackground( + mIcon = context.getPackageManager().getUserBadgeForDensityNoBackground( userHandle, /* density= */ 0); + mIcon.setTint(tintColor); } else { - mName = userInfo.name; - final int userId = userInfo.id; - if (um.getUserIcon(userId) != null) { - icon = new BitmapDrawable(context.getResources(), um.getUserIcon(userId)); - } else { - icon = UserIcons.getDefaultUserIcon( - context.getResources(), userId, /* light= */ false); - } + mIcon = UserIcons.getDefaultUserIconInColor(context.getResources(), tintColor); } - this.mIcon = encircle(context, icon); + mTitle = getTitle(context); } - private static Drawable encircle(Context context, Drawable icon) { - return new UserIconDrawable(UserIconDrawable.getDefaultSize(context)) - .setIconDrawable(icon).bake(); + private String getTitle(Context context) { + DevicePolicyManager devicePolicyManager = + Objects.requireNonNull(context.getSystemService(DevicePolicyManager.class)); + DevicePolicyResourcesManager resources = devicePolicyManager.getResources(); + int userHandle = mUserHandle.getIdentifier(); + if (userHandle == UserHandle.USER_CURRENT + || userHandle == ActivityManager.getCurrentUser()) { + return resources.getString(PERSONAL_CATEGORY_HEADER, + () -> context.getString(R.string.category_personal)); + } else { + return resources.getString(WORK_CATEGORY_HEADER, + () -> context.getString(R.string.category_work)); + } } } - private ArrayList data; - private final Context mContext; + private final ArrayList mUserDetails; private final LayoutInflater mInflater; - private final DevicePolicyManager mDevicePolicyManager; public UserAdapter(Context context, ArrayList users) { if (users == null) { throw new IllegalArgumentException("A list of user details must be provided"); } - mContext = context; - this.data = users; - mInflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - mDevicePolicyManager = context.getSystemService(DevicePolicyManager.class); + mUserDetails = users; + mInflater = context.getSystemService(LayoutInflater.class); } public UserHandle getUserHandle(int position) { - if (position < 0 || position >= data.size()) { + if (position < 0 || position >= mUserDetails.size()) { return null; } - return data.get(position).mUserHandle; - } - - @Override - public View getDropDownView(int position, View convertView, ViewGroup parent) { - final View row = convertView != null ? convertView : createUser(parent); - - UserDetails user = data.get(position); - ((ImageView) row.findViewById(android.R.id.icon)).setImageDrawable(user.mIcon); - ((TextView) row.findViewById(android.R.id.title)).setText(getTitle(user)); - return row; - } - - private String getTitle(UserDetails user) { - int userHandle = user.mUserHandle.getIdentifier(); - if (userHandle == UserHandle.USER_CURRENT - || userHandle == ActivityManager.getCurrentUser()) { - return mDevicePolicyManager.getResources().getString(PERSONAL_CATEGORY_HEADER, - () -> mContext.getString(R.string.category_personal)); - } else { - return mDevicePolicyManager.getResources().getString(WORK_CATEGORY_HEADER, - () -> mContext.getString(R.string.category_work)); - } - } - - private View createUser(ViewGroup parent) { - return mInflater.inflate(R.layout.user_preference, parent, false); - } - - @Override - public void registerDataSetObserver(DataSetObserver observer) { - // We don't support observers - } - - @Override - public void unregisterDataSetObserver(DataSetObserver observer) { - // We don't support observers - } - - @Override - public int getCount() { - return data.size(); - } - - @Override - public UserAdapter.UserDetails getItem(int position) { - return data.get(position); - } - - @Override - public long getItemId(int position) { - return data.get(position).mUserHandle.getIdentifier(); - } - - @Override - public boolean hasStableIds() { - return false; + return mUserDetails.get(position).mUserHandle; } @Override public View getView(int position, View convertView, ViewGroup parent) { - return getDropDownView(position, convertView, parent); + ViewHolder holder; + if (convertView != null) { + holder = (ViewHolder) convertView.getTag(); + } else { + convertView = mInflater.inflate(R.layout.user_preference, parent, false); + holder = new ViewHolder(convertView); + convertView.setTag(holder); + } + bindViewHolder(holder, position); + return convertView; + } + + private void bindViewHolder(ViewHolder holder, int position) { + UserDetails userDetails = getItem(position); + holder.getIconView().setImageDrawable(userDetails.mIcon); + holder.getTitleView().setText(userDetails.mTitle); } @Override - public int getItemViewType(int position) { - return 0; + public int getCount() { + return mUserDetails.size(); } @Override - public int getViewTypeCount() { - return 1; + public UserAdapter.UserDetails getItem(int position) { + return mUserDetails.get(position); } @Override - public boolean isEmpty() { - return data.isEmpty(); + public long getItemId(int position) { + return mUserDetails.get(position).mUserHandle.getIdentifier(); } - @Override - public boolean areAllItemsEnabled() { - return true; - } + private RecyclerView.Adapter createRecyclerViewAdapter( + OnClickListener onClickListener) { + return new RecyclerView.Adapter() { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View view = LayoutInflater.from(parent.getContext()) + .inflate(R.layout.user_select_item, parent, false); - @Override - public boolean isEnabled(int position) { - return true; + return new ViewHolder(view, onClickListener); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + UserAdapter.this.bindViewHolder(holder, position); + } + + @Override + public int getItemCount() { + return getCount(); + } + }; } /** - * Creates a {@link UserAdapter} if there is more than one - * profile on the device. + * Creates a {@link UserAdapter} if there is more than one profile on the device. * - *

The adapter can be used to populate a spinner that switches between the Settings - * app on the different profiles. + *

The adapter can be used to populate a spinner that switches between the different + * profiles. * - * @return a {@link UserAdapter} or null if there is only one - * profile. + * @return a {@link UserAdapter} or null if there is only one profile. */ public static UserAdapter createUserSpinnerAdapter(UserManager userManager, Context context) { List userProfiles = userManager.getUserProfiles(); @@ -215,13 +183,60 @@ public class UserAdapter implements SpinnerAdapter, ListAdapter { return createUserAdapter(userManager, context, userProfiles); } - public static UserAdapter createUserAdapter( + /** + * Creates a {@link RecyclerView} adapter which be used to populate a {@link RecyclerView} that + * select one of the different profiles. + */ + public static RecyclerView.Adapter createUserRecycleViewAdapter( + Context context, List userProfiles, OnClickListener onClickListener) { + UserManager systemService = context.getSystemService(UserManager.class); + return createUserAdapter(systemService, context, userProfiles) + .createRecyclerViewAdapter(onClickListener); + } + + private static UserAdapter createUserAdapter( UserManager userManager, Context context, List userProfiles) { ArrayList userDetails = new ArrayList<>(userProfiles.size()); - final int count = userProfiles.size(); - for (int i = 0; i < count; i++) { - userDetails.add(new UserDetails(userProfiles.get(i), userManager, context)); + for (UserHandle userProfile : userProfiles) { + userDetails.add(new UserDetails(userProfile, userManager, context)); } return new UserAdapter(context, userDetails); } + + static class ViewHolder extends RecyclerView.ViewHolder { + private final ImageView mIconView; + private final TextView mTitleView; + + private ViewHolder(View view) { + super(view); + mIconView = view.findViewById(android.R.id.icon); + mTitleView = view.findViewById(android.R.id.title); + } + + private ViewHolder(View view, OnClickListener onClickListener) { + this(view); + View button = view.findViewById(R.id.button); + if (button != null) { + button.setOnClickListener(v -> onClickListener.onClick(getAdapterPosition())); + } + } + + private ImageView getIconView() { + return mIconView; + } + + private TextView getTitleView() { + return mTitleView; + } + } + + /** + * Interface definition for a callback to be invoked when a user is clicked. + */ + public interface OnClickListener { + /** + * Called when a user has been clicked. + */ + void onClick(int position); + } } diff --git a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java index c3878d51b62..fcb2347f5b0 100644 --- a/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java +++ b/src/com/android/settings/privacy/EnableContentCaptureWithServiceSettingsPreferenceController.java @@ -19,19 +19,18 @@ package com.android.settings.privacy; import android.annotation.NonNull; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.text.TextUtils; import android.util.Log; -import androidx.appcompat.app.AlertDialog; import androidx.preference.Preference; import com.android.settings.R; import com.android.settings.core.TogglePreferenceController; -import com.android.settings.dashboard.profileselector.UserAdapter; +import com.android.settings.dashboard.profileselector.ProfileSelectDialog; import com.android.settings.utils.ContentCaptureUtils; import java.util.ArrayList; @@ -42,13 +41,9 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController private static final String TAG = "ContentCaptureController"; - private final UserManager mUserManager; - public EnableContentCaptureWithServiceSettingsPreferenceController(@NonNull Context context, @NonNull String key) { super(context, key); - - mUserManager = UserManager.get(context); } @Override @@ -74,11 +69,6 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController Log.w(TAG, "No component name for custom service settings"); preference.setSelectable(false); } - - preference.setOnPreferenceClickListener((pref) -> { - ProfileSelectDialog.show(mContext, pref); - return true; - }); } @Override @@ -93,32 +83,30 @@ public final class EnableContentCaptureWithServiceSettingsPreferenceController return R.string.menu_key_privacy; } - private static final class ProfileSelectDialog { - public static void show(Context context, Preference pref) { - final UserManager userManager = UserManager.get(context); - final List userInfos = userManager.getUsers(); - final ArrayList userHandles = new ArrayList<>(userInfos.size()); - for (UserInfo info: userInfos) { - userHandles.add(info.getUserHandle()); - } - if (userHandles.size() == 1) { - final Intent intent = pref.getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - context.startActivityAsUser(intent, userHandles.get(0)); - } else { - AlertDialog.Builder builder = new AlertDialog.Builder(context); - UserAdapter adapter = UserAdapter.createUserAdapter(userManager, context, - userHandles); - builder.setTitle(com.android.settingslib.R.string.choose_profile) - .setAdapter(adapter, (DialogInterface dialog, int which) -> { - final UserHandle user = userHandles.get(which); - // Show menu on top level items. - final Intent intent = pref.getIntent() - .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); - context.startActivityAsUser(intent, user); - }) - .show(); - } + @Override + public boolean handlePreferenceTreeClick(Preference preference) { + if (!TextUtils.equals(preference.getKey(), getPreferenceKey())) { + return false; } + show(preference); + return true; } + private void show(Preference preference) { + final UserManager userManager = UserManager.get(mContext); + final List userInfos = userManager.getUsers(); + final ArrayList userHandles = new ArrayList<>(userInfos.size()); + for (UserInfo info : userInfos) { + userHandles.add(info.getUserHandle()); + } + final Intent intent = preference.getIntent().addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + if (userHandles.size() == 1) { + mContext.startActivityAsUser(intent, userHandles.get(0)); + return; + } + ProfileSelectDialog.createDialog(mContext, userHandles, (int position) -> { + // Show menu on top level items. + mContext.startActivityAsUser(intent, userHandles.get(position)); + }).show(); + } } diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java index 53cddb1b056..e1cf52b995b 100644 --- a/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/ProfileSelectDialogTest.java @@ -23,21 +23,29 @@ import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; +import android.app.Dialog; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.pm.UserInfo; import android.os.UserHandle; import android.os.UserManager; +import android.widget.TextView; +import androidx.test.core.app.ApplicationProvider; + +import com.android.settings.R; import com.android.settingslib.drawer.ActivityTile; import com.android.settingslib.drawer.CategoryKey; import com.android.settingslib.drawer.Tile; +import com.google.android.collect.Lists; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.Spy; import org.robolectric.RobolectricTestRunner; @RunWith(RobolectricTestRunner.class) @@ -46,8 +54,9 @@ public class ProfileSelectDialogTest { private static final UserHandle NORMAL_USER = new UserHandle(1111); private static final UserHandle REMOVED_USER = new UserHandle(2222); - @Mock - private Context mContext; + @Spy + private Context mContext = ApplicationProvider.getApplicationContext(); + @Mock private UserManager mUserManager; @@ -91,4 +100,18 @@ public class ProfileSelectDialogTest { verify(mUserManager, times(1)).getUserInfo(NORMAL_USER.getIdentifier()); verify(mUserManager, times(2)).getUserInfo(REMOVED_USER.getIdentifier()); } + + @Test + public void createDialog_showsCorrectTitle() { + mContext.setTheme(R.style.Theme_AppCompat); + + Dialog dialog = ProfileSelectDialog.createDialog(mContext, Lists.newArrayList(NORMAL_USER), + (position) -> { + }); + dialog.show(); + + TextView titleView = dialog.findViewById(R.id.topPanel).findViewById(android.R.id.title); + assertThat(titleView.getText().toString()).isEqualTo( + mContext.getText(com.android.settingslib.R.string.choose_profile).toString()); + } } diff --git a/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java b/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java new file mode 100644 index 00000000000..aa7e30af809 --- /dev/null +++ b/tests/robotests/src/com/android/settings/dashboard/profileselector/UserAdapterTest.java @@ -0,0 +1,122 @@ +/* + * 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 com.android.settings.dashboard.profileselector; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.UserInfo; +import android.os.UserHandle; +import android.os.UserManager; +import android.widget.FrameLayout; +import android.widget.TextView; + +import androidx.test.core.app.ApplicationProvider; + +import com.android.internal.widget.RecyclerView; +import com.android.settingslib.R; + +import com.google.android.collect.Lists; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; +import org.robolectric.RobolectricTestRunner; + +import java.util.ArrayList; + +@RunWith(RobolectricTestRunner.class) +public class UserAdapterTest { + @Rule + public MockitoRule mRule = MockitoJUnit.rule(); + + private final int mPersonalUserId = UserHandle.myUserId(); + private static final int WORK_USER_ID = 1; + + @Mock + private UserManager mUserManager; + + @Mock + private UserInfo mPersonalUserInfo; + + @Mock + private UserInfo mWorkUserInfo; + + @Mock + private UserAdapter.OnClickListener mOnClickListener; + + @Spy + private Context mContext = ApplicationProvider.getApplicationContext(); + + @Before + public void setUp() { + when(mContext.getSystemService(UserManager.class)).thenReturn(mUserManager); + when(mUserManager.getUserInfo(mPersonalUserId)).thenReturn(mPersonalUserInfo); + when(mUserManager.getUserInfo(WORK_USER_ID)).thenReturn(mWorkUserInfo); + } + + @Test + public void createUserSpinnerAdapter_singleProfile_returnsNull() { + when(mUserManager.getUserProfiles()).thenReturn( + Lists.newArrayList(UserHandle.of(mPersonalUserId))); + + UserAdapter userSpinnerAdapter = + UserAdapter.createUserSpinnerAdapter(mUserManager, mContext); + + assertThat(userSpinnerAdapter).isNull(); + } + + @Test + public void createUserSpinnerAdapter_twoProfiles_succeed() { + when(mUserManager.getUserProfiles()).thenReturn( + Lists.newArrayList(UserHandle.of(mPersonalUserId), UserHandle.of(WORK_USER_ID))); + + UserAdapter userSpinnerAdapter = + UserAdapter.createUserSpinnerAdapter(mUserManager, mContext); + + assertThat(userSpinnerAdapter.getCount()).isEqualTo(2); + assertThat(userSpinnerAdapter.getUserHandle(0).getIdentifier()).isEqualTo(mPersonalUserId); + assertThat(userSpinnerAdapter.getUserHandle(1).getIdentifier()).isEqualTo(WORK_USER_ID); + } + + @Test + public void createUserRecycleViewAdapter_canBindViewHolderCorrectly() { + ArrayList userHandles = + Lists.newArrayList(UserHandle.of(mPersonalUserId), UserHandle.of(WORK_USER_ID)); + FrameLayout parent = new FrameLayout(mContext); + + RecyclerView.Adapter adapter = + UserAdapter.createUserRecycleViewAdapter(mContext, userHandles, mOnClickListener); + UserAdapter.ViewHolder holder = adapter.createViewHolder(parent, 0); + adapter.bindViewHolder(holder, 0); + holder.itemView.findViewById(R.id.button).performClick(); + + assertThat(adapter.getItemCount()).isEqualTo(2); + TextView textView = holder.itemView.findViewById(android.R.id.title); + assertThat(textView.getText().toString()).isEqualTo("Personal"); + verify(mOnClickListener).onClick(anyInt()); + } +}