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
This commit is contained in:
Chaohui Wang
2022-04-12 20:31:10 +08:00
parent fd7153ed39
commit f250493787
9 changed files with 486 additions and 172 deletions

View File

@@ -0,0 +1,32 @@
<?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.
-->
<ripple
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
android:color="@color/settingslib_ripple_color">
<item android:id="@android:id/mask">
<shape android:shape="rectangle">
<solid android:color="?androidprv:attr/colorSurface"/>
<corners android:radius="20dp"/>
</shape>
</item>
<item android:id="@android:id/background">
<shape android:shape="rectangle">
<stroke android:color="?androidprv:attr/colorAccentPrimaryVariant" android:width="1dp"/>
<corners android:radius="20dp"/>
</shape>
</item>
</ripple>

View File

@@ -0,0 +1,29 @@
<?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.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="40dp"
android:paddingBottom="40dp">
<com.android.internal.widget.RecyclerView
android:id="@+id/list"
android:overScrollMode="never"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"/>
</FrameLayout>

View File

@@ -0,0 +1,47 @@
<?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.
-->
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/widget_frame"
android:layout_width="120dp"
android:layout_height="112dp">
<FrameLayout
android:id="@+id/button"
android:layout_width="82dp"
android:layout_height="82dp"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="@drawable/user_select_background">
<ImageView
android:id="@android:id/icon"
android:layout_width="32dp"
android:layout_height="32dp"
android:layout_gravity="center"
android:scaleType="fitCenter"/>
</FrameLayout>
<TextView
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_centerHorizontal="true"
android:labelFor="@android:id/icon"
android:textSize="14sp"
style="@style/TextAppearance.PreferenceTitle.SettingsLib"/>
</RelativeLayout>

View File

@@ -0,0 +1,27 @@
<?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.
-->
<com.android.internal.widget.DialogTitle
xmlns:android="http://schemas.android.com/apk/res/android"
style="?android:attr/windowTitleStyle"
android:id="@android:id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:paddingTop="24dp"
android:ellipsize="end"
android:singleLine="true"
android:textAlignment="center"
android:textSize="24sp"/>

View File

@@ -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<UserHandle> 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

View File

@@ -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<UserDetails> data;
private final Context mContext;
private final ArrayList<UserDetails> mUserDetails;
private final LayoutInflater mInflater;
private final DevicePolicyManager mDevicePolicyManager;
public UserAdapter(Context context, ArrayList<UserDetails> 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<ViewHolder> createRecyclerViewAdapter(
OnClickListener onClickListener) {
return new RecyclerView.Adapter<ViewHolder>() {
@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.
*
* <p> The adapter can be used to populate a spinner that switches between the Settings
* app on the different profiles.
* <p> 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<UserHandle> 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<ViewHolder> createUserRecycleViewAdapter(
Context context, List<UserHandle> 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<UserHandle> userProfiles) {
ArrayList<UserDetails> 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);
}
}

View File

@@ -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<UserInfo> userInfos = userManager.getUsers();
final ArrayList<UserHandle> 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<UserInfo> userInfos = userManager.getUsers();
final ArrayList<UserHandle> 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();
}
}

View File

@@ -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());
}
}

View File

@@ -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<UserHandle> userHandles =
Lists.newArrayList(UserHandle.of(mPersonalUserId), UserHandle.of(WORK_USER_ID));
FrameLayout parent = new FrameLayout(mContext);
RecyclerView.Adapter<UserAdapter.ViewHolder> 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());
}
}