blob: 1978ded7c1f8d8673ba34f77b347b10fd8fc68a2 [file] [log] [blame]
/*
* Copyright (C) 2012 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.keyguard;
import com.android.internal.widget.LockPatternUtils;
import android.app.ActivityManagerNative;
import android.app.ActivityOptions;
import android.app.IActivityManager.WaitResult;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProviderInfo;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.Handler;
import android.os.RemoteException;
import android.os.SystemClock;
import android.os.UserHandle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.WindowManager;
import android.view.WindowManagerGlobal;
import com.android.keyguard.KeyguardHostView.OnDismissAction;
import java.util.List;
public abstract class KeyguardActivityLauncher {
private static final String TAG = KeyguardActivityLauncher.class.getSimpleName();
private static final boolean DEBUG = KeyguardConstants.DEBUG;
private static final String META_DATA_KEYGUARD_LAYOUT = "com.android.keyguard.layout";
private static final Intent SECURE_CAMERA_INTENT =
new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE)
.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
private static final Intent INSECURE_CAMERA_INTENT =
new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA);
abstract Context getContext();
abstract LockPatternUtils getLockPatternUtils();
abstract void setOnDismissAction(OnDismissAction action);
abstract void requestDismissKeyguard();
public static class CameraWidgetInfo {
public String contextPackage;
public int layoutId;
}
public CameraWidgetInfo getCameraWidgetInfo() {
CameraWidgetInfo info = new CameraWidgetInfo();
Intent intent = getCameraIntent();
PackageManager packageManager = getContext().getPackageManager();
final List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
if (appList.size() == 0) {
if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Nothing found");
return null;
}
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
PackageManager.MATCH_DEFAULT_ONLY | PackageManager.GET_META_DATA,
getLockPatternUtils().getCurrentUser());
if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): resolved: " + resolved);
if (wouldLaunchResolverActivity(resolved, appList)) {
if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): Would launch resolver");
return info;
}
if (resolved == null || resolved.activityInfo == null) {
return null;
}
if (resolved.activityInfo.metaData == null || resolved.activityInfo.metaData.isEmpty()) {
if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no metadata found");
return info;
}
int layoutId = resolved.activityInfo.metaData.getInt(META_DATA_KEYGUARD_LAYOUT);
if (layoutId == 0) {
if (DEBUG) Log.d(TAG, "getCameraWidgetInfo(): no layout specified");
return info;
}
info.contextPackage = resolved.activityInfo.packageName;
info.layoutId = layoutId;
return info;
}
public void launchCamera(Handler worker, Runnable onSecureCameraStarted) {
LockPatternUtils lockPatternUtils = getLockPatternUtils();
// Workaround to avoid camera release/acquisition race when resuming face unlock
// after showing lockscreen camera (bug 11063890).
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
updateMonitor.setAlternateUnlockEnabled(false);
if (mustLaunchSecurely()) {
// Launch the secure version of the camera
if (wouldLaunchResolverActivity(SECURE_CAMERA_INTENT)) {
// TODO: Show disambiguation dialog instead.
// For now, we'll treat this like launching any other app from secure keyguard.
// When they do, user sees the system's ResolverActivity which lets them choose
// which secure camera to use.
launchActivity(SECURE_CAMERA_INTENT, false, false, null, null);
} else {
launchActivity(SECURE_CAMERA_INTENT, true, false, worker, onSecureCameraStarted);
}
} else {
// Launch the normal camera
launchActivity(INSECURE_CAMERA_INTENT, false, false, null, null);
}
}
private boolean mustLaunchSecurely() {
LockPatternUtils lockPatternUtils = getLockPatternUtils();
KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(getContext());
int currentUser = lockPatternUtils.getCurrentUser();
return lockPatternUtils.isSecure() && !updateMonitor.getUserHasTrust(currentUser);
}
public void launchWidgetPicker(int appWidgetId) {
Intent pickIntent = new Intent(AppWidgetManager.ACTION_KEYGUARD_APPWIDGET_PICK);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
pickIntent.putExtra(AppWidgetManager.EXTRA_CUSTOM_SORT, false);
pickIntent.putExtra(AppWidgetManager.EXTRA_CATEGORY_FILTER,
AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD);
Bundle options = new Bundle();
options.putInt(AppWidgetManager.OPTION_APPWIDGET_HOST_CATEGORY,
AppWidgetProviderInfo.WIDGET_CATEGORY_KEYGUARD);
pickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
pickIntent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
launchActivity(pickIntent, false, false, null, null);
}
/**
* Launches the said intent for the current foreground user.
*
* @param intent
* @param showsWhileLocked true if the activity can be run on top of keyguard.
* See {@link WindowManager#FLAG_SHOW_WHEN_LOCKED}
* @param useDefaultAnimations true if default transitions should be used, else suppressed.
* @param worker if supplied along with onStarted, used to launch the blocking activity call.
* @param onStarted if supplied along with worker, called after activity is started.
*/
public void launchActivity(final Intent intent,
boolean showsWhileLocked,
boolean useDefaultAnimations,
final Handler worker,
final Runnable onStarted) {
final Context context = getContext();
final Bundle animation = useDefaultAnimations ? null
: ActivityOptions.makeCustomAnimation(context, 0, 0).toBundle();
launchActivityWithAnimation(intent, showsWhileLocked, animation, worker, onStarted);
}
public void launchActivityWithAnimation(final Intent intent,
boolean showsWhileLocked,
final Bundle animation,
final Handler worker,
final Runnable onStarted) {
LockPatternUtils lockPatternUtils = getLockPatternUtils();
intent.addFlags(
Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_SINGLE_TOP
| Intent.FLAG_ACTIVITY_CLEAR_TOP);
boolean mustLaunchSecurely = mustLaunchSecurely();
if (!mustLaunchSecurely || showsWhileLocked) {
if (!mustLaunchSecurely) {
dismissKeyguardOnNextActivity();
}
try {
if (DEBUG) Log.d(TAG, String.format("Starting activity for intent %s at %s",
intent, SystemClock.uptimeMillis()));
startActivityForCurrentUser(intent, animation, worker, onStarted);
} catch (ActivityNotFoundException e) {
Log.w(TAG, "Activity not found for intent + " + intent.getAction());
}
} else {
// Create a runnable to start the activity and ask the user to enter their
// credentials.
setOnDismissAction(new OnDismissAction() {
@Override
public boolean onDismiss() {
dismissKeyguardOnNextActivity();
startActivityForCurrentUser(intent, animation, worker, onStarted);
return true;
}
});
requestDismissKeyguard();
}
}
private void dismissKeyguardOnNextActivity() {
try {
WindowManagerGlobal.getWindowManagerService().dismissKeyguard();
} catch (RemoteException e) {
Log.w(TAG, "Error dismissing keyguard", e);
}
}
private void startActivityForCurrentUser(final Intent intent, final Bundle options,
Handler worker, final Runnable onStarted) {
final UserHandle user = new UserHandle(UserHandle.USER_CURRENT);
if (worker == null || onStarted == null) {
getContext().startActivityAsUser(intent, options, user);
return;
}
// if worker + onStarted are supplied, run blocking activity launch call in the background
worker.post(new Runnable(){
@Override
public void run() {
try {
WaitResult result = ActivityManagerNative.getDefault().startActivityAndWait(
null /*caller*/,
null /*caller pkg*/,
intent,
intent.resolveTypeIfNeeded(getContext().getContentResolver()),
null /*resultTo*/,
null /*resultWho*/,
0 /*requestCode*/,
Intent.FLAG_ACTIVITY_NEW_TASK,
null /*profilerInfo*/,
options,
user.getIdentifier());
if (DEBUG) Log.d(TAG, String.format("waitResult[%s,%s,%s,%s] at %s",
result.result, result.thisTime, result.totalTime, result.who,
SystemClock.uptimeMillis()));
} catch (RemoteException e) {
Log.w(TAG, "Error starting activity", e);
return;
}
try {
onStarted.run();
} catch (Throwable t) {
Log.w(TAG, "Error running onStarted callback", t);
}
}});
}
private Intent getCameraIntent() {
return mustLaunchSecurely() ? SECURE_CAMERA_INTENT : INSECURE_CAMERA_INTENT;
}
private boolean wouldLaunchResolverActivity(Intent intent) {
PackageManager packageManager = getContext().getPackageManager();
ResolveInfo resolved = packageManager.resolveActivityAsUser(intent,
PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
List<ResolveInfo> appList = packageManager.queryIntentActivitiesAsUser(
intent, PackageManager.MATCH_DEFAULT_ONLY, getLockPatternUtils().getCurrentUser());
return wouldLaunchResolverActivity(resolved, appList);
}
private boolean wouldLaunchResolverActivity(ResolveInfo resolved, List<ResolveInfo> appList) {
// If the list contains the above resolved activity, then it can't be
// ResolverActivity itself.
for (int i = 0; i < appList.size(); i++) {
ResolveInfo tmp = appList.get(i);
if (tmp.activityInfo.name.equals(resolved.activityInfo.name)
&& tmp.activityInfo.packageName.equals(resolved.activityInfo.packageName)) {
return false;
}
}
return true;
}
}