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