/* * 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.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Point; 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 WindowManager mWindowManager; private final Point mRenderedSize = new Point(); private final int[] mScreenLocation = new int[2]; private View mWidgetView; private long mLaunchCameraStart; private boolean mActive; private boolean mTransitioning; private boolean mRecovering; private boolean mDown; 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 mRecoverRunnable = new Runnable() { @Override public void run() { recover(); }}; private final Runnable mRecoverEndAction = new Runnable() { @Override public void run() { if (!mRecovering) return; mCallbacks.onCameraLaunchedUnsuccessfully(); reset(); }}; 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 CameraWidgetFrame(Context context, Callbacks callbacks, KeyguardActivityLauncher activityLauncher) { super(context); mCallbacks = callbacks; mActivityLauncher = activityLauncher; mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback); 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 widgetView = widgetInfo.layoutId > 0 ? inflateWidgetView(context, widgetInfo) : inflateGenericWidgetView(context); if (widgetView == null) return null; ImageView preview = new ImageView(context); preview.setLayoutParams(new FrameLayout.LayoutParams( FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT)); preview.setScaleType(ScaleType.FIT_CENTER); preview.setContentDescription(preview.getContext().getString( R.string.keyguard_accessibility_camera)); CameraWidgetFrame cameraWidgetFrame = new CameraWidgetFrame(context, callbacks, launcher); cameraWidgetFrame.addView(preview); cameraWidgetFrame.mWidgetView = widgetView; preview.setOnClickListener(cameraWidgetFrame); return cameraWidgetFrame; } 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; } public void render() { final Throwable[] thrown = new Throwable[1]; final Bitmap[] offscreen = new Bitmap[1]; try { final int width = getRootView().getWidth(); final int height = getRootView().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; } final long start = SystemClock.uptimeMillis(); offscreen[0] = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); final Canvas c = new Canvas(offscreen[0]); mWidgetView.measure( MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); mWidgetView.layout(0, 0, width, height); mWidgetView.draw(c); final long end = SystemClock.uptimeMillis(); if (DEBUG) Log.d(TAG, String.format( "Rendered camera widget in %sms size=%sx%s instance=%s at %s", end - start, width, height, instanceId(), end)); mRenderedSize.set(width, height); } catch (Throwable t) { thrown[0] = t; } mHandler.post(new Runnable() { @Override public void run() { if (thrown[0] == null) { try { ((ImageView) getChildAt(0)).setImageBitmap(offscreen[0]); } catch (Throwable t) { thrown[0] = t; } } if (thrown[0] == null) return; Log.w(TAG, "Error rendering camera widget", thrown[0]); try { removeAllViews(); final View genericView = inflateGenericWidgetView(mContext); addView(genericView); } catch (Throwable t) { Log.w(TAG, "Error inflating generic camera widget", t); } }}); } private void transitionToCamera() { if (mTransitioning || mDown) return; mTransitioning = true; final View child = getChildAt(0); final View root = getRootView(); final int startWidth = child.getWidth(); final int startHeight = child.getHeight(); final int finishWidth = root.getWidth(); final int finishHeight = root.getHeight(); final float scaleX = (float) finishWidth / startWidth; final float scaleY = (float) finishHeight / startHeight; final float scale = Math.round( Math.max(scaleX, scaleY) * 100) / 100f; final int[] loc = new int[2]; root.getLocationInWindow(loc); final int finishCenter = loc[1] + finishHeight / 2; child.getLocationInWindow(loc); final int startCenter = loc[1] + startHeight / 2; if (DEBUG) Log.d(TAG, String.format("Transitioning to camera. " + "(start=%sx%s, finish=%sx%s, scale=%s,%s, startCenter=%s, finishCenter=%s)", startWidth, startHeight, finishWidth, finishHeight, scaleX, scaleY, startCenter, finishCenter)); enableWindowExitAnimation(false); animate() .scaleX(scale) .scaleY(scale) .translationY(finishCenter - startCenter) .setDuration(WIDGET_ANIMATION_DURATION) .withEndAction(mTransitionToCameraEndAction) .start(); mCallbacks.onLaunchingCamera(); } private void recover() { if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis()); mRecovering = true; animate() .scaleX(1) .scaleY(1) .translationY(0) .setDuration(WIDGET_ANIMATION_DURATION) .withEndAction(mRecoverEndAction) .start(); } @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(mScreenLocation); int rawBottom = mScreenLocation[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; mRecovering = false; mDown = false; cancelTransitionToCamera(); mHandler.removeCallbacks(mRecoverRunnable); animate().cancel(); setScaleX(1); setScaleY(1); setTranslationY(0); 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())); final Handler worker = getWorkerHandler(); (worker != null ? worker : mHandler).post(mRenderRunnable); super.onSizeChanged(w, h, oldw, oldh); } 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; mRecovering = 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()); } }