blob: 762711d1b745c827252dd80ebd9d56cb650a2df3 [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.internal.policy.impl.keyguard;
import android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.Color;
import android.graphics.Point;
import android.graphics.Rect;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;
import com.android.internal.R;
import com.android.internal.policy.impl.keyguard.KeyguardActivityLauncher.CameraWidgetInfo;
public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnClickListener {
private static final String TAG = CameraWidgetFrame.class.getSimpleName();
private static final boolean DEBUG = KeyguardHostView.DEBUG;
private static final int WIDGET_ANIMATION_DURATION = 250; // ms
private static final int WIDGET_WAIT_DURATION = 650; // ms
private static final int RECOVERY_DELAY = 1000; // ms
interface Callbacks {
void onLaunchingCamera();
void onCameraLaunchedSuccessfully();
void onCameraLaunchedUnsuccessfully();
}
private final Handler mHandler = new Handler();
private final KeyguardActivityLauncher mActivityLauncher;
private final Callbacks mCallbacks;
private final CameraWidgetInfo mWidgetInfo;
private final WindowManager mWindowManager;
private final Point mRenderedSize = new Point();
private final int[] mTmpLoc = new int[2];
private final Rect mTmpRect = new Rect();
private long mLaunchCameraStart;
private boolean mActive;
private boolean mTransitioning;
private boolean mDown;
private FixedSizeFrameLayout mPreview;
private View mFullscreenPreview;
private final Runnable mTransitionToCameraRunnable = new Runnable() {
@Override
public void run() {
transitionToCamera();
}};
private final Runnable mTransitionToCameraEndAction = new Runnable() {
@Override
public void run() {
if (!mTransitioning)
return;
Handler worker = getWorkerHandler() != null ? getWorkerHandler() : mHandler;
mLaunchCameraStart = SystemClock.uptimeMillis();
if (DEBUG) Log.d(TAG, "Launching camera at " + mLaunchCameraStart);
mActivityLauncher.launchCamera(worker, mSecureCameraActivityStartedRunnable);
}};
private final Runnable mPostTransitionToCameraEndAction = new Runnable() {
@Override
public void run() {
mHandler.post(mTransitionToCameraEndAction);
}};
private final Runnable mRecoverRunnable = new Runnable() {
@Override
public void run() {
recover();
}};
private final Runnable mRenderRunnable = new Runnable() {
@Override
public void run() {
render();
}};
private final Runnable mSecureCameraActivityStartedRunnable = new Runnable() {
@Override
public void run() {
onSecureCameraActivityStarted();
}
};
private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
private boolean mShowing;
void onKeyguardVisibilityChanged(boolean showing) {
if (mShowing == showing)
return;
mShowing = showing;
CameraWidgetFrame.this.onKeyguardVisibilityChanged(mShowing);
};
};
private static final class FixedSizeFrameLayout extends FrameLayout {
int width;
int height;
FixedSizeFrameLayout(Context context) {
super(context);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
measureChildren(
MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
setMeasuredDimension(width, height);
}
}
private CameraWidgetFrame(Context context, Callbacks callbacks,
KeyguardActivityLauncher activityLauncher,
CameraWidgetInfo widgetInfo, View previewWidget) {
super(context);
mCallbacks = callbacks;
mActivityLauncher = activityLauncher;
mWidgetInfo = widgetInfo;
mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback);
mPreview = new FixedSizeFrameLayout(context);
mPreview.addView(previewWidget);
addView(mPreview);
View clickBlocker = new View(context);
clickBlocker.setBackgroundColor(Color.TRANSPARENT);
clickBlocker.setOnClickListener(this);
addView(clickBlocker);
setContentDescription(context.getString(R.string.keyguard_accessibility_camera));
if (DEBUG) Log.d(TAG, "new CameraWidgetFrame instance " + instanceId());
}
public static CameraWidgetFrame create(Context context, Callbacks callbacks,
KeyguardActivityLauncher launcher) {
if (context == null || callbacks == null || launcher == null)
return null;
CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo();
if (widgetInfo == null)
return null;
View previewWidget = getPreviewWidget(context, widgetInfo);
if (previewWidget == null)
return null;
return new CameraWidgetFrame(context, callbacks, launcher, widgetInfo, previewWidget);
}
private static View getPreviewWidget(Context context, CameraWidgetInfo widgetInfo) {
return widgetInfo.layoutId > 0 ?
inflateWidgetView(context, widgetInfo) :
inflateGenericWidgetView(context);
}
private static View inflateWidgetView(Context context, CameraWidgetInfo widgetInfo) {
if (DEBUG) Log.d(TAG, "inflateWidgetView: " + widgetInfo.contextPackage);
View widgetView = null;
Exception exception = null;
try {
Context cameraContext = context.createPackageContext(
widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
LayoutInflater cameraInflater = (LayoutInflater)
cameraContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
cameraInflater = cameraInflater.cloneInContext(cameraContext);
widgetView = cameraInflater.inflate(widgetInfo.layoutId, null, false);
} catch (NameNotFoundException e) {
exception = e;
} catch (RuntimeException e) {
exception = e;
}
if (exception != null) {
Log.w(TAG, "Error creating camera widget view", exception);
}
return widgetView;
}
private static View inflateGenericWidgetView(Context context) {
if (DEBUG) Log.d(TAG, "inflateGenericWidgetView");
ImageView iv = new ImageView(context);
iv.setImageResource(com.android.internal.R.drawable.ic_lockscreen_camera);
iv.setScaleType(ScaleType.CENTER);
iv.setBackgroundColor(Color.argb(127, 0, 0, 0));
return iv;
}
private void render() {
final View root = getRootView();
final int width = root.getWidth();
final int height = root.getHeight();
if (mRenderedSize.x == width && mRenderedSize.y == height) {
if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s", width, height));
return;
}
if (width == 0 || height == 0) {
return;
}
mPreview.width = width;
mPreview.height = height;
mPreview.requestLayout();
final int thisWidth = getWidth() - getPaddingLeft() - getPaddingRight();
final int thisHeight = getHeight() - getPaddingTop() - getPaddingBottom();
final float pvScaleX = (float) thisWidth / width;
final float pvScaleY = (float) thisHeight / height;
final float pvScale = Math.min(pvScaleX, pvScaleY);
final int pvWidth = (int) (pvScale * width);
final int pvHeight = (int) (pvScale * height);
final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0;
final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0;
mPreview.setPivotX(0);
mPreview.setPivotY(0);
mPreview.setScaleX(pvScale);
mPreview.setScaleY(pvScale);
mPreview.setTranslationX(pvTransX);
mPreview.setTranslationY(pvTransY);
mRenderedSize.set(width, height);
if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s instance=%s",
width, height, instanceId()));
}
private void transitionToCamera() {
if (mTransitioning || mDown) return;
mTransitioning = true;
enableWindowExitAnimation(false);
mPreview.getLocationInWindow(mTmpLoc);
final float pvHeight = mPreview.getHeight() * mPreview.getScaleY();
final float pvCenter = mTmpLoc[1] + pvHeight / 2f;
final ViewGroup root = (ViewGroup) getRootView();
if (mFullscreenPreview == null) {
mFullscreenPreview = getPreviewWidget(mContext, mWidgetInfo);
mFullscreenPreview.setClickable(false);
root.addView(mFullscreenPreview);
}
root.getWindowVisibleDisplayFrame(mTmpRect);
final float fsHeight = mTmpRect.height();
final float fsCenter = mTmpRect.top + fsHeight / 2;
final float fsScaleY = pvHeight / fsHeight;
final float fsTransY = pvCenter - fsCenter;
final float fsScaleX = mPreview.getScaleX();
mPreview.setVisibility(View.GONE);
mFullscreenPreview.setVisibility(View.VISIBLE);
mFullscreenPreview.setTranslationY(fsTransY);
mFullscreenPreview.setScaleX(fsScaleX);
mFullscreenPreview.setScaleY(fsScaleY);
mFullscreenPreview
.animate()
.scaleX(1)
.scaleY(1)
.translationX(0)
.translationY(0)
.setDuration(WIDGET_ANIMATION_DURATION)
.withEndAction(mPostTransitionToCameraEndAction)
.start();
mCallbacks.onLaunchingCamera();
}
private void recover() {
if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis());
mCallbacks.onCameraLaunchedUnsuccessfully();
reset();
}
@Override
public void setOnLongClickListener(OnLongClickListener l) {
// ignore
}
@Override
public void onClick(View v) {
if (DEBUG) Log.d(TAG, "clicked");
if (mTransitioning) return;
if (mActive) {
cancelTransitionToCamera();
transitionToCamera();
}
}
@Override
protected void onDetachedFromWindow() {
if (DEBUG) Log.d(TAG, "onDetachedFromWindow: instance " + instanceId()
+ " at " + SystemClock.uptimeMillis());
super.onDetachedFromWindow();
KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback);
cancelTransitionToCamera();
mHandler.removeCallbacks(mRecoverRunnable);
}
@Override
public void onActive(boolean isActive) {
mActive = isActive;
if (mActive) {
rescheduleTransitionToCamera();
} else {
reset();
}
}
@Override
public boolean onUserInteraction(MotionEvent event) {
if (mTransitioning) {
if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning");
return true;
}
getLocationOnScreen(mTmpLoc);
int rawBottom = mTmpLoc[1] + getHeight();
if (event.getRawY() > rawBottom) {
if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget");
return true;
}
int action = event.getAction();
mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE;
if (mActive) {
rescheduleTransitionToCamera();
}
if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten");
return false;
}
@Override
protected void onFocusLost() {
if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis());
cancelTransitionToCamera();
super.onFocusLost();
}
public void onScreenTurnedOff() {
if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
reset();
}
private void rescheduleTransitionToCamera() {
if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis());
mHandler.removeCallbacks(mTransitionToCameraRunnable);
mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION);
}
private void cancelTransitionToCamera() {
if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis());
mHandler.removeCallbacks(mTransitionToCameraRunnable);
}
private void onCameraLaunched() {
mCallbacks.onCameraLaunchedSuccessfully();
reset();
}
private void reset() {
if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis());
mLaunchCameraStart = 0;
mTransitioning = false;
mDown = false;
cancelTransitionToCamera();
mHandler.removeCallbacks(mRecoverRunnable);
mPreview.setVisibility(View.VISIBLE);
if (mFullscreenPreview != null) {
mFullscreenPreview.animate().cancel();
mFullscreenPreview.setVisibility(View.GONE);
}
enableWindowExitAnimation(true);
}
@Override
protected void onSizeChanged(int w, int h, int oldw, int oldh) {
if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s",
w, h, oldw, oldh, SystemClock.uptimeMillis()));
mHandler.post(mRenderRunnable);
super.onSizeChanged(w, h, oldw, oldh);
}
@Override
public void onBouncerShowing(boolean showing) {
if (showing) {
mTransitioning = false;
mHandler.post(mRecoverRunnable);
}
}
private void enableWindowExitAnimation(boolean isEnabled) {
View root = getRootView();
ViewGroup.LayoutParams lp = root.getLayoutParams();
if (!(lp instanceof WindowManager.LayoutParams))
return;
WindowManager.LayoutParams wlp = (WindowManager.LayoutParams) lp;
int newWindowAnimations = isEnabled ? com.android.internal.R.style.Animation_LockScreen : 0;
if (newWindowAnimations != wlp.windowAnimations) {
if (DEBUG) Log.d(TAG, "setting windowAnimations to: " + newWindowAnimations
+ " at " + SystemClock.uptimeMillis());
wlp.windowAnimations = newWindowAnimations;
mWindowManager.updateViewLayout(root, wlp);
}
}
private void onKeyguardVisibilityChanged(boolean showing) {
if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged " + showing
+ " at " + SystemClock.uptimeMillis());
if (mTransitioning && !showing) {
mTransitioning = false;
mHandler.removeCallbacks(mRecoverRunnable);
if (mLaunchCameraStart > 0) {
long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart;
if (DEBUG) Log.d(TAG, String.format("Camera took %sms to launch", launchTime));
mLaunchCameraStart = 0;
onCameraLaunched();
}
}
}
private void onSecureCameraActivityStarted() {
if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis());
mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY);
}
private String instanceId() {
return Integer.toHexString(hashCode());
}
}