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.internal.policy.impl.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.LayoutInflater;
     28 import android.view.MotionEvent;
     29 import android.view.View;
     30 import android.view.ViewGroup;
     31 import android.view.WindowManager;
     32 import android.widget.FrameLayout;
     33 import android.widget.ImageView;
     34 import android.widget.ImageView.ScaleType;
     35 
     36 import com.android.internal.R;
     37 import com.android.internal.policy.impl.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 = 650; // 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     private final Rect mTmpRect = new Rect();
     60 
     61     private long mLaunchCameraStart;
     62     private boolean mActive;
     63     private boolean mTransitioning;
     64     private boolean mDown;
     65 
     66     private FixedSizeFrameLayout mPreview;
     67     private View mFullscreenPreview;
     68 
     69     private final Runnable mTransitionToCameraRunnable = new Runnable() {
     70         @Override
     71         public void run() {
     72             transitionToCamera();
     73         }};
     74 
     75     private final Runnable mTransitionToCameraEndAction = new Runnable() {
     76         @Override
     77         public void run() {
     78             if (!mTransitioning)
     79                 return;
     80             Handler worker =  getWorkerHandler() != null ? getWorkerHandler() : mHandler;
     81             mLaunchCameraStart = SystemClock.uptimeMillis();
     82             if (DEBUG) Log.d(TAG, "Launching camera at " + mLaunchCameraStart);
     83             mActivityLauncher.launchCamera(worker, mSecureCameraActivityStartedRunnable);
     84         }};
     85 
     86     private final Runnable mPostTransitionToCameraEndAction = new Runnable() {
     87         @Override
     88         public void run() {
     89             mHandler.post(mTransitionToCameraEndAction);
     90         }};
     91 
     92     private final Runnable mRecoverRunnable = new Runnable() {
     93         @Override
     94         public void run() {
     95             recover();
     96         }};
     97 
     98     private final Runnable mRenderRunnable = new Runnable() {
     99         @Override
    100         public void run() {
    101             render();
    102         }};
    103 
    104     private final Runnable mSecureCameraActivityStartedRunnable = new Runnable() {
    105         @Override
    106         public void run() {
    107             onSecureCameraActivityStarted();
    108         }
    109     };
    110 
    111     private final KeyguardUpdateMonitorCallback mCallback = new KeyguardUpdateMonitorCallback() {
    112         private boolean mShowing;
    113         void onKeyguardVisibilityChanged(boolean showing) {
    114             if (mShowing == showing)
    115                 return;
    116             mShowing = showing;
    117             CameraWidgetFrame.this.onKeyguardVisibilityChanged(mShowing);
    118         };
    119     };
    120 
    121     private static final class FixedSizeFrameLayout extends FrameLayout {
    122         int width;
    123         int height;
    124 
    125         FixedSizeFrameLayout(Context context) {
    126             super(context);
    127         }
    128 
    129         @Override
    130         protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    131             measureChildren(
    132                     MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
    133                     MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
    134             setMeasuredDimension(width, height);
    135         }
    136     }
    137 
    138     private CameraWidgetFrame(Context context, Callbacks callbacks,
    139             KeyguardActivityLauncher activityLauncher,
    140             CameraWidgetInfo widgetInfo, View previewWidget) {
    141         super(context);
    142         mCallbacks = callbacks;
    143         mActivityLauncher = activityLauncher;
    144         mWidgetInfo = widgetInfo;
    145         mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    146         KeyguardUpdateMonitor.getInstance(context).registerCallback(mCallback);
    147 
    148         mPreview = new FixedSizeFrameLayout(context);
    149         mPreview.addView(previewWidget);
    150         addView(mPreview);
    151 
    152         View clickBlocker = new View(context);
    153         clickBlocker.setBackgroundColor(Color.TRANSPARENT);
    154         clickBlocker.setOnClickListener(this);
    155         addView(clickBlocker);
    156 
    157         setContentDescription(context.getString(R.string.keyguard_accessibility_camera));
    158         if (DEBUG) Log.d(TAG, "new CameraWidgetFrame instance " + instanceId());
    159     }
    160 
    161     public static CameraWidgetFrame create(Context context, Callbacks callbacks,
    162             KeyguardActivityLauncher launcher) {
    163         if (context == null || callbacks == null || launcher == null)
    164             return null;
    165 
    166         CameraWidgetInfo widgetInfo = launcher.getCameraWidgetInfo();
    167         if (widgetInfo == null)
    168             return null;
    169         View previewWidget = getPreviewWidget(context, widgetInfo);
    170         if (previewWidget == null)
    171             return null;
    172 
    173         return new CameraWidgetFrame(context, callbacks, launcher, widgetInfo, previewWidget);
    174     }
    175 
    176     private static View getPreviewWidget(Context context, CameraWidgetInfo widgetInfo) {
    177         return widgetInfo.layoutId > 0 ?
    178                 inflateWidgetView(context, widgetInfo) :
    179                 inflateGenericWidgetView(context);
    180     }
    181 
    182     private static View inflateWidgetView(Context context, CameraWidgetInfo widgetInfo) {
    183         if (DEBUG) Log.d(TAG, "inflateWidgetView: " + widgetInfo.contextPackage);
    184         View widgetView = null;
    185         Exception exception = null;
    186         try {
    187             Context cameraContext = context.createPackageContext(
    188                     widgetInfo.contextPackage, Context.CONTEXT_RESTRICTED);
    189             LayoutInflater cameraInflater = (LayoutInflater)
    190                     cameraContext.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    191             cameraInflater = cameraInflater.cloneInContext(cameraContext);
    192             widgetView = cameraInflater.inflate(widgetInfo.layoutId, null, false);
    193         } catch (NameNotFoundException e) {
    194             exception = e;
    195         } catch (RuntimeException e) {
    196             exception = e;
    197         }
    198         if (exception != null) {
    199             Log.w(TAG, "Error creating camera widget view", exception);
    200         }
    201         return widgetView;
    202     }
    203 
    204     private static View inflateGenericWidgetView(Context context) {
    205         if (DEBUG) Log.d(TAG, "inflateGenericWidgetView");
    206         ImageView iv = new ImageView(context);
    207         iv.setImageResource(com.android.internal.R.drawable.ic_lockscreen_camera);
    208         iv.setScaleType(ScaleType.CENTER);
    209         iv.setBackgroundColor(Color.argb(127, 0, 0, 0));
    210         return iv;
    211     }
    212 
    213     private void render() {
    214         final View root = getRootView();
    215         final int width = root.getWidth();
    216         final int height = root.getHeight();
    217         if (mRenderedSize.x == width && mRenderedSize.y == height) {
    218             if (DEBUG) Log.d(TAG, String.format("Already rendered at size=%sx%s", width, height));
    219             return;
    220         }
    221         if (width == 0 || height == 0) {
    222             return;
    223         }
    224 
    225         mPreview.width = width;
    226         mPreview.height = height;
    227         mPreview.requestLayout();
    228 
    229         final int thisWidth = getWidth() - getPaddingLeft() - getPaddingRight();
    230         final int thisHeight = getHeight() - getPaddingTop() - getPaddingBottom();
    231 
    232         final float pvScaleX = (float) thisWidth / width;
    233         final float pvScaleY = (float) thisHeight / height;
    234         final float pvScale = Math.min(pvScaleX, pvScaleY);
    235 
    236         final int pvWidth = (int) (pvScale * width);
    237         final int pvHeight = (int) (pvScale * height);
    238 
    239         final float pvTransX = pvWidth < thisWidth ? (thisWidth - pvWidth) / 2 : 0;
    240         final float pvTransY = pvHeight < thisHeight ? (thisHeight - pvHeight) / 2 : 0;
    241 
    242         mPreview.setPivotX(0);
    243         mPreview.setPivotY(0);
    244         mPreview.setScaleX(pvScale);
    245         mPreview.setScaleY(pvScale);
    246         mPreview.setTranslationX(pvTransX);
    247         mPreview.setTranslationY(pvTransY);
    248 
    249         mRenderedSize.set(width, height);
    250         if (DEBUG) Log.d(TAG, String.format("Rendered camera widget size=%sx%s instance=%s",
    251                 width, height, instanceId()));
    252     }
    253 
    254     private void transitionToCamera() {
    255         if (mTransitioning || mDown) return;
    256 
    257         mTransitioning = true;
    258 
    259         enableWindowExitAnimation(false);
    260 
    261         mPreview.getLocationInWindow(mTmpLoc);
    262         final float pvHeight = mPreview.getHeight() * mPreview.getScaleY();
    263         final float pvCenter = mTmpLoc[1] + pvHeight / 2f;
    264 
    265         final ViewGroup root = (ViewGroup) getRootView();
    266         if (mFullscreenPreview == null) {
    267             mFullscreenPreview = getPreviewWidget(mContext, mWidgetInfo);
    268             mFullscreenPreview.setClickable(false);
    269             root.addView(mFullscreenPreview);
    270         }
    271 
    272         root.getWindowVisibleDisplayFrame(mTmpRect);
    273         final float fsHeight = mTmpRect.height();
    274         final float fsCenter = mTmpRect.top + fsHeight / 2;
    275 
    276         final float fsScaleY = pvHeight / fsHeight;
    277         final float fsTransY = pvCenter - fsCenter;
    278         final float fsScaleX = mPreview.getScaleX();
    279 
    280         mPreview.setVisibility(View.GONE);
    281         mFullscreenPreview.setVisibility(View.VISIBLE);
    282         mFullscreenPreview.setTranslationY(fsTransY);
    283         mFullscreenPreview.setScaleX(fsScaleX);
    284         mFullscreenPreview.setScaleY(fsScaleY);
    285         mFullscreenPreview
    286             .animate()
    287             .scaleX(1)
    288             .scaleY(1)
    289             .translationX(0)
    290             .translationY(0)
    291             .setDuration(WIDGET_ANIMATION_DURATION)
    292             .withEndAction(mPostTransitionToCameraEndAction)
    293             .start();
    294         mCallbacks.onLaunchingCamera();
    295     }
    296 
    297     private void recover() {
    298         if (DEBUG) Log.d(TAG, "recovering at " + SystemClock.uptimeMillis());
    299         mCallbacks.onCameraLaunchedUnsuccessfully();
    300         reset();
    301     }
    302 
    303     @Override
    304     public void setOnLongClickListener(OnLongClickListener l) {
    305         // ignore
    306     }
    307 
    308     @Override
    309     public void onClick(View v) {
    310         if (DEBUG) Log.d(TAG, "clicked");
    311         if (mTransitioning) return;
    312         if (mActive) {
    313             cancelTransitionToCamera();
    314             transitionToCamera();
    315         }
    316     }
    317 
    318     @Override
    319     protected void onDetachedFromWindow() {
    320         if (DEBUG) Log.d(TAG, "onDetachedFromWindow: instance " + instanceId()
    321                 + " at " + SystemClock.uptimeMillis());
    322         super.onDetachedFromWindow();
    323         KeyguardUpdateMonitor.getInstance(mContext).removeCallback(mCallback);
    324         cancelTransitionToCamera();
    325         mHandler.removeCallbacks(mRecoverRunnable);
    326     }
    327 
    328     @Override
    329     public void onActive(boolean isActive) {
    330         mActive = isActive;
    331         if (mActive) {
    332             rescheduleTransitionToCamera();
    333         } else {
    334             reset();
    335         }
    336     }
    337 
    338     @Override
    339     public boolean onUserInteraction(MotionEvent event) {
    340         if (mTransitioning) {
    341             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: mTransitioning");
    342             return true;
    343         }
    344 
    345         getLocationOnScreen(mTmpLoc);
    346         int rawBottom = mTmpLoc[1] + getHeight();
    347         if (event.getRawY() > rawBottom) {
    348             if (DEBUG) Log.d(TAG, "onUserInteraction eaten: below widget");
    349             return true;
    350         }
    351 
    352         int action = event.getAction();
    353         mDown = action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_MOVE;
    354         if (mActive) {
    355             rescheduleTransitionToCamera();
    356         }
    357         if (DEBUG) Log.d(TAG, "onUserInteraction observed, not eaten");
    358         return false;
    359     }
    360 
    361     @Override
    362     protected void onFocusLost() {
    363         if (DEBUG) Log.d(TAG, "onFocusLost at " + SystemClock.uptimeMillis());
    364         cancelTransitionToCamera();
    365         super.onFocusLost();
    366     }
    367 
    368     public void onScreenTurnedOff() {
    369         if (DEBUG) Log.d(TAG, "onScreenTurnedOff");
    370         reset();
    371     }
    372 
    373     private void rescheduleTransitionToCamera() {
    374         if (DEBUG) Log.d(TAG, "rescheduleTransitionToCamera at " + SystemClock.uptimeMillis());
    375         mHandler.removeCallbacks(mTransitionToCameraRunnable);
    376         mHandler.postDelayed(mTransitionToCameraRunnable, WIDGET_WAIT_DURATION);
    377     }
    378 
    379     private void cancelTransitionToCamera() {
    380         if (DEBUG) Log.d(TAG, "cancelTransitionToCamera at " + SystemClock.uptimeMillis());
    381         mHandler.removeCallbacks(mTransitionToCameraRunnable);
    382     }
    383 
    384     private void onCameraLaunched() {
    385         mCallbacks.onCameraLaunchedSuccessfully();
    386         reset();
    387     }
    388 
    389     private void reset() {
    390         if (DEBUG) Log.d(TAG, "reset at " + SystemClock.uptimeMillis());
    391         mLaunchCameraStart = 0;
    392         mTransitioning = false;
    393         mDown = false;
    394         cancelTransitionToCamera();
    395         mHandler.removeCallbacks(mRecoverRunnable);
    396         mPreview.setVisibility(View.VISIBLE);
    397         if (mFullscreenPreview != null) {
    398             mFullscreenPreview.animate().cancel();
    399             mFullscreenPreview.setVisibility(View.GONE);
    400         }
    401         enableWindowExitAnimation(true);
    402     }
    403 
    404     @Override
    405     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    406         if (DEBUG) Log.d(TAG, String.format("onSizeChanged new=%sx%s old=%sx%s at %s",
    407                 w, h, oldw, oldh, SystemClock.uptimeMillis()));
    408         mHandler.post(mRenderRunnable);
    409         super.onSizeChanged(w, h, oldw, oldh);
    410     }
    411 
    412     @Override
    413     public void onBouncerShowing(boolean showing) {
    414         if (showing) {
    415             mTransitioning = false;
    416             mHandler.post(mRecoverRunnable);
    417         }
    418     }
    419 
    420     private void enableWindowExitAnimation(boolean isEnabled) {
    421         View root = getRootView();
    422         ViewGroup.LayoutParams lp = root.getLayoutParams();
    423         if (!(lp instanceof WindowManager.LayoutParams))
    424             return;
    425         WindowManager.LayoutParams wlp = (WindowManager.LayoutParams) lp;
    426         int newWindowAnimations = isEnabled ? com.android.internal.R.style.Animation_LockScreen : 0;
    427         if (newWindowAnimations != wlp.windowAnimations) {
    428             if (DEBUG) Log.d(TAG, "setting windowAnimations to: " + newWindowAnimations
    429                     + " at " + SystemClock.uptimeMillis());
    430             wlp.windowAnimations = newWindowAnimations;
    431             mWindowManager.updateViewLayout(root, wlp);
    432         }
    433     }
    434 
    435     private void onKeyguardVisibilityChanged(boolean showing) {
    436         if (DEBUG) Log.d(TAG, "onKeyguardVisibilityChanged " + showing
    437                 + " at " + SystemClock.uptimeMillis());
    438         if (mTransitioning && !showing) {
    439             mTransitioning = false;
    440             mHandler.removeCallbacks(mRecoverRunnable);
    441             if (mLaunchCameraStart > 0) {
    442                 long launchTime = SystemClock.uptimeMillis() - mLaunchCameraStart;
    443                 if (DEBUG) Log.d(TAG, String.format("Camera took %sms to launch", launchTime));
    444                 mLaunchCameraStart = 0;
    445                 onCameraLaunched();
    446             }
    447         }
    448     }
    449 
    450     private void onSecureCameraActivityStarted() {
    451         if (DEBUG) Log.d(TAG, "onSecureCameraActivityStarted at " + SystemClock.uptimeMillis());
    452         mHandler.postDelayed(mRecoverRunnable, RECOVERY_DELAY);
    453     }
    454 
    455     private String instanceId() {
    456         return Integer.toHexString(hashCode());
    457     }
    458 }
    459