1 /* 2 * Copyright (C) 2017 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.keyguard; 18 19 import static android.app.ActivityManager.TaskDescription; 20 21 import android.annotation.UserIdInt; 22 import android.app.Activity; 23 import android.app.ActivityOptions; 24 import android.app.KeyguardManager; 25 import android.app.PendingIntent; 26 import android.content.BroadcastReceiver; 27 import android.content.Context; 28 import android.content.Intent; 29 import android.content.IntentFilter; 30 import android.content.pm.PackageManager; 31 import android.graphics.drawable.Drawable; 32 import android.os.Bundle; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.widget.ImageView; 36 import android.window.OnBackInvokedCallback; 37 import android.window.OnBackInvokedDispatcher; 38 39 import com.android.internal.annotations.VisibleForTesting; 40 import com.android.systemui.R; 41 import com.android.systemui.broadcast.BroadcastDispatcher; 42 43 import javax.inject.Inject; 44 45 /** 46 * Bouncer between work activities and the activity used to confirm credentials before unlocking 47 * a managed profile. 48 * <p> 49 * Shows a solid color when started, based on the organization color of the user it is supposed to 50 * be blocking. Once focused, it switches to a screen to confirm credentials and auto-dismisses if 51 * credentials are accepted. 52 */ 53 public class WorkLockActivity extends Activity { 54 private static final String TAG = "WorkLockActivity"; 55 56 private static final int REQUEST_CODE_CONFIRM_CREDENTIALS = 1; 57 58 /** 59 * Cached keyguard manager instance populated by {@link #getKeyguardManager}. 60 * @see KeyguardManager 61 */ 62 private KeyguardManager mKgm; 63 private UserManager mUserManager; 64 private PackageManager mPackageManager; 65 private final BroadcastDispatcher mBroadcastDispatcher; 66 private final OnBackInvokedCallback mBackCallback = this::onBackInvoked; 67 68 @Inject WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager, PackageManager packageManager)69 public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager, 70 PackageManager packageManager) { 71 super(); 72 mBroadcastDispatcher = broadcastDispatcher; 73 mUserManager = userManager; 74 mPackageManager = packageManager; 75 } 76 77 @Override onCreate(Bundle savedInstanceState)78 public void onCreate(Bundle savedInstanceState) { 79 super.onCreate(savedInstanceState); 80 81 mBroadcastDispatcher.registerReceiver(mLockEventReceiver, 82 new IntentFilter(Intent.ACTION_DEVICE_LOCKED_CHANGED), null /* handler */, 83 UserHandle.ALL); 84 85 // Once the receiver is registered, check whether anything happened between now and the time 86 // when this activity was launched. If it did and the user is unlocked now, just quit. 87 if (!getKeyguardManager().isDeviceLocked(getTargetUserId())) { 88 finish(); 89 return; 90 } 91 92 // Draw captions overlaid on the content view, so the whole window is one solid color. 93 setOverlayWithDecorCaptionEnabled(true); 94 95 // Add background protection that contains a badged icon of the app being opened. 96 setContentView(R.layout.auth_biometric_background); 97 Drawable badgedIcon = getBadgedIcon(); 98 if (badgedIcon != null) { 99 ((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon); 100 } 101 102 getOnBackInvokedDispatcher().registerOnBackInvokedCallback( 103 OnBackInvokedDispatcher.PRIORITY_DEFAULT, 104 mBackCallback); 105 } 106 107 @VisibleForTesting getBadgedIcon()108 protected Drawable getBadgedIcon() { 109 String packageName = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME); 110 if (!packageName.isEmpty()) { 111 try { 112 return mUserManager.getBadgedIconForUser(mPackageManager.getApplicationIcon( 113 mPackageManager.getApplicationInfoAsUser(packageName, 114 PackageManager.ApplicationInfoFlags.of(0), getTargetUserId())), 115 UserHandle.of(getTargetUserId())); 116 } catch (PackageManager.NameNotFoundException e) { 117 // Unable to set the badged icon, show the background protection without an icon. 118 } 119 } 120 return null; 121 } 122 123 /** 124 * Respond to focus events by showing the prompt to confirm credentials. 125 * <p> 126 * We don't have anything particularly interesting to show here (just a solid-colored page) so 127 * there is no sense in sitting in the foreground doing nothing. 128 */ 129 @Override onWindowFocusChanged(boolean hasFocus)130 public void onWindowFocusChanged(boolean hasFocus) { 131 if (hasFocus) { 132 showConfirmCredentialActivity(); 133 } 134 } 135 136 @VisibleForTesting unregisterBroadcastReceiver()137 protected void unregisterBroadcastReceiver() { 138 mBroadcastDispatcher.unregisterReceiver(mLockEventReceiver); 139 } 140 141 @Override onDestroy()142 public void onDestroy() { 143 unregisterBroadcastReceiver(); 144 getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback); 145 super.onDestroy(); 146 } 147 148 @Override onBackPressed()149 public void onBackPressed() { 150 onBackInvoked(); 151 } 152 onBackInvoked()153 private void onBackInvoked() { 154 // Ignore back presses. 155 } 156 157 @Override setTaskDescription(TaskDescription taskDescription)158 public void setTaskDescription(TaskDescription taskDescription) { 159 // Leave unset so we use the previous activity's task description. 160 } 161 162 private final BroadcastReceiver mLockEventReceiver = new BroadcastReceiver() { 163 @Override 164 public void onReceive(Context context, Intent intent) { 165 final int targetUserId = getTargetUserId(); 166 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, targetUserId); 167 if (userId == targetUserId && !getKeyguardManager().isDeviceLocked(targetUserId)) { 168 finish(); 169 } 170 } 171 }; 172 showConfirmCredentialActivity()173 private void showConfirmCredentialActivity() { 174 if (isFinishing() || !getKeyguardManager().isDeviceLocked(getTargetUserId())) { 175 // Don't show the confirm credentials screen if we are already unlocked / unlocking. 176 return; 177 } 178 179 final Intent confirmCredentialIntent = getKeyguardManager() 180 .createConfirmDeviceCredentialIntent(null, null, getTargetUserId(), 181 true /* disallowBiometricsIfPolicyExists */); 182 if (confirmCredentialIntent == null) { 183 return; 184 } 185 186 final ActivityOptions options = ActivityOptions.makeBasic(); 187 options.setLaunchTaskId(getTaskId()); 188 189 // Bring this activity back to the foreground after confirming credentials. 190 final PendingIntent target = PendingIntent.getActivity(this, /* request */ -1, getIntent(), 191 PendingIntent.FLAG_CANCEL_CURRENT | 192 PendingIntent.FLAG_ONE_SHOT | 193 PendingIntent.FLAG_IMMUTABLE, options.toBundle()); 194 195 if (target != null) { 196 confirmCredentialIntent.putExtra(Intent.EXTRA_INTENT, target.getIntentSender()); 197 } 198 199 // WorkLockActivity is started as a task overlay, so unless credential confirmation is also 200 // started as an overlay, it won't be visible. 201 final ActivityOptions launchOptions = ActivityOptions.makeBasic(); 202 launchOptions.setLaunchTaskId(getTaskId()); 203 launchOptions.setTaskOverlay(true /* taskOverlay */, true /* canResume */); 204 // Propagate it in case more than one activity is launched. 205 confirmCredentialIntent.putExtra(KeyguardManager.EXTRA_FORCE_TASK_OVERLAY, true); 206 207 startActivityForResult(confirmCredentialIntent, REQUEST_CODE_CONFIRM_CREDENTIALS, 208 launchOptions.toBundle()); 209 } 210 211 @Override onActivityResult(int requestCode, int resultCode, Intent data)212 protected void onActivityResult(int requestCode, int resultCode, Intent data) { 213 if (requestCode == REQUEST_CODE_CONFIRM_CREDENTIALS && resultCode != RESULT_OK) { 214 // The user dismissed the challenge, don't show it again. 215 goToHomeScreen(); 216 } 217 } 218 goToHomeScreen()219 private void goToHomeScreen() { 220 final Intent homeIntent = new Intent(Intent.ACTION_MAIN); 221 homeIntent.addCategory(Intent.CATEGORY_HOME); 222 homeIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 223 startActivity(homeIntent); 224 } 225 getKeyguardManager()226 private KeyguardManager getKeyguardManager() { 227 if (mKgm == null) { 228 mKgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); 229 } 230 return mKgm; 231 } 232 233 @VisibleForTesting 234 @UserIdInt getTargetUserId()235 final int getTargetUserId() { 236 return getIntent().getIntExtra(Intent.EXTRA_USER_ID, UserHandle.myUserId()); 237 } 238 } 239