Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 1 | /* |
Brad Stenning | 8d1a51c | 2018-11-20 17:34:16 -0800 | [diff] [blame] | 2 | * Copyright (C) 2018 The Android Open Source Project |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 3 | * |
| 4 | * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | * you may not use this file except in compliance with the License. |
| 6 | * You may obtain a copy of the License at |
| 7 | * |
| 8 | * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | * |
| 10 | * Unless required by applicable law or agreed to in writing, software |
| 11 | * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | * See the License for the specific language governing permissions and |
Brad Stenning | 8d1a51c | 2018-11-20 17:34:16 -0800 | [diff] [blame] | 14 | * limitations under the License. |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 15 | */ |
| 16 | |
Rakesh Iyer | 2790a37 | 2016-01-22 15:33:39 -0800 | [diff] [blame] | 17 | package com.android.systemui.statusbar.car; |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 18 | |
Rakesh Iyer | 33d6ce4 | 2017-05-30 11:02:18 -0700 | [diff] [blame] | 19 | import android.animation.Animator; |
| 20 | import android.animation.AnimatorListenerAdapter; |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 21 | import android.car.trust.CarTrustAgentEnrollmentManager; |
| 22 | import android.car.userlib.CarUserManagerHelper; |
| 23 | import android.content.BroadcastReceiver; |
Aarthi Balachander | d8bf249 | 2018-03-30 11:15:59 -0700 | [diff] [blame] | 24 | import android.content.Context; |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 25 | import android.content.Intent; |
| 26 | import android.content.IntentFilter; |
| 27 | import android.content.pm.UserInfo; |
| 28 | import android.os.UserHandle; |
| 29 | import android.os.UserManager; |
| 30 | import android.util.Log; |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 31 | import android.view.View; |
| 32 | import android.view.ViewStub; |
| 33 | |
Aurimas Liutikas | fd52c14 | 2018-04-17 09:50:46 -0700 | [diff] [blame] | 34 | import androidx.recyclerview.widget.GridLayoutManager; |
jovanak | b803051 | 2018-04-11 15:20:16 -0700 | [diff] [blame] | 35 | |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 36 | import com.android.systemui.R; |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 37 | import com.android.systemui.statusbar.car.CarTrustAgentUnlockDialogHelper.OnHideListener; |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 38 | import com.android.systemui.statusbar.car.UserGridRecyclerView.UserRecord; |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 39 | |
| 40 | /** |
| 41 | * Manages the fullscreen user switcher. |
| 42 | */ |
| 43 | public class FullscreenUserSwitcher { |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 44 | private static final String TAG = FullscreenUserSwitcher.class.getSimpleName(); |
| 45 | // Because user 0 is headless, user count for single user is 2 |
| 46 | private static final int NUMBER_OF_BACKGROUND_USERS = 1; |
Aarthi Balachander | d8bf249 | 2018-03-30 11:15:59 -0700 | [diff] [blame] | 47 | private final UserGridRecyclerView mUserGridView; |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 48 | private final View mParent; |
Rakesh Iyer | 33d6ce4 | 2017-05-30 11:02:18 -0700 | [diff] [blame] | 49 | private final int mShortAnimDuration; |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 50 | private final CarStatusBar mStatusBar; |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 51 | private final Context mContext; |
| 52 | private final UserManager mUserManager; |
| 53 | private final CarTrustAgentEnrollmentManager mEnrollmentManager; |
| 54 | private CarTrustAgentUnlockDialogHelper mUnlockDialogHelper; |
| 55 | private UserGridRecyclerView.UserRecord mSelectedUser; |
| 56 | private CarUserManagerHelper mCarUserManagerHelper; |
| 57 | private final BroadcastReceiver mUserUnlockReceiver = new BroadcastReceiver() { |
| 58 | @Override |
| 59 | public void onReceive(Context context, Intent intent) { |
| 60 | if (Log.isLoggable(TAG, Log.DEBUG)) { |
| 61 | Log.d(TAG, "user 0 is unlocked, SharedPreference is accessible."); |
| 62 | } |
| 63 | showDialogForInitialUser(); |
| 64 | mContext.unregisterReceiver(mUserUnlockReceiver); |
| 65 | } |
| 66 | }; |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 67 | |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 68 | |
| 69 | public FullscreenUserSwitcher(CarStatusBar statusBar, ViewStub containerStub, |
| 70 | CarTrustAgentEnrollmentManager enrollmentManager, Context context) { |
jovanak | 0535abc | 2018-04-10 15:14:50 -0700 | [diff] [blame] | 71 | mStatusBar = statusBar; |
Rakesh Iyer | 33d6ce4 | 2017-05-30 11:02:18 -0700 | [diff] [blame] | 72 | mParent = containerStub.inflate(); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 73 | mEnrollmentManager = enrollmentManager; |
| 74 | mContext = context; |
| 75 | |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 76 | View container = mParent.findViewById(R.id.container); |
| 77 | |
| 78 | // Initialize user grid. |
| 79 | mUserGridView = container.findViewById(R.id.user_grid); |
Aarthi Balachander | d8bf249 | 2018-03-30 11:15:59 -0700 | [diff] [blame] | 80 | GridLayoutManager layoutManager = new GridLayoutManager(context, |
Brad Stenning | 8d1a51c | 2018-11-20 17:34:16 -0800 | [diff] [blame] | 81 | context.getResources().getInteger(R.integer.user_fullscreen_switcher_num_col)); |
Heemin Seog | 0d5e018 | 2019-03-13 13:49:24 -0700 | [diff] [blame] | 82 | mUserGridView.setLayoutManager(layoutManager); |
Aarthi Balachander | d8bf249 | 2018-03-30 11:15:59 -0700 | [diff] [blame] | 83 | mUserGridView.buildAdapter(); |
jovanak | 0535abc | 2018-04-10 15:14:50 -0700 | [diff] [blame] | 84 | mUserGridView.setUserSelectionListener(this::onUserSelected); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 85 | mCarUserManagerHelper = new CarUserManagerHelper(context); |
| 86 | mUnlockDialogHelper = new CarTrustAgentUnlockDialogHelper(mContext); |
| 87 | mUserManager = mContext.getSystemService(UserManager.class); |
Rakesh Iyer | 5b3278f | 2017-04-19 11:28:26 -0700 | [diff] [blame] | 88 | |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 89 | mShortAnimDuration = container.getResources() |
Brad Stenning | 8d1a51c | 2018-11-20 17:34:16 -0800 | [diff] [blame] | 90 | .getInteger(android.R.integer.config_shortAnimTime); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 91 | IntentFilter filter = new IntentFilter(Intent.ACTION_USER_UNLOCKED); |
| 92 | if (mUserManager.isUserUnlocked(UserHandle.USER_SYSTEM)) { |
| 93 | // User0 is unlocked, switched to the initial user |
| 94 | showDialogForInitialUser(); |
| 95 | } else { |
| 96 | // listen to USER_UNLOCKED |
| 97 | mContext.registerReceiverAsUser(mUserUnlockReceiver, |
| 98 | UserHandle.getUserHandleForUid(UserHandle.USER_SYSTEM), |
| 99 | filter, |
| 100 | /* broadcastPermission= */ null, |
| 101 | /* scheduler */ null); |
| 102 | } |
| 103 | } |
| 104 | |
| 105 | private void showDialogForInitialUser() { |
| 106 | int initialUser = mCarUserManagerHelper.getInitialUser(); |
| 107 | UserInfo initialUserInfo = mUserManager.getUserInfo(initialUser); |
| 108 | mSelectedUser = new UserRecord(initialUserInfo, |
| 109 | /* isStartGuestSession= */ false, |
| 110 | /* isAddUser= */ false, |
| 111 | /* isForeground= */ true); |
| 112 | // For single user without trusted device, hide the user switcher. |
| 113 | if (!hasMultipleUsers() && !hasTrustedDevice(initialUser)) { |
| 114 | dismissUserSwitcher(); |
| 115 | return; |
| 116 | } |
| 117 | // Show unlock dialog for initial user |
| 118 | if (hasTrustedDevice(initialUser)) { |
| 119 | mUnlockDialogHelper.showUnlockDialogAfterDelay(initialUser, |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 120 | mOnHideListener); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 121 | } |
| 122 | } |
| 123 | |
| 124 | /** |
| 125 | * Check if there is only one possible user to login in. |
| 126 | * In a Multi-User system there is always one background user (user 0) |
| 127 | */ |
| 128 | private boolean hasMultipleUsers() { |
| 129 | return mUserManager.getUserCount() > NUMBER_OF_BACKGROUND_USERS + 1; |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 130 | } |
| 131 | |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 132 | /** |
| 133 | * Makes user grid visible. |
| 134 | */ |
jovanak | 0535abc | 2018-04-10 15:14:50 -0700 | [diff] [blame] | 135 | public void show() { |
Brad Stenning | 3b0d764 | 2019-03-28 11:04:30 -0700 | [diff] [blame] | 136 | mParent.setVisibility(View.VISIBLE); |
Aarthi Balachander | 0a427ef | 2018-07-13 15:00:58 -0700 | [diff] [blame] | 137 | } |
| 138 | |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 139 | /** |
| 140 | * Hides the user grid. |
| 141 | */ |
| 142 | public void hide() { |
Brad Stenning | 3b0d764 | 2019-03-28 11:04:30 -0700 | [diff] [blame] | 143 | mParent.setVisibility(View.INVISIBLE); |
Aarthi Balachander | 0a427ef | 2018-07-13 15:00:58 -0700 | [diff] [blame] | 144 | } |
| 145 | |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 146 | /** |
| 147 | * @return {@code true} if user grid is visible, {@code false} otherwise. |
| 148 | */ |
| 149 | public boolean isVisible() { |
Brad Stenning | 3b0d764 | 2019-03-28 11:04:30 -0700 | [diff] [blame] | 150 | return mParent.getVisibility() == View.VISIBLE; |
Xiyuan Xia | cc3a74f6 | 2015-07-22 14:16:34 -0700 | [diff] [blame] | 151 | } |
| 152 | |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 153 | /** |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 154 | * Every time user clicks on an item in the switcher, if the clicked user has no trusted device, |
| 155 | * we hide the switcher, either gradually or immediately. |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 156 | * |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 157 | * If the user has trusted device, we show an unlock dialog to notify user the unlock state. |
| 158 | * When the unlock dialog is dismissed by user, we hide the unlock dialog and the switcher. |
| 159 | * |
| 160 | * We dismiss the entire keyguard when we hide the switcher if user clicked on the foreground |
| 161 | * user (user we're already logged in as). |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 162 | */ |
| 163 | private void onUserSelected(UserGridRecyclerView.UserRecord record) { |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 164 | mSelectedUser = record; |
| 165 | if (hasTrustedDevice(record.mInfo.id)) { |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 166 | mUnlockDialogHelper.showUnlockDialog(record.mInfo.id, mOnHideListener); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 167 | return; |
| 168 | } |
| 169 | if (Log.isLoggable(TAG, Log.DEBUG)) { |
| 170 | Log.d(TAG, "no trusted device enrolled for uid: " + record.mInfo.id); |
| 171 | } |
| 172 | dismissUserSwitcher(); |
| 173 | } |
| 174 | |
| 175 | private void dismissUserSwitcher() { |
| 176 | if (mSelectedUser == null) { |
| 177 | Log.e(TAG, "Request to dismiss user switcher, but no user selected"); |
| 178 | return; |
| 179 | } |
| 180 | if (mSelectedUser.mIsForeground) { |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 181 | hide(); |
| 182 | mStatusBar.dismissKeyguard(); |
| 183 | return; |
Rakesh Iyer | 33d6ce4 | 2017-05-30 11:02:18 -0700 | [diff] [blame] | 184 | } |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 185 | // Switching is about to happen, since it takes time, fade out the switcher gradually. |
| 186 | fadeOut(); |
Rakesh Iyer | 33d6ce4 | 2017-05-30 11:02:18 -0700 | [diff] [blame] | 187 | } |
| 188 | |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 189 | private void fadeOut() { |
| 190 | mUserGridView.animate() |
jovanak | 9c17720 | 2018-04-20 12:35:09 -0700 | [diff] [blame] | 191 | .alpha(0.0f) |
| 192 | .setDuration(mShortAnimDuration) |
| 193 | .setListener(new AnimatorListenerAdapter() { |
| 194 | @Override |
| 195 | public void onAnimationEnd(Animator animation) { |
jovanak | 604ad51 | 2018-08-14 18:41:27 -0700 | [diff] [blame] | 196 | hide(); |
| 197 | mUserGridView.setAlpha(1.0f); |
jovanak | 9c17720 | 2018-04-20 12:35:09 -0700 | [diff] [blame] | 198 | } |
| 199 | }); |
Rakesh Iyer | 33d6ce4 | 2017-05-30 11:02:18 -0700 | [diff] [blame] | 200 | |
Rakesh Iyer | 33d6ce4 | 2017-05-30 11:02:18 -0700 | [diff] [blame] | 201 | } |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 202 | |
| 203 | private boolean hasTrustedDevice(int uid) { |
| 204 | return !mEnrollmentManager.getEnrolledDeviceInfoForUser(uid).isEmpty(); |
| 205 | } |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 206 | |
| 207 | private OnHideListener mOnHideListener = new OnHideListener() { |
| 208 | @Override |
| 209 | public void onHide(boolean dismissUserSwitcher) { |
| 210 | if (dismissUserSwitcher) { |
| 211 | dismissUserSwitcher(); |
| 212 | } else { |
| 213 | // Re-draw the parent view, otherwise the unlock dialog will not be removed from |
| 214 | // the screen immediately. |
| 215 | mParent.invalidate(); |
| 216 | } |
| 217 | |
| 218 | } |
| 219 | }; |
Brad Stenning | 8d1a51c | 2018-11-20 17:34:16 -0800 | [diff] [blame] | 220 | } |