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