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.internal.policy.impl.keyguard;
18
19import android.app.ActivityManagerNative;
20import android.app.ActivityOptions;
21import android.app.IActivityManager.WaitResult;
22import android.appwidget.AppWidgetManager;
23import android.appwidget.AppWidgetProviderInfo;
24import android.content.ActivityNotFoundException;
25import android.content.Context;
26import android.content.Intent;
27import android.content.pm.PackageManager;
28import android.content.pm.ResolveInfo;
29import android.os.Bundle;
30import android.os.Handler;
31import android.os.RemoteException;
32import android.os.SystemClock;
33import android.os.UserHandle;
34import android.provider.MediaStore;
35import android.util.Log;
36import android.view.WindowManager;
37
38import com.android.internal.policy.impl.keyguard.KeyguardHostView.OnDismissAction;
39import com.android.internal.widget.LockPatternUtils;
40
41import java.util.List;
42
43public 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                            intent,
223                            intent.resolveTypeIfNeeded(getContext().getContentResolver()),
224                            null /*resultTo*/,
225                            null /*resultWho*/,
226                            0 /*requestCode*/,
227                            Intent.FLAG_ACTIVITY_NEW_TASK,
228                            null /*profileFile*/,
229                            null /*profileFd*/,
230                            options,
231                            user.getIdentifier());
232                    if (DEBUG) Log.d(TAG, String.format("waitResult[%s,%s,%s,%s] at %s",
233                            result.result, result.thisTime, result.totalTime, result.who,
234                            SystemClock.uptimeMillis()));
235                } catch (RemoteException e) {
236                    Log.w(TAG, "Error starting activity", e);
237                    return;
238                }
239                try {
240                    onStarted.run();
241                } catch (Throwable t) {
242                    Log.w(TAG, "Error running onStarted callback", t);
243                }
244            }});
245    }
246
247    private Intent getCameraIntent() {
248        return getLockPatternUtils().isSecure() ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
249    }
250
251    private boolean wouldLaunchResolverActivity(Intent intent) {
252        PackageManager packageManager = getContext().getPackageManager();
253        ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
254                PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
255        List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
256                intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
257        return wouldLaunchResolverActivity(resolved, appList);
258    }
259
260    private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
261        // If the list contains the above resolved activity, then it can't be
262        // ResolverActivity itself.
263        for (int i = 0; i < appList.size(); i++) {
264            ResolveInfo tmp = appList.get(i);
265            if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
266                    && tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
267                return false;
268            }
269        }
270        return true;
271    }
272}
273