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