OSDN Git Service

Merge "Fix SystemUI crash on devices with WiFi only" into klp-dev
[android-x86/frameworks-base.git] / packages / Keyguard / src / com / android / keyguard / CameraWidgetFrame.java
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.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;
37
38 import com.android.keyguard.KeyguardActivityLauncher.CameraWidgetInfo;
39
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
46
47     interface Callbacks {
48         void onLaunchingCamera();
49         void onCameraLaunchedSuccessfully();
50         void onCameraLaunchedUnsuccessfully();
51     }
52
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];
60
61     private long mLaunchCameraStart;
62     private boolean mActive;
63     private boolean mTransitioning;
64     private boolean mDown;
65
66     private final Rect mInsets = new Rect();
67
68     private FixedSizeFrameLayout mPreview;
69     private View mFullscreenPreview;
70     private View mFakeNavBar;
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         void onKeyguardVisibilityChanged(boolean showing) {
117             if (mShowing == showing)
118                 return;
119             mShowing = showing;
120             CameraWidgetFrame.this.onKeyguardVisibilityChanged(mShowing);
121         };
122     };
123
124     private static final class FixedSizeFrameLayout extends FrameLayout {
125         int width;
126         int height;
127
128         FixedSizeFrameLayout(Context context) {
129             super(context);
130         }
131
132         @Override
133         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
134             measureChildren(
135                     MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
136                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
137             setMeasuredDimension(width, height);
138         }
139     }
140
141     private CameraWidgetFrame(Context context, Callbacks callbacks,
142             KeyguardActivityLauncher activityLauncher,
143             CameraWidgetInfo widgetInfo, View previewWidget) {
144         super(context);
145         mCallbacks = callbacks;
146         mActivityLauncher = activityLauncher;
147         mWidgetInfo = widgetInfo;
148         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
149         KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback);
150
151         mPreview = new FixedSizeFrameLayout(context);
152         mPreview.addView(previewWidget);
153         addView(mPreview);
154
155         View clickBlocker = new View(context);
156         clickBlocker.setBackgroundColor(Color.TRANSPARENT);
157         clickBlocker.setOnClickListener(this);
158         addView(clickBlocker);
159
160         setContentDescription(context.getString(R.string.keyguard_accessibility_camera));
161         if (DEBUG) Log.d(TAG, "new CameraWidgetFrame instance " + instanceId());
162     }
163
164     public static CameraWidgetFrame create(Context context, Callbacks callbacks,
165             KeyguardActivityLauncher launcher) {
166         if (context == null || callbacks == null || launcher == null)
167             return null;
168
169         CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo();
170         if (widgetInfo == null)
171             return null;
172         View previewWidget = getPreviewWidget(context, widgetInfo);
173         if (previewWidget == null)
174             return null;
175
176         return new CameraWidgetFrame(context, callbacks, launcher, widgetInfo, previewWidget);
177     }
178
179     private static View getPreviewWidget(Context context, CameraWidgetInfo widgetInfo) {
180         return widgetInfo.layoutId > 0 ?
181                 inflateWidgetView(context, widgetInfo) :
182                 inflateGenericWidgetView(context);
183     }
184
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;
189         try {
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) {
197             exception = e;
198         } catch (RuntimeException e) {
199             exception = e;
200         }
201         if (exception != null) {
202             Log.w(TAG, "Error creating camera widget view", exception);
203         }
204         return widgetView;
205     }
206
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));
213         return iv;
214     }
215
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())));
223             return;
224         }
225         if (width == 0 || height == 0) {
226             return;
227         }
228
229         mPreview.width = width;
230         mPreview.height = height;
231         mPreview.requestLayout();
232
233         final int thisWidth = getWidth() - getPaddingLeft() - getPaddingRight();
234         final int thisHeight = getHeight() - getPaddingTop() - getPaddingBottom();
235
236         final float pvScaleX = (float) thisWidth / width;
237         final float pvScaleY = (float) thisHeight / height;
238         final float pvScale = Math.min(pvScaleX, pvScaleY);
239
240         final int pvWidth = (int) (pvScale * width);
241         final int pvHeight = (int) (pvScale * height);
242
243         final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0;
244         final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0;
245
246         mPreview.setPivotX(0);
247         mPreview.setPivotY(0);
248         mPreview.setScaleX(pvScale);
249         mPreview.setScaleY(pvScale);
250         mPreview.setTranslationX(pvTransX);
251         mPreview.setTranslationY(pvTransY);
252
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()));
256     }
257
258     private void transitionToCamera() {
259         if (mTransitioning || mDown) return;
260
261         mTransitioning = true;
262
263         enableWindowExitAnimation(false);
264
265         final int navHeight = mInsets.bottom;
266         final int navWidth = mInsets.right;
267
268         mPreview.getLocationInWindow(mTmpLoc);
269         final float pvHeight = mPreview.getHeight() * mPreview.getScaleY();
270         final float pvCenter = mTmpLoc[1] + pvHeight / 2f;
271
272         final ViewGroup root = (ViewGroup) getRootView();
273
274         if (DEBUG) {
275             Log.d(TAG, "root = " + root.getLeft() + "," + root.getTop() + " "
276                     + root.getWidth() + "x" + root.getHeight());
277         }
278
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));
285         }
286
287         final float fsHeight = root.getHeight() - navHeight;
288         final float fsCenter = root.getTop() + fsHeight / 2;
289
290         final float fsScaleY = mPreview.getScaleY();
291         final float fsTransY = pvCenter - fsCenter;
292         final float fsScaleX = fsScaleY;
293
294         mPreview.setVisibility(View.GONE);
295         mFullscreenPreview.setVisibility(View.VISIBLE);
296         mFullscreenPreview.setTranslationY(fsTransY);
297         mFullscreenPreview.setScaleX(fsScaleX);
298         mFullscreenPreview.setScaleY(fsScaleY);
299         mFullscreenPreview
300             .animate()
301             .scaleX(1)
302             .scaleY(1)
303             .translationX(0)
304             .translationY(0)
305             .setDuration(WIDGET_ANIMATION_DURATION)
306             .withEndAction(mPostTransitionToCameraEndAction)
307             .start();
308
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
316                                      : navWidth,
317                             atBottom ? navHeight
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);
323             }
324             mFakeNavBar.setAlpha(0f);
325             if (atBottom) {
326                 mFakeNavBar.setScaleY(0.5f);
327             } else {
328                 mFakeNavBar.setScaleX(0.5f);
329             }
330             mFakeNavBar.setVisibility(View.VISIBLE);
331             mFakeNavBar.animate()
332                 .alpha(1f)
333                 .scaleY(1f)
334                 .scaleY(1f)
335                 .setDuration(WIDGET_ANIMATION_DURATION)
336                 .start();
337         }
338         mCallbacks.onLaunchingCamera();
339     }
340
341     private void recover() {
342         if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis());
343         mCallbacks.onCameraLaunchedUnsuccessfully();
344         reset();
345     }
346
347     @Override
348     public void setOnLongClickListener(OnLongClickListener l) {
349         // ignore
350     }
351
352     @Override
353     public void onClick(View v) {
354         if (DEBUG) Log.d(TAG, "clicked");
355         if (mTransitioning) return;
356         if (mActive) {
357             cancelTransitionToCamera();
358             transitionToCamera();
359         }
360     }
361
362     @Override
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);
370     }
371
372     @Override
373     public void onActive(boolean isActive) {
374         mActive = isActive;
375         if (mActive) {
376             rescheduleTransitionToCamera();
377         } else {
378             reset();
379         }
380     }
381
382     @Override
383     public boolean onUserInteraction(MotionEvent event) {
384         if (mTransitioning) {
385             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning");
386             return true;
387         }
388
389         getLocationOnScreen(mTmpLoc);
390         int rawBottom = mTmpLoc[1] + getHeight();
391         if (event.getRawY() > rawBottom) {
392             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget");
393             return true;
394         }
395
396         int action = event.getAction();
397         mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE;
398         if (mActive) {
399             rescheduleTransitionToCamera();
400         }
401         if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten");
402         return false;
403     }
404
405     @Override
406     protected void onFocusLost() {
407         if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis());
408         cancelTransitionToCamera();
409         super.onFocusLost();
410     }
411
412     public void onScreenTurnedOff() {
413         if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
414         reset();
415     }
416
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);
421     }
422
423     private void cancelTransitionToCamera() {
424         if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis());
425         mHandler.removeCallbacks(mTransitionToCameraRunnable);
426     }
427
428     private void onCameraLaunched() {
429         mCallbacks.onCameraLaunchedSuccessfully();
430         reset();
431     }
432
433     private void reset() {
434         if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis());
435         mLaunchCameraStart = 0;
436         mTransitioning = false;
437         mDown = 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);
444         }
445         if (mFakeNavBar != null) {
446             mFakeNavBar.animate().cancel();
447             mFakeNavBar.setVisibility(View.GONE);
448         }
449         enableWindowExitAnimation(true);
450     }
451
452     @Override
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;
459         }
460         mHandler.post(mRenderRunnable);
461         super.onSizeChanged(w, h, oldw, oldh);
462     }
463
464     @Override
465     public void onBouncerShowing(boolean showing) {
466         if (showing) {
467             mTransitioning = false;
468             mHandler.post(mRecoverRunnable);
469         }
470     }
471
472     private void enableWindowExitAnimation(boolean isEnabled) {
473         View root = getRootView();
474         ViewGroup.LayoutParams lp = root.getLayoutParams();
475         if (!(lp instanceof WindowManager.LayoutParams))
476             return;
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);
484         }
485     }
486
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;
497                 onCameraLaunched();
498             }
499         }
500     }
501
502     private void onSecureCameraActivityStarted() {
503         if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis());
504         mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY);
505     }
506
507     private String instanceId() {
508         return Integer.toHexString(hashCode());
509     }
510
511     public void setInsets(Rect insets) {
512         if (DEBUG) Log.d(TAG, "setInsets: " + insets);
513         mInsets.set(insets);
514     }
515 }