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;
39
40import com.android.keyguard.KeyguardHostView.OnDismissAction;
41
42import java.util.List;
43
44public 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