• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.app.Activity;
20 import android.content.res.Resources;
21 import android.graphics.Bitmap;
22 import android.graphics.Canvas;
23 import android.graphics.Matrix;
24 import android.graphics.RectF;
25 import android.graphics.SurfaceTexture;
26 import android.hardware.display.DisplayManager;
27 import android.util.CameraPerformanceTracker;
28 import android.view.GestureDetector;
29 import android.view.LayoutInflater;
30 import android.view.MotionEvent;
31 import android.view.TextureView;
32 import android.view.View;
33 import android.view.ViewConfiguration;
34 import android.view.ViewGroup;
35 import android.widget.FrameLayout;
36 import android.widget.ImageButton;
37 
38 import com.android.camera.AccessibilityUtil;
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.CaptureAnimationOverlay;
53 import com.android.camera.ui.GridLines;
54 import com.android.camera.ui.MainActivityLayout;
55 import com.android.camera.ui.ModeListView;
56 import com.android.camera.ui.ModeTransitionView;
57 import com.android.camera.ui.PreviewOverlay;
58 import com.android.camera.ui.PreviewStatusListener;
59 import com.android.camera.ui.StickyBottomCaptureLayout;
60 import com.android.camera.ui.TouchCoordinate;
61 import com.android.camera.ui.focus.FocusRing;
62 import com.android.camera.util.AndroidServices;
63 import com.android.camera.util.ApiHelper;
64 import com.android.camera.util.CameraUtil;
65 import com.android.camera.util.Gusterpolator;
66 import com.android.camera.util.PhotoSphereHelper;
67 import com.android.camera.widget.Cling;
68 import com.android.camera.widget.FilmstripLayout;
69 import com.android.camera.widget.IndicatorIconController;
70 import com.android.camera.widget.ModeOptionsOverlay;
71 import com.android.camera.widget.RoundedThumbnailView;
72 import com.android.camera2.R;
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         /**
357          * Set true if manual exposure compensation should be visible.
358          *
359          * This option is not constrained by hardware limitations.
360          * For example, this is false in HDR+ mode.
361          */
362         public boolean enableExposureCompensation;
363 
364         /**
365          * Set true if the device and module support exposure compensation.
366          * Used only to show exposure button in disabled (greyed out) state.
367          */
368         public boolean isExposureCompensationSupported;
369 
370         /** Intent UI */
371 
372         /**
373          * Set true if the intent ui cancel option should be visible.
374          */
375         public boolean showCancel;
376         /**
377          * Set true if the intent ui done option should be visible.
378          */
379         public boolean showDone;
380         /**
381          * Set true if the intent ui retake option should be visible.
382          */
383         public boolean showRetake;
384         /**
385          * Set true if the intent ui review option should be visible.
386          */
387         public boolean showReview;
388 
389         /** Mode options callbacks */
390 
391         /**
392          * A {@link com.android.camera.ButtonManager.ButtonCallback}
393          * that will be executed when the camera option is pressed. This
394          * callback can be null.
395          */
396         public ButtonManager.ButtonCallback cameraCallback;
397 
398         /**
399          * A {@link com.android.camera.ButtonManager.ButtonCallback}
400          * that will be executed when the flash option is pressed. This
401          * callback can be null.
402          */
403         public ButtonManager.ButtonCallback flashCallback;
404 
405         /**
406          * A {@link com.android.camera.ButtonManager.ButtonCallback}
407          * that will be executed when the hdr/hdr+ option is pressed. This
408          * callback can be null.
409          */
410         public ButtonManager.ButtonCallback hdrCallback;
411 
412         /**
413          * A {@link com.android.camera.ButtonManager.ButtonCallback}
414          * that will be executed when the grid lines option is pressed. This
415          * callback can be null.
416          */
417         public ButtonManager.ButtonCallback gridLinesCallback;
418 
419         /**
420          * A {@link com.android.camera.ButtonManager.ButtonCallback}
421          * that will execute when the panorama orientation option is pressed.
422          * This callback can be null.
423          */
424         public ButtonManager.ButtonCallback panoOrientationCallback;
425 
426         /** Intent UI callbacks */
427 
428         /**
429          * A {@link android.view.View.OnClickListener} that will execute
430          * when the cancel option is pressed. This callback can be null.
431          */
432         public View.OnClickListener cancelCallback;
433 
434         /**
435          * A {@link android.view.View.OnClickListener} that will execute
436          * when the done option is pressed. This callback can be null.
437          */
438         public View.OnClickListener doneCallback;
439 
440         /**
441          * A {@link android.view.View.OnClickListener} that will execute
442          * when the retake option is pressed. This callback can be null.
443          */
444         public View.OnClickListener retakeCallback;
445 
446         /**
447          * A {@link android.view.View.OnClickListener} that will execute
448          * when the review option is pressed. This callback can be null.
449          */
450         public View.OnClickListener reviewCallback;
451 
452         /**
453          * A ExposureCompensationSetCallback that will execute
454          * when an expsosure button is pressed. This callback can be null.
455          */
456         public interface ExposureCompensationSetCallback {
setExposure(int value)457             public void setExposure(int value);
458         }
459         public ExposureCompensationSetCallback exposureCompensationSetCallback;
460 
461         /**
462          * Exposure compensation parameters.
463          */
464         public int minExposureCompensation;
465         public int maxExposureCompensation;
466         public float exposureCompensationStep;
467 
468         /**
469          * Whether self-timer is enabled.
470          */
471         public boolean enableSelfTimer = false;
472 
473         /**
474          * Whether the option for self-timer should show. If true and
475          * {@link #enableSelfTimer} is false, then the option should be shown
476          * disabled.
477          */
478         public boolean showSelfTimer = false;
479     }
480 
481 
482     private final static Log.Tag TAG = new Log.Tag("CameraAppUI");
483 
484     private final AppController mController;
485     private final boolean mIsCaptureIntent;
486     private final AnimationManager mAnimationManager;
487 
488     // Swipe states:
489     private final static int IDLE = 0;
490     private final static int SWIPE_UP = 1;
491     private final static int SWIPE_DOWN = 2;
492     private final static int SWIPE_LEFT = 3;
493     private final static int SWIPE_RIGHT = 4;
494     private boolean mSwipeEnabled = true;
495 
496     // Shared Surface Texture properities.
497     private SurfaceTexture mSurface;
498     private int mSurfaceWidth;
499     private int mSurfaceHeight;
500 
501     // Touch related measures:
502     private final int mSlop;
503     private final static int SWIPE_TIME_OUT_MS = 500;
504 
505     // Mode cover states:
506     private final static int COVER_HIDDEN = 0;
507     private final static int COVER_SHOWN = 1;
508     private final static int COVER_WILL_HIDE_AT_NEXT_FRAME = 2;
509     private final static int COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE = 3;
510     private final static int COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE = 4;
511 
512     /**
513      * Preview down-sample rate when taking a screenshot.
514      */
515     private final static int DOWN_SAMPLE_RATE_FOR_SCREENSHOT = 2;
516 
517     // App level views:
518     private final FrameLayout mCameraRootView;
519     private final ModeTransitionView mModeTransitionView;
520     private final MainActivityLayout mAppRootView;
521     private final ModeListView mModeListView;
522     private final FilmstripLayout mFilmstripLayout;
523     private TextureView mTextureView;
524     private FrameLayout mModuleUI;
525     private ShutterButton mShutterButton;
526     private ImageButton mCountdownCancelButton;
527     private BottomBar mBottomBar;
528     private ModeOptionsOverlay mModeOptionsOverlay;
529     private IndicatorIconController mIndicatorIconController;
530     private FocusRing mFocusRing;
531     private FrameLayout mTutorialsPlaceHolderWrapper;
532     private StickyBottomCaptureLayout mStickyBottomCaptureLayout;
533     private TextureViewHelper mTextureViewHelper;
534     private final GestureDetector mGestureDetector;
535     private DisplayManager.DisplayListener mDisplayListener;
536     private int mLastRotation;
537     private int mSwipeState = IDLE;
538     private PreviewOverlay mPreviewOverlay;
539     private GridLines mGridLines;
540     private CaptureAnimationOverlay mCaptureOverlay;
541     private PreviewStatusListener mPreviewStatusListener;
542     private int mModeCoverState = COVER_HIDDEN;
543     private final FilmstripBottomPanel mFilmstripBottomControls;
544     private final FilmstripContentPanel mFilmstripPanel;
545     private Runnable mHideCoverRunnable;
546     private final View.OnLayoutChangeListener mPreviewLayoutChangeListener
547             = new View.OnLayoutChangeListener() {
548         @Override
549         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
550                 int oldTop, int oldRight, int oldBottom) {
551             if (mPreviewStatusListener != null) {
552                 mPreviewStatusListener.onPreviewLayoutChanged(v, left, top, right, bottom, oldLeft,
553                         oldTop, oldRight, oldBottom);
554             }
555         }
556     };
557     private View mModeOptionsToggle;
558     private final RoundedThumbnailView mRoundedThumbnailView;
559     private final CaptureLayoutHelper mCaptureLayoutHelper;
560     private final View mAccessibilityAffordances;
561     private AccessibilityUtil mAccessibilityUtil;
562 
563     private boolean mDisableAllUserInteractions;
564     /** Whether to prevent capture indicator from being triggered. */
565     private boolean mSuppressCaptureIndicator;
566 
567     /** Supported HDR mode (none, hdr, hdr+). */
568     private String mHdrSupportMode;
569 
570     /** Used to track the last scope used to update the bottom bar UI. */
571     private String mCurrentCameraScope;
572     private String mCurrentModuleScope;
573 
574     /**
575      * Provides current preview frame and the controls/overlay from the module that
576      * are shown on top of the preview.
577      */
578     public interface CameraModuleScreenShotProvider {
579         /**
580          * Returns the current preview frame down-sampled using the given down-sample
581          * factor.
582          *
583          * @param downSampleFactor the down sample factor for down sampling the
584          *                         preview frame. (e.g. a down sample factor of
585          *                         2 means to scale down the preview frame to 1/2
586          *                         the width and height.)
587          * @return down-sampled preview frame
588          */
getPreviewFrame(int downSampleFactor)589         public Bitmap getPreviewFrame(int downSampleFactor);
590 
591         /**
592          * @return the controls and overlays that are currently showing on top of
593          *         the preview drawn into a bitmap with no scaling applied.
594          */
getPreviewOverlayAndControls()595         public Bitmap getPreviewOverlayAndControls();
596 
597         /**
598          * Returns a bitmap containing the current screenshot.
599          *
600          * @param previewDownSampleFactor the downsample factor applied on the
601          *                                preview frame when taking the screenshot
602          */
getScreenShot(int previewDownSampleFactor)603         public Bitmap getScreenShot(int previewDownSampleFactor);
604     }
605 
606     /**
607      * This listener gets called when the size of the window (excluding the system
608      * decor such as status bar and nav bar) has changed.
609      */
610     public interface NonDecorWindowSizeChangedListener {
onNonDecorWindowSizeChanged(int width, int height, int rotation)611         public void onNonDecorWindowSizeChanged(int width, int height, int rotation);
612     }
613 
614     private final CameraModuleScreenShotProvider mCameraModuleScreenShotProvider =
615             new CameraModuleScreenShotProvider() {
616                 @Override
617                 public Bitmap getPreviewFrame(int downSampleFactor) {
618                     if (mCameraRootView == null || mTextureView == null) {
619                         return null;
620                     }
621                     // Gets the bitmap from the preview TextureView.
622                     Bitmap preview = mTextureViewHelper.getPreviewBitmap(downSampleFactor);
623                     return preview;
624                 }
625 
626                 @Override
627                 public Bitmap getPreviewOverlayAndControls() {
628                     Bitmap overlays = Bitmap.createBitmap(mCameraRootView.getWidth(),
629                             mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888);
630                     Canvas canvas = new Canvas(overlays);
631                     mCameraRootView.draw(canvas);
632                     return overlays;
633                 }
634 
635                 @Override
636                 public Bitmap getScreenShot(int previewDownSampleFactor) {
637                     Bitmap screenshot = Bitmap.createBitmap(mCameraRootView.getWidth(),
638                             mCameraRootView.getHeight(), Bitmap.Config.ARGB_8888);
639                     Canvas canvas = new Canvas(screenshot);
640                     canvas.drawARGB(255, 0, 0, 0);
641                     Bitmap preview = mTextureViewHelper.getPreviewBitmap(previewDownSampleFactor);
642                     if (preview != null) {
643                         canvas.drawBitmap(preview, null, mTextureViewHelper.getPreviewArea(), null);
644                     }
645                     Bitmap overlay = getPreviewOverlayAndControls();
646                     if (overlay != null) {
647                         canvas.drawBitmap(overlay, 0f, 0f, null);
648                     }
649                     return screenshot;
650                 }
651             };
652 
653     private long mCoverHiddenTime = -1; // System time when preview cover was hidden.
654 
getCoverHiddenTime()655     public long getCoverHiddenTime() {
656         return mCoverHiddenTime;
657     }
658 
659     /**
660      * This resets the preview to have no applied transform matrix.
661      */
clearPreviewTransform()662     public void clearPreviewTransform() {
663         mTextureViewHelper.clearTransform();
664     }
665 
updatePreviewAspectRatio(float aspectRatio)666     public void updatePreviewAspectRatio(float aspectRatio) {
667         mTextureViewHelper.updateAspectRatio(aspectRatio);
668     }
669 
670     /**
671      * WAR: Reset the SurfaceTexture's default buffer size to the current view dimensions of
672      * its TextureView.  This is necessary to get the expected behavior for the TextureView's
673      * HardwareLayer transform matrix (set by TextureView#setTransform) after configuring the
674      * SurfaceTexture as an output for the Camera2 API (which involves changing the default buffer
675      * size).
676      *
677      * b/17286155 - Tracking a fix for this in HardwareLayer.
678      */
setDefaultBufferSizeToViewDimens()679     public void setDefaultBufferSizeToViewDimens() {
680         if (mSurface == null || mTextureView == null) {
681             Log.w(TAG, "Could not set SurfaceTexture default buffer dimensions, not yet setup");
682             return;
683         }
684         mSurface.setDefaultBufferSize(mTextureView.getWidth(), mTextureView.getHeight());
685     }
686 
687     /**
688      * Updates the preview matrix without altering it.
689      *
690      * @param matrix
691      * @param aspectRatio the desired aspect ratio for the preview.
692      */
updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio)693     public void updatePreviewTransformFullscreen(Matrix matrix, float aspectRatio) {
694         mTextureViewHelper.updateTransformFullScreen(matrix, aspectRatio);
695     }
696 
697     /**
698      * @return the rect that will display the preview.
699      */
getFullscreenRect()700     public RectF getFullscreenRect() {
701         return mTextureViewHelper.getFullscreenRect();
702     }
703 
704     /**
705      * This is to support modules that calculate their own transform matrix because
706      * they need to use a transform matrix to rotate the preview.
707      *
708      * @param matrix transform matrix to be set on the TextureView
709      */
updatePreviewTransform(Matrix matrix)710     public void updatePreviewTransform(Matrix matrix) {
711         mTextureViewHelper.updateTransform(matrix);
712     }
713 
714     public interface AnimationFinishedListener {
onAnimationFinished(boolean success)715         public void onAnimationFinished(boolean success);
716     }
717 
718     private class MyTouchListener implements View.OnTouchListener {
719         private boolean mScaleStarted = false;
720         @Override
onTouch(View v, MotionEvent event)721         public boolean onTouch(View v, MotionEvent event) {
722             if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
723                 mScaleStarted = false;
724             } else if (event.getActionMasked() == MotionEvent.ACTION_POINTER_DOWN) {
725                 mScaleStarted = true;
726             }
727             return (!mScaleStarted) && mGestureDetector.onTouchEvent(event);
728         }
729     }
730 
731     /**
732      * This gesture listener finds out the direction of the scroll gestures and
733      * sends them to CameraAppUI to do further handling.
734      */
735     private class MyGestureListener extends GestureDetector.SimpleOnGestureListener {
736         private MotionEvent mDown;
737 
738         @Override
onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY)739         public boolean onScroll(MotionEvent e1, MotionEvent ev, float distanceX, float distanceY) {
740             if (ev.getEventTime() - ev.getDownTime() > SWIPE_TIME_OUT_MS
741                     || mSwipeState != IDLE
742                     || mIsCaptureIntent
743                     || !mSwipeEnabled) {
744                 return false;
745             }
746 
747             int deltaX = (int) (ev.getX() - mDown.getX());
748             int deltaY = (int) (ev.getY() - mDown.getY());
749             if (ev.getActionMasked() == MotionEvent.ACTION_MOVE) {
750                 if (Math.abs(deltaX) > mSlop || Math.abs(deltaY) > mSlop) {
751                     // Calculate the direction of the swipe.
752                     if (deltaX >= Math.abs(deltaY)) {
753                         // Swipe right.
754                         setSwipeState(SWIPE_RIGHT);
755                     } else if (deltaX <= -Math.abs(deltaY)) {
756                         // Swipe left.
757                         setSwipeState(SWIPE_LEFT);
758                     }
759                 }
760             }
761             return true;
762         }
763 
setSwipeState(int swipeState)764         private void setSwipeState(int swipeState) {
765             mSwipeState = swipeState;
766             // Notify new swipe detected.
767             onSwipeDetected(swipeState);
768         }
769 
770         @Override
onDown(MotionEvent ev)771         public boolean onDown(MotionEvent ev) {
772             mDown = MotionEvent.obtain(ev);
773             mSwipeState = IDLE;
774             return false;
775         }
776     }
777 
CameraAppUI(AppController controller, MainActivityLayout appRootView, boolean isCaptureIntent)778     public CameraAppUI(AppController controller, MainActivityLayout appRootView,
779             boolean isCaptureIntent) {
780         mSlop = ViewConfiguration.get(controller.getAndroidContext()).getScaledTouchSlop();
781         mController = controller;
782         mIsCaptureIntent = isCaptureIntent;
783 
784         mAppRootView = appRootView;
785         mFilmstripLayout = (FilmstripLayout) appRootView.findViewById(R.id.filmstrip_layout);
786         mCameraRootView = (FrameLayout) appRootView.findViewById(R.id.camera_app_root);
787         mModeTransitionView = (ModeTransitionView)
788                 mAppRootView.findViewById(R.id.mode_transition_view);
789         mFilmstripBottomControls = new FilmstripBottomPanel(controller,
790                 (ViewGroup) mAppRootView.findViewById(R.id.filmstrip_bottom_panel));
791         mFilmstripPanel = (FilmstripContentPanel) mAppRootView.findViewById(R.id.filmstrip_layout);
792         mGestureDetector = new GestureDetector(controller.getAndroidContext(),
793                 new MyGestureListener());
794         Resources res = controller.getAndroidContext().getResources();
795         mCaptureLayoutHelper = new CaptureLayoutHelper(
796                 res.getDimensionPixelSize(R.dimen.bottom_bar_height_min),
797                 res.getDimensionPixelSize(R.dimen.bottom_bar_height_max),
798                 res.getDimensionPixelSize(R.dimen.bottom_bar_height_optimal));
799         mModeListView = (ModeListView) appRootView.findViewById(R.id.mode_list_layout);
800         if (mModeListView != null) {
801             mModeListView.setModeSwitchListener(this);
802             mModeListView.setModeListOpenListener(this);
803             mModeListView.setCameraModuleScreenShotProvider(mCameraModuleScreenShotProvider);
804             mModeListView.setCaptureLayoutHelper(mCaptureLayoutHelper);
805             boolean shouldShowSettingsCling = mController.getSettingsManager().getBoolean(
806                     SettingsManager.SCOPE_GLOBAL,
807                     Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING);
808             mModeListView.setShouldShowSettingsCling(shouldShowSettingsCling);
809         } else {
810             Log.e(TAG, "Cannot find mode list in the view hierarchy");
811         }
812         mAnimationManager = new AnimationManager();
813         mRoundedThumbnailView = (RoundedThumbnailView) appRootView.findViewById(R.id.rounded_thumbnail_view);
814         mRoundedThumbnailView.setCallback(new RoundedThumbnailView.Callback() {
815             @Override
816             public void onHitStateFinished() {
817                 mFilmstripLayout.showFilmstrip();
818             }
819         });
820 
821         mAppRootView.setNonDecorWindowSizeChangedListener(mCaptureLayoutHelper);
822         initDisplayListener();
823         mAccessibilityAffordances = mAppRootView.findViewById(R.id.accessibility_affordances);
824         View modeListToggle = mAppRootView.findViewById(R.id.accessibility_mode_toggle_button);
825         modeListToggle.setOnClickListener(new View.OnClickListener() {
826             @Override
827             public void onClick(View view) {
828                 openModeList();
829             }
830         });
831         View filmstripToggle = mAppRootView.findViewById(
832                 R.id.accessibility_filmstrip_toggle_button);
833         filmstripToggle.setOnClickListener(new View.OnClickListener() {
834             @Override
835             public void onClick(View view) {
836                 showFilmstrip();
837             }
838         });
839 
840         mSuppressCaptureIndicator = false;
841     }
842 
843 
844     /**
845      * Freeze what is currently shown on screen until the next preview frame comes
846      * in.
847      */
freezeScreenUntilPreviewReady()848     public void freezeScreenUntilPreviewReady() {
849         Log.v(TAG, "freezeScreenUntilPreviewReady");
850         mModeTransitionView.setupModeCover(mCameraModuleScreenShotProvider
851                 .getScreenShot(DOWN_SAMPLE_RATE_FOR_SCREENSHOT));
852         mHideCoverRunnable = new Runnable() {
853             @Override
854             public void run() {
855                 mModeTransitionView.hideImageCover();
856             }
857         };
858         mModeCoverState = COVER_SHOWN;
859     }
860 
861     /**
862      * Creates a cling for the specific viewer and links the cling to the corresponding
863      * button for layout position.
864      *
865      * @param viewerType defines which viewer the cling is for.
866      */
setupClingForViewer(int viewerType)867     public void setupClingForViewer(int viewerType) {
868         if (viewerType == BottomPanel.VIEWER_REFOCUS) {
869             FrameLayout filmstripContent = (FrameLayout) mAppRootView
870                     .findViewById(R.id.camera_filmstrip_content_layout);
871             if (filmstripContent != null) {
872                 // Creates refocus cling.
873                 LayoutInflater inflater = AndroidServices.instance().provideLayoutInflater();
874                 Cling refocusCling = (Cling) inflater.inflate(R.layout.cling_widget, null, false);
875                 // Sets instruction text in the cling.
876                 refocusCling.setText(mController.getAndroidContext().getResources()
877                         .getString(R.string.cling_text_for_refocus_editor_button));
878 
879                 // Adds cling into view hierarchy.
880                 int clingWidth = mController.getAndroidContext()
881                         .getResources().getDimensionPixelSize(R.dimen.default_cling_width);
882                 filmstripContent.addView(refocusCling, clingWidth,
883                         ViewGroup.LayoutParams.WRAP_CONTENT);
884                 mFilmstripBottomControls.setClingForViewer(viewerType, refocusCling);
885             }
886         }
887     }
888 
889     /**
890      * Clears the listeners for the cling and remove it from the view hierarchy.
891      *
892      * @param viewerType defines which viewer the cling is for.
893      */
clearClingForViewer(int viewerType)894     public void clearClingForViewer(int viewerType) {
895         Cling clingToBeRemoved = mFilmstripBottomControls.getClingForViewer(viewerType);
896         if (clingToBeRemoved == null) {
897             // No cling is created for the specific viewer type.
898             return;
899         }
900         mFilmstripBottomControls.clearClingForViewer(viewerType);
901         clingToBeRemoved.setVisibility(View.GONE);
902         mAppRootView.removeView(clingToBeRemoved);
903     }
904 
905     /**
906      * Enable or disable swipe gestures. We want to disable them e.g. while we
907      * record a video.
908      */
setSwipeEnabled(boolean enabled)909     public void setSwipeEnabled(boolean enabled) {
910         mSwipeEnabled = enabled;
911         // TODO: This can be removed once we come up with a new design for handling swipe
912         // on shutter button and mode options. (More details: b/13751653)
913         mAppRootView.setSwipeEnabled(enabled);
914     }
915 
onDestroy()916     public void onDestroy() {
917         AndroidServices.instance().provideDisplayManager()
918                 .unregisterDisplayListener(mDisplayListener);
919     }
920 
921     /**
922      * Initializes the display listener to listen to display changes such as
923      * 180-degree rotation change, which will not have an onConfigurationChanged
924      * callback.
925      */
initDisplayListener()926     private void initDisplayListener() {
927         if (ApiHelper.HAS_DISPLAY_LISTENER) {
928             mLastRotation = CameraUtil.getDisplayRotation((Activity) mAppRootView.getContext());
929 
930             mDisplayListener = new DisplayManager.DisplayListener() {
931                 @Override
932                 public void onDisplayAdded(int arg0) {
933                     // Do nothing.
934                 }
935 
936                 @Override
937                 public void onDisplayChanged(int displayId) {
938                     int rotation = CameraUtil.getDisplayRotation(
939                         (Activity) mAppRootView.getContext());
940                     if ((rotation - mLastRotation + 360) % 360 == 180
941                             && mPreviewStatusListener != null) {
942                         mPreviewStatusListener.onPreviewFlipped();
943                         mStickyBottomCaptureLayout.requestLayout();
944                         mModeListView.requestLayout();
945                         mTextureView.requestLayout();
946                     }
947                     mLastRotation = rotation;
948                 }
949 
950                 @Override
951                 public void onDisplayRemoved(int arg0) {
952                     // Do nothing.
953                 }
954             };
955 
956             AndroidServices.instance().provideDisplayManager()
957                   .registerDisplayListener(mDisplayListener, null);
958         }
959     }
960 
961     /**
962      * Redirects touch events to appropriate recipient views based on swipe direction.
963      * More specifically, swipe up and swipe down will be handled by the view that handles
964      * mode transition; swipe left will be send to filmstrip; swipe right will be redirected
965      * to mode list in order to bring up mode list.
966      */
onSwipeDetected(int swipeState)967     private void onSwipeDetected(int swipeState) {
968         if (swipeState == SWIPE_UP || swipeState == SWIPE_DOWN) {
969             // TODO: Polish quick switch after this release.
970             // Quick switch between modes.
971             int currentModuleIndex = mController.getCurrentModuleIndex();
972             final int moduleToTransitionTo =
973                     mController.getQuickSwitchToModuleId(currentModuleIndex);
974             if (currentModuleIndex != moduleToTransitionTo) {
975                 mAppRootView.redirectTouchEventsTo(mModeTransitionView);
976                 int shadeColorId = R.color.camera_gray_background;
977                 int iconRes = CameraUtil.getCameraModeCoverIconResId(moduleToTransitionTo,
978                         mController.getAndroidContext());
979 
980                 AnimationFinishedListener listener = new AnimationFinishedListener() {
981                     @Override
982                     public void onAnimationFinished(boolean success) {
983                         if (success) {
984                             mHideCoverRunnable = new Runnable() {
985                                 @Override
986                                 public void run() {
987                                     mModeTransitionView.startPeepHoleAnimation();
988                                 }
989                             };
990                             mModeCoverState = COVER_SHOWN;
991                             // Go to new module when the previous operation is successful.
992                             mController.onModeSelected(moduleToTransitionTo);
993                         }
994                     }
995                 };
996             }
997         } else if (swipeState == SWIPE_LEFT) {
998             // Pass the touch sequence to filmstrip layout.
999             mAppRootView.redirectTouchEventsTo(mFilmstripLayout);
1000         } else if (swipeState == SWIPE_RIGHT) {
1001             // Pass the touch to mode switcher
1002             mAppRootView.redirectTouchEventsTo(mModeListView);
1003         }
1004     }
1005 
1006     /**
1007      * Gets called when activity resumes in preview.
1008      */
resume()1009     public void resume() {
1010         // Show mode theme cover until preview is ready
1011         showModeCoverUntilPreviewReady();
1012 
1013         // Hide action bar first since we are in full screen mode first, and
1014         // switch the system UI to lights-out mode.
1015         mFilmstripPanel.hide();
1016 
1017         // Show UI that is meant to only be used when spoken feedback is
1018         // enabled.
1019         mAccessibilityAffordances.setVisibility(
1020                 (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) ? View.VISIBLE
1021                         : View.GONE);
1022     }
1023 
1024     /**
1025      * Opens the mode list (e.g. because of the menu button being pressed) and
1026      * adapts the rest of the UI.
1027      */
openModeList()1028     public void openModeList() {
1029         mModeOptionsOverlay.closeModeOptions();
1030         mModeListView.onMenuPressed();
1031     }
1032 
showAccessibilityZoomUI(float maxZoom)1033     public void showAccessibilityZoomUI(float maxZoom) {
1034         mAccessibilityUtil.showZoomUI(maxZoom);
1035     }
1036 
hideAccessibilityZoomUI()1037     public void hideAccessibilityZoomUI() {
1038         mAccessibilityUtil.hideZoomUI();
1039     }
1040 
1041     /**
1042      * A cover view showing the mode theme color and mode icon will be visible on
1043      * top of preview until preview is ready (i.e. camera preview is started and
1044      * the first frame has been received).
1045      */
showModeCoverUntilPreviewReady()1046     private void showModeCoverUntilPreviewReady() {
1047         int modeId = mController.getCurrentModuleIndex();
1048         int colorId = R.color.camera_gray_background;;
1049         int iconId = CameraUtil.getCameraModeCoverIconResId(modeId, mController.getAndroidContext());
1050         mModeTransitionView.setupModeCover(colorId, iconId);
1051         mHideCoverRunnable = new Runnable() {
1052             @Override
1053             public void run() {
1054                 mModeTransitionView.hideModeCover(null);
1055                 if (!mDisableAllUserInteractions) {
1056                     showShimmyDelayed();
1057                 }
1058             }
1059         };
1060         mModeCoverState = COVER_SHOWN;
1061     }
1062 
showShimmyDelayed()1063     private void showShimmyDelayed() {
1064         if (!mIsCaptureIntent) {
1065             // Show shimmy in SHIMMY_DELAY_MS
1066             mModeListView.showModeSwitcherHint();
1067         }
1068     }
1069 
hideModeCover()1070     private void hideModeCover() {
1071         if (mHideCoverRunnable != null) {
1072             mAppRootView.post(mHideCoverRunnable);
1073             mHideCoverRunnable = null;
1074         }
1075         mModeCoverState = COVER_HIDDEN;
1076         if (mCoverHiddenTime < 0) {
1077             mCoverHiddenTime = System.currentTimeMillis();
1078         }
1079     }
1080 
1081 
onPreviewVisiblityChanged(int visibility)1082     public void onPreviewVisiblityChanged(int visibility) {
1083         if (visibility == ModuleController.VISIBILITY_HIDDEN) {
1084             setIndicatorBottomBarWrapperVisible(false);
1085             mAccessibilityAffordances.setVisibility(View.GONE);
1086         } else {
1087             setIndicatorBottomBarWrapperVisible(true);
1088             if (!mIsCaptureIntent && mAccessibilityUtil.isAccessibilityEnabled()) {
1089                 mAccessibilityAffordances.setVisibility(View.VISIBLE);
1090             } else {
1091                 mAccessibilityAffordances.setVisibility(View.GONE);
1092             }
1093         }
1094     }
1095 
1096     /**
1097      * Call to stop the preview from being rendered. Sets the entire capture
1098      * root view to invisible which includes the preview plus focus indicator
1099      * and any other auxiliary views for capture modes.
1100      */
pausePreviewRendering()1101     public void pausePreviewRendering() {
1102         mCameraRootView.setVisibility(View.INVISIBLE);
1103     }
1104 
1105     /**
1106      * Call to begin rendering the preview and auxiliary views again.
1107      */
resumePreviewRendering()1108     public void resumePreviewRendering() {
1109         mCameraRootView.setVisibility(View.VISIBLE);
1110     }
1111 
1112     /**
1113      * Returns the transform associated with the preview view.
1114      *
1115      * @param m the Matrix in which to copy the current transform.
1116      * @return The specified matrix if not null or a new Matrix instance
1117      *         otherwise.
1118      */
getPreviewTransform(Matrix m)1119     public Matrix getPreviewTransform(Matrix m) {
1120         return mTextureView.getTransform(m);
1121     }
1122 
1123     @Override
onOpenFullScreen()1124     public void onOpenFullScreen() {
1125         // Do nothing.
1126     }
1127 
1128     @Override
onModeListOpenProgress(float progress)1129     public void onModeListOpenProgress(float progress) {
1130         // When the mode list is in transition, ensure the large layers are
1131         // hardware accelerated.
1132         if (progress >= 1.0f || progress <= 0.0f) {
1133             // Convert hardware layers back to default layer types when animation stops
1134             // to prevent accidental artifacting.
1135             if(mModeOptionsToggle.getLayerType() == View.LAYER_TYPE_HARDWARE ||
1136                   mShutterButton.getLayerType() == View.LAYER_TYPE_HARDWARE) {
1137                 Log.v(TAG, "Disabling hardware layer for the Mode Options Toggle Button.");
1138                 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_NONE, null);
1139                 Log.v(TAG, "Disabling hardware layer for the Shutter Button.");
1140                 mShutterButton.setLayerType(View.LAYER_TYPE_NONE, null);
1141             }
1142         } else {
1143             if(mModeOptionsToggle.getLayerType() != View.LAYER_TYPE_HARDWARE ||
1144                   mShutterButton.getLayerType() != View.LAYER_TYPE_HARDWARE) {
1145                 Log.v(TAG, "Enabling hardware layer for the Mode Options Toggle Button.");
1146                 mModeOptionsToggle.setLayerType(View.LAYER_TYPE_HARDWARE, null);
1147                 Log.v(TAG, "Enabling hardware layer for the Shutter Button.");
1148                 mShutterButton.setLayerType(View.LAYER_TYPE_HARDWARE, null);
1149             }
1150         }
1151 
1152         progress = 1 - progress;
1153         float interpolatedProgress = Gusterpolator.INSTANCE.getInterpolation(progress);
1154         mModeOptionsToggle.setAlpha(interpolatedProgress);
1155         // Change shutter button alpha linearly based on the mode list open progress:
1156         // set the alpha to disabled alpha when list is fully open, to enabled alpha
1157         // when the list is fully closed.
1158         mShutterButton.setAlpha(progress * ShutterButton.ALPHA_WHEN_ENABLED
1159                 + (1 - progress) * ShutterButton.ALPHA_WHEN_DISABLED);
1160     }
1161 
1162     @Override
onModeListClosed()1163     public void onModeListClosed() {
1164         // Convert hardware layers back to default layer types when animation stops
1165         // to prevent accidental artifacting.
1166         if(mModeOptionsToggle.getLayerType() == View.LAYER_TYPE_HARDWARE ||
1167               mShutterButton.getLayerType() == View.LAYER_TYPE_HARDWARE) {
1168             Log.v(TAG, "Disabling hardware layer for the Mode Options Toggle Button.");
1169             mModeOptionsToggle.setLayerType(View.LAYER_TYPE_NONE, null);
1170             Log.v(TAG, "Disabling hardware layer for the Shutter Button.");
1171             mShutterButton.setLayerType(View.LAYER_TYPE_NONE, null);
1172         }
1173 
1174         // Make sure the alpha on mode options ellipse is reset when mode drawer
1175         // is closed.
1176         mModeOptionsToggle.setAlpha(1f);
1177         mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
1178     }
1179 
1180     /**
1181      * Called when the back key is pressed.
1182      *
1183      * @return Whether the UI responded to the key event.
1184      */
onBackPressed()1185     public boolean onBackPressed() {
1186         if (mFilmstripLayout.getVisibility() == View.VISIBLE) {
1187             return mFilmstripLayout.onBackPressed();
1188         } else {
1189             return mModeListView.onBackPressed();
1190         }
1191     }
1192 
1193     /**
1194      * Sets a {@link com.android.camera.ui.PreviewStatusListener} that
1195      * listens to SurfaceTexture changes. In addition, listeners are set on
1196      * dependent app ui elements.
1197      *
1198      * @param previewStatusListener the listener that gets notified when SurfaceTexture
1199      *                              changes
1200      */
setPreviewStatusListener(PreviewStatusListener previewStatusListener)1201     public void setPreviewStatusListener(PreviewStatusListener previewStatusListener) {
1202         mPreviewStatusListener = previewStatusListener;
1203         if (mPreviewStatusListener != null) {
1204             onPreviewListenerChanged();
1205         }
1206     }
1207 
1208     /**
1209      * When the PreviewStatusListener changes, listeners need to be
1210      * set on the following app ui elements:
1211      * {@link com.android.camera.ui.PreviewOverlay},
1212      * {@link com.android.camera.ui.BottomBar},
1213      * {@link com.android.camera.ui.IndicatorIconController}.
1214      */
onPreviewListenerChanged()1215     private void onPreviewListenerChanged() {
1216         // Set a listener for recognizing preview gestures.
1217         GestureDetector.OnGestureListener gestureListener
1218             = mPreviewStatusListener.getGestureListener();
1219         if (gestureListener != null) {
1220             mPreviewOverlay.setGestureListener(gestureListener);
1221         }
1222         View.OnTouchListener touchListener = mPreviewStatusListener.getTouchListener();
1223         if (touchListener != null) {
1224             mPreviewOverlay.setTouchListener(touchListener);
1225         }
1226 
1227         mTextureViewHelper.setAutoAdjustTransform(
1228                 mPreviewStatusListener.shouldAutoAdjustTransformMatrixOnLayout());
1229     }
1230 
1231     /**
1232      * This method should be called in onCameraOpened.  It defines CameraAppUI
1233      * specific changes that depend on the camera or camera settings.
1234      */
onChangeCamera()1235     public void onChangeCamera() {
1236         ModuleController moduleController = mController.getCurrentModuleController();
1237         HardwareSpec hardwareSpec = moduleController.getHardwareSpec();
1238 
1239         /**
1240          * The current UI requires that the flash option visibility in front-
1241          * facing camera be
1242          *   * disabled if back facing camera supports flash
1243          *   * hidden if back facing camera does not support flash
1244          * We save whether back facing camera supports flash because we cannot
1245          * get this in front facing camera without a camera switch.
1246          *
1247          * If this preference is cleared, we also need to clear the camera
1248          * facing setting so we default to opening the camera in back facing
1249          * camera, and can save this flash support value again.
1250          */
1251         if (hardwareSpec != null) {
1252             if (!mController.getSettingsManager().isSet(SettingsManager.SCOPE_GLOBAL,
1253                     Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA)) {
1254                 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
1255                         Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA,
1256                         hardwareSpec.isFlashSupported());
1257             }
1258             /** Similar logic applies to the HDR option. */
1259             if (!mController.getSettingsManager().isSet(SettingsManager.SCOPE_GLOBAL,
1260                     Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA)) {
1261                 String hdrSupportMode;
1262                 if (hardwareSpec.isHdrPlusSupported()) {
1263                     hdrSupportMode = getResourceString(
1264                             R.string.pref_camera_hdr_supportmode_hdr_plus);
1265                 } else if (hardwareSpec.isHdrSupported()) {
1266                     hdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_hdr);
1267                 } else {
1268                     hdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_none);
1269                 }
1270                 mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
1271                         Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA, hdrSupportMode);
1272             }
1273         }
1274 
1275         applyModuleSpecs(hardwareSpec, moduleController.getBottomBarSpec(),
1276                 true /*skipScopeCheck*/);
1277         syncModeOptionIndicators();
1278     }
1279 
1280     /**
1281      * Updates the mode option indicators according to the current settings.
1282      */
syncModeOptionIndicators()1283     public void syncModeOptionIndicators() {
1284         if (mIndicatorIconController != null) {
1285             // Sync the settings state with the indicator state.
1286             mIndicatorIconController.syncIndicators();
1287         }
1288     }
1289 
1290     /**
1291      * Adds a listener to receive callbacks when preview area changes.
1292      */
addPreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1293     public void addPreviewAreaChangedListener(
1294             PreviewStatusListener.PreviewAreaChangedListener listener) {
1295         mTextureViewHelper.addPreviewAreaSizeChangedListener(listener);
1296     }
1297 
1298     /**
1299      * Removes a listener that receives callbacks when preview area changes.
1300      */
removePreviewAreaChangedListener( PreviewStatusListener.PreviewAreaChangedListener listener)1301     public void removePreviewAreaChangedListener(
1302             PreviewStatusListener.PreviewAreaChangedListener listener) {
1303         mTextureViewHelper.removePreviewAreaSizeChangedListener(listener);
1304     }
1305 
1306     /**
1307      * This inflates generic_module layout, which contains all the shared views across
1308      * modules. Then each module inflates their own views in the given view group. For
1309      * now, this is called every time switching from a not-yet-refactored module to a
1310      * refactored module. In the future, this should only need to be done once per app
1311      * start.
1312      */
prepareModuleUI()1313     public void prepareModuleUI() {
1314         mController.getSettingsManager().addListener(this);
1315         mModuleUI = (FrameLayout) mCameraRootView.findViewById(R.id.module_layout);
1316         mTextureView = (TextureView) mCameraRootView.findViewById(R.id.preview_content);
1317         mTextureViewHelper = new TextureViewHelper(mTextureView, mCaptureLayoutHelper,
1318                 mController.getCameraProvider(), mController);
1319         mTextureViewHelper.setSurfaceTextureListener(this);
1320         mTextureViewHelper.setOnLayoutChangeListener(mPreviewLayoutChangeListener);
1321 
1322         mBottomBar = (BottomBar) mCameraRootView.findViewById(R.id.bottom_bar);
1323         int unpressedColor = mController.getAndroidContext().getResources()
1324             .getColor(R.color.camera_gray_background);
1325         setBottomBarColor(unpressedColor);
1326         updateModeSpecificUIColors();
1327 
1328         mBottomBar.setCaptureLayoutHelper(mCaptureLayoutHelper);
1329 
1330         mModeOptionsOverlay
1331             = (ModeOptionsOverlay) mCameraRootView.findViewById(R.id.mode_options_overlay);
1332 
1333         // Sets the visibility of the bottom bar and the mode options.
1334         resetBottomControls(mController.getCurrentModuleController(),
1335             mController.getCurrentModuleIndex());
1336         mModeOptionsOverlay.setCaptureLayoutHelper(mCaptureLayoutHelper);
1337 
1338         mShutterButton = (ShutterButton) mCameraRootView.findViewById(R.id.shutter_button);
1339         addShutterListener(mController.getCurrentModuleController());
1340         addShutterListener(mModeOptionsOverlay);
1341         addShutterListener(this);
1342 
1343         mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
1344         mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
1345 
1346         mPreviewOverlay = (PreviewOverlay) mCameraRootView.findViewById(R.id.preview_overlay);
1347         mPreviewOverlay.setOnTouchListener(new MyTouchListener());
1348         mPreviewOverlay.setOnPreviewTouchedListener(mModeOptionsOverlay);
1349         mAccessibilityUtil = new AccessibilityUtil(mPreviewOverlay, mAccessibilityAffordances);
1350 
1351         mCaptureOverlay = (CaptureAnimationOverlay)
1352                 mCameraRootView.findViewById(R.id.capture_overlay);
1353         mTextureViewHelper.addPreviewAreaSizeChangedListener(mPreviewOverlay);
1354         mTextureViewHelper.addPreviewAreaSizeChangedListener(mCaptureOverlay);
1355 
1356         if (mIndicatorIconController == null) {
1357             mIndicatorIconController =
1358                 new IndicatorIconController(mController, mAppRootView);
1359         }
1360 
1361         mController.getButtonManager().load(mCameraRootView);
1362         mController.getButtonManager().setListener(mIndicatorIconController);
1363         mController.getSettingsManager().addListener(mIndicatorIconController);
1364 
1365         mModeOptionsToggle = mCameraRootView.findViewById(R.id.mode_options_toggle);
1366         mFocusRing = (FocusRing) mCameraRootView.findViewById(R.id.focus_ring);
1367         mTutorialsPlaceHolderWrapper = (FrameLayout) mCameraRootView
1368                 .findViewById(R.id.tutorials_placeholder_wrapper);
1369         mStickyBottomCaptureLayout = (StickyBottomCaptureLayout) mAppRootView
1370                 .findViewById(R.id.sticky_bottom_capture_layout);
1371         mStickyBottomCaptureLayout.setCaptureLayoutHelper(mCaptureLayoutHelper);
1372         mCountdownCancelButton = (ImageButton) mStickyBottomCaptureLayout
1373                 .findViewById(R.id.shutter_cancel_button);
1374 
1375         mTextureViewHelper.addPreviewAreaSizeChangedListener(mModeListView);
1376         mTextureViewHelper.addAspectRatioChangedListener(
1377                 new PreviewStatusListener.PreviewAspectRatioChangedListener() {
1378                     @Override
1379                     public void onPreviewAspectRatioChanged(float aspectRatio) {
1380                         mModeOptionsOverlay.requestLayout();
1381                         mBottomBar.requestLayout();
1382                     }
1383                 }
1384         );
1385     }
1386 
1387     /**
1388      * Called indirectly from each module in their initialization to get a view group
1389      * to inflate the module specific views in.
1390      *
1391      * @return a view group for modules to attach views to
1392      */
getModuleRootView()1393     public FrameLayout getModuleRootView() {
1394         // TODO: Change it to mModuleUI when refactor is done
1395         return mCameraRootView;
1396     }
1397 
1398     /**
1399      * Remove all the module specific views.
1400      */
clearModuleUI()1401     public void clearModuleUI() {
1402         if (mModuleUI != null) {
1403             mModuleUI.removeAllViews();
1404         }
1405         removeShutterListener(mController.getCurrentModuleController());
1406         mTutorialsPlaceHolderWrapper.removeAllViews();
1407         mTutorialsPlaceHolderWrapper.setVisibility(View.GONE);
1408 
1409         setShutterButtonEnabled(true);
1410         mPreviewStatusListener = null;
1411         mPreviewOverlay.reset();
1412 
1413         Log.v(TAG, "mFocusRing.stopFocusAnimations()");
1414         mFocusRing.stopFocusAnimations();
1415     }
1416 
1417     /**
1418      * Gets called when preview is ready to start. It sets up one shot preview callback
1419      * in order to receive a callback when the preview frame is available, so that
1420      * the preview cover can be hidden to reveal preview.
1421      *
1422      * An alternative for getting the timing to hide preview cover is through
1423      * {@link CameraAppUI#onSurfaceTextureUpdated(android.graphics.SurfaceTexture)},
1424      * which is less accurate but therefore is the fallback for modules that manage
1425      * their own preview callbacks (as setting one preview callback will override
1426      * any other installed preview callbacks), or use camera2 API.
1427      */
onPreviewReadyToStart()1428     public void onPreviewReadyToStart() {
1429         if (mModeCoverState == COVER_SHOWN) {
1430             mModeCoverState = COVER_WILL_HIDE_AT_NEXT_FRAME;
1431             mController.setupOneShotPreviewListener();
1432         }
1433     }
1434 
1435     /**
1436      * Gets called when preview is started.
1437      */
onPreviewStarted()1438     public void onPreviewStarted() {
1439         Log.v(TAG, "onPreviewStarted");
1440         if (mModeCoverState == COVER_SHOWN) {
1441             // This is a work around of the face detection failure in b/20724126.
1442             // In particular, we need to drop the first preview frame in order to
1443             // make face detection work and also need to hide this preview frame to
1444             // avoid potential janks. We do this only for L, Nexus 6 and Haleakala.
1445             if (ApiHelper.isLorLMr1() && ApiHelper.IS_NEXUS_6) {
1446                 mModeCoverState = COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE;
1447             } else {
1448                 mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1449             }
1450         }
1451         enableModeOptions();
1452     }
1453 
1454     /**
1455      * Gets notified when next preview frame comes in.
1456      */
onNewPreviewFrame()1457     public void onNewPreviewFrame() {
1458         Log.v(TAG, "onNewPreviewFrame");
1459         CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1460         hideModeCover();
1461     }
1462 
1463     @Override
onShutterButtonClick()1464     public void onShutterButtonClick() {
1465         /*
1466          * Set the mode options toggle unclickable, generally
1467          * throughout the app, whenever the shutter button is clicked.
1468          *
1469          * This could be done in the OnShutterButtonListener of the
1470          * ModeOptionsOverlay, but since it is very important that we
1471          * can clearly see when the toggle becomes clickable again,
1472          * keep all of that logic at this level.
1473          */
1474         // disableModeOptions();
1475     }
1476 
1477     @Override
onShutterCoordinate(TouchCoordinate coord)1478     public void onShutterCoordinate(TouchCoordinate coord) {
1479         // Do nothing.
1480     }
1481 
1482     @Override
onShutterButtonFocus(boolean pressed)1483     public void onShutterButtonFocus(boolean pressed) {
1484         // noop
1485     }
1486 
1487     @Override
onShutterButtonLongPressed()1488     public void onShutterButtonLongPressed() {
1489         // noop
1490     }
1491 
1492     /**
1493      * Set the mode options toggle clickable.
1494      */
enableModeOptions()1495     public void enableModeOptions() {
1496         /*
1497          * For modules using camera 1 api, this gets called in
1498          * onSurfaceTextureUpdated whenever the preview gets stopped and
1499          * started after each capture.  This also takes care of the
1500          * case where the mode options might be unclickable when we
1501          * switch modes
1502          *
1503          * For modules using camera 2 api, they're required to call this
1504          * method when a capture is "completed".  Unfortunately this differs
1505          * per module implementation.
1506          */
1507         if (!mDisableAllUserInteractions) {
1508             mModeOptionsOverlay.setToggleClickable(true);
1509         }
1510     }
1511 
1512     /**
1513      * Set the mode options toggle not clickable.
1514      */
disableModeOptions()1515     public void disableModeOptions() {
1516         mModeOptionsOverlay.setToggleClickable(false);
1517     }
1518 
setDisableAllUserInteractions(boolean disable)1519     public void setDisableAllUserInteractions(boolean disable) {
1520         if (disable) {
1521             disableModeOptions();
1522             setShutterButtonEnabled(false);
1523             setSwipeEnabled(false);
1524             mModeListView.hideAnimated();
1525         } else {
1526             enableModeOptions();
1527             setShutterButtonEnabled(true);
1528             setSwipeEnabled(true);
1529         }
1530         mDisableAllUserInteractions = disable;
1531     }
1532 
1533     @Override
onModeButtonPressed(int modeIndex)1534     public void onModeButtonPressed(int modeIndex) {
1535         // TODO: Make CameraActivity listen to ModeListView's events.
1536         int pressedModuleId = mController.getModuleId(modeIndex);
1537         int currentModuleId = mController.getCurrentModuleIndex();
1538         if (pressedModuleId != currentModuleId) {
1539             hideCaptureIndicator();
1540         }
1541     }
1542 
1543     /**
1544      * Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
1545      *
1546      * @param modeIndex mode index of the selected mode
1547      */
1548     @Override
onModeSelected(int modeIndex)1549     public void onModeSelected(int modeIndex) {
1550         mHideCoverRunnable = new Runnable() {
1551             @Override
1552             public void run() {
1553                 mModeListView.startModeSelectionAnimation();
1554             }
1555         };
1556         mShutterButton.setAlpha(ShutterButton.ALPHA_WHEN_ENABLED);
1557         mModeCoverState = COVER_SHOWN;
1558 
1559         int lastIndex = mController.getCurrentModuleIndex();
1560         // Actual mode teardown / new mode initialization happens here
1561         mController.onModeSelected(modeIndex);
1562         int currentIndex = mController.getCurrentModuleIndex();
1563 
1564         if (lastIndex == currentIndex) {
1565             hideModeCover();
1566         }
1567 
1568         updateModeSpecificUIColors();
1569     }
1570 
updateModeSpecificUIColors()1571     private void updateModeSpecificUIColors() {
1572         setBottomBarColorsForModeIndex(mController.getCurrentModuleIndex());
1573     }
1574 
1575     @Override
onSettingsSelected()1576     public void onSettingsSelected() {
1577         mController.getSettingsManager().set(SettingsManager.SCOPE_GLOBAL,
1578                                              Keys.KEY_SHOULD_SHOW_SETTINGS_BUTTON_CLING, false);
1579         mModeListView.setShouldShowSettingsCling(false);
1580         mController.onSettingsSelected();
1581     }
1582 
1583     @Override
getCurrentModeIndex()1584     public int getCurrentModeIndex() {
1585         return mController.getCurrentModuleIndex();
1586     }
1587 
1588     /********************** Capture animation **********************/
1589     /* TODO: This session is subject to UX changes. In addition to the generic
1590        flash animation and post capture animation, consider designating a parameter
1591        for specifying the type of animation, as well as an animation finished listener
1592        so that modules can have more knowledge of the status of the animation. */
1593 
1594     /**
1595      * Turns on or off the capture indicator suppression.
1596      */
setShouldSuppressCaptureIndicator(boolean suppress)1597     public void setShouldSuppressCaptureIndicator(boolean suppress) {
1598         mSuppressCaptureIndicator = suppress;
1599     }
1600 
1601     /**
1602      * Starts the capture indicator pop-out animation.
1603      *
1604      * @param accessibilityString An accessibility String to be announced during the peek animation.
1605      */
startCaptureIndicatorRevealAnimation(String accessibilityString)1606     public void startCaptureIndicatorRevealAnimation(String accessibilityString) {
1607         if (mSuppressCaptureIndicator || mFilmstripLayout.getVisibility() == View.VISIBLE) {
1608             return;
1609         }
1610         mRoundedThumbnailView.startRevealThumbnailAnimation(accessibilityString);
1611     }
1612 
1613     /**
1614      * Updates the thumbnail image in the capture indicator.
1615      *
1616      * @param thumbnailBitmap The thumbnail image to be shown.
1617      */
updateCaptureIndicatorThumbnail(Bitmap thumbnailBitmap, int rotation)1618     public void updateCaptureIndicatorThumbnail(Bitmap thumbnailBitmap, int rotation) {
1619         if (mSuppressCaptureIndicator || mFilmstripLayout.getVisibility() == View.VISIBLE) {
1620             return;
1621         }
1622         mRoundedThumbnailView.setThumbnail(thumbnailBitmap, rotation);
1623     }
1624 
1625     /**
1626      * Hides the capture indicator.
1627      */
hideCaptureIndicator()1628     public void hideCaptureIndicator() {
1629         mRoundedThumbnailView.hideThumbnail();
1630     }
1631 
1632     /**
1633      * Starts the flash animation.
1634      */
startFlashAnimation(boolean shortFlash)1635     public void startFlashAnimation(boolean shortFlash) {
1636         mCaptureOverlay.startFlashAnimation(shortFlash);
1637     }
1638 
1639     /**
1640      * Cancels the pre-capture animation.
1641      */
cancelPreCaptureAnimation()1642     public void cancelPreCaptureAnimation() {
1643         mAnimationManager.cancelAnimations();
1644     }
1645 
1646     /**
1647      * Cancels the post-capture animation.
1648      */
cancelPostCaptureAnimation()1649     public void cancelPostCaptureAnimation() {
1650         mAnimationManager.cancelAnimations();
1651     }
1652 
getFilmstripContentPanel()1653     public FilmstripContentPanel getFilmstripContentPanel() {
1654         return mFilmstripPanel;
1655     }
1656 
1657     /**
1658      * @return The {@link com.android.camera.app.CameraAppUI.BottomPanel} on the
1659      * bottom of the filmstrip.
1660      */
getFilmstripBottomControls()1661     public BottomPanel getFilmstripBottomControls() {
1662         return mFilmstripBottomControls;
1663     }
1664 
showBottomControls()1665     public void showBottomControls() {
1666         mFilmstripBottomControls.show();
1667     }
1668 
hideBottomControls()1669     public void hideBottomControls() {
1670         mFilmstripBottomControls.hide();
1671     }
1672 
1673     /**
1674      * @param listener The listener for bottom controls.
1675      */
setFilmstripBottomControlsListener(BottomPanel.Listener listener)1676     public void setFilmstripBottomControlsListener(BottomPanel.Listener listener) {
1677         mFilmstripBottomControls.setListener(listener);
1678     }
1679 
1680     /***************************SurfaceTexture Api and Listener*********************************/
1681 
1682     /**
1683      * Return the shared surface texture.
1684      */
getSurfaceTexture()1685     public SurfaceTexture getSurfaceTexture() {
1686         return mSurface;
1687     }
1688 
1689     /**
1690      * Return the shared {@link android.graphics.SurfaceTexture}'s width.
1691      */
getSurfaceWidth()1692     public int getSurfaceWidth() {
1693         return mSurfaceWidth;
1694     }
1695 
1696     /**
1697      * Return the shared {@link android.graphics.SurfaceTexture}'s height.
1698      */
getSurfaceHeight()1699     public int getSurfaceHeight() {
1700         return mSurfaceHeight;
1701     }
1702 
1703     @Override
onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height)1704     public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
1705         mSurface = surface;
1706         mSurfaceWidth = width;
1707         mSurfaceHeight = height;
1708         Log.v(TAG, "SurfaceTexture is available");
1709         if (mPreviewStatusListener != null) {
1710             mPreviewStatusListener.onSurfaceTextureAvailable(surface, width, height);
1711         }
1712         enableModeOptions();
1713     }
1714 
1715     @Override
onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height)1716     public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
1717         mSurface = surface;
1718         mSurfaceWidth = width;
1719         mSurfaceHeight = height;
1720         if (mPreviewStatusListener != null) {
1721             mPreviewStatusListener.onSurfaceTextureSizeChanged(surface, width, height);
1722         }
1723     }
1724 
1725     @Override
onSurfaceTextureDestroyed(SurfaceTexture surface)1726     public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
1727         mSurface = null;
1728         Log.v(TAG, "SurfaceTexture is destroyed");
1729         if (mPreviewStatusListener != null) {
1730             return mPreviewStatusListener.onSurfaceTextureDestroyed(surface);
1731         }
1732         return false;
1733     }
1734 
1735     @Override
onSurfaceTextureUpdated(SurfaceTexture surface)1736     public void onSurfaceTextureUpdated(SurfaceTexture surface) {
1737         mSurface = surface;
1738         if (mPreviewStatusListener != null) {
1739             mPreviewStatusListener.onSurfaceTextureUpdated(surface);
1740         }
1741         // Do not show the first preview frame. Due to the bug b/20724126, we need to have
1742         // a WAR to request a preview frame followed by 5-frame ZSL burst before the repeating
1743         // preview and ZSL streams. Need to hide the first preview frame since it is janky.
1744         // We do this only for L, Nexus 6 and Haleakala.
1745         if (mModeCoverState == COVER_WILL_HIDE_AFTER_NEXT_TEXTURE_UPDATE) {
1746             mModeCoverState = COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE;
1747         } else if (mModeCoverState == COVER_WILL_HIDE_AT_NEXT_TEXTURE_UPDATE){
1748             Log.v(TAG, "hiding cover via onSurfaceTextureUpdated");
1749             CameraPerformanceTracker.onEvent(CameraPerformanceTracker.FIRST_PREVIEW_FRAME);
1750             hideModeCover();
1751         }
1752     }
1753 
1754     /****************************Grid lines api ******************************/
1755 
1756     /**
1757      * Show a set of evenly spaced lines over the preview.  The number
1758      * of lines horizontally and vertically is determined by
1759      * {@link com.android.camera.ui.GridLines}.
1760      */
showGridLines()1761     public void showGridLines() {
1762         if (mGridLines != null) {
1763             mGridLines.setVisibility(View.VISIBLE);
1764         }
1765     }
1766 
1767     /**
1768      * Hide the set of evenly spaced grid lines overlaying the preview.
1769      */
hideGridLines()1770     public void hideGridLines() {
1771         if (mGridLines != null) {
1772             mGridLines.setVisibility(View.INVISIBLE);
1773         }
1774     }
1775 
1776     /**
1777      * Return a callback which shows or hide the preview grid lines
1778      * depending on whether the grid lines setting is set on.
1779      */
getGridLinesCallback()1780     public ButtonManager.ButtonCallback getGridLinesCallback() {
1781         return new ButtonManager.ButtonCallback() {
1782             @Override
1783             public void onStateChanged(int state) {
1784                 if (!mController.isPaused()) {
1785                     if (Keys.areGridLinesOn(mController.getSettingsManager())) {
1786                         showGridLines();
1787                     } else {
1788                         hideGridLines();
1789                     }
1790                 }
1791             }
1792         };
1793     }
1794 
1795     /***************************Mode options api *****************************/
1796 
1797     /**
1798      * Set the mode options visible.
1799      */
1800     public void showModeOptions() {
1801         /* Make mode options clickable. */
1802         enableModeOptions();
1803         mModeOptionsOverlay.setVisibility(View.VISIBLE);
1804     }
1805 
1806     /**
1807      * Set the mode options invisible.  This is necessary for modes
1808      * that don't show a bottom bar for the capture UI.
1809      */
1810     public void hideModeOptions() {
1811         mModeOptionsOverlay.setVisibility(View.INVISIBLE);
1812     }
1813 
1814     /****************************Bottom bar api ******************************/
1815 
1816     /**
1817      * Sets up the bottom bar and mode options with the correct
1818      * shutter button and visibility based on the current module.
1819      */
1820     public void resetBottomControls(ModuleController module, int moduleIndex) {
1821         if (areBottomControlsUsed(module)) {
1822             setBottomBarShutterIcon(moduleIndex);
1823             mCaptureLayoutHelper.setShowBottomBar(true);
1824         } else {
1825             mCaptureLayoutHelper.setShowBottomBar(false);
1826         }
1827     }
1828 
1829     /**
1830      * Show or hide the mode options and bottom bar, based on
1831      * whether the current module is using the bottom bar.  Returns
1832      * whether the mode options and bottom bar are used.
1833      */
1834     private boolean areBottomControlsUsed(ModuleController module) {
1835         if (module.isUsingBottomBar()) {
1836             showBottomBar();
1837             showModeOptions();
1838             return true;
1839         } else {
1840             hideBottomBar();
1841             hideModeOptions();
1842             return false;
1843         }
1844     }
1845 
1846     /**
1847      * Set the bottom bar visible.
1848      */
1849     public void showBottomBar() {
1850         mBottomBar.setVisibility(View.VISIBLE);
1851     }
1852 
1853     /**
1854      * Set the bottom bar invisible.
1855      */
1856     public void hideBottomBar() {
1857         mBottomBar.setVisibility(View.INVISIBLE);
1858     }
1859 
1860     /**
1861      * Sets the color of the bottom bar.
1862      */
1863     public void setBottomBarColor(int colorId) {
1864         mBottomBar.setBackgroundColor(colorId);
1865     }
1866 
1867     /**
1868      * Sets the pressed color of the bottom bar for a camera mode index.
1869      */
1870     public void setBottomBarColorsForModeIndex(int index) {
1871         mBottomBar.setColorsForModeIndex(index);
1872     }
1873 
1874     /**
1875      * Sets the shutter button icon on the bottom bar, based on
1876      * the mode index.
1877      */
1878     public void setBottomBarShutterIcon(int modeIndex) {
1879         int shutterIconId = CameraUtil.getCameraShutterIconId(modeIndex,
1880             mController.getAndroidContext());
1881         mBottomBar.setShutterButtonIcon(shutterIconId);
1882     }
1883 
1884     public void animateBottomBarToVideoStop(int shutterIconId) {
1885         mBottomBar.animateToVideoStop(shutterIconId);
1886     }
1887 
1888     public void animateBottomBarToFullSize(int shutterIconId) {
1889         mBottomBar.animateToFullSize(shutterIconId);
1890     }
1891 
1892     public void setShutterButtonEnabled(final boolean enabled) {
1893         if (!mDisableAllUserInteractions) {
1894             mBottomBar.post(new Runnable() {
1895                 @Override
1896                 public void run() {
1897                     mBottomBar.setShutterButtonEnabled(enabled);
1898                 }
1899             });
1900         }
1901     }
1902 
1903     public void setShutterButtonImportantToA11y(boolean important) {
1904         mBottomBar.setShutterButtonImportantToA11y(important);
1905     }
1906 
1907     public boolean isShutterButtonEnabled() {
1908         return mBottomBar.isShutterButtonEnabled();
1909     }
1910 
1911     public void setIndicatorBottomBarWrapperVisible(boolean visible) {
1912         mStickyBottomCaptureLayout.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1913     }
1914 
1915     /**
1916      * Set the visibility of the bottom bar.
1917      */
1918     // TODO: needed for when panorama is managed by the generic module ui.
1919     public void setBottomBarVisible(boolean visible) {
1920         mBottomBar.setVisibility(visible ? View.VISIBLE : View.INVISIBLE);
1921     }
1922 
1923     /**
1924      * Add a {@link #ShutterButton.OnShutterButtonListener} to the shutter button.
1925      */
1926     public void addShutterListener(ShutterButton.OnShutterButtonListener listener) {
1927         mShutterButton.addOnShutterButtonListener(listener);
1928     }
1929 
1930     /**
1931      * Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button.
1932      */
1933     public void removeShutterListener(ShutterButton.OnShutterButtonListener listener) {
1934         mShutterButton.removeOnShutterButtonListener(listener);
1935     }
1936 
1937     /**
1938      * Sets or replaces the "cancel shutter" button listener.
1939      * <p>
1940      * TODO: Make this part of the interface the same way shutter button
1941      * listeners are.
1942      */
1943     public void setCancelShutterButtonListener(View.OnClickListener listener) {
1944         mCountdownCancelButton.setOnClickListener(listener);
1945     }
1946 
1947     /**
1948      * Performs a transition to the capture layout of the bottom bar.
1949      */
1950     public void transitionToCapture() {
1951         ModuleController moduleController = mController.getCurrentModuleController();
1952         applyModuleSpecs(moduleController.getHardwareSpec(),
1953             moduleController.getBottomBarSpec());
1954         mBottomBar.transitionToCapture();
1955     }
1956 
1957     /**
1958      * Displays the Cancel button instead of the capture button.
1959      */
1960     public void transitionToCancel() {
1961         ModuleController moduleController = mController.getCurrentModuleController();
1962         applyModuleSpecs(moduleController.getHardwareSpec(),
1963                 moduleController.getBottomBarSpec());
1964         mBottomBar.transitionToCancel();
1965     }
1966 
1967     /**
1968      * Performs a transition to the global intent layout.
1969      */
1970     public void transitionToIntentCaptureLayout() {
1971         ModuleController moduleController = mController.getCurrentModuleController();
1972         applyModuleSpecs(moduleController.getHardwareSpec(),
1973             moduleController.getBottomBarSpec());
1974         mBottomBar.transitionToIntentCaptureLayout();
1975         showModeOptions();
1976     }
1977 
1978     /**
1979      * Performs a transition to the global intent review layout.
1980      */
1981     public void transitionToIntentReviewLayout() {
1982         ModuleController moduleController = mController.getCurrentModuleController();
1983         applyModuleSpecs(moduleController.getHardwareSpec(),
1984             moduleController.getBottomBarSpec());
1985         mBottomBar.transitionToIntentReviewLayout();
1986         hideModeOptions();
1987 
1988         // Hide the preview snapshot since the screen is frozen when users tap
1989         // shutter button in capture intent.
1990         hideModeCover();
1991     }
1992 
1993     /**
1994      * @return whether UI is in intent review mode
1995      */
1996     public boolean isInIntentReview() {
1997         return mBottomBar.isInIntentReview();
1998     }
1999 
2000     @Override
2001     public void onSettingChanged(SettingsManager settingsManager, String key) {
2002         // Update the mode options based on the hardware spec,
2003         // when hdr changes to prevent flash from getting out of sync.
2004         if (key.equals(Keys.KEY_CAMERA_HDR)) {
2005             ModuleController moduleController = mController.getCurrentModuleController();
2006             applyModuleSpecs(moduleController.getHardwareSpec(),
2007                              moduleController.getBottomBarSpec(),
2008                              true /*skipScopeCheck*/);
2009         }
2010     }
2011 
2012     /**
2013      * Applies a {@link com.android.camera.CameraAppUI.BottomBarUISpec}
2014      * to the bottom bar mode options based on limitations from a
2015      * {@link com.android.camera.hardware.HardwareSpec}.
2016      *
2017      * Options not supported by the hardware are either hidden
2018      * or disabled, depending on the option.
2019      *
2020      * Otherwise, the option is fully enabled and clickable.
2021      */
2022     public void applyModuleSpecs(HardwareSpec hardwareSpec,
2023             BottomBarUISpec bottomBarSpec) {
2024         applyModuleSpecs(hardwareSpec, bottomBarSpec, false /*skipScopeCheck*/);
2025     }
2026 
2027     private void applyModuleSpecs(final HardwareSpec hardwareSpec,
2028            final BottomBarUISpec bottomBarSpec, boolean skipScopeCheck) {
2029         if (hardwareSpec == null || bottomBarSpec == null) {
2030             return;
2031         }
2032 
2033         ButtonManager buttonManager = mController.getButtonManager();
2034         SettingsManager settingsManager = mController.getSettingsManager();
2035 
2036         buttonManager.setToInitialState();
2037 
2038         if (skipScopeCheck
2039                 || !mController.getModuleScope().equals(mCurrentModuleScope)
2040                 || !mController.getCameraScope().equals(mCurrentCameraScope)) {
2041 
2042             // Scope dependent options, update only if the module or the
2043             // camera scope changed or scope-check skip was requested.
2044             mCurrentModuleScope = mController.getModuleScope();
2045             mCurrentCameraScope = mController.getCameraScope();
2046 
2047             mHdrSupportMode = settingsManager.getString(SettingsManager.SCOPE_GLOBAL,
2048                     Keys.KEY_HDR_SUPPORT_MODE_BACK_CAMERA);
2049 
2050             /** Standard mode options */
2051             if (mController.getCameraProvider().getNumberOfCameras() > 1 &&
2052                     hardwareSpec.isFrontCameraSupported()) {
2053                 if (bottomBarSpec.enableCamera) {
2054                     int hdrButtonId = ButtonManager.BUTTON_HDR;
2055                     if (mHdrSupportMode.equals(getResourceString(
2056                             R.string.pref_camera_hdr_supportmode_hdr_plus))) {
2057                         hdrButtonId = ButtonManager.BUTTON_HDR_PLUS;
2058                     }
2059                     buttonManager.initializeButton(ButtonManager.BUTTON_CAMERA,
2060                             bottomBarSpec.cameraCallback,
2061                             getDisableButtonCallback(hdrButtonId));
2062                 } else {
2063                     buttonManager.disableButton(ButtonManager.BUTTON_CAMERA);
2064                 }
2065             } else {
2066                 // Hide camera icon if front camera not available.
2067                 buttonManager.hideButton(ButtonManager.BUTTON_CAMERA);
2068             }
2069 
2070             boolean flashBackCamera = settingsManager.getBoolean(SettingsManager.SCOPE_GLOBAL,
2071                     Keys.KEY_FLASH_SUPPORTED_BACK_CAMERA);
2072             if (bottomBarSpec.hideFlash
2073                     || !flashBackCamera) {
2074                 // Hide both flash and torch button in flash disable logic
2075                 buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
2076                 buttonManager.hideButton(ButtonManager.BUTTON_TORCH);
2077             } else {
2078                 if (hardwareSpec.isFlashSupported()) {
2079                     if (bottomBarSpec.enableFlash) {
2080                         buttonManager.initializeButton(ButtonManager.BUTTON_FLASH,
2081                                 bottomBarSpec.flashCallback);
2082                     } else if (bottomBarSpec.enableTorchFlash) {
2083                         buttonManager.initializeButton(ButtonManager.BUTTON_TORCH,
2084                                 bottomBarSpec.flashCallback);
2085                     } else if (bottomBarSpec.enableHdrPlusFlash) {
2086                         buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS_FLASH,
2087                                 bottomBarSpec.flashCallback);
2088                     } else {
2089                         // Disable both flash and torch button in flash disable
2090                         // logic. Need to ensure it's visible, it may be hidden
2091                         // from previous non-flash mode.
2092                         buttonManager.showButton(ButtonManager.BUTTON_FLASH);
2093                         buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
2094                         buttonManager.disableButton(ButtonManager.BUTTON_TORCH);
2095                     }
2096                 } else {
2097                     // Flash not supported but another module does.
2098                     // Disable flash button. Need to ensure it's visible,
2099                     // it may be hidden from previous non-flash mode.
2100                     buttonManager.showButton(ButtonManager.BUTTON_FLASH);
2101                     buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
2102                     buttonManager.disableButton(ButtonManager.BUTTON_TORCH);
2103                 }
2104             }
2105 
2106             if (bottomBarSpec.hideHdr || mIsCaptureIntent) {
2107                 // Force hide hdr or hdr plus icon.
2108                 buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS);
2109             } else {
2110                 if (hardwareSpec.isHdrPlusSupported()) {
2111                     mHdrSupportMode = getResourceString(
2112                             R.string.pref_camera_hdr_supportmode_hdr_plus);
2113                     if (bottomBarSpec.enableHdr) {
2114                         buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS,
2115                                 bottomBarSpec.hdrCallback,
2116                                 getDisableButtonCallback(ButtonManager.BUTTON_CAMERA));
2117                     } else {
2118                         buttonManager.disableButton(ButtonManager.BUTTON_HDR_PLUS);
2119                     }
2120                 } else if (hardwareSpec.isHdrSupported()) {
2121                     mHdrSupportMode = getResourceString(R.string.pref_camera_hdr_supportmode_hdr);
2122                     if (bottomBarSpec.enableHdr) {
2123                         buttonManager.initializeButton(ButtonManager.BUTTON_HDR,
2124                                 bottomBarSpec.hdrCallback,
2125                                 getDisableButtonCallback(ButtonManager.BUTTON_CAMERA));
2126                     } else {
2127                         buttonManager.disableButton(ButtonManager.BUTTON_HDR);
2128                     }
2129                 } else {
2130                     // Hide hdr plus or hdr icon if neither are supported overall.
2131                     if (mHdrSupportMode.isEmpty() || mHdrSupportMode
2132                             .equals(getResourceString(R.string.pref_camera_hdr_supportmode_none))) {
2133                         buttonManager.hideButton(ButtonManager.BUTTON_HDR_PLUS);
2134                     } else {
2135                         // Disable HDR button. Need to ensure it's visible,
2136                         // it may be hidden from previous non HDR mode (eg. Video).
2137                         int buttonId = ButtonManager.BUTTON_HDR;
2138                         if (mHdrSupportMode.equals(
2139                                 getResourceString(R.string.pref_camera_hdr_supportmode_hdr_plus))) {
2140                             buttonId = ButtonManager.BUTTON_HDR_PLUS;
2141                         }
2142                         buttonManager.showButton(buttonId);
2143                         buttonManager.disableButton(buttonId);
2144                     }
2145                 }
2146             }
2147 
2148         }
2149         if (bottomBarSpec.hideGridLines) {
2150             // Force hide grid lines icon.
2151             buttonManager.hideButton(ButtonManager.BUTTON_GRID_LINES);
2152             hideGridLines();
2153         } else {
2154             if (bottomBarSpec.enableGridLines) {
2155                 buttonManager.initializeButton(ButtonManager.BUTTON_GRID_LINES,
2156                         bottomBarSpec.gridLinesCallback != null ?
2157                                 bottomBarSpec.gridLinesCallback : getGridLinesCallback()
2158                 );
2159             } else {
2160                 buttonManager.disableButton(ButtonManager.BUTTON_GRID_LINES);
2161                 hideGridLines();
2162             }
2163         }
2164 
2165         if (bottomBarSpec.enableSelfTimer) {
2166             buttonManager.initializeButton(ButtonManager.BUTTON_COUNTDOWN, null);
2167         } else {
2168             if (bottomBarSpec.showSelfTimer) {
2169                 buttonManager.disableButton(ButtonManager.BUTTON_COUNTDOWN);
2170             } else {
2171                 buttonManager.hideButton(ButtonManager.BUTTON_COUNTDOWN);
2172             }
2173         }
2174 
2175         if (bottomBarSpec.enablePanoOrientation
2176                 && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
2177             buttonManager.initializePanoOrientationButtons(bottomBarSpec.panoOrientationCallback);
2178         }
2179 
2180 
2181 
2182         // If manual exposure is enabled both in SettingsManager and
2183         // BottomBarSpec,then show the exposure button.
2184         // If manual exposure is disabled in the BottomBarSpec (eg. HDR+
2185         // enabled), but the device/module has the feature, then disable the exposure
2186         // button.
2187         // Otherwise, hide the button.
2188         if (bottomBarSpec.enableExposureCompensation
2189                 && !(bottomBarSpec.minExposureCompensation == 0 && bottomBarSpec.maxExposureCompensation == 0)
2190                 && mController.getSettingsManager().getBoolean(SettingsManager.SCOPE_GLOBAL,
2191                         Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)) {
2192             buttonManager.initializePushButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION,
2193                     new View.OnClickListener() {
2194                         @Override
2195                         public void onClick(View v) {
2196                             mModeOptionsOverlay.showExposureOptions();
2197                         }
2198                     });
2199             buttonManager.setExposureCompensationParameters(
2200                     bottomBarSpec.minExposureCompensation,
2201                     bottomBarSpec.maxExposureCompensation,
2202                     bottomBarSpec.exposureCompensationStep);
2203 
2204             buttonManager.setExposureCompensationCallback(
2205                     bottomBarSpec.exposureCompensationSetCallback);
2206             buttonManager.updateExposureButtons();
2207         } else if (mController.getSettingsManager().getBoolean(SettingsManager.SCOPE_GLOBAL,
2208                 Keys.KEY_EXPOSURE_COMPENSATION_ENABLED)
2209                 && bottomBarSpec.isExposureCompensationSupported) {
2210             buttonManager.disableButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION);
2211         } else {
2212             buttonManager.hideButton(ButtonManager.BUTTON_EXPOSURE_COMPENSATION);
2213             buttonManager.setExposureCompensationCallback(null);
2214         }
2215 
2216         /** Intent UI */
2217         if (bottomBarSpec.showCancel) {
2218             buttonManager.initializePushButton(ButtonManager.BUTTON_CANCEL,
2219                     bottomBarSpec.cancelCallback);
2220         }
2221         if (bottomBarSpec.showDone) {
2222             buttonManager.initializePushButton(ButtonManager.BUTTON_DONE,
2223                     bottomBarSpec.doneCallback);
2224         }
2225         if (bottomBarSpec.showRetake) {
2226             buttonManager.initializePushButton(ButtonManager.BUTTON_RETAKE,
2227                     bottomBarSpec.retakeCallback,
2228                     R.drawable.ic_back,
2229                     R.string.retake_button_description);
2230         }
2231         if (bottomBarSpec.showReview) {
2232             buttonManager.initializePushButton(ButtonManager.BUTTON_REVIEW,
2233                     bottomBarSpec.reviewCallback,
2234                     R.drawable.ic_play,
2235                     R.string.review_button_description);
2236         }
2237     }
2238 
2239     /**
2240      * Returns a {@link com.android.camera.ButtonManager.ButtonCallback} that
2241      * will disable the button identified by the parameter.
2242      *
2243      * @param conflictingButton The button id to be disabled.
2244      */
2245     private ButtonManager.ButtonCallback getDisableButtonCallback(final int conflictingButton) {
2246         return new ButtonManager.ButtonCallback() {
2247             @Override
2248             public void onStateChanged(int state) {
2249                 mController.getButtonManager().disableButton(conflictingButton);
2250             }
2251         };
2252     }
2253 
2254     private String getResourceString(int stringId) {
2255         try {
2256             return mController.getAndroidContext().getResources().getString(stringId);
2257         } catch (Resources.NotFoundException e) {
2258             // String not found, returning empty string.
2259             return "";
2260         }
2261     }
2262 
2263     /**
2264      * Shows the given tutorial on the screen.
2265      */
2266     public void showTutorial(AbstractTutorialOverlay tutorial, LayoutInflater inflater) {
2267         tutorial.show(mTutorialsPlaceHolderWrapper, inflater);
2268     }
2269 
2270     /**
2271      * Whether the capture ratio selector dialog must be shown on this device.
2272      * */
2273     public boolean shouldShowAspectRatioDialog() {
2274         final boolean isAspectRatioPreferenceSet = mController.getSettingsManager().getBoolean(
2275                 SettingsManager.SCOPE_GLOBAL, Keys.KEY_USER_SELECTED_ASPECT_RATIO);
2276         final boolean isAspectRatioDevice =
2277                 ApiHelper.IS_NEXUS_4 || ApiHelper.IS_NEXUS_5 || ApiHelper.IS_NEXUS_6;
2278         return isAspectRatioDevice && !isAspectRatioPreferenceSet;
2279     }
2280 
2281 
2282     /***************************Filmstrip api *****************************/
2283 
2284     public void showFilmstrip() {
2285         mModeListView.onBackPressed();
2286         mFilmstripLayout.showFilmstrip();
2287     }
2288 
2289     public void hideFilmstrip() {
2290         mFilmstripLayout.hideFilmstrip();
2291     }
2292 
2293     public int getFilmstripVisibility() {
2294         return mFilmstripLayout.getVisibility();
2295     }
2296 }
2297