Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 1 | /* |
| 2 | * Copyright (C) 2019 The Android Open Source Project |
| 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 |
| 14 | * limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package com.android.systemui.statusbar.car; |
| 18 | |
| 19 | import android.app.admin.DevicePolicyManager; |
| 20 | import android.bluetooth.BluetoothAdapter; |
| 21 | import android.content.BroadcastReceiver; |
| 22 | import android.content.Context; |
| 23 | import android.content.Intent; |
| 24 | import android.content.IntentFilter; |
| 25 | import android.graphics.PixelFormat; |
| 26 | import android.os.Handler; |
| 27 | import android.os.UserHandle; |
| 28 | import android.os.UserManager; |
| 29 | import android.util.Log; |
| 30 | import android.view.LayoutInflater; |
| 31 | import android.view.View; |
| 32 | import android.view.WindowManager; |
| 33 | import android.widget.Button; |
| 34 | import android.widget.ImageView; |
| 35 | import android.widget.TextView; |
| 36 | |
| 37 | import com.android.internal.widget.LockPatternUtils; |
| 38 | import com.android.systemui.R; |
| 39 | |
| 40 | /** |
| 41 | * A helper class displays an unlock dialog and receives broadcast about detecting trusted device |
| 42 | * & unlocking state to show the appropriate message on the dialog. |
| 43 | */ |
| 44 | class CarTrustAgentUnlockDialogHelper extends BroadcastReceiver{ |
| 45 | private static final String TAG = CarTrustAgentUnlockDialogHelper.class.getSimpleName(); |
| 46 | |
| 47 | private final Context mContext; |
| 48 | private final WindowManager mWindowManager; |
| 49 | private final UserManager mUserManager; |
| 50 | private final WindowManager.LayoutParams mParams; |
| 51 | /** |
| 52 | * Not using Dialog because context passed from {@link FullscreenUserSwitcher} is not an |
| 53 | * activity. |
| 54 | */ |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 55 | private final View mUnlockDialogLayout; |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 56 | private final TextView mUnlockingText; |
| 57 | private final Button mButton; |
| 58 | private final IntentFilter mFilter; |
| 59 | private int mUid; |
| 60 | private boolean mIsDialogShowing; |
| 61 | private OnHideListener mOnHideListener; |
| 62 | |
| 63 | CarTrustAgentUnlockDialogHelper(Context context) { |
| 64 | mContext = context; |
| 65 | mUserManager = mContext.getSystemService(UserManager.class); |
| 66 | mWindowManager = mContext.getSystemService(WindowManager.class); |
| 67 | mParams = createLayoutParams(); |
| 68 | mFilter = getIntentFilter(); |
| 69 | |
| 70 | mParams.packageName = mContext.getPackageName(); |
| 71 | mParams.setTitle(mContext.getString(R.string.unlock_dialog_title)); |
| 72 | |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 73 | mUnlockDialogLayout = LayoutInflater.from(mContext).inflate( |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 74 | R.layout.trust_agent_unlock_dialog, null); |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 75 | mUnlockDialogLayout.setLayoutParams(mParams); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 76 | |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 77 | View dialogParent = mUnlockDialogLayout.findViewById(R.id.unlock_dialog_parent); |
| 78 | dialogParent.setOnTouchListener((v, event)-> { |
| 79 | hideUnlockDialog(/* dismissUserSwitcher= */ false); |
| 80 | return true; |
| 81 | }); |
| 82 | View unlockDialog = mUnlockDialogLayout.findViewById(R.id.unlock_dialog); |
| 83 | unlockDialog.setOnTouchListener((v, event) -> { |
| 84 | // If the person taps inside the unlock dialog, the touch event will be intercepted here |
| 85 | // and the dialog will not exit |
| 86 | return true; |
| 87 | }); |
| 88 | mUnlockingText = mUnlockDialogLayout.findViewById(R.id.unlocking_text); |
| 89 | mButton = mUnlockDialogLayout.findViewById(R.id.enter_pin_button); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 90 | mButton.setOnClickListener(v -> { |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 91 | hideUnlockDialog(/* dismissUserSwitcher= */true); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 92 | // TODO(b/138250105) Stop unlock advertising |
| 93 | }); |
| 94 | |
| 95 | BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); |
| 96 | if (bluetoothAdapter != null |
| 97 | && bluetoothAdapter.getLeState() == BluetoothAdapter.STATE_BLE_ON) { |
| 98 | mUnlockingText.setText(R.string.unlock_dialog_message_start); |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * This filter is listening on: |
| 104 | * {@link BluetoothAdapter#ACTION_BLE_STATE_CHANGED} for starting unlock advertising; |
| 105 | * {@link Intent#ACTION_USER_UNLOCKED} for IHU unlocked |
| 106 | */ |
| 107 | private IntentFilter getIntentFilter() { |
| 108 | IntentFilter filter = new IntentFilter(); |
| 109 | filter.addAction(BluetoothAdapter.ACTION_BLE_STATE_CHANGED); |
| 110 | filter.addAction(Intent.ACTION_USER_UNLOCKED); |
| 111 | return filter; |
| 112 | } |
| 113 | |
| 114 | /** |
| 115 | * Show dialog for the given user |
| 116 | */ |
| 117 | void showUnlockDialog(int uid, OnHideListener listener) { |
| 118 | showUnlockDialogAfterDelay(uid, 0, listener); |
| 119 | } |
| 120 | |
| 121 | /** |
| 122 | * Show dialog for the given user after the certain time of delay has elapsed |
| 123 | * |
| 124 | * @param uid the user to unlock |
| 125 | * @param listener listener that listens to dialog hide |
| 126 | */ |
| 127 | void showUnlockDialogAfterDelay(int uid, OnHideListener listener) { |
| 128 | long delayMillis = mContext.getResources().getInteger(R.integer.unlock_dialog_delay_ms); |
| 129 | showUnlockDialogAfterDelay(uid, delayMillis, listener); |
| 130 | } |
| 131 | |
| 132 | /** |
| 133 | * Show dialog for the given user after the supplied delay has elapsed |
| 134 | */ |
| 135 | private void showUnlockDialogAfterDelay(int uid, long delayMillis, OnHideListener listener) { |
| 136 | setUid(uid); |
| 137 | mOnHideListener = listener; |
| 138 | if (!mIsDialogShowing) { |
| 139 | logd("Receiver registered"); |
| 140 | mContext.registerReceiverAsUser(this, UserHandle.ALL, mFilter, |
| 141 | /* broadcastPermission= */ null, |
| 142 | /* scheduler= */ null); |
| 143 | new Handler().postDelayed(() -> { |
| 144 | if (!mUserManager.isUserUnlocked(uid)) { |
| 145 | logd("Showed unlock dialog for user: " + uid + " after " + delayMillis |
| 146 | + " delay."); |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 147 | mWindowManager.addView(mUnlockDialogLayout, mParams); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 148 | } |
| 149 | }, delayMillis); |
| 150 | } |
| 151 | mIsDialogShowing = true; |
| 152 | } |
| 153 | |
| 154 | private void setUid(int uid) { |
| 155 | mUid = uid; |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 156 | TextView userName = mUnlockDialogLayout.findViewById(R.id.user_name); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 157 | userName.setText(mUserManager.getUserInfo(mUid).name); |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 158 | ImageView avatar = mUnlockDialogLayout.findViewById(R.id.avatar); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 159 | avatar.setImageBitmap(mUserManager.getUserIcon(mUid)); |
| 160 | setButtonText(); |
| 161 | } |
| 162 | |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 163 | private void hideUnlockDialog(boolean dismissUserSwitcher) { |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 164 | if (!mIsDialogShowing) { |
| 165 | return; |
| 166 | } |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 167 | mWindowManager.removeView(mUnlockDialogLayout); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 168 | logd("Receiver unregistered"); |
| 169 | mContext.unregisterReceiver(this); |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 170 | if (mOnHideListener != null) { |
| 171 | mOnHideListener.onHide(dismissUserSwitcher); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 172 | } |
| 173 | mIsDialogShowing = false; |
| 174 | } |
| 175 | |
| 176 | @Override |
| 177 | public void onReceive(Context context, Intent intent) { |
| 178 | String action = intent.getAction(); |
| 179 | if (action == null) { |
| 180 | return; |
| 181 | } |
| 182 | switch (action) { |
| 183 | case BluetoothAdapter.ACTION_BLE_STATE_CHANGED: |
| 184 | logd("Received ACTION_BLE_STATE_CHANGED"); |
| 185 | int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1); |
| 186 | if (state == BluetoothAdapter.STATE_BLE_ON) { |
| 187 | logd("Received BLE_ON"); |
| 188 | mUnlockingText.setText(R.string.unlock_dialog_message_start); |
| 189 | } |
| 190 | break; |
| 191 | case Intent.ACTION_USER_UNLOCKED: |
| 192 | int uid = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1); |
| 193 | if (uid == mUid) { |
| 194 | logd("IHU unlocked"); |
| 195 | hideUnlockDialog(/* notifyOnHideListener= */false); |
| 196 | } else { |
| 197 | Log.e(TAG, "Received ACTION_USER_UNLOCKED for unexpected uid: " + uid); |
| 198 | } |
| 199 | break; |
| 200 | default: |
| 201 | Log.e(TAG, "Encountered unexpected action when attempting to set " |
| 202 | + "unlock state message: " + action); |
| 203 | } |
| 204 | } |
| 205 | |
Erin Yan | b1af47b | 2019-08-29 16:00:47 -0700 | [diff] [blame] | 206 | // Set button text based on screen lock type |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 207 | private void setButtonText() { |
| 208 | LockPatternUtils lockPatternUtils = new LockPatternUtils(mContext); |
| 209 | int passwordQuality = lockPatternUtils.getActivePasswordQuality(mUid); |
| 210 | switch (passwordQuality) { |
| 211 | // PIN |
Erin Yan | b1af47b | 2019-08-29 16:00:47 -0700 | [diff] [blame] | 212 | case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC: |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 213 | case DevicePolicyManager.PASSWORD_QUALITY_NUMERIC_COMPLEX: |
Erin Yan | b1af47b | 2019-08-29 16:00:47 -0700 | [diff] [blame] | 214 | mButton.setText(R.string.unlock_dialog_button_text_pin); |
| 215 | break; |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 216 | // Pattern |
| 217 | case DevicePolicyManager.PASSWORD_QUALITY_SOMETHING: |
| 218 | mButton.setText(R.string.unlock_dialog_button_text_pattern); |
| 219 | break; |
| 220 | // Password |
Erin Yan | b1af47b | 2019-08-29 16:00:47 -0700 | [diff] [blame] | 221 | case DevicePolicyManager.PASSWORD_QUALITY_ALPHABETIC: |
| 222 | case DevicePolicyManager.PASSWORD_QUALITY_ALPHANUMERIC: |
| 223 | case DevicePolicyManager.PASSWORD_QUALITY_COMPLEX: |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 224 | mButton.setText(R.string.unlock_dialog_button_text_password); |
| 225 | break; |
| 226 | default: |
Erin Yan | b1af47b | 2019-08-29 16:00:47 -0700 | [diff] [blame] | 227 | Log.e(TAG, "Encountered unexpected screen lock type when attempting to set " |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 228 | + "button text:" + passwordQuality); |
| 229 | } |
| 230 | } |
| 231 | |
| 232 | private WindowManager.LayoutParams createLayoutParams() { |
| 233 | return new WindowManager.LayoutParams( |
| 234 | WindowManager.LayoutParams.MATCH_PARENT, |
| 235 | WindowManager.LayoutParams.MATCH_PARENT, |
| 236 | WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG, |
| 237 | WindowManager.LayoutParams.FLAG_FULLSCREEN |
| 238 | | WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS |
| 239 | | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, |
| 240 | PixelFormat.TRANSLUCENT |
| 241 | ); |
| 242 | } |
| 243 | |
| 244 | private void logd(String message) { |
| 245 | if (Log.isLoggable(TAG, Log.DEBUG)) { |
| 246 | Log.d(TAG, message); |
| 247 | } |
| 248 | } |
| 249 | |
| 250 | /** |
| 251 | * Listener used to notify when the dialog is hidden |
| 252 | */ |
| 253 | interface OnHideListener { |
Erin Yan | 5eb60a2 | 2019-09-04 11:55:30 -0700 | [diff] [blame] | 254 | void onHide(boolean dismissUserSwitcher); |
Erin Yan | 8182bfd | 2019-08-14 12:03:12 -0700 | [diff] [blame] | 255 | } |
| 256 | } |