1 /*
2  * Copyright (C) 2015 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.messaging.ui.mediapicker;
18 
19 
20 import android.app.Activity;
21 import android.app.Fragment;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.os.Bundle;
25 import android.os.Handler;
26 import androidx.viewpager.widget.PagerAdapter;
27 import androidx.viewpager.widget.ViewPager;
28 import androidx.appcompat.app.ActionBar;
29 import android.view.LayoutInflater;
30 import android.view.Menu;
31 import android.view.MenuInflater;
32 import android.view.MenuItem;
33 import android.view.View;
34 import android.view.ViewGroup;
35 import android.widget.ImageButton;
36 import android.widget.LinearLayout;
37 
38 import com.android.messaging.Factory;
39 import com.android.messaging.R;
40 import com.android.messaging.datamodel.DataModel;
41 import com.android.messaging.datamodel.binding.Binding;
42 import com.android.messaging.datamodel.binding.BindingBase;
43 import com.android.messaging.datamodel.binding.ImmutableBindingRef;
44 import com.android.messaging.datamodel.data.DraftMessageData;
45 import com.android.messaging.datamodel.data.MediaPickerData;
46 import com.android.messaging.datamodel.data.MessagePartData;
47 import com.android.messaging.datamodel.data.PendingAttachmentData;
48 import com.android.messaging.datamodel.data.DraftMessageData.DraftMessageSubscriptionDataProvider;
49 import com.android.messaging.ui.BugleActionBarActivity;
50 import com.android.messaging.ui.FixedViewPagerAdapter;
51 import com.android.messaging.ui.mediapicker.DocumentImagePicker.SelectionListener;
52 import com.android.messaging.util.AccessibilityUtil;
53 import com.android.messaging.util.Assert;
54 import com.android.messaging.util.UiUtils;
55 import com.google.common.annotations.VisibleForTesting;
56 
57 import java.util.ArrayList;
58 import java.util.Collection;
59 import java.util.List;
60 
61 /**
62  * Fragment used to select or capture media to be added to the message
63  */
64 public class MediaPicker extends Fragment implements DraftMessageSubscriptionDataProvider {
65     /** The listener interface for events from the media picker */
66     public interface MediaPickerListener {
67         /** Called when the media picker is opened so the host can accommodate the UI */
onOpened()68         void onOpened();
69 
70         /**
71          * Called when the media picker goes into or leaves full screen mode so the host can
72          * accommodate the fullscreen UI
73          */
onFullScreenChanged(boolean fullScreen)74         void onFullScreenChanged(boolean fullScreen);
75 
76         /**
77          * Called when the user selects one or more items
78          * @param items The list of items which were selected
79          */
onItemsSelected(Collection<MessagePartData> items, boolean dismissMediaPicker)80         void onItemsSelected(Collection<MessagePartData> items, boolean dismissMediaPicker);
81 
82         /**
83          * Called when the user unselects one item.
84          */
onItemUnselected(MessagePartData item)85         void onItemUnselected(MessagePartData item);
86 
87         /**
88          * Called when the media picker is closed.  Always called immediately after onItemsSelected
89          */
onDismissed()90         void onDismissed();
91 
92         /**
93          * Called when media item selection is confirmed in a multi-select action.
94          */
onConfirmItemSelection()95         void onConfirmItemSelection();
96 
97         /**
98          * Called when a pending attachment is added.
99          * @param pendingItem the pending attachment data being loaded.
100          */
onPendingItemAdded(PendingAttachmentData pendingItem)101         void onPendingItemAdded(PendingAttachmentData pendingItem);
102 
103         /**
104          * Called when a new media chooser is selected.
105          */
onChooserSelected(final int chooserIndex)106         void onChooserSelected(final int chooserIndex);
107     }
108 
109     /** The tag used when registering and finding this fragment */
110     public static final String FRAGMENT_TAG = "mediapicker";
111 
112     // Media type constants that the media picker supports
113     public static final int MEDIA_TYPE_DEFAULT     = 0x0000;
114     public static final int MEDIA_TYPE_NONE        = 0x0000;
115     public static final int MEDIA_TYPE_IMAGE       = 0x0001;
116     public static final int MEDIA_TYPE_VIDEO       = 0x0002;
117     public static final int MEDIA_TYPE_AUDIO       = 0x0004;
118     public static final int MEDIA_TYPE_VCARD       = 0x0008;
119     public static final int MEDIA_TYPE_LOCATION    = 0x0010;
120     private static final int MEDA_TYPE_INVALID     = 0x0020;
121     public static final int MEDIA_TYPE_ALL         = 0xFFFF;
122 
123     /** The listener to call when events occur */
124     private MediaPickerListener mListener;
125 
126     /** The handler used to dispatch calls to the listener */
127     private Handler mListenerHandler;
128 
129     /** The bit flags of media types supported */
130     private int mSupportedMediaTypes;
131 
132     /** The list of choosers which could be within the media picker */
133     private final MediaChooser[] mChoosers;
134 
135     /** The list of currently enabled choosers */
136     private final ArrayList<MediaChooser> mEnabledChoosers;
137 
138     /** The currently selected chooser */
139     private MediaChooser mSelectedChooser;
140 
141     /** The main panel that controls the custom layout */
142     private MediaPickerPanel mMediaPickerPanel;
143 
144     /** The linear layout that holds the icons to select individual chooser tabs */
145     private LinearLayout mTabStrip;
146 
147     /** The view pager to swap between choosers */
148     private ViewPager mViewPager;
149 
150     /** The current pager adapter for the view pager */
151     private FixedViewPagerAdapter<MediaChooser> mPagerAdapter;
152 
153     /** True if the media picker is visible */
154     private boolean mOpen;
155 
156     /** The theme color to use to make the media picker match the rest of the UI */
157     private int mThemeColor;
158 
159     @VisibleForTesting
160     final Binding<MediaPickerData> mBinding = BindingBase.createBinding(this);
161 
162     /** Handles picking a media from the document picker. */
163     private DocumentImagePicker mDocumentImagePicker;
164 
165     /** Provides subscription-related data to access per-subscription configurations. */
166     private DraftMessageSubscriptionDataProvider mSubscriptionDataProvider;
167 
168     /** Provides access to DraftMessageData associated with the current conversation */
169     private ImmutableBindingRef<DraftMessageData> mDraftMessageDataModel;
170 
MediaPicker()171     public MediaPicker() {
172         this(Factory.get().getApplicationContext());
173     }
174 
MediaPicker(final Context context)175     public MediaPicker(final Context context) {
176         mBinding.bind(DataModel.get().createMediaPickerData(context));
177         mEnabledChoosers = new ArrayList<MediaChooser>();
178         mChoosers = new MediaChooser[] {
179             new CameraMediaChooser(this),
180             new GalleryMediaChooser(this),
181             new AudioMediaChooser(this),
182         };
183 
184         mOpen = false;
185         setSupportedMediaTypes(MEDIA_TYPE_ALL);
186     }
187 
188     private boolean mIsAttached;
189     private int mStartingMediaTypeOnAttach = MEDA_TYPE_INVALID;
190     private boolean mAnimateOnAttach;
191 
192     @Override
onAttach(final Activity activity)193     public void onAttach (final Activity activity) {
194         super.onAttach(activity);
195         mIsAttached = true;
196         if (mStartingMediaTypeOnAttach != MEDA_TYPE_INVALID) {
197             // open() was previously called. Do the pending open now.
198             doOpen(mStartingMediaTypeOnAttach, mAnimateOnAttach);
199         }
200     }
201 
202     @Override
onCreate(final Bundle savedInstanceState)203     public void onCreate(final Bundle savedInstanceState) {
204         super.onCreate(savedInstanceState);
205         mBinding.getData().init(getLoaderManager());
206         mDocumentImagePicker = new DocumentImagePicker(this,
207                 new SelectionListener() {
208             @Override
209             public void onDocumentSelected(final PendingAttachmentData data) {
210                 if (mBinding.isBound()) {
211                     dispatchPendingItemAdded(data);
212                 }
213             }
214         });
215     }
216 
217     @Override
onCreateView( final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState)218     public View onCreateView(
219             final LayoutInflater inflater,
220             final ViewGroup container,
221             final Bundle savedInstanceState) {
222         mMediaPickerPanel = (MediaPickerPanel) inflater.inflate(
223                 R.layout.mediapicker_fragment,
224                 container,
225                 false);
226         mMediaPickerPanel.setMediaPicker(this);
227         mTabStrip = (LinearLayout) mMediaPickerPanel.findViewById(R.id.mediapicker_tabstrip);
228         mTabStrip.setBackgroundColor(mThemeColor);
229         for (final MediaChooser chooser : mChoosers) {
230             chooser.onCreateTabButton(inflater, mTabStrip);
231             final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
232                     MEDIA_TYPE_NONE;
233             final ImageButton tabButton = chooser.getTabButton();
234             if (tabButton != null) {
235                 tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
236                 mTabStrip.addView(tabButton);
237             }
238         }
239 
240         mViewPager = (ViewPager) mMediaPickerPanel.findViewById(R.id.mediapicker_view_pager);
241         mViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
242             @Override
243             public void onPageScrolled(
244                     final int position,
245                     final float positionOffset,
246                     final int positionOffsetPixels) {
247             }
248 
249             @Override
250             public void onPageSelected(int position) {
251                 // The position returned is relative to if we are in RtL mode. This class never
252                 // switches the indices of the elements if we are in RtL mode so we need to
253                 // translate the index back. For example, if the user clicked the item most to the
254                 // right in RtL mode we would want the index to appear as 0 here, however the
255                 // position returned would the last possible index.
256                 if (UiUtils.isRtlMode()) {
257                     position = mEnabledChoosers.size() - 1 - position;
258                 }
259                 selectChooser(mEnabledChoosers.get(position));
260             }
261 
262             @Override
263             public void onPageScrollStateChanged(final int state) {
264             }
265         });
266         // Camera initialization is expensive, so don't realize offscreen pages if not needed.
267         mViewPager.setOffscreenPageLimit(0);
268         mViewPager.setAdapter(mPagerAdapter);
269         final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
270                 getActivity());
271         mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
272         mMediaPickerPanel.setExpanded(mOpen, true, mEnabledChoosers.indexOf(mSelectedChooser));
273         return mMediaPickerPanel;
274     }
275 
276     @Override
onPause()277     public void onPause() {
278         super.onPause();
279         CameraManager.get().onPause();
280         for (final MediaChooser chooser : mEnabledChoosers) {
281             chooser.onPause();
282         }
283     }
284 
285     @Override
onResume()286     public void onResume() {
287         super.onResume();
288         CameraManager.get().onResume();
289 
290         for (final MediaChooser chooser : mEnabledChoosers) {
291             chooser.onResume();
292         }
293     }
294 
295     @Override
onActivityResult(final int requestCode, final int resultCode, final Intent data)296     public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
297         super.onActivityResult(requestCode, resultCode, data);
298         mDocumentImagePicker.onActivityResult(requestCode, resultCode, data);
299     }
300 
301     @Override
onDestroy()302     public void onDestroy() {
303         super.onDestroy();
304         mBinding.unbind();
305     }
306 
307     /**
308      * Sets the theme color to make the media picker match the surrounding UI
309      * @param themeColor The new theme color
310      */
setConversationThemeColor(final int themeColor)311     public void setConversationThemeColor(final int themeColor) {
312         mThemeColor = themeColor;
313         if (mTabStrip != null) {
314             mTabStrip.setBackgroundColor(mThemeColor);
315         }
316 
317         for (final MediaChooser chooser : mEnabledChoosers) {
318             chooser.setThemeColor(mThemeColor);
319         }
320     }
321 
322     /**
323      * Gets the current conversation theme color.
324      */
getConversationThemeColor()325     public int getConversationThemeColor() {
326         return mThemeColor;
327     }
328 
setDraftMessageDataModel(final BindingBase<DraftMessageData> draftBinding)329     public void setDraftMessageDataModel(final BindingBase<DraftMessageData> draftBinding) {
330         mDraftMessageDataModel = Binding.createBindingReference(draftBinding);
331     }
332 
getDraftMessageDataModel()333     public ImmutableBindingRef<DraftMessageData> getDraftMessageDataModel() {
334         return mDraftMessageDataModel;
335     }
336 
setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider)337     public void setSubscriptionDataProvider(final DraftMessageSubscriptionDataProvider provider) {
338         mSubscriptionDataProvider = provider;
339     }
340 
341     @Override
getConversationSelfSubId()342     public int getConversationSelfSubId() {
343         return mSubscriptionDataProvider.getConversationSelfSubId();
344     }
345 
346     /**
347      * Opens the media picker and optionally shows the chooser for the supplied media type
348      * @param startingMediaType The media type of the chooser to open if {@link #MEDIA_TYPE_DEFAULT}
349      *                          is used, then the default chooser from saved shared prefs is opened
350      */
open(final int startingMediaType, final boolean animate)351     public void open(final int startingMediaType, final boolean animate) {
352         mOpen = true;
353         if (mIsAttached) {
354             doOpen(startingMediaType, animate);
355         } else {
356             // open() can get called immediately after the MediaPicker is created. In that case,
357             // we defer doing work as it may require an attached fragment (eg. calling
358             // Fragment#requestPermission)
359             mStartingMediaTypeOnAttach = startingMediaType;
360             mAnimateOnAttach = animate;
361         }
362     }
363 
doOpen(int startingMediaType, final boolean animate)364     private void doOpen(int startingMediaType, final boolean animate) {
365         final boolean isTouchExplorationEnabled = AccessibilityUtil.isTouchExplorationEnabled(
366                 // getActivity() will be null at this point
367                 Factory.get().getApplicationContext());
368 
369         // If no specific starting type is specified (i.e. MEDIA_TYPE_DEFAULT), try to get the
370         // last opened chooser index from shared prefs.
371         if (startingMediaType == MEDIA_TYPE_DEFAULT) {
372             final int selectedChooserIndex = mBinding.getData().getSelectedChooserIndex();
373             if (selectedChooserIndex >= 0 && selectedChooserIndex < mEnabledChoosers.size()) {
374                 selectChooser(mEnabledChoosers.get(selectedChooserIndex));
375             } else {
376                 // This is the first time the picker is being used
377                 if (isTouchExplorationEnabled) {
378                     // Accessibility defaults to audio attachment mode.
379                     startingMediaType = MEDIA_TYPE_AUDIO;
380                 }
381             }
382         }
383 
384         if (mSelectedChooser == null) {
385             for (final MediaChooser chooser : mEnabledChoosers) {
386                 if (startingMediaType == MEDIA_TYPE_DEFAULT ||
387                         (startingMediaType & chooser.getSupportedMediaTypes()) != MEDIA_TYPE_NONE) {
388                     selectChooser(chooser);
389                     break;
390                 }
391             }
392         }
393 
394         if (mSelectedChooser == null) {
395             // Fall back to the first chooser.
396             selectChooser(mEnabledChoosers.get(0));
397         }
398 
399         if (mMediaPickerPanel != null) {
400             mMediaPickerPanel.setFullScreenOnly(isTouchExplorationEnabled);
401             mMediaPickerPanel.setExpanded(true, animate,
402                     mEnabledChoosers.indexOf(mSelectedChooser));
403         }
404     }
405 
406     /** @return True if the media picker is open */
isOpen()407     public boolean isOpen() {
408         return mOpen;
409     }
410 
411     /**
412      * Sets the list of media types to allow the user to select
413      * @param mediaTypes The bit flags of media types to allow.  Can be any combination of the
414      *                   MEDIA_TYPE_* values
415      */
setSupportedMediaTypes(final int mediaTypes)416     void setSupportedMediaTypes(final int mediaTypes) {
417         mSupportedMediaTypes = mediaTypes;
418         mEnabledChoosers.clear();
419         boolean selectNextChooser = false;
420         for (final MediaChooser chooser : mChoosers) {
421             final boolean enabled = (chooser.getSupportedMediaTypes() & mSupportedMediaTypes) !=
422                     MEDIA_TYPE_NONE;
423             if (enabled) {
424                 // TODO Add a way to inform the chooser which media types are supported
425                 mEnabledChoosers.add(chooser);
426                 if (selectNextChooser) {
427                     selectChooser(chooser);
428                     selectNextChooser = false;
429                 }
430             } else if (mSelectedChooser == chooser) {
431                 selectNextChooser = true;
432             }
433             final ImageButton tabButton = chooser.getTabButton();
434             if (tabButton != null) {
435                 tabButton.setVisibility(enabled ? View.VISIBLE : View.GONE);
436             }
437         }
438 
439         if (selectNextChooser && mEnabledChoosers.size() > 0) {
440             selectChooser(mEnabledChoosers.get(0));
441         }
442         final MediaChooser[] enabledChoosers = new MediaChooser[mEnabledChoosers.size()];
443         mEnabledChoosers.toArray(enabledChoosers);
444         mPagerAdapter = new FixedViewPagerAdapter<MediaChooser>(enabledChoosers);
445         if (mViewPager != null) {
446             mViewPager.setAdapter(mPagerAdapter);
447         }
448 
449         // Only rebind data if we are currently bound. Otherwise, we must have not
450         // bound to any data yet and should wait until onCreate() to bind data.
451         if (mBinding.isBound() && getActivity() != null) {
452             mBinding.unbind();
453             mBinding.bind(DataModel.get().createMediaPickerData(getActivity()));
454             mBinding.getData().init(getLoaderManager());
455         }
456     }
457 
getViewPager()458     ViewPager getViewPager() {
459         return mViewPager;
460     }
461 
462     /** Hides the media picker, and frees up any resources it’s using */
dismiss(final boolean animate)463     public void dismiss(final boolean animate) {
464         mOpen = false;
465         if (mMediaPickerPanel != null) {
466             mMediaPickerPanel.setExpanded(false, animate, MediaPickerPanel.PAGE_NOT_SET);
467         }
468         mSelectedChooser = null;
469     }
470 
471     /**
472      * Sets the listener for the media picker events
473      * @param listener The listener which will receive events
474      */
setListener(final MediaPickerListener listener)475     public void setListener(final MediaPickerListener listener) {
476         Assert.isMainThread();
477         mListener = listener;
478         mListenerHandler = listener != null ? new Handler() : null;
479     }
480 
481     /** @return True if the media picker is in full-screen mode */
isFullScreen()482     public boolean isFullScreen() {
483         return mMediaPickerPanel != null && mMediaPickerPanel.isFullScreen();
484     }
485 
setFullScreen(final boolean fullScreen)486     public void setFullScreen(final boolean fullScreen) {
487         mMediaPickerPanel.setFullScreenView(fullScreen, true);
488     }
489 
updateActionBar(final ActionBar actionBar)490     public void updateActionBar(final ActionBar actionBar) {
491         if (getActivity() == null) {
492             return;
493         }
494         if (isFullScreen() && mSelectedChooser != null) {
495             mSelectedChooser.updateActionBar(actionBar);
496         } else {
497             actionBar.hide();
498         }
499     }
500 
501     /**
502      * Selects a new chooser
503      * @param newSelectedChooser The newly selected chooser
504      */
selectChooser(final MediaChooser newSelectedChooser)505     void selectChooser(final MediaChooser newSelectedChooser) {
506         if (mSelectedChooser == newSelectedChooser) {
507             return;
508         }
509 
510         if (mSelectedChooser != null) {
511             mSelectedChooser.setSelected(false);
512         }
513         mSelectedChooser = newSelectedChooser;
514         if (mSelectedChooser != null) {
515             mSelectedChooser.setSelected(true);
516         }
517 
518         final int chooserIndex = mEnabledChoosers.indexOf(mSelectedChooser);
519         if (mViewPager != null) {
520             mViewPager.setCurrentItem(chooserIndex, true /* smoothScroll */);
521         }
522 
523         if (isFullScreen()) {
524             invalidateOptionsMenu();
525         }
526 
527         // Save the newly selected chooser's index so we may directly switch to it the
528         // next time user opens the media picker.
529         mBinding.getData().saveSelectedChooserIndex(chooserIndex);
530         if (mMediaPickerPanel != null) {
531             mMediaPickerPanel.onChooserChanged();
532         }
533         dispatchChooserSelected(chooserIndex);
534     }
535 
canShowIme()536     public boolean canShowIme() {
537         if (mSelectedChooser != null) {
538             return mSelectedChooser.canShowIme();
539         }
540         return false;
541     }
542 
onBackPressed()543     public boolean onBackPressed() {
544         return mSelectedChooser != null && mSelectedChooser.onBackPressed();
545     }
546 
invalidateOptionsMenu()547     void invalidateOptionsMenu() {
548         ((BugleActionBarActivity) getActivity()).supportInvalidateOptionsMenu();
549     }
550 
dispatchOpened()551     void dispatchOpened() {
552         setHasOptionsMenu(false);
553         mOpen = true;
554         mPagerAdapter.notifyDataSetChanged();
555         if (mListener != null) {
556             mListenerHandler.post(new Runnable() {
557                 @Override
558                 public void run() {
559                     mListener.onOpened();
560                 }
561             });
562         }
563         if (mSelectedChooser != null) {
564             mSelectedChooser.onFullScreenChanged(false);
565             mSelectedChooser.onOpenedChanged(true);
566         }
567     }
568 
dispatchDismissed()569     void dispatchDismissed() {
570         setHasOptionsMenu(false);
571         mOpen = false;
572         if (mListener != null) {
573             mListenerHandler.post(new Runnable() {
574                 @Override
575                 public void run() {
576                     mListener.onDismissed();
577                 }
578             });
579         }
580         if (mSelectedChooser != null) {
581             mSelectedChooser.onOpenedChanged(false);
582         }
583     }
584 
dispatchFullScreen(final boolean fullScreen)585     void dispatchFullScreen(final boolean fullScreen) {
586         setHasOptionsMenu(fullScreen);
587         if (mListener != null) {
588             mListenerHandler.post(new Runnable() {
589                 @Override
590                 public void run() {
591                     mListener.onFullScreenChanged(fullScreen);
592                 }
593             });
594         }
595         if (mSelectedChooser != null) {
596             mSelectedChooser.onFullScreenChanged(fullScreen);
597         }
598     }
599 
dispatchItemsSelected(final MessagePartData item, final boolean dismissMediaPicker)600     void dispatchItemsSelected(final MessagePartData item, final boolean dismissMediaPicker) {
601         final List<MessagePartData> items = new ArrayList<MessagePartData>(1);
602         items.add(item);
603         dispatchItemsSelected(items, dismissMediaPicker);
604     }
605 
dispatchItemsSelected(final Collection<MessagePartData> items, final boolean dismissMediaPicker)606     void dispatchItemsSelected(final Collection<MessagePartData> items,
607             final boolean dismissMediaPicker) {
608         if (mListener != null) {
609             mListenerHandler.post(new Runnable() {
610                 @Override
611                 public void run() {
612                     mListener.onItemsSelected(items, dismissMediaPicker);
613                 }
614             });
615         }
616 
617         if (isFullScreen() && !dismissMediaPicker) {
618             invalidateOptionsMenu();
619         }
620     }
621 
dispatchItemUnselected(final MessagePartData item)622     void dispatchItemUnselected(final MessagePartData item) {
623         if (mListener != null) {
624             mListenerHandler.post(new Runnable() {
625                 @Override
626                 public void run() {
627                     mListener.onItemUnselected(item);
628                 }
629             });
630         }
631 
632         if (isFullScreen()) {
633             invalidateOptionsMenu();
634         }
635     }
636 
dispatchConfirmItemSelection()637     void dispatchConfirmItemSelection() {
638         if (mListener != null) {
639             mListenerHandler.post(new Runnable() {
640                 @Override
641                 public void run() {
642                     mListener.onConfirmItemSelection();
643                 }
644             });
645         }
646     }
647 
dispatchPendingItemAdded(final PendingAttachmentData pendingItem)648     void dispatchPendingItemAdded(final PendingAttachmentData pendingItem) {
649         if (mListener != null) {
650             mListenerHandler.post(new Runnable() {
651                 @Override
652                 public void run() {
653                     mListener.onPendingItemAdded(pendingItem);
654                 }
655             });
656         }
657 
658         if (isFullScreen()) {
659             invalidateOptionsMenu();
660         }
661     }
662 
dispatchChooserSelected(final int chooserIndex)663     void dispatchChooserSelected(final int chooserIndex) {
664         if (mListener != null) {
665             mListenerHandler.post(new Runnable() {
666                 @Override
667                 public void run() {
668                     mListener.onChooserSelected(chooserIndex);
669                 }
670             });
671         }
672     }
673 
canSwipeDownChooser()674     public boolean canSwipeDownChooser() {
675         return mSelectedChooser == null ? false : mSelectedChooser.canSwipeDown();
676     }
677 
isChooserHandlingTouch()678     public boolean isChooserHandlingTouch() {
679         return mSelectedChooser == null ? false : mSelectedChooser.isHandlingTouch();
680     }
681 
stopChooserTouchHandling()682     public void stopChooserTouchHandling() {
683         if (mSelectedChooser != null) {
684             mSelectedChooser.stopTouchHandling();
685         }
686     }
687 
getChooserShowsActionBarInFullScreen()688     boolean getChooserShowsActionBarInFullScreen() {
689         return mSelectedChooser == null ? false : mSelectedChooser.getActionBarTitleResId() != 0;
690     }
691 
692     @Override
onCreateOptionsMenu(final Menu menu, final MenuInflater inflater)693     public void onCreateOptionsMenu(final Menu menu, final MenuInflater inflater) {
694         if (mSelectedChooser != null) {
695             mSelectedChooser.onCreateOptionsMenu(inflater, menu);
696         }
697     }
698 
699     @Override
onOptionsItemSelected(final MenuItem item)700     public boolean onOptionsItemSelected(final MenuItem item) {
701         return (mSelectedChooser != null && mSelectedChooser.onOptionsItemSelected(item)) ||
702                 super.onOptionsItemSelected(item);
703     }
704 
getPagerAdapter()705     PagerAdapter getPagerAdapter() {
706         return mPagerAdapter;
707     }
708 
resetViewHolderState()709     public void resetViewHolderState() {
710         mPagerAdapter.resetState();
711     }
712 
713     /**
714      * Launch an external picker to pick item from document picker as attachment.
715      */
launchDocumentPicker()716     public void launchDocumentPicker() {
717         mDocumentImagePicker.launchPicker();
718     }
719 
getMediaPickerDataBinding()720     public ImmutableBindingRef<MediaPickerData> getMediaPickerDataBinding() {
721         return BindingBase.createBindingReference(mBinding);
722     }
723 
724     protected static final int CAMERA_PERMISSION_REQUEST_CODE = 1;
725     protected static final int LOCATION_PERMISSION_REQUEST_CODE = 2;
726     protected static final int RECORD_AUDIO_PERMISSION_REQUEST_CODE = 3;
727     protected static final int GALLERY_PERMISSION_REQUEST_CODE = 4;
728 
729     @Override
onRequestPermissionsResult( final int requestCode, final String permissions[], final int[] grantResults)730     public void onRequestPermissionsResult(
731             final int requestCode, final String permissions[], final int[] grantResults) {
732         if (mSelectedChooser != null) {
733             mSelectedChooser.onRequestPermissionsResult(requestCode, permissions, grantResults);
734         }
735     }
736 }
737