• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
17 package com.android.keyguard;
18 
19 import android.content.Context;
20 import android.content.pm.PackageManager.NameNotFoundException;
21 import android.graphics.Color;
22 import android.graphics.Point;
23 import android.graphics.Rect;
24 import android.os.Handler;
25 import android.os.SystemClock;
26 import android.util.Log;
27 import android.view.Gravity;
28 import android.view.LayoutInflater;
29 import android.view.MotionEvent;
30 import android.view.View;
31 import android.view.ViewGroup;
32 import android.view.WindowManager;
33 import android.widget.FrameLayout;
34 import android.widget.ImageView;
35 import android.widget.ImageView.ScaleType;
36 
37 import com.android.keyguard.KeyguardActivityLauncher.CameraWidgetInfo;
38 
39 public class CameraWidgetFrame extends KeyguardWidgetFrame implements View.OnClickListener {
40     private static final String TAG = CameraWidgetFrame.class.getSimpleName();
41     private static final boolean DEBUG = KeyguardConstants.DEBUG;
42     private static final int WIDGET_ANIMATION_DURATION = 250; // ms
43     private static final int WIDGET_WAIT_DURATION = 400; // ms
44     private static final int RECOVERY_DELAY = 1000; // ms
45 
46     interface Callbacks {
onLaunchingCamera()47         void onLaunchingCamera();
onCameraLaunchedSuccessfully()48         void onCameraLaunchedSuccessfully();
onCameraLaunchedUnsuccessfully()49         void onCameraLaunchedUnsuccessfully();
50     }
51 
52     private final Handler mHandler = new Handler();
53     private final KeyguardActivityLauncher mActivityLauncher;
54     private final Callbacks mCallbacks;
55     private final CameraWidgetInfo mWidgetInfo;
56     private final WindowManager mWindowManager;
57     private final Point mRenderedSize = new Point();
58     private final int[] mTmpLoc = new int[2];
59 
60     private long mLaunchCameraStart;
61     private boolean mActive;
62     private boolean mTransitioning;
63     private boolean mDown;
64 
65     private final Rect mInsets = new Rect();
66 
67     private FixedSizeFrameLayout mPreview;
68     private View mFullscreenPreview;
69     private View mFakeNavBar;
70     private boolean mUseFastTransition;
71 
72     private final Runnable mTransitionToCameraRunnable = new Runnable() {
73         @Override
74         public void run() {
75             transitionToCamera();
76         }};
77 
78     private final Runnable mTransitionToCameraEndAction = new Runnable() {
79         @Override
80         public void run() {
81             if (!mTransitioning)
82                 return;
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);
87         }};
88 
89     private final Runnable mPostTransitionToCameraEndAction = new Runnable() {
90         @Override
91         public void run() {
92             mHandler.post(mTransitionToCameraEndAction);
93         }};
94 
95     private final Runnable mRecoverRunnable = new Runnable() {
96         @Override
97         public void run() {
98             recover();
99         }};
100 
101     private final Runnable mRenderRunnable = new Runnable() {
102         @Override
103         public void run() {
104             render();
105         }};
106 
107     private final Runnable mSecureCameraActivityStartedRunnable = new Runnable() {
108         @Override
109         public void run() {
110             onSecureCameraActivityStarted();
111         }
112     };
113 
114     private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
115         private boolean mShowing;
116 
117         @Override
118         public void onKeyguardVisibilityChanged(boolean showing) {
119             if (mShowing == showing)
120                 return;
121             mShowing = showing;
122             CameraWidgetFrame.this.onKeyguardVisibilityChanged(mShowing);
123         }
124     };
125 
126     private static final class FixedSizeFrameLayout extends FrameLayout {
127         int width;
128         int height;
129 
FixedSizeFrameLayout(Context context)130         FixedSizeFrameLayout(Context context) {
131             super(context);
132         }
133 
134         @Override
onMeasure(int widthMeasureSpec, int heightMeasureSpec)135         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
136             measureChildren(
137                     MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
138                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
139             setMeasuredDimension(width, height);
140         }
141     }
142 
CameraWidgetFrame(Context context, Callbacks callbacks, KeyguardActivityLauncher activityLauncher, CameraWidgetInfo widgetInfo, View previewWidget)143     private CameraWidgetFrame(Context context, Callbacks callbacks,
144             KeyguardActivityLauncher activityLauncher,
145             CameraWidgetInfo widgetInfo, View previewWidget) {
146         super(context);
147         mCallbacks = callbacks;
148         mActivityLauncher = activityLauncher;
149         mWidgetInfo = widgetInfo;
150         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
151         KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback);
152 
153         mPreview = new FixedSizeFrameLayout(context);
154         mPreview.addView(previewWidget);
155         addView(mPreview);
156 
157         View clickBlocker = new View(context);
158         clickBlocker.setBackgroundColor(Color.TRANSPARENT);
159         clickBlocker.setOnClickListener(this);
160         addView(clickBlocker);
161 
162         setContentDescription(context.getString(R.string.keyguard_accessibility_camera));
163         if (DEBUG) Log.d(TAG, "new CameraWidgetFrame instance " + instanceId());
164     }
165 
create(Context context, Callbacks callbacks, KeyguardActivityLauncher launcher)166     public static CameraWidgetFrame create(Context context, Callbacks callbacks,
167             KeyguardActivityLauncher launcher) {
168         if (context == null || callbacks == null || launcher == null)
169             return null;
170 
171         CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo();
172         if (widgetInfo == null)
173             return null;
174         View previewWidget = getPreviewWidget(context, widgetInfo);
175         if (previewWidget == null)
176             return null;
177 
178         return new CameraWidgetFrame(context, callbacks, launcher, widgetInfo, previewWidget);
179     }
180 
getPreviewWidget(Context context, CameraWidgetInfo widgetInfo)181     private static View getPreviewWidget(Context context, CameraWidgetInfo widgetInfo) {
182         return widgetInfo.layoutId > 0 ?
183                 inflateWidgetView(context, widgetInfo) :
184                 inflateGenericWidgetView(context);
185     }
186 
inflateWidgetView(Context context, CameraWidgetInfo widgetInfo)187     private static View inflateWidgetView(Context context, CameraWidgetInfo widgetInfo) {
188         if (DEBUG) Log.d(TAG, "inflateWidgetView: " + widgetInfo.contextPackage);
189         View widgetView = null;
190         Exception exception = null;
191         try {
192             Context cameraContext = context.createPackageContext(
193                     widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
194             LayoutInflater cameraInflater = (LayoutInflater)
195                     cameraContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
196             cameraInflater = cameraInflater.cloneInContext(cameraContext);
197             widgetView = cameraInflater.inflate(widgetInfo.layoutId, null, false);
198         } catch (NameNotFoundException e) {
199             exception = e;
200         } catch (RuntimeException e) {
201             exception = e;
202         }
203         if (exception != null) {
204             Log.w(TAG, "Error creating camera widget view", exception);
205         }
206         return widgetView;
207     }
208 
inflateGenericWidgetView(Context context)209     private static View inflateGenericWidgetView(Context context) {
210         if (DEBUG) Log.d(TAG, "inflateGenericWidgetView");
211         ImageView iv = new ImageView(context);
212         iv.setImageResource(R.drawable.ic_lockscreen_camera);
213         iv.setScaleType(ScaleType.CENTER);
214         iv.setBackgroundColor(Color.argb(127, 0, 0, 0));
215         return iv;
216     }
217 
render()218     private void render() {
219         final View root = getRootView();
220         final int width = root.getWidth() - mInsets.right;    // leave room
221         final int height = root.getHeight() - mInsets.bottom; // for bars
222         if (mRenderedSize.x == width && mRenderedSize.y == height) {
223             if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s %d%%",
224                     width, height, (int)(100*mPreview.getScaleX())));
225             return;
226         }
227         if (width == 0 || height == 0) {
228             return;
229         }
230 
231         mPreview.width = width;
232         mPreview.height = height;
233         mPreview.requestLayout();
234 
235         final int thisWidth = getWidth() - getPaddingLeft() - getPaddingRight();
236         final int thisHeight = getHeight() - getPaddingTop() - getPaddingBottom();
237 
238         final float pvScaleX = (float) thisWidth / width;
239         final float pvScaleY = (float) thisHeight / height;
240         final float pvScale = Math.min(pvScaleX, pvScaleY);
241 
242         final int pvWidth = (int) (pvScale * width);
243         final int pvHeight = (int) (pvScale * height);
244 
245         final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0;
246         final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0;
247 
248         final boolean isRtl = mPreview.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
249         mPreview.setPivotX(isRtl ? mPreview.width : 0);
250         mPreview.setPivotY(0);
251         mPreview.setScaleX(pvScale);
252         mPreview.setScaleY(pvScale);
253         mPreview.setTranslationX((isRtl ? -1 : 1) * pvTransX);
254         mPreview.setTranslationY(pvTransY);
255 
256         mRenderedSize.set(width, height);
257         if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s %d%% instance=%s",
258                 width, height, (int)(100*mPreview.getScaleX()), instanceId()));
259     }
260 
261     private void transitionToCamera() {
262         if (mTransitioning || mDown) return;
263 
264         mTransitioning = true;
265 
266         enableWindowExitAnimation(false);
267 
268         final int navHeight = mInsets.bottom;
269         final int navWidth = mInsets.right;
270 
271         mPreview.getLocationInWindow(mTmpLoc);
272         final float pvHeight = mPreview.getHeight() * mPreview.getScaleY();
273         final float pvCenter = mTmpLoc[1] + pvHeight / 2f;
274 
275         final ViewGroup root = (ViewGroup) getRootView();
276 
277         if (DEBUG) {
278             Log.d(TAG, "root = " + root.getLeft() + "," + root.getTop() + " "
279                     + root.getWidth() + "x" + root.getHeight());
280         }
281 
282         if (mFullscreenPreview == null) {
283             mFullscreenPreview = getPreviewWidget(mContext, mWidgetInfo);
284             mFullscreenPreview.setClickable(false);
285             root.addView(mFullscreenPreview, new FrameLayout.LayoutParams(
286                         root.getWidth() - navWidth,
287                         root.getHeight() - navHeight));
288         }
289 
290         final float fsHeight = root.getHeight() - navHeight;
291         final float fsCenter = root.getTop() + fsHeight / 2;
292 
293         final float fsScaleY = mPreview.getScaleY();
294         final float fsTransY = pvCenter - fsCenter;
295         final float fsScaleX = fsScaleY;
296 
297         mPreview.setVisibility(View.GONE);
298         mFullscreenPreview.setVisibility(View.VISIBLE);
299         mFullscreenPreview.setTranslationY(fsTransY);
300         mFullscreenPreview.setScaleX(fsScaleX);
301         mFullscreenPreview.setScaleY(fsScaleY);
302         mFullscreenPreview
303             .animate()
304             .scaleX(1)
305             .scaleY(1)
306             .translationX(0)
307             .translationY(0)
308             .setDuration(WIDGET_ANIMATION_DURATION)
309             .withEndAction(mPostTransitionToCameraEndAction)
310             .start();
311 
312         if (navHeight > 0 || navWidth > 0) {
313             final boolean atBottom = navHeight > 0;
314             if (mFakeNavBar == null) {
315                 mFakeNavBar = new View(mContext);
316                 mFakeNavBar.setBackgroundColor(Color.BLACK);
317                 root.addView(mFakeNavBar, new FrameLayout.LayoutParams(
318                             atBottom ? FrameLayout.LayoutParams.MATCH_PARENT
319                                      : navWidth,
320                             atBottom ? navHeight
321                                      : FrameLayout.LayoutParams.MATCH_PARENT,
322                             atBottom ? Gravity.BOTTOM|Gravity.FILL_HORIZONTAL
323                                      : Gravity.RIGHT|Gravity.FILL_VERTICAL));
324                 mFakeNavBar.setPivotY(navHeight);
325                 mFakeNavBar.setPivotX(navWidth);
326             }
327             mFakeNavBar.setAlpha(0f);
328             if (atBottom) {
329                 mFakeNavBar.setScaleY(0.5f);
330             } else {
331                 mFakeNavBar.setScaleX(0.5f);
332             }
333             mFakeNavBar.setVisibility(View.VISIBLE);
334             mFakeNavBar.animate()
335                 .alpha(1f)
336                 .scaleY(1f)
337                 .scaleY(1f)
338                 .setDuration(WIDGET_ANIMATION_DURATION)
339                 .start();
340         }
341         mCallbacks.onLaunchingCamera();
342     }
343 
recover()344     private void recover() {
345         if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis());
346         mCallbacks.onCameraLaunchedUnsuccessfully();
347         reset();
348     }
349 
350     @Override
setOnLongClickListener(OnLongClickListener l)351     public void setOnLongClickListener(OnLongClickListener l) {
352         // ignore
353     }
354 
355     @Override
onClick(View v)356     public void onClick(View v) {
357         if (DEBUG) Log.d(TAG, "clicked");
358         if (mTransitioning) return;
359         if (mActive) {
360             cancelTransitionToCamera();
361             transitionToCamera();
362         }
363     }
364 
365     @Override
onDetachedFromWindow()366     protected void onDetachedFromWindow() {
367         if (DEBUG) Log.d(TAG, "onDetachedFromWindow: instance " + instanceId()
368                 + " at " + SystemClock.uptimeMillis());
369         super.onDetachedFromWindow();
370         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback);
371         cancelTransitionToCamera();
372         mHandler.removeCallbacks(mRecoverRunnable);
373     }
374 
375     @Override
onActive(boolean isActive)376     public void onActive(boolean isActive) {
377         mActive = isActive;
378         if (mActive) {
379             rescheduleTransitionToCamera();
380         } else {
381             reset();
382         }
383     }
384 
385     @Override
onUserInteraction(MotionEvent event)386     public boolean onUserInteraction(MotionEvent event) {
387         if (mTransitioning) {
388             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning");
389             return true;
390         }
391 
392         getLocationOnScreen(mTmpLoc);
393         int rawBottom = mTmpLoc[1] + getHeight();
394         if (event.getRawY() > rawBottom) {
395             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget");
396             return true;
397         }
398 
399         int action = event.getAction();
400         mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE;
401         if (mActive) {
402             rescheduleTransitionToCamera();
403         }
404         if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten");
405         return false;
406     }
407 
408     @Override
onFocusLost()409     protected void onFocusLost() {
410         if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis());
411         cancelTransitionToCamera();
412         super.onFocusLost();
413     }
414 
onScreenTurnedOff()415     public void onScreenTurnedOff() {
416         if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
417         reset();
418     }
419 
rescheduleTransitionToCamera()420     private void rescheduleTransitionToCamera() {
421         if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis());
422         mHandler.removeCallbacks(mTransitionToCameraRunnable);
423         final long duration = mUseFastTransition ? 0 : WIDGET_WAIT_DURATION;
424         mHandler.postDelayed(mTransitionToCameraRunnable, duration);
425     }
426 
cancelTransitionToCamera()427     private void cancelTransitionToCamera() {
428         if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis());
429         mHandler.removeCallbacks(mTransitionToCameraRunnable);
430     }
431 
onCameraLaunched()432     private void onCameraLaunched() {
433         mCallbacks.onCameraLaunchedSuccessfully();
434         reset();
435     }
436 
reset()437     private void reset() {
438         if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis());
439         mLaunchCameraStart = 0;
440         mTransitioning = false;
441         mDown = false;
442         cancelTransitionToCamera();
443         mHandler.removeCallbacks(mRecoverRunnable);
444         mPreview.setVisibility(View.VISIBLE);
445         if (mFullscreenPreview != null) {
446             mFullscreenPreview.animate().cancel();
447             mFullscreenPreview.setVisibility(View.GONE);
448         }
449         if (mFakeNavBar != null) {
450             mFakeNavBar.animate().cancel();
451             mFakeNavBar.setVisibility(View.GONE);
452         }
453         enableWindowExitAnimation(true);
454     }
455 
456     @Override
onSizeChanged(int w, int h, int oldw, int oldh)457     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
458         if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s",
459                 w, h, oldw, oldh, SystemClock.uptimeMillis()));
460         if ((w != oldw && oldw > 0) || (h != oldh && oldh > 0)) {
461             // we can't trust the old geometry anymore; force a re-render
462             mRenderedSize.x = mRenderedSize.y = -1;
463         }
464         mHandler.post(mRenderRunnable);
465         super.onSizeChanged(w, h, oldw, oldh);
466     }
467 
468     @Override
onBouncerShowing(boolean showing)469     public void onBouncerShowing(boolean showing) {
470         if (showing) {
471             mTransitioning = false;
472             mHandler.post(mRecoverRunnable);
473         }
474     }
475 
enableWindowExitAnimation(boolean isEnabled)476     private void enableWindowExitAnimation(boolean isEnabled) {
477         View root = getRootView();
478         ViewGroup.LayoutParams lp = root.getLayoutParams();
479         if (!(lp instanceof WindowManager.LayoutParams))
480             return;
481         WindowManager.LayoutParams wlp = (WindowManager.LayoutParams) lp;
482         int newWindowAnimations = isEnabled ? R.style.Animation_LockScreen : 0;
483         if (newWindowAnimations != wlp.windowAnimations) {
484             if (DEBUG) Log.d(TAG, "setting windowAnimations to: " + newWindowAnimations
485                     + " at " + SystemClock.uptimeMillis());
486             wlp.windowAnimations = newWindowAnimations;
487             mWindowManager.updateViewLayout(root, wlp);
488         }
489     }
490 
onKeyguardVisibilityChanged(boolean showing)491     private void onKeyguardVisibilityChanged(boolean showing) {
492         if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged " + showing
493                 + " at " + SystemClock.uptimeMillis());
494         if (mTransitioning && !showing) {
495             mTransitioning = false;
496             mHandler.removeCallbacks(mRecoverRunnable);
497             if (mLaunchCameraStart > 0) {
498                 long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart;
499                 if (DEBUG) Log.d(TAG, String.format("Camera took %sms to launch", launchTime));
500                 mLaunchCameraStart = 0;
501                 onCameraLaunched();
502             }
503         }
504     }
505 
onSecureCameraActivityStarted()506     private void onSecureCameraActivityStarted() {
507         if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis());
508         mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY);
509     }
510 
instanceId()511     private String instanceId() {
512         return Integer.toHexString(hashCode());
513     }
514 
setInsets(Rect insets)515     public void setInsets(Rect insets) {
516         if (DEBUG) Log.d(TAG, "setInsets: " + insets);
517         mInsets.set(insets);
518     }
519 
setUseFastTransition(boolean useFastTransition)520     public void setUseFastTransition(boolean useFastTransition) {
521         mUseFastTransition = useFastTransition;
522     }
523 }
524