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 17package com.android.keyguard; 18 19import com.android.internal.widget.LockPatternUtils; 20 21import android.app.ActivityManagerNative; 22import android.app.ActivityOptions; 23import android.app.IActivityManager.WaitResult; 24import android.appwidget.AppWidgetManager; 25import android.appwidget.AppWidgetProviderInfo; 26import android.content.ActivityNotFoundException; 27import android.content.Context; 28import android.content.Intent; 29import android.content.pm.PackageManager; 30import android.content.pm.ResolveInfo; 31import android.os.Bundle; 32import android.os.Handler; 33import android.os.RemoteException; 34import android.os.SystemClock; 35import android.os.UserHandle; 36import android.provider.MediaStore; 37import android.util.Log; 38import android.view.WindowManager; 39import android.view.WindowManagerGlobal; 40 41import com.android.keyguard.KeyguardHostView.OnDismissAction; 42 43import java.util.List; 44 45public 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 55 abstract Context getContext(); 56 57 abstract LockPatternUtils getLockPatternUtils(); 58 59 abstract void setOnDismissAction(OnDismissAction action); 60 61 abstract void requestDismissKeyguard(); 62 63 public static class CameraWidgetInfo { 64 public String contextPackage; 65 public int layoutId; 66 } 67 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 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 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 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 */ 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 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 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 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 263 private Intent getCameraIntent() { 264 return mustLaunchSecurely() ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; 265 } 266 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 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