• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
VideoRecordingHints(Context context, AttributeSet attrs)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      */
continueRotationAnimation()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
onVisibilityChanged(View v, int visibility)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
onLayout(boolean changed, int left, int top, int right, int bottom)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
draw(Canvas canvas)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      */
onOrientationChanged(int orientation)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      */
isInLandscape()236     private boolean isInLandscape() {
237         return (mLastOrientation % 180 == 90 && mIsDefaultToPortrait)
238                 || (mLastOrientation % 180 == 0 && !mIsDefaultToPortrait);
239     }
240 }
241