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