Home | History | Annotate | Download | only in keyguard
      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