Home | History | Annotate | Download | only in keyguard
      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 = KeyguardHostView.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 {
     47         void onLaunchingCamera();
     48         void onCameraLaunchedSuccessfully();
     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         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         final boolean isRtl = mPreview.getLayoutDirection() == LAYOUT_DIRECTION_RTL;
    247         mPreview.setPivotX(isRtl ? mPreview.width : 0);
    248         mPreview.setPivotY(0);
    249         mPreview.setScaleX(pvScale);
    250         mPreview.setScaleY(pvScale);
    251         mPreview.setTranslationX((isRtl ? -1 : 1) * pvTransX);
    252         mPreview.setTranslationY(pvTransY);
    253 
    254         mRenderedSize.set(width, height);
    255         if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s %d%% instance=%s",
    256                 width, height, (int)(100*mPreview.getScaleX()), instanceId()));
    257     }
    258 
    259     private void transitionToCamera() {
    260         if (mTransitioning || mDown) return;
    261 
    262         mTransitioning = true;
    263 
    264         enableWindowExitAnimation(false);
    265 
    266         final int navHeight = mInsets.bottom;
    267         final int navWidth = mInsets.right;
    268 
    269         mPreview.getLocationInWindow(mTmpLoc);
    270         final float pvHeight = mPreview.getHeight() * mPreview.getScaleY();
    271         final float pvCenter = mTmpLoc[1] + pvHeight / 2f;
    272 
    273         final ViewGroup root = (ViewGroup) getRootView();
    274 
    275         if (DEBUG) {
    276             Log.d(TAG, "root = " + root.getLeft() + "," + root.getTop() + " "
    277                     + root.getWidth() + "x" + root.getHeight());
    278         }
    279 
    280         if (mFullscreenPreview == null) {
    281             mFullscreenPreview = getPreviewWidget(mContext, mWidgetInfo);
    282             mFullscreenPreview.setClickable(false);
    283             root.addView(mFullscreenPreview, new FrameLayout.LayoutParams(
    284                         root.getWidth() - navWidth,
    285                         root.getHeight() - navHeight));
    286         }
    287 
    288         final float fsHeight = root.getHeight() - navHeight;
    289         final float fsCenter = root.getTop() + fsHeight / 2;
    290 
    291         final float fsScaleY = mPreview.getScaleY();
    292         final float fsTransY = pvCenter - fsCenter;
    293         final float fsScaleX = fsScaleY;
    294 
    295         mPreview.setVisibility(View.GONE);
    296         mFullscreenPreview.setVisibility(View.VISIBLE);
    297         mFullscreenPreview.setTranslationY(fsTransY);
    298         mFullscreenPreview.setScaleX(fsScaleX);
    299         mFullscreenPreview.setScaleY(fsScaleY);
    300         mFullscreenPreview
    301             .animate()
    302             .scaleX(1)
    303             .scaleY(1)
    304             .translationX(0)
    305             .translationY(0)
    306             .setDuration(WIDGET_ANIMATION_DURATION)
    307             .withEndAction(mPostTransitionToCameraEndAction)
    308             .start();
    309 
    310         if (navHeight > 0 || navWidth > 0) {
    311             final boolean atBottom = navHeight > 0;
    312             if (mFakeNavBar == null) {
    313                 mFakeNavBar = new View(mContext);
    314                 mFakeNavBar.setBackgroundColor(Color.BLACK);
    315                 root.addView(mFakeNavBar, new FrameLayout.LayoutParams(
    316                             atBottom ? FrameLayout.LayoutParams.MATCH_PARENT
    317                                      : navWidth,
    318                             atBottom ? navHeight
    319                                      : FrameLayout.LayoutParams.MATCH_PARENT,
    320                             atBottom ? Gravity.BOTTOM|Gravity.FILL_HORIZONTAL
    321                                      : Gravity.RIGHT|Gravity.FILL_VERTICAL));
    322                 mFakeNavBar.setPivotY(navHeight);
    323                 mFakeNavBar.setPivotX(navWidth);
    324             }
    325             mFakeNavBar.setAlpha(0f);
    326             if (atBottom) {
    327                 mFakeNavBar.setScaleY(0.5f);
    328             } else {
    329                 mFakeNavBar.setScaleX(0.5f);
    330             }
    331             mFakeNavBar.setVisibility(View.VISIBLE);
    332             mFakeNavBar.animate()
    333                 .alpha(1f)
    334                 .scaleY(1f)
    335                 .scaleY(1f)
    336                 .setDuration(WIDGET_ANIMATION_DURATION)
    337                 .start();
    338         }
    339         mCallbacks.onLaunchingCamera();
    340     }
    341 
    342     private void recover() {
    343         if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis());
    344         mCallbacks.onCameraLaunchedUnsuccessfully();
    345         reset();
    346     }
    347 
    348     @Override
    349     public void setOnLongClickListener(OnLongClickListener l) {
    350         // ignore
    351     }
    352 
    353     @Override
    354     public void onClick(View v) {
    355         if (DEBUG) Log.d(TAG, "clicked");
    356         if (mTransitioning) return;
    357         if (mActive) {
    358             cancelTransitionToCamera();
    359             transitionToCamera();
    360         }
    361     }
    362 
    363     @Override
    364     protected void onDetachedFromWindow() {
    365         if (DEBUG) Log.d(TAG, "onDetachedFromWindow: instance " + instanceId()
    366                 + " at " + SystemClock.uptimeMillis());
    367         super.onDetachedFromWindow();
    368         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback);
    369         cancelTransitionToCamera();
    370         mHandler.removeCallbacks(mRecoverRunnable);
    371     }
    372 
    373     @Override
    374     public void onActive(boolean isActive) {
    375         mActive = isActive;
    376         if (mActive) {
    377             rescheduleTransitionToCamera();
    378         } else {
    379             reset();
    380         }
    381     }
    382 
    383     @Override
    384     public boolean onUserInteraction(MotionEvent event) {
    385         if (mTransitioning) {
    386             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning");
    387             return true;
    388         }
    389 
    390         getLocationOnScreen(mTmpLoc);
    391         int rawBottom = mTmpLoc[1] + getHeight();
    392         if (event.getRawY() > rawBottom) {
    393             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget");
    394             return true;
    395         }
    396 
    397         int action = event.getAction();
    398         mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE;
    399         if (mActive) {
    400             rescheduleTransitionToCamera();
    401         }
    402         if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten");
    403         return false;
    404     }
    405 
    406     @Override
    407     protected void onFocusLost() {
    408         if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis());
    409         cancelTransitionToCamera();
    410         super.onFocusLost();
    411     }
    412 
    413     public void onScreenTurnedOff() {
    414         if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
    415         reset();
    416     }
    417 
    418     private void rescheduleTransitionToCamera() {
    419         if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis());
    420         mHandler.removeCallbacks(mTransitionToCameraRunnable);
    421         final long duration = mUseFastTransition ? 0 : WIDGET_WAIT_DURATION;
    422         mHandler.postDelayed(mTransitionToCameraRunnable, duration);
    423     }
    424 
    425     private void cancelTransitionToCamera() {
    426         if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis());
    427         mHandler.removeCallbacks(mTransitionToCameraRunnable);
    428     }
    429 
    430     private void onCameraLaunched() {
    431         mCallbacks.onCameraLaunchedSuccessfully();
    432         reset();
    433     }
    434 
    435     private void reset() {
    436         if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis());
    437         mLaunchCameraStart = 0;
    438         mTransitioning = false;
    439         mDown = false;
    440         cancelTransitionToCamera();
    441         mHandler.removeCallbacks(mRecoverRunnable);
    442         mPreview.setVisibility(View.VISIBLE);
    443         if (mFullscreenPreview != null) {
    444             mFullscreenPreview.animate().cancel();
    445             mFullscreenPreview.setVisibility(View.GONE);
    446         }
    447         if (mFakeNavBar != null) {
    448             mFakeNavBar.animate().cancel();
    449             mFakeNavBar.setVisibility(View.GONE);
    450         }
    451         enableWindowExitAnimation(true);
    452     }
    453 
    454     @Override
    455     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    456         if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s",
    457                 w, h, oldw, oldh, SystemClock.uptimeMillis()));
    458         if ((w != oldw && oldw > 0) || (h != oldh && oldh > 0)) {
    459             // we can't trust the old geometry anymore; force a re-render
    460             mRenderedSize.x = mRenderedSize.y = -1;
    461         }
    462         mHandler.post(mRenderRunnable);
    463         super.onSizeChanged(w, h, oldw, oldh);
    464     }
    465 
    466     @Override
    467     public void onBouncerShowing(boolean showing) {
    468         if (showing) {
    469             mTransitioning = false;
    470             mHandler.post(mRecoverRunnable);
    471         }
    472     }
    473 
    474     private void enableWindowExitAnimation(boolean isEnabled) {
    475         View root = getRootView();
    476         ViewGroup.LayoutParams lp = root.getLayoutParams();
    477         if (!(lp instanceof WindowManager.LayoutParams))
    478             return;
    479         WindowManager.LayoutParams wlp = (WindowManager.LayoutParams) lp;
    480         int newWindowAnimations = isEnabled ? R.style.Animation_LockScreen : 0;
    481         if (newWindowAnimations != wlp.windowAnimations) {
    482             if (DEBUG) Log.d(TAG, "setting windowAnimations to: " + newWindowAnimations
    483                     + " at " + SystemClock.uptimeMillis());
    484             wlp.windowAnimations = newWindowAnimations;
    485             mWindowManager.updateViewLayout(root, wlp);
    486         }
    487     }
    488 
    489     private void onKeyguardVisibilityChanged(boolean showing) {
    490         if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged " + showing
    491                 + " at " + SystemClock.uptimeMillis());
    492         if (mTransitioning && !showing) {
    493             mTransitioning = false;
    494             mHandler.removeCallbacks(mRecoverRunnable);
    495             if (mLaunchCameraStart > 0) {
    496                 long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart;
    497                 if (DEBUG) Log.d(TAG, String.format("Camera took %sms to launch", launchTime));
    498                 mLaunchCameraStart = 0;
    499                 onCameraLaunched();
    500             }
    501         }
    502     }
    503 
    504     private void onSecureCameraActivityStarted() {
    505         if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis());
    506         mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY);
    507     }
    508 
    509     private String instanceId() {
    510         return Integer.toHexString(hashCode());
    511     }
    512 
    513     public void setInsets(Rect insets) {
    514         if (DEBUG) Log.d(TAG, "setInsets: " + insets);
    515         mInsets.set(insets);
    516     }
    517 
    518     public void setUseFastTransition(boolean useFastTransition) {
    519         mUseFastTransition = useFastTransition;
    520     }
    521 }
    522