• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2012 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.keyguard;
18 
19 import com.android.internal.widget.LockPatternUtils;
20 
21 import android.app.ActivityManagerNative;
22 import android.app.ActivityOptions;
23 import android.app.IActivityManager.WaitResult;
24 import android.appwidget.AppWidgetManager;
25 import android.appwidget.AppWidgetProviderInfo;
26 import android.content.ActivityNotFoundException;
27 import android.content.Context;
28 import android.content.Intent;
29 import android.content.pm.PackageManager;
30 import android.content.pm.ResolveInfo;
31 import android.os.Bundle;
32 import android.os.Handler;
33 import android.os.RemoteException;
34 import android.os.SystemClock;
35 import android.os.UserHandle;
36 import android.provider.MediaStore;
37 import android.util.Log;
38 import android.view.WindowManager;
39 import android.view.WindowManagerGlobal;
40 
41 import com.android.keyguard.KeyguardHostView.OnDismissAction;
42 
43 import java.util.List;
44 
45 public abstract class KeyguardActivityLauncher {
46     private static final String TAG = KeyguardActivityLauncher.class.getSimpleName();
47     private static final boolean DEBUG = KeyguardConstants.DEBUG;
48     private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
49     private static final Intent SECURE_CAMERA_INTENT =
50             new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
51                     .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
52     private static final Intent INSECURE_CAMERA_INTENT =
53             new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
54 
getContext()55     abstract Context getContext();
56 
getLockPatternUtils()57     abstract LockPatternUtils getLockPatternUtils();
58 
setOnDismissAction(OnDismissAction action)59     abstract void setOnDismissAction(OnDismissAction action);
60 
requestDismissKeyguard()61     abstract void requestDismissKeyguard();
62 
63     public static class CameraWidgetInfo {
64         public String contextPackage;
65         public int layoutId;
66     }
67 
getCameraWidgetInfo()68     public CameraWidgetInfo getCameraWidgetInfo() {
69         CameraWidgetInfo info = new CameraWidgetInfo();
70         Intent intent = getCameraIntent();
71         PackageManager packageManager = getContext().getPackageManager();
72         final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
73                 intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
74         if (appList.size() == 0) {
75             if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Nothing found");
76             return null;
77         }
78         ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
79                 PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
80                 getLockPatternUtils().getCurrentUser());
81         if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): resolved: " + resolved);
82         if (wouldLaunchResolverActivity(resolved, appList)) {
83             if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Would launch resolver");
84             return info;
85         }
86         if (resolved == null || resolved.activityInfo == null) {
87             return null;
88         }
89         if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
90             if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no metadata found");
91             return info;
92         }
93         int layoutId = resolved.activityInfo.metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
94         if (layoutId == 0) {
95             if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no layout specified");
96             return info;
97         }
98         info.contextPackage = resolved.activityInfo.packageName;
99         info.layoutId = layoutId;
100         return info;
101     }
102 
launchCamera(Handler worker, Runnable onSecureCameraStarted)103     public void launchCamera(Handler worker, Runnable onSecureCameraStarted) {
104         LockPatternUtils lockPatternUtils = getLockPatternUtils();
105 
106         // Workaround to avoid camera release/acquisition race when resuming face unlock
107         // after showing lockscreen camera (bug 11063890).
108         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
109         updateMonitor.setAlternateUnlockEnabled(false);
110 
111         if (mustLaunchSecurely()) {
112             // Launch the secure version of the camera
113             if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) {
114                 // TODO: Show disambiguation dialog instead.
115                 // For now, we'll treat this like launching any other app from secure keyguard.
116                 // When they do, user sees the system's ResolverActivity which lets them choose
117                 // which secure camera to use.
118                 launchActivity(SECURE_CAMERA_INTENT, false, false, null, null);
119             } else {
120                 launchActivity(SECURE_CAMERA_INTENT, true, false, worker, onSecureCameraStarted);
121             }
122         } else {
123             // Launch the normal camera
124             launchActivity(INSECURE_CAMERA_INTENT, false, false, null, null);
125         }
126     }
127 
mustLaunchSecurely()128     private boolean mustLaunchSecurely() {
129         LockPatternUtils lockPatternUtils = getLockPatternUtils();
130         KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
131         int currentUser = lockPatternUtils.getCurrentUser();
132         return lockPatternUtils.isSecure() && !updateMonitor.getUserHasTrust(currentUser);
133     }
134 
launchWidgetPicker(int appWidgetId)135     public void launchWidgetPicker(int appWidgetId) {
136         Intent pickIntent = new Intent(AppWidgetManager.ACTION_KEYGUARD_APPWIDGET_PICK);
137 
138         pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
139         pickIntent.putExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, false);
140         pickIntent.putExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER,
141                 AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD);
142 
143         Bundle options = new Bundle();
144         options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
145                 AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD);
146         pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
147         pickIntent.addFlags(
148                 Intent.FLAG_ACTIVITY_NEW_TASK
149                 | Intent.FLAG_ACTIVITY_SINGLE_TOP
150                 | Intent.FLAG_ACTIVITY_CLEAR_TOP
151                 | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
152 
153         launchActivity(pickIntent, false, false, null, null);
154     }
155 
156     /**
157      * Launches the said intent for the current foreground user.
158      *
159      * @param intent
160      * @param showsWhileLocked true if the activity can be run on top of keyguard.
161      *   See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED}
162      * @param useDefaultAnimations true if default transitions should be used, else suppressed.
163      * @param worker if supplied along with onStarted, used to launch the blocking activity call.
164      * @param onStarted if supplied along with worker, called after activity is started.
165      */
launchActivity(final Intent intent, boolean showsWhileLocked, boolean useDefaultAnimations, final Handler worker, final Runnable onStarted)166     public void launchActivity(final Intent intent,
167             boolean showsWhileLocked,
168             boolean useDefaultAnimations,
169             final Handler worker,
170             final Runnable onStarted) {
171 
172         final Context context = getContext();
173         final Bundle animation = useDefaultAnimations ? null
174                 : ActivityOptions.makeCustomAnimation(context, 0, 0).toBundle();
175         launchActivityWithAnimation(intent, showsWhileLocked, animation, worker, onStarted);
176     }
177 
launchActivityWithAnimation(final Intent intent, boolean showsWhileLocked, final Bundle animation, final Handler worker, final Runnable onStarted)178     public void launchActivityWithAnimation(final Intent intent,
179             boolean showsWhileLocked,
180             final Bundle animation,
181             final Handler worker,
182             final Runnable onStarted) {
183 
184         LockPatternUtils lockPatternUtils = getLockPatternUtils();
185         intent.addFlags(
186                 Intent.FLAG_ACTIVITY_NEW_TASK
187                 | Intent.FLAG_ACTIVITY_SINGLE_TOP
188                 | Intent.FLAG_ACTIVITY_CLEAR_TOP);
189         boolean mustLaunchSecurely = mustLaunchSecurely();
190         if (!mustLaunchSecurely || showsWhileLocked) {
191             if (!mustLaunchSecurely) {
192                 dismissKeyguardOnNextActivity();
193             }
194             try {
195                 if (DEBUG) Log.d(TAG, String.format("Starting activity for intent %s at %s",
196                         intent, SystemClock.uptimeMillis()));
197                 startActivityForCurrentUser(intent, animation, worker, onStarted);
198             } catch (ActivityNotFoundException e) {
199                 Log.w(TAG, "Activity not found for intent + " + intent.getAction());
200             }
201         } else {
202             // Create a runnable to start the activity and ask the user to enter their
203             // credentials.
204             setOnDismissAction(new OnDismissAction() {
205                 @Override
206                 public boolean onDismiss() {
207                     dismissKeyguardOnNextActivity();
208                     startActivityForCurrentUser(intent, animation, worker, onStarted);
209                     return true;
210                 }
211             });
212             requestDismissKeyguard();
213         }
214     }
215 
dismissKeyguardOnNextActivity()216     private void dismissKeyguardOnNextActivity() {
217         try {
218             WindowManagerGlobal.getWindowManagerService().dismissKeyguard();
219         } catch (RemoteException e) {
220             Log.w(TAG, "Error dismissing keyguard", e);
221         }
222     }
223 
startActivityForCurrentUser(final Intent intent, final Bundle options, Handler worker, final Runnable onStarted)224     private void startActivityForCurrentUser(final Intent intent, final Bundle options,
225             Handler worker, final Runnable onStarted) {
226         final UserHandle user = new UserHandle(UserHandle.USER_CURRENT);
227         if (worker == null || onStarted == null) {
228             getContext().startActivityAsUser(intent, options, user);
229             return;
230         }
231         // if worker + onStarted are supplied, run blocking activity launch call in the background
232         worker.post(new Runnable(){
233             @Override
234             public void run() {
235                 try {
236                     WaitResult result = ActivityManagerNative.getDefault().startActivityAndWait(
237                             null /*caller*/,
238                             null /*caller pkg*/,
239                             intent,
240                             intent.resolveTypeIfNeeded(getContext().getContentResolver()),
241                             null /*resultTo*/,
242                             null /*resultWho*/,
243                             0 /*requestCode*/,
244                             Intent.FLAG_ACTIVITY_NEW_TASK,
245                             null /*profilerInfo*/,
246                             options,
247                             user.getIdentifier());
248                     if (DEBUG) Log.d(TAG, String.format("waitResult[%s,%s,%s,%s] at %s",
249                             result.result, result.thisTime, result.totalTime, result.who,
250                             SystemClock.uptimeMillis()));
251                 } catch (RemoteException e) {
252                     Log.w(TAG, "Error starting activity", e);
253                     return;
254                 }
255                 try {
256                     onStarted.run();
257                 } catch (Throwable t) {
258                     Log.w(TAG, "Error running onStarted callback", t);
259                 }
260             }});
261     }
262 
getCameraIntent()263     private Intent getCameraIntent() {
264         return mustLaunchSecurely() ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
265     }
266 
wouldLaunchResolverActivity(Intent intent)267     private boolean wouldLaunchResolverActivity(Intent intent) {
268         PackageManager packageManager = getContext().getPackageManager();
269         ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
270                 PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
271         List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
272                 intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
273         return wouldLaunchResolverActivity(resolved, appList);
274     }
275 
wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList)276     private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
277         // If the list contains the above resolved activity, then it can't be
278         // ResolverActivity itself.
279         for (int i = 0; i < appList.size(); i++) {
280             ResolveInfo tmp = appList.get(i);
281             if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
282                     && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
283                 return false;
284             }
285         }
286         return true;
287     }
288 }
289