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