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 = 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 {
     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 
    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 
    130         FixedSizeFrameLayout(Context context) {
    131             super(context);
    132         }
    133 
    134         @Override
    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 
    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 
    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 
    181     private static View getPreviewWidget(Context context, CameraWidgetInfo widgetInfo) {
    182         return widgetInfo.layoutId > 0 ?
    183                 inflateWidgetView(context, widgetInfo) :
    184                 inflateGenericWidgetView(context);
    185     }
    186 
    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 
    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 
    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 
    344     private void recover() {
    345         if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis());
    346         mCallbacks.onCameraLaunchedUnsuccessfully();
    347         reset();
    348     }
    349 
    350     @Override
    351     public void setOnLongClickListener(OnLongClickListener l) {
    352         // ignore
    353     }
    354 
    355     @Override
    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
    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
    376     public void onActive(boolean isActive) {
    377         mActive = isActive;
    378         if (mActive) {
    379             rescheduleTransitionToCamera();
    380         } else {
    381             reset();
    382         }
    383     }
    384 
    385     @Override
    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
    409     protected void onFocusLost() {
    410         if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis());
    411         cancelTransitionToCamera();
    412         super.onFocusLost();
    413     }
    414 
    415     public void onScreenTurnedOff() {
    416         if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
    417         reset();
    418     }
    419 
    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 
    427     private void cancelTransitionToCamera() {
    428         if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis());
    429         mHandler.removeCallbacks(mTransitionToCameraRunnable);
    430     }
    431 
    432     private void onCameraLaunched() {
    433         mCallbacks.onCameraLaunchedSuccessfully();
    434         reset();
    435     }
    436 
    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
    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
    469     public void onBouncerShowing(boolean showing) {
    470         if (showing) {
    471             mTransitioning = false;
    472             mHandler.post(mRecoverRunnable);
    473         }
    474     }
    475 
    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 
    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 
    506     private void onSecureCameraActivityStarted() {
    507         if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis());
    508         mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY);
    509     }
    510 
    511     private String instanceId() {
    512         return Integer.toHexString(hashCode());
    513     }
    514 
    515     public void setInsets(Rect insets) {
    516         if (DEBUG) Log.d(TAG, "setInsets: " + insets);
    517         mInsets.set(insets);
    518     }
    519 
    520     public void setUseFastTransition(boolean useFastTransition) {
    521         mUseFastTransition = useFastTransition;
    522     }
    523 }
    524