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
17package com.android.camera.widget;
18
19import android.animation.Animator;
20import android.animation.ObjectAnimator;
21import android.animation.ValueAnimator;
22import android.content.Context;
23import android.graphics.Canvas;
24import android.graphics.drawable.Drawable;
25import android.util.AttributeSet;
26import android.view.OrientationEventListener;
27import android.view.View;
28
29import com.android.camera.util.CameraUtil;
30import 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 */
38public 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