CameraAppUI.java revision f55f3c461c5a6ae6b61fa75562ca01683aa93f9a
1/* 2 * Copyright (C) 2013 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.app; 18 19import android.content.Context; 20import android.graphics.Bitmap; 21import android.graphics.Matrix; 22import android.util.Log; 23import android.view.GestureDetector; 24import android.view.LayoutInflater; 25import android.view.MotionEvent; 26import android.view.TextureView; 27import android.view.View; 28import android.view.ViewConfiguration; 29import android.view.ViewGroup; 30import android.widget.ImageView; 31 32import com.android.camera.AnimationManager; 33import com.android.camera.ui.CameraControls; 34import com.android.camera.ui.MainActivityLayout; 35import com.android.camera.ui.ModeListView; 36import com.android.camera.ui.ModeTransitionView; 37import com.android.camera.ui.RenderOverlay; 38import com.android.camera2.R; 39 40/** 41 * CameraAppUI centralizes control of views shared across modules. Whereas module 42 * specific views will be handled in each Module UI. For example, we can now 43 * bring the flash animation and capture animation up from each module to app 44 * level, as these animations are largely the same for all modules. 45 * 46 * This class also serves to disambiguate touch events. It recognizes all the 47 * swipe gestures that happen on the preview by attaching a touch listener to 48 * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge 49 * of how swipe from each direction should be handled, it can then redirect these 50 * events to appropriate recipient views. 51 */ 52public class CameraAppUI implements ModeListView.ModeSwitchListener { 53 private final static String TAG = "CameraAppUI"; 54 55 private final AppController mController; 56 private final boolean mIsCaptureIntent; 57 private final boolean mIsSecureCamera; 58 private final AnimationManager mAnimationManager; 59 60 // Swipe states: 61 private final int IDLE = 0; 62 private final int SWIPE_UP = 1; 63 private final int SWIPE_DOWN = 2; 64 private final int SWIPE_LEFT = 3; 65 private final int SWIPE_RIGHT = 4; 66 67 // Touch related measures: 68 private final int mSlop; 69 private final int SWIPE_TIME_OUT = 500; 70 71 // App level views: 72 private final ViewGroup mCameraRootView; 73 private final ModeTransitionView mModeTransitionView; 74 private final MainActivityLayout mAppRootView; 75 private final ModeListView mModeListView; 76 private final View mFilmStripView; 77 private TextureView mTextureView; 78 private CameraControls mCameraControls; 79 private RenderOverlay mRenderOverlay; 80 private View mFlashOverlay; 81 private ViewGroup mModuleUI; 82 83 private GestureDetector mGestureDetector; 84 private int mSwipeState = IDLE; 85 private ImageView mPreviewThumbView; 86 87 public interface AnimationFinishedListener { 88 public void onAnimationFinished(boolean success); 89 } 90 91 private class MyTouchListener implements View.OnTouchListener { 92 93 @Override 94 public boolean onTouch(View v, MotionEvent event) { 95 return mGestureDetector.onTouchEvent(event); 96 } 97 } 98 99 /** 100 * This gesture listener finds out the direction of the scroll gestures and 101 * sends them to CameraAppUI to do further handling. 102 */ 103 private class MyGestureListener extends GestureDetector.SimpleOnGestureListener { 104 private MotionEvent mDown; 105 106 @Override 107 public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) { 108 if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT 109 || mSwipeState != IDLE) { 110 return true; 111 } 112 113 int deltaX = (int) (ev.getX() - mDown.getX()); 114 int deltaY = (int) (ev.getY() - mDown.getY()); 115 if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) { 116 if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) { 117 // Calculate the direction of the swipe. 118 if (deltaX >= Math.abs(deltaY)) { 119 // Swipe right. 120 setSwipeState(SWIPE_RIGHT); 121 } else if (deltaX <= -Math.abs(deltaY)) { 122 // Swipe left. 123 setSwipeState(SWIPE_LEFT); 124 } else if (deltaY >= Math.abs(deltaX)) { 125 // Swipe down. 126 setSwipeState(SWIPE_DOWN); 127 } else if (deltaY <= -Math.abs(deltaX)) { 128 // Swipe up. 129 setSwipeState(SWIPE_UP); 130 } 131 } 132 } 133 return true; 134 } 135 136 private void setSwipeState(int swipeState) { 137 mSwipeState = swipeState; 138 // Notify new swipe detected. 139 onSwipeDetected(swipeState); 140 } 141 142 @Override 143 public boolean onDown(MotionEvent ev) { 144 mDown = MotionEvent.obtain(ev); 145 mSwipeState = IDLE; 146 return true; 147 } 148 149 @Override 150 public boolean onSingleTapUp(MotionEvent ev) { 151 // This keeps pie menu functioning until the alternative is in. 152 // TODO: Remove after bottom bar is finalized. 153 mRenderOverlay.directTouchEventsToPie(mDown); 154 mRenderOverlay.directTouchEventsToPie(ev); 155 return true; 156 } 157 } 158 159 public CameraAppUI(AppController controller, MainActivityLayout appRootView, 160 ViewGroup cameraRoot, 161 boolean isSecureCamera, boolean isCaptureIntent) { 162 mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop(); 163 mController = controller; 164 mIsSecureCamera = isSecureCamera; 165 mIsCaptureIntent = isCaptureIntent; 166 167 mAppRootView = appRootView; 168 mFilmStripView = appRootView.findViewById(R.id.filmstrip_view); 169 mCameraRootView = cameraRoot; 170 mModeTransitionView = (ModeTransitionView) 171 mAppRootView.findViewById(R.id.mode_transition_view); 172 mGestureDetector = new GestureDetector(controller.getAndroidContext(), 173 new MyGestureListener()); 174 mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout); 175 if (mModeListView != null) { 176 mModeListView.setModeSwitchListener(this); 177 } else { 178 Log.e(TAG, "Cannot find mode list in the view hierarchy"); 179 } 180 mAnimationManager = new AnimationManager(); 181 } 182 183 /** 184 * Redirects touch events to appropriate recipient views based on swipe direction. 185 * More specifically, swipe up and swipe down will be handled by the view that handles 186 * mode transition; swipe left will be send to filmstrip; swipe right will be redirected 187 * to mode list in order to bring up mode list. 188 */ 189 private void onSwipeDetected(int swipeState) { 190 if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) { 191 // Quick switch between photo/video. 192 if (mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO || 193 mController.getCurrentModuleIndex() == ModeListView.MODE_VIDEO) { 194 mAppRootView.redirectTouchEventsTo(mModeTransitionView); 195 196 final int moduleToTransitionTo = 197 mController.getCurrentModuleIndex() == ModeListView.MODE_PHOTO ? 198 ModeListView.MODE_VIDEO : ModeListView.MODE_PHOTO; 199 int shadeColorId = ModeListView.getModeThemeColor(moduleToTransitionTo); 200 int iconRes = ModeListView.getModeIconResourceId(moduleToTransitionTo); 201 202 AnimationFinishedListener listener = new AnimationFinishedListener() { 203 public void onAnimationFinished(boolean success) { 204 if (success) { 205 // Go to new module when the previous operation is successful. 206 mController.onModeSelected(moduleToTransitionTo); 207 mModeTransitionView.startPeepHoleAnimation(); 208 } 209 } 210 }; 211 if (mSwipeState == SWIPE_UP) { 212 mModeTransitionView.prepareToPullUpShade(shadeColorId, iconRes, listener); 213 } else { 214 mModeTransitionView.prepareToPullDownShade(shadeColorId, iconRes, listener); 215 } 216 } 217 } else if (swipeState == SWIPE_LEFT) { 218 // Pass the touch sequence to filmstrip. 219 mAppRootView.redirectTouchEventsTo(mFilmStripView); 220 221 } else if (swipeState == SWIPE_RIGHT) { 222 // Pass the touch to mode switcher 223 mAppRootView.redirectTouchEventsTo(mModeListView); 224 } 225 } 226 227 /** 228 * This inflates generic_module layout, which contains all the shared views across 229 * modules. Then each module inflates their own views in the given view group. For 230 * now, this is called every time switching from a not-yet-refactored module to a 231 * refactored module. In the future, this should only need to be done once per app 232 * start. 233 */ 234 public void prepareModuleUI() { 235 mCameraRootView.removeAllViews(); 236 LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext() 237 .getSystemService(Context.LAYOUT_INFLATER_SERVICE); 238 inflater.inflate(R.layout.generic_module, mCameraRootView, true); 239 240 mModuleUI = (ViewGroup) mCameraRootView.findViewById(R.id.module_layout); 241 mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content); 242 mRenderOverlay = (RenderOverlay) mCameraRootView.findViewById(R.id.render_overlay); 243 mRenderOverlay.setOnTouchListener(new MyTouchListener()); 244 mFlashOverlay = mCameraRootView.findViewById(R.id.flash_overlay); 245 mPreviewThumbView = (ImageView) mCameraRootView.findViewById(R.id.preview_thumb); 246 247 // TODO: Remove camera controls. 248 mCameraControls = (CameraControls) mCameraRootView.findViewById(R.id.camera_controls); 249 } 250 251 // TODO: Remove this when refactor is done. 252 // This is here to ensure refactored modules can work with not-yet-refactored ones. 253 public void clearCameraUI() { 254 mCameraRootView.removeAllViews(); 255 mModuleUI = null; 256 mTextureView = null; 257 mRenderOverlay = null; 258 mFlashOverlay = null; 259 mCameraControls = null; 260 } 261 262 /** 263 * Called indirectly from each module in their initialization to get a view group 264 * to inflate the module specific views in. 265 * 266 * @return a view group for modules to attach views to 267 */ 268 public ViewGroup getModuleRootView() { 269 return mModuleUI; 270 } 271 272 /** 273 * Remove all the module specific views. 274 */ 275 public void clearModuleUI() { 276 if (mModuleUI != null) { 277 mModuleUI.removeAllViews(); 278 } 279 mRenderOverlay.clear(); 280 } 281 282 @Override 283 public void onModeSelected(int modeIndex) { 284 mController.onModeSelected(modeIndex); 285 } 286 287 /** 288 * Sets the transform matrix on the preview TextureView 289 */ 290 public void setPreviewTransformMatrix(Matrix transformMatrix) { 291 if (mTextureView == null) { 292 throw new UnsupportedOperationException("Cannot set transform matrix on a null" + 293 " TextureView"); 294 } 295 mTextureView.setTransform(transformMatrix); 296 } 297 298 299 /********************** Capture animation **********************/ 300 /* TODO: This session is subject to UX changes. In addition to the generic 301 flash animation and post capture animation, consider designating a parameter 302 for specifying the type of animation, as well as an animation finished listener 303 so that modules can have more knowledge of the status of the animation. */ 304 305 /** 306 * Starts the pre-capture animation. 307 */ 308 public void startPreCaptureAnimation() { 309 mAnimationManager.startFlashAnimation(mFlashOverlay); 310 } 311 312 /** 313 * Cancels the pre-capture animation. 314 */ 315 public void cancelPreCaptureAnimation() { 316 mAnimationManager.cancelAnimations(); 317 } 318 319 /** 320 * Starts the post-capture animation with the current preview image. 321 */ 322 public void startPostCaptureAnimation() { 323 if (mTextureView == null) { 324 Log.e(TAG, "Cannot get a frame from a null TextureView for animation"); 325 return; 326 } 327 // TODO: Down sample bitmap 328 startPostCaptureAnimation(mTextureView.getBitmap()); 329 } 330 331 /** 332 * Starts the post-capture animation with the given thumbnail. 333 * 334 * @param thumbnail The thumbnail for the animation. 335 */ 336 public void startPostCaptureAnimation(Bitmap thumbnail) { 337 mPreviewThumbView.setImageBitmap(thumbnail); 338 mAnimationManager.startCaptureAnimation(mPreviewThumbView); 339 } 340 341 /** 342 * Cancels the post-capture animation. 343 */ 344 public void cancelPostCaptureAnimation() { 345 mAnimationManager.cancelAnimations(); 346 } 347} 348