2 * Copyright (C) 2012 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.keyguard;
19 import android.content.Context;
20 import android.content.pm.PackageManager.NameNotFoundException;
21 import android.graphics.Color;
22 import android.graphics.Paint;
23 import android.graphics.Point;
24 import android.graphics.Rect;
25 import android.os.Handler;
26 import android.os.SystemClock;
27 import android.util.Log;
28 import android.view.Gravity;
29 import android.view.LayoutInflater;
30 import android.view.MotionEvent;
31 import android.view.View;
32 import android.view.ViewGroup;
33 import android.view.WindowManager;
34 import android.widget.FrameLayout;
35 import android.widget.ImageView;
36 import android.widget.ImageView.ScaleType;
38 import com.android.keyguard.KeyguardActivityLauncher.CameraWidgetInfo;
40 public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnClickListener {
41 private static final String TAG = CameraWidgetFrame.class.getSimpleName();
42 private static final boolean DEBUG = KeyguardHostView.DEBUG;
43 private static final int WIDGET_ANIMATION_DURATION = 250; // ms
44 private static final int WIDGET_WAIT_DURATION = 650; // ms
45 private static final int RECOVERY_DELAY = 1000; // ms
48 void onLaunchingCamera();
49 void onCameraLaunchedSuccessfully();
50 void onCameraLaunchedUnsuccessfully();
53 private final Handler mHandler = new Handler();
54 private final KeyguardActivityLauncher mActivityLauncher;
55 private final Callbacks mCallbacks;
56 private final CameraWidgetInfo mWidgetInfo;
57 private final WindowManager mWindowManager;
58 private final Point mRenderedSize = new Point();
59 private final int[] mTmpLoc = new int[2];
61 private long mLaunchCameraStart;
62 private boolean mActive;
63 private boolean mTransitioning;
64 private boolean mDown;
66 private final Rect mInsets = new Rect();
68 private FixedSizeFrameLayout mPreview;
69 private View mFullscreenPreview;
70 private View mFakeNavBar;
72 private final Runnable mTransitionToCameraRunnable = new Runnable() {
78 private final Runnable mTransitionToCameraEndAction = new Runnable() {
83 Handler worker = getWorkerHandler() != null ? getWorkerHandler() : mHandler;
84 mLaunchCameraStart = SystemClock.uptimeMillis();
85 if (DEBUG) Log.d(TAG, "Launching camera at " + mLaunchCameraStart);
86 mActivityLauncher.launchCamera(worker, mSecureCameraActivityStartedRunnable);
89 private final Runnable mPostTransitionToCameraEndAction = new Runnable() {
92 mHandler.post(mTransitionToCameraEndAction);
95 private final Runnable mRecoverRunnable = new Runnable() {
101 private final Runnable mRenderRunnable = new Runnable() {
107 private final Runnable mSecureCameraActivityStartedRunnable = new Runnable() {
110 onSecureCameraActivityStarted();
114 private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
115 private boolean mShowing;
116 void onKeyguardVisibilityChanged(boolean showing) {
117 if (mShowing == showing)
120 CameraWidgetFrame.this.onKeyguardVisibilityChanged(mShowing);
124 private static final class FixedSizeFrameLayout extends FrameLayout {
128 FixedSizeFrameLayout(Context context) {
133 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
135 MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
136 MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
137 setMeasuredDimension(width, height);
141 private CameraWidgetFrame(Context context, Callbacks callbacks,
142 KeyguardActivityLauncher activityLauncher,
143 CameraWidgetInfo widgetInfo, View previewWidget) {
145 mCallbacks = callbacks;
146 mActivityLauncher = activityLauncher;
147 mWidgetInfo = widgetInfo;
148 mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
149 KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback);
151 mPreview = new FixedSizeFrameLayout(context);
152 mPreview.addView(previewWidget);
155 View clickBlocker = new View(context);
156 clickBlocker.setBackgroundColor(Color.TRANSPARENT);
157 clickBlocker.setOnClickListener(this);
158 addView(clickBlocker);
160 setContentDescription(context.getString(R.string.keyguard_accessibility_camera));
161 if (DEBUG) Log.d(TAG, "new CameraWidgetFrame instance " + instanceId());
164 public static CameraWidgetFrame create(Context context, Callbacks callbacks,
165 KeyguardActivityLauncher launcher) {
166 if (context == null || callbacks == null || launcher == null)
169 CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo();
170 if (widgetInfo == null)
172 View previewWidget = getPreviewWidget(context, widgetInfo);
173 if (previewWidget == null)
176 return new CameraWidgetFrame(context, callbacks, launcher, widgetInfo, previewWidget);
179 private static View getPreviewWidget(Context context, CameraWidgetInfo widgetInfo) {
180 return widgetInfo.layoutId > 0 ?
181 inflateWidgetView(context, widgetInfo) :
182 inflateGenericWidgetView(context);
185 private static View inflateWidgetView(Context context, CameraWidgetInfo widgetInfo) {
186 if (DEBUG) Log.d(TAG, "inflateWidgetView: " + widgetInfo.contextPackage);
187 View widgetView = null;
188 Exception exception = null;
190 Context cameraContext = context.createPackageContext(
191 widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
192 LayoutInflater cameraInflater = (LayoutInflater)
193 cameraContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
194 cameraInflater = cameraInflater.cloneInContext(cameraContext);
195 widgetView = cameraInflater.inflate(widgetInfo.layoutId, null, false);
196 } catch (NameNotFoundException e) {
198 } catch (RuntimeException e) {
201 if (exception != null) {
202 Log.w(TAG, "Error creating camera widget view", exception);
207 private static View inflateGenericWidgetView(Context context) {
208 if (DEBUG) Log.d(TAG, "inflateGenericWidgetView");
209 ImageView iv = new ImageView(context);
210 iv.setImageResource(R.drawable.ic_lockscreen_camera);
211 iv.setScaleType(ScaleType.CENTER);
212 iv.setBackgroundColor(Color.argb(127, 0, 0, 0));
216 private void render() {
217 final View root = getRootView();
218 final int width = root.getWidth() - mInsets.right; // leave room
219 final int height = root.getHeight() - mInsets.bottom; // for bars
220 if (mRenderedSize.x == width && mRenderedSize.y == height) {
221 if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s %d%%",
222 width, height, (int)(100*mPreview.getScaleX())));
225 if (width == 0 || height == 0) {
229 mPreview.width = width;
230 mPreview.height = height;
231 mPreview.requestLayout();
233 final int thisWidth = getWidth() - getPaddingLeft() - getPaddingRight();
234 final int thisHeight = getHeight() - getPaddingTop() - getPaddingBottom();
236 final float pvScaleX = (float) thisWidth / width;
237 final float pvScaleY = (float) thisHeight / height;
238 final float pvScale = Math.min(pvScaleX, pvScaleY);
240 final int pvWidth = (int) (pvScale * width);
241 final int pvHeight = (int) (pvScale * height);
243 final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0;
244 final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0;
246 mPreview.setPivotX(0);
247 mPreview.setPivotY(0);
248 mPreview.setScaleX(pvScale);
249 mPreview.setScaleY(pvScale);
250 mPreview.setTranslationX(pvTransX);
251 mPreview.setTranslationY(pvTransY);
253 mRenderedSize.set(width, height);
254 if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s %d%% instance=%s",
255 width, height, (int)(100*mPreview.getScaleX()), instanceId()));
258 private void transitionToCamera() {
259 if (mTransitioning || mDown) return;
261 mTransitioning = true;
263 enableWindowExitAnimation(false);
265 final int navHeight = mInsets.bottom;
266 final int navWidth = mInsets.right;
268 mPreview.getLocationInWindow(mTmpLoc);
269 final float pvHeight = mPreview.getHeight() * mPreview.getScaleY();
270 final float pvCenter = mTmpLoc[1] + pvHeight / 2f;
272 final ViewGroup root = (ViewGroup) getRootView();
275 Log.d(TAG, "root = " + root.getLeft() + "," + root.getTop() + " "
276 + root.getWidth() + "x" + root.getHeight());
279 if (mFullscreenPreview == null) {
280 mFullscreenPreview = getPreviewWidget(mContext, mWidgetInfo);
281 mFullscreenPreview.setClickable(false);
282 root.addView(mFullscreenPreview, new FrameLayout.LayoutParams(
283 root.getWidth() - navWidth,
284 root.getHeight() - navHeight));
287 final float fsHeight = root.getHeight() - navHeight;
288 final float fsCenter = root.getTop() + fsHeight / 2;
290 final float fsScaleY = mPreview.getScaleY();
291 final float fsTransY = pvCenter - fsCenter;
292 final float fsScaleX = fsScaleY;
294 mPreview.setVisibility(View.GONE);
295 mFullscreenPreview.setVisibility(View.VISIBLE);
296 mFullscreenPreview.setTranslationY(fsTransY);
297 mFullscreenPreview.setScaleX(fsScaleX);
298 mFullscreenPreview.setScaleY(fsScaleY);
305 .setDuration(WIDGET_ANIMATION_DURATION)
306 .withEndAction(mPostTransitionToCameraEndAction)
309 if (navHeight > 0 || navWidth > 0) {
310 final boolean atBottom = navHeight > 0;
311 if (mFakeNavBar == null) {
312 mFakeNavBar = new View(mContext);
313 mFakeNavBar.setBackgroundColor(Color.BLACK);
314 root.addView(mFakeNavBar, new FrameLayout.LayoutParams(
315 atBottom ? FrameLayout.LayoutParams.MATCH_PARENT
318 : FrameLayout.LayoutParams.MATCH_PARENT,
319 atBottom ? Gravity.BOTTOM|Gravity.FILL_HORIZONTAL
320 : Gravity.RIGHT|Gravity.FILL_VERTICAL));
321 mFakeNavBar.setPivotY(navHeight);
322 mFakeNavBar.setPivotX(navWidth);
324 mFakeNavBar.setAlpha(0f);
326 mFakeNavBar.setScaleY(0.5f);
328 mFakeNavBar.setScaleX(0.5f);
330 mFakeNavBar.setVisibility(View.VISIBLE);
331 mFakeNavBar.animate()
335 .setDuration(WIDGET_ANIMATION_DURATION)
338 mCallbacks.onLaunchingCamera();
341 private void recover() {
342 if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis());
343 mCallbacks.onCameraLaunchedUnsuccessfully();
348 public void setOnLongClickListener(OnLongClickListener l) {
353 public void onClick(View v) {
354 if (DEBUG) Log.d(TAG, "clicked");
355 if (mTransitioning) return;
357 cancelTransitionToCamera();
358 transitionToCamera();
363 protected void onDetachedFromWindow() {
364 if (DEBUG) Log.d(TAG, "onDetachedFromWindow: instance " + instanceId()
365 + " at " + SystemClock.uptimeMillis());
366 super.onDetachedFromWindow();
367 KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback);
368 cancelTransitionToCamera();
369 mHandler.removeCallbacks(mRecoverRunnable);
373 public void onActive(boolean isActive) {
376 rescheduleTransitionToCamera();
383 public boolean onUserInteraction(MotionEvent event) {
384 if (mTransitioning) {
385 if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning");
389 getLocationOnScreen(mTmpLoc);
390 int rawBottom = mTmpLoc[1] + getHeight();
391 if (event.getRawY() > rawBottom) {
392 if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget");
396 int action = event.getAction();
397 mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE;
399 rescheduleTransitionToCamera();
401 if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten");
406 protected void onFocusLost() {
407 if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis());
408 cancelTransitionToCamera();
412 public void onScreenTurnedOff() {
413 if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
417 private void rescheduleTransitionToCamera() {
418 if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis());
419 mHandler.removeCallbacks(mTransitionToCameraRunnable);
420 mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION);
423 private void cancelTransitionToCamera() {
424 if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis());
425 mHandler.removeCallbacks(mTransitionToCameraRunnable);
428 private void onCameraLaunched() {
429 mCallbacks.onCameraLaunchedSuccessfully();
433 private void reset() {
434 if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis());
435 mLaunchCameraStart = 0;
436 mTransitioning = false;
438 cancelTransitionToCamera();
439 mHandler.removeCallbacks(mRecoverRunnable);
440 mPreview.setVisibility(View.VISIBLE);
441 if (mFullscreenPreview != null) {
442 mFullscreenPreview.animate().cancel();
443 mFullscreenPreview.setVisibility(View.GONE);
445 if (mFakeNavBar != null) {
446 mFakeNavBar.animate().cancel();
447 mFakeNavBar.setVisibility(View.GONE);
449 enableWindowExitAnimation(true);
453 protected void onSizeChanged(int w, int h, int oldw, int oldh) {
454 if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s",
455 w, h, oldw, oldh, SystemClock.uptimeMillis()));
456 if ((w != oldw && oldw > 0) || (h != oldh && oldh > 0)) {
457 // we can't trust the old geometry anymore; force a re-render
458 mRenderedSize.x = mRenderedSize.y = -1;
460 mHandler.post(mRenderRunnable);
461 super.onSizeChanged(w, h, oldw, oldh);
465 public void onBouncerShowing(boolean showing) {
467 mTransitioning = false;
468 mHandler.post(mRecoverRunnable);
472 private void enableWindowExitAnimation(boolean isEnabled) {
473 View root = getRootView();
474 ViewGroup.LayoutParams lp = root.getLayoutParams();
475 if (!(lp instanceof WindowManager.LayoutParams))
477 WindowManager.LayoutParams wlp = (WindowManager.LayoutParams) lp;
478 int newWindowAnimations = isEnabled ? R.style.Animation_LockScreen : 0;
479 if (newWindowAnimations != wlp.windowAnimations) {
480 if (DEBUG) Log.d(TAG, "setting windowAnimations to: " + newWindowAnimations
481 + " at " + SystemClock.uptimeMillis());
482 wlp.windowAnimations = newWindowAnimations;
483 mWindowManager.updateViewLayout(root, wlp);
487 private void onKeyguardVisibilityChanged(boolean showing) {
488 if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged " + showing
489 + " at " + SystemClock.uptimeMillis());
490 if (mTransitioning && !showing) {
491 mTransitioning = false;
492 mHandler.removeCallbacks(mRecoverRunnable);
493 if (mLaunchCameraStart > 0) {
494 long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart;
495 if (DEBUG) Log.d(TAG, String.format("Camera took %sms to launch", launchTime));
496 mLaunchCameraStart = 0;
502 private void onSecureCameraActivityStarted() {
503 if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis());
504 mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY);
507 private String instanceId() {
508 return Integer.toHexString(hashCode());
511 public void setInsets(Rect insets) {
512 if (DEBUG) Log.d(TAG, "setInsets: " + insets);