diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java index f45178cd22300..90f5577531320 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcher.java @@ -43,6 +43,8 @@ import com.android.systemui.qs.tiles.UserDetailItemView; import com.android.systemui.statusbar.phone.KeyguardStatusBarView; import com.android.systemui.statusbar.phone.NotificationPanelViewController; +import java.util.ArrayList; + /** * Manages the user switcher on the Keyguard. */ @@ -53,7 +55,7 @@ public class KeyguardUserSwitcher { private final Container mUserSwitcherContainer; private final KeyguardStatusBarView mStatusBarView; - private final Adapter mAdapter; + private final KeyguardUserAdapter mAdapter; private final AppearAnimationUtils mAppearAnimationUtils; private final KeyguardUserSwitcherScrim mBackground; @@ -76,7 +78,7 @@ public class KeyguardUserSwitcher { mStatusBarView = statusBarView; mStatusBarView.setKeyguardUserSwitcher(this); panelViewController.setKeyguardUserSwitcher(this); - mAdapter = new Adapter(context, userSwitcherController, this); + mAdapter = new KeyguardUserAdapter(context, userSwitcherController, this); mAdapter.registerDataSetObserver(mDataSetObserver); mUserSwitcherController = userSwitcherController; mAppearAnimationUtils = new AppearAnimationUtils(context, 400, -0.5f, 0.5f, @@ -259,30 +261,70 @@ public class KeyguardUserSwitcher { } } - public static class Adapter extends UserSwitcherController.BaseUserAdapter implements - View.OnClickListener { + static class KeyguardUserAdapter extends + UserSwitcherController.BaseUserAdapter implements View.OnClickListener { private Context mContext; private KeyguardUserSwitcher mKeyguardUserSwitcher; private View mCurrentUserView; + // List of users where the first entry is always the current user + private ArrayList mUsersOrdered = new ArrayList<>(); - public Adapter(Context context, UserSwitcherController controller, + KeyguardUserAdapter(Context context, UserSwitcherController controller, KeyguardUserSwitcher kgu) { super(controller); mContext = context; mKeyguardUserSwitcher = kgu; } + @Override + public void notifyDataSetChanged() { + refreshUserOrder(); + super.notifyDataSetChanged(); + } + + void refreshUserOrder() { + ArrayList users = super.getUsers(); + mUsersOrdered = new ArrayList<>(users.size()); + for (int i = 0; i < users.size(); i++) { + UserSwitcherController.UserRecord record = users.get(i); + if (record.isCurrent) { + mUsersOrdered.add(0, record); + } else { + mUsersOrdered.add(record); + } + } + } + + @Override + protected ArrayList getUsers() { + return mUsersOrdered; + } + @Override public View getView(int position, View convertView, ViewGroup parent) { UserSwitcherController.UserRecord item = getItem(position); - if (!(convertView instanceof UserDetailItemView) + return createUserDetailItemView(convertView, parent, item); + } + + KeyguardUserDetailItemView convertOrInflate(View convertView, ViewGroup parent) { + if (!(convertView instanceof KeyguardUserDetailItemView) || !(convertView.getTag() instanceof UserSwitcherController.UserRecord)) { convertView = LayoutInflater.from(mContext).inflate( R.layout.keyguard_user_switcher_item, parent, false); - convertView.setOnClickListener(this); } - UserDetailItemView v = (UserDetailItemView) convertView; + return (KeyguardUserDetailItemView) convertView; + } + + UserDetailItemView createUserDetailItemView(View convertView, ViewGroup parent, + UserSwitcherController.UserRecord item) { + KeyguardUserDetailItemView v = convertOrInflate(convertView, parent); + if (!item.isCurrent || item.isGuest) { + v.setOnClickListener(this); + } else { + v.setOnClickListener(null); + v.setClickable(false); + } String name = getName(mContext, item); if (item.picture == null) { diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java index 9a8e26dbdeb01..0552396bab077 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/UserSwitcherController.java @@ -605,19 +605,23 @@ public class UserSwitcherController implements Dumpable { controller.addAdapter(new WeakReference<>(this)); } + protected ArrayList getUsers() { + return mController.getUsers(); + } + public int getUserCount() { boolean secureKeyguardShowing = mKeyguardStateController.isShowing() && mKeyguardStateController.isMethodSecure() && !mKeyguardStateController.canDismissLockScreen(); if (!secureKeyguardShowing) { - return mController.getUsers().size(); + return getUsers().size(); } // The lock screen is secure and showing. Filter out restricted records. - final int N = mController.getUsers().size(); + final int userSize = getUsers().size(); int count = 0; - for (int i = 0; i < N; i++) { - if (mController.getUsers().get(i).isGuest) continue; - if (mController.getUsers().get(i).isRestricted) { + for (int i = 0; i < userSize; i++) { + if (getUsers().get(i).isGuest) continue; + if (getUsers().get(i).isRestricted) { break; } else { count++; @@ -632,13 +636,13 @@ public class UserSwitcherController implements Dumpable { && mKeyguardStateController.isMethodSecure() && !mKeyguardStateController.canDismissLockScreen(); if (!secureKeyguardShowing) { - return mController.getUsers().size(); + return getUsers().size(); } // The lock screen is secure and showing. Filter out restricted records. - final int N = mController.getUsers().size(); + final int userSize = getUsers().size(); int count = 0; - for (int i = 0; i < N; i++) { - if (mController.getUsers().get(i).isRestricted) { + for (int i = 0; i < userSize; i++) { + if (getUsers().get(i).isRestricted) { break; } else { count++; @@ -649,7 +653,7 @@ public class UserSwitcherController implements Dumpable { @Override public UserRecord getItem(int position) { - return mController.getUsers().get(position); + return getUsers().get(position); } @Override diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt new file mode 100644 index 0000000000000..fc1a79105db18 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/KeyguardUserSwitcherAdapterTest.kt @@ -0,0 +1,194 @@ +/* + * 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.systemui.statusbar.policy + +import android.content.Context +import android.content.pm.UserInfo +import android.graphics.Bitmap +import android.testing.AndroidTestingRunner +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.test.filters.SmallTest +import com.android.internal.util.UserIcons +import com.android.systemui.R +import com.android.systemui.SysuiTestCase +import com.android.systemui.qs.tiles.UserDetailItemView +import org.junit.Assert.assertFalse +import org.junit.Assert.assertNotNull +import org.junit.Assert.assertTrue +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.any +import org.mockito.ArgumentMatchers.anyBoolean +import org.mockito.ArgumentMatchers.anyInt +import org.mockito.Mock +import org.mockito.Mockito.`when` +import org.mockito.Mockito.verify +import org.mockito.MockitoAnnotations + +@RunWith(AndroidTestingRunner::class) +@SmallTest +class KeyguardUserSwitcherAdapterTest : SysuiTestCase() { + @Mock + private lateinit var userSwitcherController: UserSwitcherController + @Mock + private lateinit var parent: ViewGroup + @Mock + private lateinit var keyguardUserDetailItemView: KeyguardUserDetailItemView + @Mock + private lateinit var otherView: View + @Mock + private lateinit var inflatedUserDetailItemView: KeyguardUserDetailItemView + @Mock + private lateinit var userInfo: UserInfo + @Mock + private lateinit var layoutInflater: LayoutInflater + @Mock + private lateinit var keyguardUserSwitcher: KeyguardUserSwitcher + + private lateinit var adapter: KeyguardUserSwitcher.KeyguardUserAdapter + private lateinit var picture: Bitmap + + @Before + fun setUp() { + MockitoAnnotations.initMocks(this) + + mContext.addMockSystemService(Context.LAYOUT_INFLATER_SERVICE, layoutInflater) + `when`(layoutInflater.inflate(anyInt(), any(ViewGroup::class.java), anyBoolean())) + .thenReturn(inflatedUserDetailItemView) + adapter = KeyguardUserSwitcher.KeyguardUserAdapter(mContext, userSwitcherController, + keyguardUserSwitcher) + picture = UserIcons.convertToBitmap(mContext.getDrawable(R.drawable.ic_avatar_user)) + } + + /** + * Uses the KeyguardUserAdapter to create a UserDetailItemView where the convertView has an + * incompatible type + */ + private fun createViewFromDifferentType( + isCurrentUser: Boolean, + isGuestUser: Boolean + ): UserDetailItemView? { + val user = createUserRecord(isCurrentUser, isGuestUser) + return adapter.createUserDetailItemView(otherView, parent, user) + } + + /** + * Uses the KeyguardUserAdapter to create a UserDetailItemView where the convertView is an + * instance of KeyguardUserDetailItemView + */ + private fun createViewFromSameType( + isCurrentUser: Boolean, + isGuestUser: Boolean + ): UserDetailItemView? { + val user = createUserRecord(isCurrentUser, isGuestUser) + return adapter.createUserDetailItemView(keyguardUserDetailItemView, parent, user) + } + + @Test + fun shouldSetOnClickListener_notCurrentUser_notGuestUser_oldViewIsSameType() { + val v: UserDetailItemView? = createViewFromSameType( + isCurrentUser = false, isGuestUser = false) + assertNotNull(v) + verify(v)!!.setOnClickListener(adapter) + } + + @Test + fun shouldSetOnClickListener_notCurrentUser_guestUser_oldViewIsSameType() { + val v: UserDetailItemView? = createViewFromSameType( + isCurrentUser = false, isGuestUser = true) + assertNotNull(v) + verify(v)!!.setOnClickListener(adapter) + } + + @Test + fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsSameType() { + val v: UserDetailItemView? = createViewFromSameType( + isCurrentUser = true, isGuestUser = false) + assertNotNull(v) + verify(v)!!.setOnClickListener(null) + } + + @Test + fun shouldSetOnClickListener_currentUser_guestUser_oldViewIsSameType() { + val v: UserDetailItemView? = createViewFromSameType( + isCurrentUser = true, isGuestUser = true) + assertNotNull(v) + verify(v)!!.setOnClickListener(adapter) + } + + @Test + fun shouldSetOnClickListener_notCurrentUser_notGuestUser_oldViewIsDifferentType() { + val v: UserDetailItemView? = createViewFromDifferentType( + isCurrentUser = false, isGuestUser = false) + assertNotNull(v) + verify(v)!!.setOnClickListener(adapter) + } + + @Test + fun shouldSetOnClickListener_notCurrentUser_guestUser_oldViewIsDifferentType() { + val v: UserDetailItemView? = createViewFromDifferentType( + isCurrentUser = false, isGuestUser = true) + assertNotNull(v) + verify(v)!!.setOnClickListener(adapter) + } + + @Test + fun shouldRemoveOnClickListener_currentUser_notGuestUser_oldViewIsDifferentType() { + val v: UserDetailItemView? = createViewFromDifferentType( + isCurrentUser = true, isGuestUser = false) + assertNotNull(v) + verify(v)!!.setOnClickListener(null) + } + + @Test + fun shouldSetOnClickListener_currentUser_guestUser_oldViewIsDifferentType() { + val v: UserDetailItemView? = createViewFromDifferentType( + isCurrentUser = true, isGuestUser = true) + assertNotNull(v) + verify(v)!!.setOnClickListener(adapter) + } + + @Test + fun testCurrentUserIsAlwaysFirst() { + `when`(userSwitcherController.users).thenReturn(arrayListOf( + createUserRecord(isCurrentUser = false, isGuestUser = false), + createUserRecord(isCurrentUser = true, isGuestUser = false), + createUserRecord(isCurrentUser = false, isGuestUser = true), + createUserRecord(isCurrentUser = false, isGuestUser = false) + )) + + adapter.notifyDataSetChanged() + assertTrue("Expected current user to be first in list", adapter.getItem(0).isCurrent) + assertFalse("Did not expect current user in position 1", adapter.getItem(1).isCurrent) + assertFalse("Did not expect current user in position 2", adapter.getItem(2).isCurrent) + assertTrue("Expected guest user to remain in position 2", adapter.getItem(2).isGuest) + assertFalse("Did not expect current user in position 3", adapter.getItem(3).isCurrent) + } + + private fun createUserRecord(isCurrentUser: Boolean, isGuestUser: Boolean) = + UserSwitcherController.UserRecord( + userInfo, + picture, + isGuestUser, + isCurrentUser, + false /* isAddUser */, + false /* isRestricted */, + true /* isSwitchToEnabled */) +}