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