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 
17 package com.android.camera.app;
18 
19 import android.accessibilityservice.AccessibilityServiceInfo;
20 import android.content.Context;
21 import android.content.res.Resources;
22 import android.graphics.Bitmap;
23 import android.graphics.Canvas;
24 import android.graphics.Matrix;
25 import android.graphics.RectF;
26 import android.graphics.SurfaceTexture;
27 import android.hardware.display.DisplayManager;
28 import android.util.CameraPerformanceTracker;
29 import android.view.GestureDetector;
30 import android.view.LayoutInflater;
31 import android.view.MotionEvent;
32 import android.view.TextureView;
33 import android.view.View;
34 import android.view.ViewConfiguration;
35 import android.view.ViewGroup;
36 import android.view.accessibility.AccessibilityManager;
37 import android.widget.FrameLayout;
38 
39 import com.android.camera.AnimationManager;
40 import com.android.camera.ButtonManager;
41 import com.android.camera.CaptureLayoutHelper;
42 import com.android.camera.ShutterButton;
43 import com.android.camera.TextureViewHelper;
44 import com.android.camera.debug.Log;
45 import com.android.camera.filmstrip.FilmstripContentPanel;
46 import com.android.camera.hardware.HardwareSpec;
47 import com.android.camera.module.ModuleController;
48 import com.android.camera.settings.Keys;
49 import com.android.camera.settings.SettingsManager;
50 import com.android.camera.ui.AbstractTutorialOverlay;
51 import com.android.camera.ui.BottomBar;
52 import com.android.camera.ui.BottomBarModeOptionsWrapper;
53 import com.android.camera.ui.CaptureAnimationOverlay;
54 import com.android.camera.ui.GridLines;
55 import com.android.camera.ui.MainActivityLayout;
56 import com.android.camera.ui.ModeListView;
57 import com.android.camera.ui.ModeTransitionView;
58 import com.android.camera.ui.PreviewOverlay;
59 import com.android.camera.ui.PreviewStatusListener;
60 import com.android.camera.ui.TouchCoordinate;
61 import com.android.camera.util.ApiHelper;
62 import com.android.camera.util.CameraUtil;
63 import com.android.camera.util.Gusterpolator;
64 import com.android.camera.util.PhotoSphereHelper;
65 import com.android.camera.widget.Cling;
66 import com.android.camera.widget.FilmstripLayout;
67 import com.android.camera.widget.IndicatorIconController;
68 import com.android.camera.widget.ModeOptionsOverlay;
69 import com.android.camera.widget.PeekView;
70 import com.android.camera2.R;
71 
72 import java.util.List;
73 
74 /**
75  * CameraAppUI centralizes control of views shared across modules. Whereas module
76  * specific views will be handled in each Module UI. For example, we can now
77  * bring the flash animation and capture animation up from each module to app
78  * level, as these animations are largely the same for all modules.
79  *
80  * This class also serves to disambiguate touch events. It recognizes all the
81  * swipe gestures that happen on the preview by attaching a touch listener to
82  * a full-screen view on top of preview TextureView. Since CameraAppUI has knowledge
83  * of how swipe from each direction should be handled, it can then redirect these
84  * events to appropriate recipient views.
85  */
86 public class CameraAppUI implements ModeListView.ModeSwitchListener,
87                                     TextureView.SurfaceTextureListener,
88                                     ModeListView.ModeListOpenListener,
89                                     SettingsManager.OnSettingChangedListener,
90                                     ShutterButton.OnShutterButtonListener {
91 
92     /**
93      * The bottom controls on the filmstrip.
94      */
95     public static interface BottomPanel {
96         /** Values for the view state of the button. */
97         public final int VIEWER_NONE = 0;
98         public final int VIEWER_PHOTO_SPHERE = 1;
99         public final int VIEWER_REFOCUS = 2;
100         public final int VIEWER_OTHER = 3;
101 
102         /**
103          * Sets a new or replaces an existing listener for bottom control events.
104          */
setListener(Listener listener)105         void setListener(Listener listener);
106 
107         /**
108          * Sets cling for external viewer button.
109          */
setClingForViewer(int viewerType, Cling cling)110         void setClingForViewer(int viewerType, Cling cling);
111 
112         /**
113          * Clears cling for external viewer button.
114          */
clearClingForViewer(int viewerType)115         void clearClingForViewer(int viewerType);
116 
117         /**
118          * Returns a cling for the specified viewer type.
119          */
getClingForViewer(int viewerType)120         Cling getClingForViewer(int viewerType);
121 
122         /**
123          * Set if the bottom controls are visible.
124          * @param visible {@code true} if visible.
125          */
setVisible(boolean visible)126         void setVisible(boolean visible);
127 
128         /**
129          * @param visible Whether the button is visible.
130          */
setEditButtonVisibility(boolean visible)131         void setEditButtonVisibility(boolean visible);
132 
133         /**
134          * @param enabled Whether the button is enabled.
135          */
setEditEnabled(boolean enabled)136         void setEditEnabled(boolean enabled);
137 
138         /**
139          * Sets the visibility of the view-photosphere button.
140          *
141          * @param state one of {@link #VIEWER_NONE}, {@link #VIEWER_PHOTO_SPHERE},
142          *            {@link #VIEWER_REFOCUS}.
143          */
setViewerButtonVisibility(int state)144         void setViewerButtonVisibility(int state);
145 
146         /**
147          * @param enabled Whether the button is enabled.
148          */
setViewEnabled(boolean enabled)149         void setViewEnabled(boolean enabled);
150 
151         /**
152          * @param enabled Whether the button is enabled.
153          */
setTinyPlanetEnabled(boolean enabled)154         void setTinyPlanetEnabled(boolean enabled);
155 
156         /**
157          * @param visible Whether the button is visible.
158          */
setDeleteButtonVisibility(boolean visible)159         void setDeleteButtonVisibility(boolean visible);
160 
161         /**
162          * @param enabled Whether the button is enabled.
163          */
setDeleteEnabled(boolean enabled)164         void setDeleteEnabled(boolean enabled);
165 
166         /**
167          * @param visible Whether the button is visible.
168          */
setShareButtonVisibility(boolean visible)169         void setShareButtonVisibility(boolean visible);
170 
171         /**
172          * @param enabled Whether the button is enabled.
173          */
setShareEnabled(boolean enabled)174         void setShareEnabled(boolean enabled);
175 
176         /**
177          * Sets the texts for progress UI.
178          *
179          * @param text The text to show.
180          */
setProgressText(CharSequence text)181         void setProgressText(CharSequence text);
182 
183         /**
184          * Sets the progress.
185          *
186          * @param progress The progress value. Should be between 0 and 100.
187          */
setProgress(int progress)188         void setProgress(int progress);
189 
190         /**
191          * Replaces the progress UI with an error message.
192          */
showProgressError(CharSequence message)193         void showProgressError(CharSequence message);
194 
195         /**
196          * Hide the progress error message.
197          */
hideProgressError()198         void hideProgressError();
199 
200         /**
201          * Shows the progress.
202          */
showProgress()203         void showProgress();
204 
205         /**
206          * Hides the progress.
207          */
hideProgress()208         void hideProgress();
209 
210         /**
211          * Shows the controls.
212          */
showControls()213         void showControls();
214 
215         /**
216          * Hides the controls.
217          */
hideControls()218         void hideControls();
219 
220         /**
221          * Classes implementing this interface can listen for events on the bottom
222          * controls.
223          */
224         public static interface Listener {
225             /**
226              * Called when the user pressed the "view" button to e.g. view a photo
227              * sphere or RGBZ image.
228              */
onExternalViewer()229             public void onExternalViewer();
230 
231             /**
232              * Called when the "edit" button is pressed.
233              */
onEdit()234             public void onEdit();
235 
236             /**
237              * Called when the "tiny planet" button is pressed.
238              */
onTinyPlanet()239             public void onTinyPlanet();
240 
241             /**
242              * Called when the "delete" button is pressed.
243              */
onDelete()244             public void onDelete();
245 
246             /**
247              * Called when the "share" button is pressed.
248              */
onShare()249             public void onShare();
250 
251             /**
252              * Called when the progress error message is clicked.
253              */
onProgressErrorClicked()254             public void onProgressErrorClicked();
255         }
256     }
257 
258     /**
259      * BottomBarUISpec provides a structure for modules
260      * to specify their ideal bottom bar mode options layout.
261      *
262      * Once constructed by a module, this class should be
263      * treated as read only.
264      *
265      * The application then edits this spec according to
266      * hardware limitations and displays the final bottom
267      * bar ui.
268      */
269     public static class BottomBarUISpec {
270         /** Mode options UI */
271 
272         /**
273          * Set true if the camera option should be enabled.
274          * If not set or false, and multiple cameras are supported,
275          * the camera option will be disabled.
276          *
277          * If multiple cameras are not supported, this preference
278          * is ignored and the camera option will not be visible.
279          */
280         public boolean enableCamera;
281 
282         /**
283          * Set true if the camera option should not be visible, regardless
284          * of hardware limitations.
285          */
286         public boolean hideCamera;
287 
288         /**
289          * Set true if the photo flash option should be enabled.
290          * If not set or false, the photo flash option will be
291          * disabled.
292          *
293          * If the hardware does not support multiple flash values,
294          * this preference is ignored and the flash option will
295          * be disabled.  It will not be made invisible in order to
296          * preserve a consistent experience across devices and between
297          * front and back cameras.
298          */
299         public boolean enableFlash;
300 
301         /**
302          * Set true if the video flash option should be enabled.
303          * Same disable rules apply as the photo flash option.
304          */
305         public boolean enableTorchFlash;
306 
307         /**
308          * Set true if the HDR+ flash option should be enabled.
309          * Same disable rules apply as the photo flash option.
310          */
311         public boolean enableHdrPlusFlash;
312 
313         /**
314          * Set true if flash should not be visible, regardless of
315          * hardware limitations.
316          */
317         public boolean hideFlash;
318 
319         /**
320          * Set true if the hdr/hdr+ option should be enabled.
321          * If not set or false, the hdr/hdr+ option will be disabled.
322          *
323          * Hdr or hdr+ will be chosen based on hardware limitations,
324          * with hdr+ prefered.
325          *
326          * If hardware supports neither hdr nor hdr+, then the hdr/hdr+
327          * will not be visible.
328          */
329         public boolean enableHdr;
330 
331         /**
332          * Set true if hdr/hdr+ should not be visible, regardless of
333          * hardware limitations.
334          */
335         public boolean hideHdr;
336 
337         /**
338          * Set true if grid lines should be visible.  Not setting this
339          * causes grid lines to be disabled.  This option is agnostic to
340          * the hardware.
341          */
342         public boolean enableGridLines;
343 
344         /**
345          * Set true if grid lines should not be visible.
346          */
347         public boolean hideGridLines;
348 
349         /**
350          * Set true if the panorama orientation option should be visible.
351          *
352          * This option is not constrained by hardware limitations.
353          */
354         public boolean enablePanoOrientation;
355 
356         public boolean enableExposureCompensation;
357 
358         /** Intent UI */
359 
360         /**
361          * Set true if the intent ui cancel option should be visible.
362          */
363         public boolean showCancel;
364         /**
365          * Set true if the intent ui done option should be visible.
366          */
367         public boolean showDone;
368         /**
369          * Set true if the intent ui retake option should be visible.
370          */
371         public boolean showRetake;
372         /**
373          * Set true if the intent ui review option should be visible.
374          */
375         public boolean showReview;
376 
377         /** Mode options callbacks */
378 
379         /**
380          * A {@link com.android.camera.ButtonManager.ButtonCallback}
381          * that will be executed when the camera option is pressed. This
382          * callback can be null.
383          */
384         public ButtonManager.ButtonCallback cameraCallback;
385 
386         /**
387          * A {@link com.android.camera.ButtonManager.ButtonCallback}
388          * that will be executed when the flash option is pressed. This
389          * callback can be null.
390          */
391         public ButtonManager.ButtonCallback flashCallback;
392 
393         /**
394          * A {@link com.android.camera.ButtonManager.ButtonCallback}
395          * that will be executed when the hdr/hdr+ option is pressed. This
396          * callback can be null.
397          */
398         public ButtonManager.ButtonCallback hdrCallback;
399 
400         /**
401          * A {@link com.android.camera.ButtonManager.ButtonCallback}
402          * that will be executed when the grid lines option is pressed. This
403          * callback can be null.
404          */
405         public ButtonManager.ButtonCallback gridLinesCallback;
406 
407         /**
408          * A {@link com.android.camera.ButtonManager.ButtonCallback}
409          * that will execute when the panorama orientation option is pressed.
410          * This callback can be null.
411          */
412         public ButtonManager.ButtonCallback panoOrientationCallback;
413 
414         /** Intent UI callbacks */
415 
416         /**
417          * A {@link android.view.View.OnClickListener} that will execute
418          * when the cancel option is pressed. This callback can be null.
419          */
420         public View.OnClickListener cancelCallback;
421 
422         /**
423          * A {@link android.view.View.OnClickListener} that will execute
424          * when the done option is pressed. This callback can be null.
425          */
426         public View.OnClickListener doneCallback;
427 
428         /**
429          * A {@link android.view.View.OnClickListener} that will execute
430          * when the retake option is pressed. This callback can be null.
431          */
432         public View.OnClickListener retakeCallback;
433 
434         /**
435          * A {@link android.view.View.OnClickListener} that will execute
436          * when the review option is pressed. This callback can be null.
437          */
438         public View.OnClickListener reviewCallback;
439 
440         /**
441          * A ExposureCompensationSetCallback that will execute
442          * when an expsosure button is pressed. This callback can be null.
443          */
444         public interface ExposureCompensationSetCallback {
setExposure(int value)445             public void setExposure(int value);
446         }
447         public ExposureCompensationSetCallback exposureCompensationSetCallback;
448 
449         /**
450          * Exposure compensation parameters.
451          */
452         public int minExposureCompensation;
453         public int maxExposureCompensation;
454         public float exposureCompensationStep;
455 
456         /**
457          * Whether self-timer is enabled.
458          */
459         public boolean enableSelfTimer = false;
460 
461         /**
462          * Whether the option for self-timer should show. If true and
463          * {@link #enableSelfTimer} is false, then the option should be shown
464          * disabled.
465          */
466         public boolean showSelfTimer = false;
467     }
468 
469 
470     private final static Log.Tag TAG = new Log.Tag("CameraAppUI");
471 
472     private final AppController mController;
473     private final boolean mIsCaptureIntent;
474     private final AnimationManager mAnimationManager;
475 
476     // Swipe states:
477     private final static int IDLE = 0;
478     private final static int SWIPE_UP = 1;
479     private final static int SWIPE_DOWN = 2;
480     private final static int SWIPE_LEFT = 3;
481     private final static int SWIPE_RIGHT = 4;
482     private boolean mSwipeEnabled = true;
483 
484     // Shared Surface Texture properities.
485     private SurfaceTexture mSurface;
486     private int mSurfaceWidth;
487     private int mSurfaceHeight;
488 
489     // Touch related measures:
490     private final int mSlop;
491     private final static int SWIPE_TIME_OUT_MS = 500;
492 
493     // Mode cover states:
494     private final static int COVER_HIDDEN = 0;
495     private final static int COVER_SHOWN = 1;
496     private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
497     private static final int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 3;
498 
499     /**
500      * Preview down-sample rate when taking a screenshot.
501      */
502     private final static int DOWN_SAMPLE_RATE_FOR_SCREENSHOT = 2;
503 
504     // App level views:
505     private final FrameLayout mCameraRootView;
506     private final ModeTransitionView mModeTransitionView;
507     private final MainActivityLayout mAppRootView;
508     private final ModeListView mModeListView;
509     private final FilmstripLayout mFilmstripLayout;
510     private TextureView mTextureView;
511     private FrameLayout mModuleUI;
512     private ShutterButton mShutterButton;
513     private BottomBar mBottomBar;
514     private ModeOptionsOverlay mModeOptionsOverlay;
515     private IndicatorIconController mIndicatorIconController;
516     private View mFocusOverlay;
517     private FrameLayout mTutorialsPlaceHolderWrapper;
518     private BottomBarModeOptionsWrapper mIndicatorBottomBarWrapper;
519     private TextureViewHelper mTextureViewHelper;
520     private final GestureDetector mGestureDetector;
521     private DisplayManager.DisplayListener mDisplayListener;
522     private int mLastRotation;
523     private int mSwipeState = IDLE;
524     private PreviewOverlay mPreviewOverlay;
525     private GridLines mGridLines;
526     private CaptureAnimationOverlay mCaptureOverlay;
527     private PreviewStatusListener mPreviewStatusListener;
528     private int mModeCoverState = COVER_HIDDEN;
529     private final FilmstripBottomPanel mFilmstripBottomControls;
530     private final FilmstripContentPanel mFilmstripPanel;
531     private Runnable mHideCoverRunnable;
532     private final View.OnLayoutChangeListener mPreviewLayoutChangeListener
533             = new View.OnLayoutChangeListener() {
534         @Override
535         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
536                 int oldTop, int oldRight, int oldBottom) {
537             if (mPreviewStatusListener != null) {
538                 mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft,
539                         oldTop, oldRight, oldBottom);
540             }
541         }
542     };
543     private View mModeOptionsToggle;
544     private final PeekView mPeekView;
545     private final CaptureLayoutHelper mCaptureLayoutHelper;
546     private boolean mAccessibilityEnabled;
547     private final View mAccessibilityAffordances;
548 
549     private boolean mDisableAllUserInteractions;
550     /**
551      * Provides current preview frame and the controls/overlay from the module that
552      * are shown on top of the preview.
553      */
554     public interface CameraModuleScreenShotProvider {
555         /**
556          * Returns the current preview frame down-sampled using the given down-sample
557          * factor.
558          *
559          * @param downSampleFactor the down sample factor for down sampling the
560          *                         preview frame. (e.g. a down sample factor of
561          *                         2 means to scale down the preview frame to 1/2
562          *                         the width and height.)
563          * @return down-sampled preview frame
564          */
getPreviewFrame(int downSampleFactor)565         public Bitmap getPreviewFrame(int downSampleFactor);
566 
567         /**
568          * @return the controls and overlays that are currently showing on top of
569          *         the preview drawn into a bitmap with no scaling applied.
570          */
getPreviewOverlayAndControls()571         public Bitmap getPreviewOverlayAndControls();
572 
573         /**
574          * Returns a bitmap containing the current screenshot.
575          *
576          * @param previewDownSampleFactor the downsample factor applied on the
577          *                                preview frame when taking the screenshot
578          */
getScreenShot(int previewDownSampleFactor)579         public Bitmap getScreenShot(int previewDownSampleFactor);
580     }
581 
582     /**
583      * This listener gets called when the size of the window (excluding the system
584      * decor such as status bar and nav bar) has changed.
585      */
586     public interface NonDecorWindowSizeChangedListener {
onNonDecorWindowSizeChanged(int width, int height, int rotation)587         public void onNonDecorWindowSizeChanged(int width, int height, int rotation);
588     }
589 
590     private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider =
591             new CameraModuleScreenShotProvider() {
592                 @Override
593                 public Bitmap getPreviewFrame(int downSampleFactor) {
594                     if (mCameraRootView == null || mTextureView == null) {
595                         return null;
596                     }
597                     // Gets the bitmap from the preview TextureView.
598                     Bitmap preview = mTextureViewHelper.getPreviewBitmap(downSampleFactor);
599                     return preview;
600                 }
601 
602                 @Override
603                 public Bitmap getPreviewOverlayAndControls() {
604                     Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(),
605                             mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888);
606                     Canvas canvas = new Canvas(overlays);
607                     mCameraRootView.draw(canvas);
608                     return overlays;
609                 }
610 
611                 @Override
612                 public Bitmap getScreenShot(int previewDownSampleFactor) {
613                     Bitmap screenshot = Bitmap.createBitmap(mCameraRootView.getWidth(),
614                             mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888);
615                     Canvas canvas = new Canvas(screenshot);
616                     canvas.drawARGB(255, 0, 0, 0);
617                     Bitmap preview = mTextureViewHelper.getPreviewBitmap(previewDownSampleFactor);
618                     if (preview != null) {
619                         canvas.drawBitmap(preview, null, mTextureViewHelper.getPreviewArea(), null);
620                     }
621                     Bitmap overlay = getPreviewOverlayAndControls();
622                     if (overlay != null) {
623                         canvas.drawBitmap(overlay, 0f, 0f, null);
624                     }
625                     return screenshot;
626                 }
627             };
628 
629     private long mCoverHiddenTime = -1; // System time when preview cover was hidden.
630 
getCoverHiddenTime()631     public long getCoverHiddenTime() {
632         return mCoverHiddenTime;
633     }
634 
635     /**
636      * This resets the preview to have no applied transform matrix.
637      */
clearPreviewTransform()638     public void clearPreviewTransform() {
639         mTextureViewHelper.clearTransform();
640     }
641 
updatePreviewAspectRatio(float aspectRatio)642     public void updatePreviewAspectRatio(float aspectRatio) {
643         mTextureViewHelper.updateAspectRatio(aspectRatio);
644     }
645 
646     /**
647      * WAR: Reset the SurfaceTexture's default buffer size to the current view dimensions of
648      * its TextureView.  This is necessary to get the expected behavior for the TextureView's
649      * HardwareLayer transform matrix (set by TextureView#setTransform) after configuring the
650      * SurfaceTexture as an output for the Camera2 API (which involves changing the default buffer
651      * size).
652      *
653      * b/17286155 - Tracking a fix for this in HardwareLayer.
654      */
setDefaultBufferSizeToViewDimens()655     public void setDefaultBufferSizeToViewDimens() {
656         if (mSurface == null || mTextureView == null) {
657             Log.w(TAG, "Could not set SurfaceTexture default buffer dimensions, not yet setup");
658             return;
659         }
660         mSurface.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight());
661     }
662 
663     /**
664      * Updates the preview matrix without altering it.
665      *
666      * @param matrix
667      * @param aspectRatio the desired aspect ratio for the preview.
668      */
updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio)669     public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
670         mTextureViewHelper.updateTransformFullScreen(matrix, aspectRatio);
671     }
672 
673     /**
674      * @return the rect that will display the preview.
675      */
getFullscreenRect()676     public RectF getFullscreenRect() {
677         return mTextureViewHelper.getFullscreenRect();
678     }
679 
680     /**
681      * This is to support modules that calculate their own transform matrix because
682      * they need to use a transform matrix to rotate the preview.
683      *
684      * @param matrix transform matrix to be set on the TextureView
685      */
updatePreviewTransform(Matrix matrix)686     public void updatePreviewTransform(Matrix matrix) {
687         mTextureViewHelper.updateTransform(matrix);
688     }
689 
690     public interface AnimationFinishedListener {
onAnimationFinished(boolean success)691         public void onAnimationFinished(boolean success);
692     }
693 
694     private class MyTouchListener implements View.OnTouchListener {
695         private boolean mScaleStarted = false;
696         @Override
onTouch(View v, MotionEvent event)697         public boolean onTouch(View v, MotionEvent event) {
698             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
699                 mScaleStarted = false;
700             } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
701                 mScaleStarted = true;
702             }
703             return (!mScaleStarted) && mGestureDetector.onTouchEvent(event);
704         }
705     }
706 
707     /**
708      * This gesture listener finds out the direction of the scroll gestures and
709      * sends them to CameraAppUI to do further handling.
710      */
711     private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
712         private MotionEvent mDown;
713 
714         @Override
onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY)715         public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
716             if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
717                     || mSwipeState != IDLE
718                     || mIsCaptureIntent
719                     || !mSwipeEnabled) {
720                 return false;
721             }
722 
723             int deltaX = (int) (ev.getX() - mDown.getX());
724             int deltaY = (int) (ev.getY() - mDown.getY());
725             if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
726                 if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
727                     // Calculate the direction of the swipe.
728                     if (deltaX >= Math.abs(deltaY)) {
729                         // Swipe right.
730                         setSwipeState(SWIPE_RIGHT);
731                     } else if (deltaX <= -Math.abs(deltaY)) {
732                         // Swipe left.
733                         setSwipeState(SWIPE_LEFT);
734                     }
735                 }
736             }
737             return true;
738         }
739 
setSwipeState(int swipeState)740         private void setSwipeState(int swipeState) {
741             mSwipeState = swipeState;
742             // Notify new swipe detected.
743             onSwipeDetected(swipeState);
744         }
745 
746         @Override
onDown(MotionEvent ev)747         public boolean onDown(MotionEvent ev) {
748             mDown = MotionEvent.obtain(ev);
749             mSwipeState = IDLE;
750             return false;
751         }
752     }
753 
CameraAppUI(AppController controller, MainActivityLayout appRootView, boolean isCaptureIntent)754     public CameraAppUI(AppController controller, MainActivityLayout appRootView,
755             boolean isCaptureIntent) {
756         mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
757         mController = controller;
758         mIsCaptureIntent = isCaptureIntent;
759 
760         mAppRootView = appRootView;
761         mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
762         mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
763         mModeTransitionView = (ModeTransitionView)
764                 mAppRootView.findViewById(R.id.mode_transition_view);
765         mFilmstripBottomControls = new FilmstripBottomPanel(controller,
766                 (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_panel));
767         mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout);
768         mGestureDetector = new GestureDetector(controller.getAndroidContext(),
769                 new MyGestureListener());
770         Resources res = controller.getAndroidContext().getResources();
771         mCaptureLayoutHelper = new CaptureLayoutHelper(
772                 res.getDimensionPixelSize(R.dimen.bottom_bar_height_min),
773                 res.getDimensionPixelSize(R.dimen.bottom_bar_height_max),
774                 res.getDimensionPixelSize(R.dimen.bottom_bar_height_optimal));
775         mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
776         if (mModeListView != null) {
777             mModeListView.setModeSwitchListener(this);
778             mModeListView.setModeListOpenListener(this);
779             mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider);
780             mModeListView.setCaptureLayoutHelper(mCaptureLayoutHelper);
781             boolean shouldShowSettingsCling = mController.getSettingsManager().getBoolean(
782                     SettingsManager.SCOPE_GLOBAL,
783                     Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING);
784             mModeListView.setShouldShowSettingsCling(shouldShowSettingsCling);
785         } else {
786             Log.e(TAG, "Cannot find mode list in the view hierarchy");
787         }
788         mAnimationManager = new AnimationManager();
789         mPeekView = (PeekView) appRootView.findViewById(R.id.peek_view);
790         mAppRootView.setNonDecorWindowSizeChangedListener(mCaptureLayoutHelper);
791         initDisplayListener();
792         mAccessibilityAffordances = mAppRootView.findViewById(R.id.accessibility_affordances);
793         View modeListToggle = mAppRootView.findViewById(R.id.accessibility_mode_toggle_button);
794         modeListToggle.setOnClickListener(new View.OnClickListener() {
795             @Override
796             public void onClick(View view) {
797                 openModeList();
798             }
799         });
800         View filmstripToggle = mAppRootView.findViewById(
801                 R.id.accessibility_filmstrip_toggle_button);
802         filmstripToggle.setOnClickListener(new View.OnClickListener() {
803             @Override
804             public void onClick(View view) {
805                 showFilmstrip();
806             }
807         });
808     }
809 
810 
811     /**
812      * Freeze what is currently shown on screen until the next preview frame comes
813      * in.
814      */
freezeScreenUntilPreviewReady()815     public void freezeScreenUntilPreviewReady() {
816         Log.v(TAG, "freezeScreenUntilPreviewReady");
817         mModeTransitionView.setupModeCover(mCameraModuleScreenShotProvider
818                 .getScreenShot(DOWN_SAMPLE_RATE_FOR_SCREENSHOT));
819         mHideCoverRunnable = new Runnable() {
820             @Override
821             public void run() {
822                 mModeTransitionView.hideImageCover();
823             }
824         };
825         mModeCoverState = COVER_SHOWN;
826     }
827 
828     /**
829      * Creates a cling for the specific viewer and links the cling to the corresponding
830      * button for layout position.
831      *
832      * @param viewerType defines which viewer the cling is for.
833      */
setupClingForViewer(int viewerType)834     public void setupClingForViewer(int viewerType) {
835         if (viewerType == BottomPanel.VIEWER_REFOCUS) {
836             FrameLayout filmstripContent = (FrameLayout) mAppRootView
837                     .findViewById(R.id.camera_filmstrip_content_layout);
838             if (filmstripContent != null) {
839                 // Creates refocus cling.
840                 LayoutInflater inflater = (LayoutInflater) mController.getAndroidContext()
841                         .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
842                 Cling refocusCling = (Cling) inflater.inflate(R.layout.cling_widget, null, false);
843                 // Sets instruction text in the cling.
844                 refocusCling.setText(mController.getAndroidContext().getResources()
845                         .getString(R.string.cling_text_for_refocus_editor_button));
846 
847                 // Adds cling into view hierarchy.
848                 int clingWidth = mController.getAndroidContext()
849                         .getResources().getDimensionPixelSize(R.dimen.default_cling_width);
850                 filmstripContent.addView(refocusCling, clingWidth,
851                         ViewGroup.LayoutParams.WRAP_CONTENT);
852                 mFilmstripBottomControls.setClingForViewer(viewerType, refocusCling);
853             }
854         }
855     }
856 
857     /**
858      * Clears the listeners for the cling and remove it from the view hierarchy.
859      *
860      * @param viewerType defines which viewer the cling is for.
861      */
clearClingForViewer(int viewerType)862     public void clearClingForViewer(int viewerType) {
863         Cling clingToBeRemoved = mFilmstripBottomControls.getClingForViewer(viewerType);
864         if (clingToBeRemoved == null) {
865             // No cling is created for the specific viewer type.
866             return;
867         }
868         mFilmstripBottomControls.clearClingForViewer(viewerType);
869         clingToBeRemoved.setVisibility(View.GONE);
870         mAppRootView.removeView(clingToBeRemoved);
871     }
872 
873     /**
874      * Enable or disable swipe gestures. We want to disable them e.g. while we
875      * record a video.
876      */
setSwipeEnabled(boolean enabled)877     public void setSwipeEnabled(boolean enabled) {
878         mSwipeEnabled = enabled;
879         // TODO: This can be removed once we come up with a new design for handling swipe
880         // on shutter button and mode options. (More details: b/13751653)
881         mAppRootView.setSwipeEnabled(enabled);
882     }
883 
onDestroy()884     public void onDestroy() {
885         ((DisplayManager) mController.getAndroidContext()
886                 .getSystemService(Context.DISPLAY_SERVICE))
887                 .unregisterDisplayListener(mDisplayListener);
888     }
889 
890     /**
891      * Initializes the display listener to listen to display changes such as
892      * 180-degree rotation change, which will not have an onConfigurationChanged
893      * callback.
894      */
initDisplayListener()895     private void initDisplayListener() {
896         if (ApiHelper.HAS_DISPLAY_LISTENER) {
897             mLastRotation = CameraUtil.getDisplayRotation(mController.getAndroidContext());
898 
899             mDisplayListener = new DisplayManager.DisplayListener() {
900                 @Override
901                 public void onDisplayAdded(int arg0) {
902                     // Do nothing.
903                 }
904 
905                 @Override
906                 public void onDisplayChanged(int displayId) {
907                     int rotation = CameraUtil.getDisplayRotation(
908                             mController.getAndroidContext());
909                     if ((rotation - mLastRotation + 360) % 360 == 180
910                             && mPreviewStatusListener != null) {
911                         mPreviewStatusListener.onPreviewFlipped();
912                         mIndicatorBottomBarWrapper.requestLayout();
913                         mModeListView.requestLayout();
914                         mTextureView.requestLayout();
915                     }
916                     mLastRotation = rotation;
917                 }
918 
919                 @Override
920                 public void onDisplayRemoved(int arg0) {
921                     // Do nothing.
922                 }
923             };
924 
925             ((DisplayManager) mController.getAndroidContext()
926                     .getSystemService(Context.DISPLAY_SERVICE))
927                     .registerDisplayListener(mDisplayListener, null);
928         }
929     }
930 
931     /**
932      * Redirects touch events to appropriate recipient views based on swipe direction.
933      * More specifically, swipe up and swipe down will be handled by the view that handles
934      * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
935      * to mode list in order to bring up mode list.
936      */
onSwipeDetected(int swipeState)937     private void onSwipeDetected(int swipeState) {
938         if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
939             // TODO: Polish quick switch after this release.
940             // Quick switch between modes.
941             int currentModuleIndex = mController.getCurrentModuleIndex();
942             final int moduleToTransitionTo =
943                     mController.getQuickSwitchToModuleId(currentModuleIndex);
944             if (currentModuleIndex != moduleToTransitionTo) {
945                 mAppRootView.redirectTouchEventsTo(mModeTransitionView);
946                 int shadeColorId = R.color.mode_cover_default_color;
947                 int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo,
948                         mController.getAndroidContext());
949 
950                 AnimationFinishedListener listener = new AnimationFinishedListener() {
951                     @Override
952                     public void onAnimationFinished(boolean success) {
953                         if (success) {
954                             mHideCoverRunnable = new Runnable() {
955                                 @Override
956                                 public void run() {
957                                     mModeTransitionView.startPeepHoleAnimation();
958                                 }
959                             };
960                             mModeCoverState = COVER_SHOWN;
961                             // Go to new module when the previous operation is successful.
962                             mController.onModeSelected(moduleToTransitionTo);
963                         }
964                     }
965                 };
966             }
967         } else if (swipeState == SWIPE_LEFT) {
968             // Pass the touch sequence to filmstrip layout.
969             mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
970         } else if (swipeState == SWIPE_RIGHT) {
971             // Pass the touch to mode switcher
972             mAppRootView.redirectTouchEventsTo(mModeListView);
973         }
974     }
975 
976     /**
977      * Gets called when activity resumes in preview.
978      */
resume()979     public void resume() {
980         // Show mode theme cover until preview is ready
981         showModeCoverUntilPreviewReady();
982 
983         // Hide action bar first since we are in full screen mode first, and
984         // switch the system UI to lights-out mode.
985         mFilmstripPanel.hide();
986 
987         // Show UI that is meant to only be used when spoken feedback is
988         // enabled.
989         mAccessibilityEnabled = isSpokenFeedbackAccessibilityEnabled();
990         mAccessibilityAffordances.setVisibility(mAccessibilityEnabled ? View.VISIBLE : View.GONE);
991     }
992 
993     /**
994      * @return Whether any spoken feedback accessibility feature is currently
995      *         enabled.
996      */
isSpokenFeedbackAccessibilityEnabled()997     private boolean isSpokenFeedbackAccessibilityEnabled() {
998         AccessibilityManager accessibilityManager = (AccessibilityManager) mController
999                 .getAndroidContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
1000         List<AccessibilityServiceInfo> infos = accessibilityManager
1001                 .getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
1002         return infos != null && !infos.isEmpty();
1003     }
1004 
1005     /**
1006      * Opens the mode list (e.g. because of the menu button being pressed) and
1007      * adapts the rest of the UI.
1008      */
openModeList()1009     public void openModeList() {
1010         mModeOptionsOverlay.closeModeOptions();
1011         mModeListView.onMenuPressed();
1012     }
1013 
1014     /**
1015      * A cover view showing the mode theme color and mode icon will be visible on
1016      * top of preview until preview is ready (i.e. camera preview is started and
1017      * the first frame has been received).
1018      */
showModeCoverUntilPreviewReady()1019     private void showModeCoverUntilPreviewReady() {
1020         int modeId = mController.getCurrentModuleIndex();
1021         int colorId = R.color.mode_cover_default_color;;
1022         int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext());
1023         mModeTransitionView.setupModeCover(colorId, iconId);
1024         mHideCoverRunnable = new Runnable() {
1025             @Override
1026             public void run() {
1027                 mModeTransitionView.hideModeCover(null);
1028                 if (!mDisableAllUserInteractions) {
1029                     showShimmyDelayed();
1030                 }
1031             }
1032         };
1033         mModeCoverState = COVER_SHOWN;
1034     }
1035 
showShimmyDelayed()1036     private void showShimmyDelayed() {
1037         if (!mIsCaptureIntent) {
1038             // Show shimmy in SHIMMY_DELAY_MS
1039             mModeListView.showModeSwitcherHint();
1040         }
1041     }
1042 
hideModeCover()1043     private void hideModeCover() {
1044         if (mHideCoverRunnable != null) {
1045             mAppRootView.post(mHideCoverRunnable);
1046             mHideCoverRunnable = null;
1047         }
1048         mModeCoverState = COVER_HIDDEN;
1049         if (mCoverHiddenTime < 0) {
1050             mCoverHiddenTime = System.currentTimeMillis();
1051         }
1052     }
1053 
1054 
onPreviewVisiblityChanged(int visibility)1055     public void onPreviewVisiblityChanged(int visibility) {
1056         if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1057             setIndicatorBottomBarWrapperVisible(false);
1058             mAccessibilityAffordances.setVisibility(View.GONE);
1059         } else {
1060             setIndicatorBottomBarWrapperVisible(true);
1061             if (mAccessibilityEnabled) {
1062                 mAccessibilityAffordances.setVisibility(View.VISIBLE);
1063             } else {
1064                 mAccessibilityAffordances.setVisibility(View.GONE);
1065             }
1066         }
1067     }
1068 
1069     /**
1070      * Call to stop the preview from being rendered.
1071      */
pausePreviewRendering()1072     public void pausePreviewRendering() {
1073         mTextureView.setVisibility(View.INVISIBLE);
1074     }
1075 
1076     /**
1077      * Call to begin rendering the preview again.
1078      */
resumePreviewRendering()1079     public void resumePreviewRendering() {
1080         mTextureView.setVisibility(View.VISIBLE);
1081     }
1082 
1083     /**
1084      * Returns the transform associated with the preview view.
1085      *
1086      * @param m the Matrix in which to copy the current transform.
1087      * @return The specified matrix if not null or a new Matrix instance
1088      *         otherwise.
1089      */
getPreviewTransform(Matrix m)1090     public Matrix getPreviewTransform(Matrix m) {
1091         return mTextureView.getTransform(m);
1092     }
1093 
1094     @Override
onOpenFullScreen()1095     public void onOpenFullScreen() {
1096         // Do nothing.
1097     }
1098 
1099     @Override
onModeListOpenProgress(float progress)1100     public void onModeListOpenProgress(float progress) {
1101         progress = 1 - progress;
1102         float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress);
1103         mModeOptionsToggle.setAlpha(interpolatedProgress);
1104         // Change shutter button alpha linearly based on the mode list open progress:
1105         // set the alpha to disabled alpha when list is fully open, to enabled alpha
1106         // when the list is fully closed.
1107         mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED
1108                 + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED);
1109     }
1110 
1111     @Override
onModeListClosed()1112     public void onModeListClosed() {
1113         // Make sure the alpha on mode options ellipse is reset when mode drawer
1114         // is closed.
1115         mModeOptionsToggle.setAlpha(1f);
1116         mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
1117     }
1118 
1119     /**
1120      * Called when the back key is pressed.
1121      *
1122      * @return Whether the UI responded to the key event.
1123      */
onBackPressed()1124     public boolean onBackPressed() {
1125         if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1126             return mFilmstripLayout.onBackPressed();
1127         } else {
1128             return mModeListView.onBackPressed();
1129         }
1130     }
1131 
1132     /**
1133      * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
1134      * listens to SurfaceTexture changes. In addition, listeners are set on
1135      * dependent app ui elements.
1136      *
1137      * @param previewStatusListener the listener that gets notified when SurfaceTexture
1138      *                              changes
1139      */
setPreviewStatusListener(PreviewStatusListener previewStatusListener)1140     public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1141         mPreviewStatusListener = previewStatusListener;
1142         if (mPreviewStatusListener != null) {
1143             onPreviewListenerChanged();
1144         }
1145     }
1146 
1147     /**
1148      * When the PreviewStatusListener changes, listeners need to be
1149      * set on the following app ui elements:
1150      * {@link com.android.camera.ui.PreviewOverlay},
1151      * {@link com.android.camera.ui.BottomBar},
1152      * {@link com.android.camera.ui.IndicatorIconController}.
1153      */
onPreviewListenerChanged()1154     private void onPreviewListenerChanged() {
1155         // Set a listener for recognizing preview gestures.
1156         GestureDetector.OnGestureListener gestureListener
1157             = mPreviewStatusListener.getGestureListener();
1158         if (gestureListener != null) {
1159             mPreviewOverlay.setGestureListener(gestureListener);
1160         }
1161         View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener();
1162         if (touchListener != null) {
1163             mPreviewOverlay.setTouchListener(touchListener);
1164         }
1165 
1166         mTextureViewHelper.setAutoAdjustTransform(
1167                 mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
1168     }
1169 
1170     /**
1171      * This method should be called in onCameraOpened.  It defines CameraAppUI
1172      * specific changes that depend on the camera or camera settings.
1173      */
onChangeCamera()1174     public void onChangeCamera() {
1175         ModuleController moduleController = mController.getCurrentModuleController();
1176         applyModuleSpecs(moduleController.getHardwareSpec(), moduleController.getBottomBarSpec());
1177 
1178         if (mIndicatorIconController != null) {
1179             // Sync the settings state with the indicator state.
1180             mIndicatorIconController.syncIndicators();
1181         }
1182     }
1183 
1184     /**
1185      * Adds a listener to receive callbacks when preview area changes.
1186      */
addPreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1187     public void addPreviewAreaChangedListener(
1188             PreviewStatusListener.PreviewAreaChangedListener listener) {
1189         mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
1190     }
1191 
1192     /**
1193      * Removes a listener that receives callbacks when preview area changes.
1194      */
removePreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1195     public void removePreviewAreaChangedListener(
1196             PreviewStatusListener.PreviewAreaChangedListener listener) {
1197         mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
1198     }
1199 
1200     /**
1201      * This inflates generic_module layout, which contains all the shared views across
1202      * modules. Then each module inflates their own views in the given view group. For
1203      * now, this is called every time switching from a not-yet-refactored module to a
1204      * refactored module. In the future, this should only need to be done once per app
1205      * start.
1206      */
prepareModuleUI()1207     public void prepareModuleUI() {
1208         mController.getSettingsManager().addListener(this);
1209         mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
1210         mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
1211         mTextureViewHelper = new TextureViewHelper(mTextureView, mCaptureLayoutHelper,
1212                 mController.getCameraProvider());
1213         mTextureViewHelper.setSurfaceTextureListener(this);
1214         mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
1215 
1216         mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
1217         int unpressedColor = mController.getAndroidContext().getResources()
1218             .getColor(R.color.bottombar_unpressed);
1219         setBottomBarColor(unpressedColor);
1220         updateModeSpecificUIColors();
1221 
1222         mBottomBar.setCaptureLayoutHelper(mCaptureLayoutHelper);
1223 
1224         mModeOptionsOverlay
1225             = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay);
1226 
1227         // Sets the visibility of the bottom bar and the mode options.
1228         resetBottomControls(mController.getCurrentModuleController(),
1229             mController.getCurrentModuleIndex());
1230         mModeOptionsOverlay.setCaptureLayoutHelper(mCaptureLayoutHelper);
1231 
1232         mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1233         addShutterListener(mController.getCurrentModuleController());
1234         addShutterListener(mModeOptionsOverlay);
1235         addShutterListener(this);
1236 
1237         mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
1238         mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
1239 
1240         mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
1241         mPreviewOverlay.setOnTouchListener(new MyTouchListener());
1242         mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
1243 
1244         mCaptureOverlay = (CaptureAnimationOverlay)
1245                 mCameraRootView.findViewById(R.id.capture_overlay);
1246         mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
1247         mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
1248 
1249         if (mIndicatorIconController == null) {
1250             mIndicatorIconController =
1251                 new IndicatorIconController(mController, mAppRootView);
1252         }
1253 
1254         mController.getButtonManager().load(mCameraRootView);
1255         mController.getButtonManager().setListener(mIndicatorIconController);
1256         mController.getSettingsManager().addListener(mIndicatorIconController);
1257 
1258         mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
1259         mFocusOverlay = mCameraRootView.findViewById(R.id.focus_overlay);
1260         mTutorialsPlaceHolderWrapper = (FrameLayout) mCameraRootView
1261                 .findViewById(R.id.tutorials_placeholder_wrapper);
1262         mIndicatorBottomBarWrapper = (BottomBarModeOptionsWrapper) mAppRootView
1263                 .findViewById(R.id.indicator_bottombar_wrapper);
1264         mIndicatorBottomBarWrapper.setCaptureLayoutHelper(mCaptureLayoutHelper);
1265         mTextureViewHelper.addPreviewAreaSizeChangedListener(
1266                 new PreviewStatusListener.PreviewAreaChangedListener() {
1267                     @Override
1268                     public void onPreviewAreaChanged(RectF previewArea) {
1269                         mPeekView.setTranslationX(previewArea.right - mAppRootView.getRight());
1270                     }
1271                 });
1272 
1273         mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
1274         mTextureViewHelper.addAspectRatioChangedListener(
1275                 new PreviewStatusListener.PreviewAspectRatioChangedListener() {
1276                     @Override
1277                     public void onPreviewAspectRatioChanged(float aspectRatio) {
1278                         mModeOptionsOverlay.requestLayout();
1279                         mBottomBar.requestLayout();
1280                     }
1281                 }
1282         );
1283     }
1284 
1285     /**
1286      * Called indirectly from each module in their initialization to get a view group
1287      * to inflate the module specific views in.
1288      *
1289      * @return a view group for modules to attach views to
1290      */
getModuleRootView()1291     public FrameLayout getModuleRootView() {
1292         // TODO: Change it to mModuleUI when refactor is done
1293         return mCameraRootView;
1294     }
1295 
1296     /**
1297      * Remove all the module specific views.
1298      */
clearModuleUI()1299     public void clearModuleUI() {
1300         if (mModuleUI != null) {
1301             mModuleUI.removeAllViews();
1302         }
1303         removeShutterListener(mController.getCurrentModuleController());
1304         mTutorialsPlaceHolderWrapper.removeAllViews();
1305         mTutorialsPlaceHolderWrapper.setVisibility(View.GONE);
1306 
1307         setShutterButtonEnabled(true);
1308         mPreviewStatusListener = null;
1309         mPreviewOverlay.reset();
1310         mFocusOverlay.setVisibility(View.INVISIBLE);
1311     }
1312 
1313     /**
1314      * Gets called when preview is ready to start. It sets up one shot preview callback
1315      * in order to receive a callback when the preview frame is available, so that
1316      * the preview cover can be hidden to reveal preview.
1317      *
1318      * An alternative for getting the timing to hide preview cover is through
1319      * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
1320      * which is less accurate but therefore is the fallback for modules that manage
1321      * their own preview callbacks (as setting one preview callback will override
1322      * any other installed preview callbacks), or use camera2 API.
1323      */
onPreviewReadyToStart()1324     public void onPreviewReadyToStart() {
1325         if (mModeCoverState == COVER_SHOWN) {
1326             mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
1327             mController.setupOneShotPreviewListener();
1328         }
1329     }
1330 
1331     /**
1332      * Gets called when preview is started.
1333      */
onPreviewStarted()1334     public void onPreviewStarted() {
1335         Log.v(TAG, "onPreviewStarted");
1336         if (mModeCoverState == COVER_SHOWN) {
1337             mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1338         }
1339         enableModeOptions();
1340     }
1341 
1342     /**
1343      * Gets notified when next preview frame comes in.
1344      */
onNewPreviewFrame()1345     public void onNewPreviewFrame() {
1346         Log.v(TAG, "onNewPreviewFrame");
1347         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1348         hideModeCover();
1349     }
1350 
1351     @Override
onShutterButtonClick()1352     public void onShutterButtonClick() {
1353         /*
1354          * Set the mode options toggle unclickable, generally
1355          * throughout the app, whenever the shutter button is clicked.
1356          *
1357          * This could be done in the OnShutterButtonListener of the
1358          * ModeOptionsOverlay, but since it is very important that we
1359          * can clearly see when the toggle becomes clickable again,
1360          * keep all of that logic at this level.
1361          */
1362         disableModeOptions();
1363     }
1364 
1365     @Override
onShutterCoordinate(TouchCoordinate coord)1366     public void onShutterCoordinate(TouchCoordinate coord) {
1367         // Do nothing.
1368     }
1369 
1370     @Override
onShutterButtonFocus(boolean pressed)1371     public void onShutterButtonFocus(boolean pressed) {
1372         // noop
1373     }
1374 
1375     /**
1376      * Set the mode options toggle clickable.
1377      */
enableModeOptions()1378     public void enableModeOptions() {
1379         /*
1380          * For modules using camera 1 api, this gets called in
1381          * onSurfaceTextureUpdated whenever the preview gets stopped and
1382          * started after each capture.  This also takes care of the
1383          * case where the mode options might be unclickable when we
1384          * switch modes
1385          *
1386          * For modules using camera 2 api, they're required to call this
1387          * method when a capture is "completed".  Unfortunately this differs
1388          * per module implementation.
1389          */
1390         if (!mDisableAllUserInteractions) {
1391             mModeOptionsOverlay.setToggleClickable(true);
1392         }
1393     }
1394 
1395     /**
1396      * Set the mode options toggle not clickable.
1397      */
disableModeOptions()1398     public void disableModeOptions() {
1399         mModeOptionsOverlay.setToggleClickable(false);
1400     }
1401 
setDisableAllUserInteractions(boolean disable)1402     public void setDisableAllUserInteractions(boolean disable) {
1403         if (disable) {
1404             disableModeOptions();
1405             setShutterButtonEnabled(false);
1406             setSwipeEnabled(false);
1407             mModeListView.hideAnimated();
1408         } else {
1409             enableModeOptions();
1410             setShutterButtonEnabled(true);
1411             setSwipeEnabled(true);
1412         }
1413         mDisableAllUserInteractions = disable;
1414     }
1415 
1416     /**
1417      * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
1418      *
1419      * @param modeIndex mode index of the selected mode
1420      */
1421     @Override
onModeSelected(int modeIndex)1422     public void onModeSelected(int modeIndex) {
1423         mHideCoverRunnable = new Runnable() {
1424             @Override
1425             public void run() {
1426                 mModeListView.startModeSelectionAnimation();
1427             }
1428         };
1429         mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
1430         mModeCoverState = COVER_SHOWN;
1431 
1432         int lastIndex = mController.getCurrentModuleIndex();
1433         // Actual mode teardown / new mode initialization happens here
1434         mController.onModeSelected(modeIndex);
1435         int currentIndex = mController.getCurrentModuleIndex();
1436 
1437         if (lastIndex == currentIndex) {
1438             hideModeCover();
1439         }
1440 
1441         updateModeSpecificUIColors();
1442     }
1443 
updateModeSpecificUIColors()1444     private void updateModeSpecificUIColors() {
1445         setBottomBarColorsForModeIndex(mController.getCurrentModuleIndex());
1446     }
1447 
1448     @Override
onSettingsSelected()1449     public void onSettingsSelected() {
1450         mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
1451                                              Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING, false);
1452         mModeListView.setShouldShowSettingsCling(false);
1453         mController.onSettingsSelected();
1454     }
1455 
1456     @Override
getCurrentModeIndex()1457     public int getCurrentModeIndex() {
1458         return mController.getCurrentModuleIndex();
1459     }
1460 
1461     /********************** Capture animation **********************/
1462     /* TODO: This session is subject to UX changes. In addition to the generic
1463        flash animation and post capture animation, consider designating a parameter
1464        for specifying the type of animation, as well as an animation finished listener
1465        so that modules can have more knowledge of the status of the animation. */
1466 
1467     /**
1468      * Starts the filmstrip peek animation.
1469      *
1470      * @param bitmap The bitmap to show.
1471      * @param strong Whether the animation shows more portion of the bitmap or
1472      *               not.
1473      * @param accessibilityString An accessibility String to be announced
1474                      during the peek animation.
1475      */
startPeekAnimation(Bitmap bitmap, boolean strong, String accessibilityString)1476     public void startPeekAnimation(Bitmap bitmap, boolean strong, String accessibilityString) {
1477         if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1478             return;
1479         }
1480         mPeekView.startPeekAnimation(bitmap, strong, accessibilityString);
1481     }
1482 
1483     /**
1484      * Starts the pre-capture animation.
1485      *
1486      * @param shortFlash show shortest possible flash instead of regular long version.
1487      */
startPreCaptureAnimation(boolean shortFlash)1488     public void startPreCaptureAnimation(boolean shortFlash) {
1489         mCaptureOverlay.startFlashAnimation(shortFlash);
1490     }
1491 
1492     /**
1493      * Cancels the pre-capture animation.
1494      */
cancelPreCaptureAnimation()1495     public void cancelPreCaptureAnimation() {
1496         mAnimationManager.cancelAnimations();
1497     }
1498 
1499     /**
1500      * Cancels the post-capture animation.
1501      */
cancelPostCaptureAnimation()1502     public void cancelPostCaptureAnimation() {
1503         mAnimationManager.cancelAnimations();
1504     }
1505 
getFilmstripContentPanel()1506     public FilmstripContentPanel getFilmstripContentPanel() {
1507         return mFilmstripPanel;
1508     }
1509 
1510     /**
1511      * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the
1512      * bottom of the filmstrip.
1513      */
getFilmstripBottomControls()1514     public BottomPanel getFilmstripBottomControls() {
1515         return mFilmstripBottomControls;
1516     }
1517 
showBottomControls()1518     public void showBottomControls() {
1519         mFilmstripBottomControls.show();
1520     }
1521 
hideBottomControls()1522     public void hideBottomControls() {
1523         mFilmstripBottomControls.hide();
1524     }
1525 
1526     /**
1527      * @param listener The listener for bottom controls.
1528      */
setFilmstripBottomControlsListener(BottomPanel.Listener listener)1529     public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) {
1530         mFilmstripBottomControls.setListener(listener);
1531     }
1532 
1533     /***************************SurfaceTexture Api and Listener*********************************/
1534 
1535     /**
1536      * Return the shared surface texture.
1537      */
getSurfaceTexture()1538     public SurfaceTexture getSurfaceTexture() {
1539         return mSurface;
1540     }
1541 
1542     /**
1543      * Return the shared {@link android.graphics.SurfaceTexture}'s width.
1544      */
getSurfaceWidth()1545     public int getSurfaceWidth() {
1546         return mSurfaceWidth;
1547     }
1548 
1549     /**
1550      * Return the shared {@link android.graphics.SurfaceTexture}'s height.
1551      */
getSurfaceHeight()1552     public int getSurfaceHeight() {
1553         return mSurfaceHeight;
1554     }
1555 
1556     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)1557     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1558         mSurface = surface;
1559         mSurfaceWidth = width;
1560         mSurfaceHeight = height;
1561         Log.v(TAG, "SurfaceTexture is available");
1562         if (mPreviewStatusListener != null) {
1563             mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1564         }
1565         enableModeOptions();
1566     }
1567 
1568     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)1569     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1570         mSurface = surface;
1571         mSurfaceWidth = width;
1572         mSurfaceHeight = height;
1573         if (mPreviewStatusListener != null) {
1574             mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1575         }
1576     }
1577 
1578     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)1579     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1580         mSurface = null;
1581         Log.v(TAG, "SurfaceTexture is destroyed");
1582         if (mPreviewStatusListener != null) {
1583             return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1584         }
1585         return false;
1586     }
1587 
1588     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)1589     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1590         mSurface = surface;
1591         if (mPreviewStatusListener != null) {
1592             mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1593         }
1594         if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE) {
1595             Log.v(TAG, "hiding cover via onSurfaceTextureUpdated");
1596             CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1597             hideModeCover();
1598         }
1599     }
1600 
1601     /****************************Grid lines api ******************************/
1602 
1603     /**
1604      * Show a set of evenly spaced lines over the preview.  The number
1605      * of lines horizontally and vertically is determined by
1606      * {@link com.android.camera.ui.GridLines}.
1607      */
showGridLines()1608     public void showGridLines() {
1609         if (mGridLines != null) {
1610             mGridLines.setVisibility(View.VISIBLE);
1611         }
1612     }
1613 
1614     /**
1615      * Hide the set of evenly spaced grid lines overlaying the preview.
1616      */
hideGridLines()1617     public void hideGridLines() {
1618         if (mGridLines != null) {
1619             mGridLines.setVisibility(View.INVISIBLE);
1620         }
1621     }
1622 
1623     /**
1624      * Return a callback which shows or hide the preview grid lines
1625      * depending on whether the grid lines setting is set on.
1626      */
getGridLinesCallback()1627     public ButtonManager.ButtonCallback getGridLinesCallback() {
1628         return new ButtonManager.ButtonCallback() {
1629             @Override
1630             public void onStateChanged(int state) {
1631                 if (Keys.areGridLinesOn(mController.getSettingsManager())) {
1632                     showGridLines();
1633                 } else {
1634                     hideGridLines();
1635                 }
1636             }
1637         };
1638     }
1639 
1640     /***************************Mode options api *****************************/
1641 
1642     /**
1643      * Set the mode options visible.
1644      */
1645     public void showModeOptions() {
1646         /* Make mode options clickable. */
1647         enableModeOptions();
1648         mModeOptionsOverlay.setVisibility(View.VISIBLE);
1649     }
1650 
1651     /**
1652      * Set the mode options invisible.  This is necessary for modes
1653      * that don't show a bottom bar for the capture UI.
1654      */
1655     public void hideModeOptions() {
1656         mModeOptionsOverlay.setVisibility(View.INVISIBLE);
1657     }
1658 
1659     /****************************Bottom bar api ******************************/
1660 
1661     /**
1662      * Sets up the bottom bar and mode options with the correct
1663      * shutter button and visibility based on the current module.
1664      */
1665     public void resetBottomControls(ModuleController module, int moduleIndex) {
1666         if (areBottomControlsUsed(module)) {
1667             setBottomBarShutterIcon(moduleIndex);
1668             mCaptureLayoutHelper.setShowBottomBar(true);
1669         } else {
1670             mCaptureLayoutHelper.setShowBottomBar(false);
1671         }
1672     }
1673 
1674     /**
1675      * Show or hide the mode options and bottom bar, based on
1676      * whether the current module is using the bottom bar.  Returns
1677      * whether the mode options and bottom bar are used.
1678      */
1679     private boolean areBottomControlsUsed(ModuleController module) {
1680         if (module.isUsingBottomBar()) {
1681             showBottomBar();
1682             showModeOptions();
1683             return true;
1684         } else {
1685             hideBottomBar();
1686             hideModeOptions();
1687             return false;
1688         }
1689     }
1690 
1691     /**
1692      * Set the bottom bar visible.
1693      */
1694     public void showBottomBar() {
1695         mBottomBar.setVisibility(View.VISIBLE);
1696     }
1697 
1698     /**
1699      * Set the bottom bar invisible.
1700      */
1701     public void hideBottomBar() {
1702         mBottomBar.setVisibility(View.INVISIBLE);
1703     }
1704 
1705     /**
1706      * Sets the color of the bottom bar.
1707      */
1708     public void setBottomBarColor(int colorId) {
1709         mBottomBar.setBackgroundColor(colorId);
1710     }
1711 
1712     /**
1713      * Sets the pressed color of the bottom bar for a camera mode index.
1714      */
1715     public void setBottomBarColorsForModeIndex(int index) {
1716         mBottomBar.setColorsForModeIndex(index);
1717     }
1718 
1719     /**
1720      * Sets the shutter button icon on the bottom bar, based on
1721      * the mode index.
1722      */
1723     public void setBottomBarShutterIcon(int modeIndex) {
1724         int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex,
1725             mController.getAndroidContext());
1726         mBottomBar.setShutterButtonIcon(shutterIconId);
1727     }
1728 
1729     public void animateBottomBarToVideoStop(int shutterIconId) {
1730         mBottomBar.animateToVideoStop(shutterIconId);
1731     }
1732 
1733     public void animateBottomBarToFullSize(int shutterIconId) {
1734         mBottomBar.animateToFullSize(shutterIconId);
1735     }
1736 
1737     public void setShutterButtonEnabled(final boolean enabled) {
1738         if (!mDisableAllUserInteractions) {
1739             mBottomBar.post(new Runnable() {
1740                 @Override
1741                 public void run() {
1742                     mBottomBar.setShutterButtonEnabled(enabled);
1743                 }
1744             });
1745         }
1746     }
1747 
1748     public void setShutterButtonImportantToA11y(boolean important) {
1749         mBottomBar.setShutterButtonImportantToA11y(important);
1750     }
1751 
1752     public boolean isShutterButtonEnabled() {
1753         return mBottomBar.isShutterButtonEnabled();
1754     }
1755 
1756     public void setIndicatorBottomBarWrapperVisible(boolean visible) {
1757         mIndicatorBottomBarWrapper.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1758     }
1759 
1760     /**
1761      * Set the visibility of the bottom bar.
1762      */
1763     // TODO: needed for when panorama is managed by the generic module ui.
1764     public void setBottomBarVisible(boolean visible) {
1765         mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1766     }
1767 
1768     /**
1769      * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button.
1770      */
1771     public void addShutterListener(ShutterButton.OnShutterButtonListener listener) {
1772         mShutterButton.addOnShutterButtonListener(listener);
1773     }
1774 
1775     /**
1776      * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button.
1777      */
1778     public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) {
1779         mShutterButton.removeOnShutterButtonListener(listener);
1780     }
1781 
1782     /**
1783      * Performs a transition to the capture layout of the bottom bar.
1784      */
1785     public void transitionToCapture() {
1786         ModuleController moduleController = mController.getCurrentModuleController();
1787         applyModuleSpecs(moduleController.getHardwareSpec(),
1788             moduleController.getBottomBarSpec());
1789         mBottomBar.transitionToCapture();
1790     }
1791 
1792     /**
1793      * Displays the Cancel button instead of the capture button.
1794      */
1795     public void transitionToCancel() {
1796         ModuleController moduleController = mController.getCurrentModuleController();
1797         applyModuleSpecs(moduleController.getHardwareSpec(),
1798                 moduleController.getBottomBarSpec());
1799         mBottomBar.transitionToCancel();
1800     }
1801 
1802     /**
1803      * Performs a transition to the global intent layout.
1804      */
1805     public void transitionToIntentCaptureLayout() {
1806         ModuleController moduleController = mController.getCurrentModuleController();
1807         applyModuleSpecs(moduleController.getHardwareSpec(),
1808             moduleController.getBottomBarSpec());
1809         mBottomBar.transitionToIntentCaptureLayout();
1810     }
1811 
1812     /**
1813      * Performs a transition to the global intent review layout.
1814      */
1815     public void transitionToIntentReviewLayout() {
1816         ModuleController moduleController = mController.getCurrentModuleController();
1817         applyModuleSpecs(moduleController.getHardwareSpec(),
1818             moduleController.getBottomBarSpec());
1819         mBottomBar.transitionToIntentReviewLayout();
1820     }
1821 
1822     /**
1823      * @return whether UI is in intent review mode
1824      */
1825     public boolean isInIntentReview() {
1826         return mBottomBar.isInIntentReview();
1827     }
1828 
1829     @Override
1830     public void onSettingChanged(SettingsManager settingsManager, String key) {
1831         // Update the mode options based on the hardware spec,
1832         // when hdr changes to prevent flash from getting out of sync.
1833         if (key.equals(Keys.KEY_CAMERA_HDR)) {
1834             ModuleController moduleController = mController.getCurrentModuleController();
1835             applyModuleSpecs(moduleController.getHardwareSpec(),
1836                              moduleController.getBottomBarSpec());
1837         }
1838     }
1839 
1840     /**
1841      * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
1842      * to the bottom bar mode options based on limitations from a
1843      * {@link com.android.camera.hardware.HardwareSpec}.
1844      *
1845      * Options not supported by the hardware are either hidden
1846      * or disabled, depending on the option.
1847      *
1848      * Otherwise, the option is fully enabled and clickable.
1849      */
1850     public void applyModuleSpecs(final HardwareSpec hardwareSpec,
1851            final BottomBarUISpec bottomBarSpec) {
1852         if (hardwareSpec == null || bottomBarSpec == null) {
1853             return;
1854         }
1855 
1856         ButtonManager buttonManager = mController.getButtonManager();
1857         SettingsManager settingsManager = mController.getSettingsManager();
1858 
1859         buttonManager.setToInitialState();
1860 
1861         /** Standard mode options */
1862         if (mController.getCameraProvider().getNumberOfCameras() > 1 &&
1863                 hardwareSpec.isFrontCameraSupported()) {
1864             if (bottomBarSpec.enableCamera) {
1865                 buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA,
1866                         bottomBarSpec.cameraCallback);
1867             } else {
1868                 buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
1869             }
1870         } else {
1871             // Hide camera icon if front camera not available.
1872             buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
1873         }
1874 
1875         boolean flashBackCamera = mController.getSettingsManager().getBoolean(
1876             SettingsManager.SCOPE_GLOBAL, Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA);
1877         if (bottomBarSpec.hideFlash || !flashBackCamera) {
1878             // Hide both flash and torch button in flash disable logic
1879             buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
1880             buttonManager.hideButton(ButtonManager.BUTTON_TORCH);
1881         } else {
1882             if (hardwareSpec.isFlashSupported()) {
1883                 if (bottomBarSpec.enableFlash) {
1884                     buttonManager.initializeButton(ButtonManager.BUTTON_FLASH,
1885                         bottomBarSpec.flashCallback);
1886                 } else if (bottomBarSpec.enableTorchFlash) {
1887                     buttonManager.initializeButton(ButtonManager.BUTTON_TORCH,
1888                         bottomBarSpec.flashCallback);
1889                 } else if (bottomBarSpec.enableHdrPlusFlash) {
1890                     buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS_FLASH,
1891                         bottomBarSpec.flashCallback);
1892                 } else {
1893                     // Hide both flash and torch button in flash disable logic
1894                     buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1895                     buttonManager.disableButton(ButtonManager.BUTTON_TORCH);
1896                 }
1897             } else {
1898                 // Disable both flash and torch icon if not supported
1899                 // by the chosen camera hardware.
1900                 buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
1901                 buttonManager.disableButton(ButtonManager.BUTTON_TORCH);
1902             }
1903         }
1904 
1905         if (bottomBarSpec.hideHdr || mIsCaptureIntent) {
1906             // Force hide hdr or hdr plus icon.
1907             buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS);
1908         } else {
1909             if (hardwareSpec.isHdrPlusSupported()) {
1910                 if (bottomBarSpec.enableHdr && Keys.isCameraBackFacing(settingsManager,
1911                                                                        mController.getModuleScope())) {
1912                     buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS,
1913                             bottomBarSpec.hdrCallback);
1914                 } else {
1915                     buttonManager.disableButton(ButtonManager.BUTTON_HDR_PLUS);
1916                 }
1917             } else if (hardwareSpec.isHdrSupported()) {
1918                 if (bottomBarSpec.enableHdr && Keys.isCameraBackFacing(settingsManager,
1919                                                                        mController.getModuleScope())) {
1920                     buttonManager.initializeButton(ButtonManager.BUTTON_HDR,
1921                             bottomBarSpec.hdrCallback);
1922                 } else {
1923                     buttonManager.disableButton(ButtonManager.BUTTON_HDR);
1924                 }
1925             } else {
1926                 // Hide hdr plus or hdr icon if neither are supported.
1927                 buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS);
1928             }
1929         }
1930 
1931         if (bottomBarSpec.hideGridLines) {
1932             // Force hide grid lines icon.
1933             buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES);
1934             hideGridLines();
1935         } else {
1936             if (bottomBarSpec.enableGridLines) {
1937                 buttonManager.initializeButton(ButtonManager.BUTTON_GRID_LINES,
1938                         bottomBarSpec.gridLinesCallback != null ?
1939                                 bottomBarSpec.gridLinesCallback : getGridLinesCallback()
1940                 );
1941             } else {
1942                 buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES);
1943                 hideGridLines();
1944             }
1945         }
1946 
1947         if (bottomBarSpec.enableSelfTimer) {
1948             buttonManager.initializeButton(ButtonManager.BUTTON_COUNTDOWN, null);
1949         } else {
1950             if (bottomBarSpec.showSelfTimer) {
1951                 buttonManager.disableButton(ButtonManager.BUTTON_COUNTDOWN);
1952             } else {
1953                 buttonManager.hideButton(ButtonManager.BUTTON_COUNTDOWN);
1954             }
1955         }
1956 
1957         if (bottomBarSpec.enablePanoOrientation
1958                 && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
1959             buttonManager.initializePanoOrientationButtons(bottomBarSpec.panoOrientationCallback);
1960         }
1961 
1962         boolean enableExposureCompensation = bottomBarSpec.enableExposureCompensation &&
1963             !(bottomBarSpec.minExposureCompensation == 0 && bottomBarSpec.maxExposureCompensation == 0) &&
1964             mController.getSettingsManager().getBoolean(SettingsManager.SCOPE_GLOBAL,
1965                         Keys.KEY_EXPOSURE_COMPENSATION_ENABLED);
1966         if (enableExposureCompensation) {
1967             buttonManager.initializePushButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION, null);
1968             buttonManager.setExposureCompensationParameters(
1969                 bottomBarSpec.minExposureCompensation,
1970                 bottomBarSpec.maxExposureCompensation,
1971                 bottomBarSpec.exposureCompensationStep);
1972 
1973             buttonManager.setExposureCompensationCallback(
1974                     bottomBarSpec.exposureCompensationSetCallback);
1975             buttonManager.updateExposureButtons();
1976         } else {
1977             buttonManager.hideButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION);
1978             buttonManager.setExposureCompensationCallback(null);
1979         }
1980 
1981         /** Intent UI */
1982         if (bottomBarSpec.showCancel) {
1983             buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL,
1984                     bottomBarSpec.cancelCallback);
1985         }
1986         if (bottomBarSpec.showDone) {
1987             buttonManager.initializePushButton(ButtonManager.BUTTON_DONE,
1988                     bottomBarSpec.doneCallback);
1989         }
1990         if (bottomBarSpec.showRetake) {
1991             buttonManager.initializePushButton(ButtonManager.BUTTON_RETAKE,
1992                     bottomBarSpec.retakeCallback);
1993         }
1994         if (bottomBarSpec.showReview) {
1995             buttonManager.initializePushButton(ButtonManager.BUTTON_REVIEW,
1996                     bottomBarSpec.reviewCallback,
1997                     R.drawable.ic_play);
1998         }
1999     }
2000 
2001     /**
2002      * Shows the given tutorial on the screen.
2003      */
2004     public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) {
2005         tutorial.show(mTutorialsPlaceHolderWrapper, inflater);
2006     }
2007 
2008     /***************************Filmstrip api *****************************/
2009 
2010     public void showFilmstrip() {
2011         mModeListView.onBackPressed();
2012         mFilmstripLayout.showFilmstrip();
2013     }
2014 
2015     public void hideFilmstrip() {
2016         mFilmstripLayout.hideFilmstrip();
2017     }
2018 }
2019