Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2014 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.camera.widget;
     18 
     19 import android.animation.Animator;
     20 import android.animation.ObjectAnimator;
     21 import android.animation.ValueAnimator;
     22 import android.content.Context;
     23 import android.graphics.Canvas;
     24 import android.graphics.drawable.Drawable;
     25 import android.util.AttributeSet;
     26 import android.view.OrientationEventListener;
     27 import android.view.View;
     28 
     29 import com.android.camera.util.CameraUtil;
     30 import com.android.camera2.R;
     31 
     32 /**
     33  * This class is designed to show the video recording hint when device is held in
     34  * portrait before video recording. The rotation device indicator will start rotating
     35  * after a time-out and will fade out if the device is rotated to landscape. A tap
     36  * on screen will dismiss the indicator.
     37  */
     38 public class VideoRecordingHints extends View {
     39 
     40     private static final int PORTRAIT_ROTATE_DELAY_MS = 1000;
     41     private static final int ROTATION_DURATION_MS = 1000;
     42     private static final int FADE_OUT_DURATION_MS = 600;
     43     private static final float ROTATION_DEGREES = 180f;
     44     private static final float INITIAL_ROTATION = 0f;
     45     private static final int UNSET = -1;
     46 
     47     private final int mRotateArrowsHalfSize;
     48     private final int mPhoneGraphicHalfWidth;
     49     private final Drawable mRotateArrows;
     50     private final Drawable mPhoneGraphic;
     51     private final int mPhoneGraphicHalfHeight;
     52     private final boolean mIsDefaultToPortrait;
     53     private float mRotation = INITIAL_ROTATION;
     54     private final ValueAnimator mRotationAnimation;
     55     private final ObjectAnimator mAlphaAnimator;
     56     private boolean mIsInLandscape = false;
     57     private int mCenterX = UNSET;
     58     private int mCenterY = UNSET;
     59     private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
     60 
     61     public VideoRecordingHints(Context context, AttributeSet attrs) {
     62         super(context, attrs);
     63         mRotateArrows = getResources().getDrawable(R.drawable.rotate_arrows);
     64         mPhoneGraphic = getResources().getDrawable(R.drawable.ic_phone_graphic);
     65         mRotateArrowsHalfSize = getResources().getDimensionPixelSize(
     66                 R.dimen.video_hint_arrow_size) / 2;
     67         mPhoneGraphicHalfWidth = getResources()
     68                 .getDimensionPixelSize(R.dimen.video_hint_phone_graphic_width) / 2;
     69         mPhoneGraphicHalfHeight = getResources()
     70                 .getDimensionPixelSize(R.dimen.video_hint_phone_graphic_height) / 2;
     71 
     72         mRotationAnimation = ValueAnimator.ofFloat(mRotation, mRotation + ROTATION_DEGREES);
     73         mRotationAnimation.setDuration(ROTATION_DURATION_MS);
     74         mRotationAnimation.setStartDelay(PORTRAIT_ROTATE_DELAY_MS);
     75         mRotationAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
     76             @Override
     77             public void onAnimationUpdate(ValueAnimator animation) {
     78                 mRotation = (Float) animation.getAnimatedValue();
     79                 invalidate();
     80             }
     81         });
     82 
     83         mRotationAnimation.addListener(new Animator.AnimatorListener() {
     84             private boolean mCanceled = false;
     85             @Override
     86             public void onAnimationStart(Animator animation) {
     87                 mCanceled = false;
     88             }
     89 
     90             @Override
     91             public void onAnimationEnd(Animator animation) {
     92                 mRotation = ((int) mRotation) % 360;
     93                 // If animation is canceled, do not restart it.
     94                 if (mCanceled) {
     95                     return;
     96                 }
     97                 post(new Runnable() {
     98                     @Override
     99                     public void run() {
    100                         continueRotationAnimation();
    101                     }
    102                 });
    103             }
    104 
    105             @Override
    106             public void onAnimationCancel(Animator animation) {
    107                 mCanceled = true;
    108             }
    109 
    110             @Override
    111             public void onAnimationRepeat(Animator animation) {
    112                 // Do nothing.
    113             }
    114         });
    115 
    116         mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f);
    117         mAlphaAnimator.setDuration(FADE_OUT_DURATION_MS);
    118         mAlphaAnimator.addListener(new Animator.AnimatorListener() {
    119             @Override
    120             public void onAnimationStart(Animator animation) {
    121                 // Do nothing.
    122             }
    123 
    124             @Override
    125             public void onAnimationEnd(Animator animation) {
    126                 invalidate();
    127                 setAlpha(1f);
    128                 mRotation = 0;
    129             }
    130 
    131             @Override
    132             public void onAnimationCancel(Animator animation) {
    133                 // Do nothing.
    134             }
    135 
    136             @Override
    137             public void onAnimationRepeat(Animator animation) {
    138                 // Do nothing.
    139             }
    140         });
    141         mIsDefaultToPortrait = CameraUtil.isDefaultToPortrait(context);
    142     }
    143 
    144     /**
    145      * Restart the rotation animation using the current rotation as the starting
    146      * rotation, and then rotate a pre-defined amount. If the rotation animation
    147      * is currently running, do nothing.
    148      */
    149     private void continueRotationAnimation() {
    150         if (mRotationAnimation.isRunning()) {
    151             return;
    152         }
    153         mRotationAnimation.setFloatValues(mRotation, mRotation + ROTATION_DEGREES);
    154         mRotationAnimation.start();
    155     }
    156 
    157     @Override
    158     public void onVisibilityChanged(View v, int visibility) {
    159         super.onVisibilityChanged(v, visibility);
    160         if (getVisibility() == VISIBLE && !isInLandscape()) {
    161             continueRotationAnimation();
    162         } else if (getVisibility() != VISIBLE) {
    163             mRotationAnimation.cancel();
    164             mRotation = 0;
    165         }
    166     }
    167 
    168     @Override
    169     public void onLayout(boolean changed, int left, int top, int right, int bottom) {
    170         super.onLayout(changed, left, top, right, bottom);
    171         // Center drawables in the layout
    172         mCenterX = (right - left) / 2;
    173         mCenterY = (bottom - top) / 2;
    174         mRotateArrows.setBounds(mCenterX - mRotateArrowsHalfSize, mCenterY - mRotateArrowsHalfSize,
    175                 mCenterX + mRotateArrowsHalfSize, mCenterY + mRotateArrowsHalfSize);
    176         mPhoneGraphic.setBounds(mCenterX - mPhoneGraphicHalfWidth, mCenterY - mPhoneGraphicHalfHeight,
    177                 mCenterX + mPhoneGraphicHalfWidth, mCenterY + mPhoneGraphicHalfHeight);
    178         invalidate();
    179     }
    180 
    181     @Override
    182     public void draw(Canvas canvas) {
    183         super.draw(canvas);
    184         // Don't draw anything after the fade-out animation in landscape.
    185         if (mIsInLandscape && !mAlphaAnimator.isRunning()) {
    186             return;
    187         }
    188         canvas.save();
    189         canvas.rotate(-mRotation, mCenterX, mCenterY);
    190         mRotateArrows.draw(canvas);
    191         canvas.restore();
    192         if (mIsInLandscape) {
    193             canvas.save();
    194             canvas.rotate(90, mCenterX, mCenterY);
    195             mPhoneGraphic.draw(canvas);
    196             canvas.restore();
    197         } else {
    198             mPhoneGraphic.draw(canvas);
    199         }
    200     }
    201 
    202     /**
    203      * Handles orientation change by starting/stopping the video hint based on the
    204      * new orientation.
    205      */
    206     public void onOrientationChanged(int orientation) {
    207         if (mLastOrientation == orientation) {
    208             return;
    209         }
    210         mLastOrientation = orientation;
    211         if (mLastOrientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
    212             return;
    213         }
    214 
    215         mIsInLandscape = isInLandscape();
    216         if (getVisibility() == VISIBLE) {
    217             if (mIsInLandscape) {
    218                 // Landscape.
    219                 mRotationAnimation.cancel();
    220                 // Start fading out.
    221                 if (mAlphaAnimator.isRunning()) {
    222                     return;
    223                 }
    224                 mAlphaAnimator.start();
    225             } else {
    226                 // Portrait.
    227                 continueRotationAnimation();
    228             }
    229         }
    230     }
    231 
    232     /**
    233      * Returns whether the device is in landscape based on the natural orientation
    234      * and rotation from natural orientation.
    235      */
    236     private boolean isInLandscape() {
    237         return (mLastOrientation % 180 == 90 && mIsDefaultToPortrait)
    238                 || (mLastOrientation % 180 == 0 && !mIsDefaultToPortrait);
    239     }
    240 }
    241