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
17package com.android.keyguard;
18
19import com.android.internal.widget.LockPatternUtils;
20
21import android.app.ActivityManagerNative;
22import android.app.ActivityOptions;
23import android.app.IActivityManager.WaitResult;
24import android.appwidget.AppWidgetManager;
25import android.appwidget.AppWidgetProviderInfo;
26import android.content.ActivityNotFoundException;
27import android.content.Context;
28import android.content.Intent;
29import android.content.pm.PackageManager;
30import android.content.pm.ResolveInfo;
31import android.os.Bundle;
32import android.os.Handler;
33import android.os.RemoteException;
34import android.os.SystemClock;
35import android.os.UserHandle;
36import android.provider.MediaStore;
37import android.util.Log;
38import android.view.WindowManager;
39import android.view.WindowManagerGlobal;
40
41import com.android.keyguard.KeyguardHostView.OnDismissAction;
42
43import java.util.List;
44
45public 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