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.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