/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.keyguard; import com.android.internal.widget.LockPatternUtils; import android.app.ActivityManagerNative; import android.app.ActivityOptions; import android.app.IActivityManager.WaitResult; import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProviderInfo; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.provider.MediaStore; import android.util.Log; import android.view.WindowManager; import android.view.WindowManagerGlobal; import com.android.keyguard.KeyguardHostView.OnDismissAction; import java.util.List; public abstract class KeyguardActivityLauncher { private static final String TAG = KeyguardActivityLauncher.class.getSimpleName(); private static final boolean DEBUG = KeyguardConstants.DEBUG; private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout"; private static final Intent SECURE_CAMERA_INTENT = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE) .addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); private static final Intent INSECURE_CAMERA_INTENT = new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA); abstract Context getContext(); abstract LockPatternUtils getLockPatternUtils(); abstract void setOnDismissAction(OnDismissAction action); abstract void requestDismissKeyguard(); public static class CameraWidgetInfo { public String contextPackage; public int layoutId; } public CameraWidgetInfo getCameraWidgetInfo() { CameraWidgetInfo info = new CameraWidgetInfo(); Intent intent = getCameraIntent(); PackageManager packageManager = getContext().getPackageManager(); final List appList = packageManager.queryIntentActivitiesAsUser( intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser()); if (appList.size() == 0) { if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Nothing found"); return null; } ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA, getLockPatternUtils().getCurrentUser()); if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): resolved: " + resolved); if (wouldLaunchResolverActivity(resolved, appList)) { if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Would launch resolver"); return info; } if (resolved == null || resolved.activityInfo == null) { return null; } if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) { if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no metadata found"); return info; } int layoutId = resolved.activityInfo.metaData.getInt(META_DATA_KEYGUARD_LAYOUT); if (layoutId == 0) { if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no layout specified"); return info; } info.contextPackage = resolved.activityInfo.packageName; info.layoutId = layoutId; return info; } public void launchCamera(Handler worker, Runnable onSecureCameraStarted) { LockPatternUtils lockPatternUtils = getLockPatternUtils(); // Workaround to avoid camera release/acquisition race when resuming face unlock // after showing lockscreen camera (bug 11063890). KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(getContext()); updateMonitor.setAlternateUnlockEnabled(false); if (mustLaunchSecurely()) { // Launch the secure version of the camera if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) { // TODO: Show disambiguation dialog instead. // For now, we'll treat this like launching any other app from secure keyguard. // When they do, user sees the system's ResolverActivity which lets them choose // which secure camera to use. launchActivity(SECURE_CAMERA_INTENT, false, false, null, null); } else { launchActivity(SECURE_CAMERA_INTENT, true, false, worker, onSecureCameraStarted); } } else { // Launch the normal camera launchActivity(INSECURE_CAMERA_INTENT, false, false, null, null); } } private boolean mustLaunchSecurely() { LockPatternUtils lockPatternUtils = getLockPatternUtils(); KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(getContext()); int currentUser = lockPatternUtils.getCurrentUser(); return lockPatternUtils.isSecure() && !updateMonitor.getUserHasTrust(currentUser); } public void launchWidgetPicker(int appWidgetId) { Intent pickIntent = new Intent(AppWidgetManager.ACTION_KEYGUARD_APPWIDGET_PICK); pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); pickIntent.putExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, false); pickIntent.putExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD); Bundle options = new Bundle(); options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY, AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD); pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options); pickIntent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); launchActivity(pickIntent, false, false, null, null); } /** * Launches the said intent for the current foreground user. * * @param intent * @param showsWhileLocked true if the activity can be run on top of keyguard. * See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED} * @param useDefaultAnimations true if default transitions should be used, else suppressed. * @param worker if supplied along with onStarted, used to launch the blocking activity call. * @param onStarted if supplied along with worker, called after activity is started. */ public void launchActivity(final Intent intent, boolean showsWhileLocked, boolean useDefaultAnimations, final Handler worker, final Runnable onStarted) { final Context context = getContext(); final Bundle animation = useDefaultAnimations ? null : ActivityOptions.makeCustomAnimation(context, 0, 0).toBundle(); launchActivityWithAnimation(intent, showsWhileLocked, animation, worker, onStarted); } public void launchActivityWithAnimation(final Intent intent, boolean showsWhileLocked, final Bundle animation, final Handler worker, final Runnable onStarted) { LockPatternUtils lockPatternUtils = getLockPatternUtils(); intent.addFlags( Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP); boolean mustLaunchSecurely = mustLaunchSecurely(); if (!mustLaunchSecurely || showsWhileLocked) { if (!mustLaunchSecurely) { dismissKeyguardOnNextActivity(); } try { if (DEBUG) Log.d(TAG, String.format("Starting activity for intent %s at %s", intent, SystemClock.uptimeMillis())); startActivityForCurrentUser(intent, animation, worker, onStarted); } catch (ActivityNotFoundException e) { Log.w(TAG, "Activity not found for intent + " + intent.getAction()); } } else { // Create a runnable to start the activity and ask the user to enter their // credentials. setOnDismissAction(new OnDismissAction() { @Override public boolean onDismiss() { dismissKeyguardOnNextActivity(); startActivityForCurrentUser(intent, animation, worker, onStarted); return true; } }); requestDismissKeyguard(); } } private void dismissKeyguardOnNextActivity() { try { WindowManagerGlobal.getWindowManagerService().dismissKeyguard(); } catch (RemoteException e) { Log.w(TAG, "Error dismissing keyguard", e); } } private void startActivityForCurrentUser(final Intent intent, final Bundle options, Handler worker, final Runnable onStarted) { final UserHandle user = new UserHandle(UserHandle.USER_CURRENT); if (worker == null || onStarted == null) { getContext().startActivityAsUser(intent, options, user); return; } // if worker + onStarted are supplied, run blocking activity launch call in the background worker.post(new Runnable(){ @Override public void run() { try { WaitResult result = ActivityManagerNative.getDefault().startActivityAndWait( null /*caller*/, null /*caller pkg*/, intent, intent.resolveTypeIfNeeded(getContext().getContentResolver()), null /*resultTo*/, null /*resultWho*/, 0 /*requestCode*/, Intent.FLAG_ACTIVITY_NEW_TASK, null /*profilerInfo*/, options, user.getIdentifier()); if (DEBUG) Log.d(TAG, String.format("waitResult[%s,%s,%s,%s] at %s", result.result, result.thisTime, result.totalTime, result.who, SystemClock.uptimeMillis())); } catch (RemoteException e) { Log.w(TAG, "Error starting activity", e); return; } try { onStarted.run(); } catch (Throwable t) { Log.w(TAG, "Error running onStarted callback", t); } }}); } private Intent getCameraIntent() { return mustLaunchSecurely() ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT; } private boolean wouldLaunchResolverActivity(Intent intent) { PackageManager packageManager = getContext().getPackageManager(); ResolveInfo resolved = packageManager.resolveActivityAsUser(intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser()); List appList = packageManager.queryIntentActivitiesAsUser( intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser()); return wouldLaunchResolverActivity(resolved, appList); } private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List appList) { // If the list contains the above resolved activity, then it can't be // ResolverActivity itself. for (int i = 0; i < appList.size(); i++) { ResolveInfo tmp = appList.get(i); if (tmp.activityInfo.name.equals(resolved.activityInfo.name) && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) { return false; } } return true; } }