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