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.contact;
18 
19 import android.app.Activity;
20 import android.app.Fragment;
21 import android.database.Cursor;
22 import android.graphics.Rect;
23 import android.os.Bundle;
24 import android.support.v7.app.ActionBar;
25 import android.support.v7.widget.Toolbar;
26 import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
27 import android.text.Editable;
28 import android.text.InputType;
29 import android.text.TextUtils;
30 import android.text.TextWatcher;
31 import android.transition.Explode;
32 import android.transition.Transition;
33 import android.transition.Transition.EpicenterCallback;
34 import android.transition.TransitionManager;
35 import android.view.LayoutInflater;
36 import android.view.Menu;
37 import android.view.MenuItem;
38 import android.view.View;
39 import android.view.View.OnClickListener;
40 import android.view.ViewGroup;
41 
42 import com.android.messaging.R;
43 import com.android.messaging.datamodel.DataModel;
44 import com.android.messaging.datamodel.action.ActionMonitor;
45 import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
46 import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
47 import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
48 import com.android.messaging.datamodel.binding.Binding;
49 import com.android.messaging.datamodel.binding.BindingBase;
50 import com.android.messaging.datamodel.data.ContactListItemData;
51 import com.android.messaging.datamodel.data.ContactPickerData;
52 import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
53 import com.android.messaging.datamodel.data.ParticipantData;
54 import com.android.messaging.ui.CustomHeaderPagerViewHolder;
55 import com.android.messaging.ui.CustomHeaderViewPager;
56 import com.android.messaging.ui.animation.ViewGroupItemVerticalExplodeAnimation;
57 import com.android.messaging.ui.contact.ContactRecipientAutoCompleteView.ContactChipsChangeListener;
58 import com.android.messaging.util.Assert;
59 import com.android.messaging.util.Assert.RunsOnMainThread;
60 import com.android.messaging.util.ContactUtil;
61 import com.android.messaging.util.ImeUtil;
62 import com.android.messaging.util.LogUtil;
63 import com.android.messaging.util.OsUtil;
64 import com.android.messaging.util.PhoneUtils;
65 import com.android.messaging.util.UiUtils;
66 import com.google.common.annotations.VisibleForTesting;
67 
68 import java.util.ArrayList;
69 import java.util.Set;
70 
71 
72 /**
73  * Shows lists of contacts to start conversations with.
74  */
75 public class ContactPickerFragment extends Fragment implements ContactPickerDataListener,
76         ContactListItemView.HostInterface, ContactChipsChangeListener, OnMenuItemClickListener,
77         GetOrCreateConversationActionListener {
78     public static final String FRAGMENT_TAG = "contactpicker";
79 
80     // Undefined contact picker mode. We should never be in this state after the host activity has
81     // been created.
82     public static final int MODE_UNDEFINED = 0;
83 
84     // The initial contact picker mode for starting a new conversation with one contact.
85     public static final int MODE_PICK_INITIAL_CONTACT = 1;
86 
87     // The contact picker mode where one initial contact has been picked and we are showing
88     // only the chips edit box.
89     public static final int MODE_CHIPS_ONLY = 2;
90 
91     // The contact picker mode for picking more contacts after starting the initial 1-1.
92     public static final int MODE_PICK_MORE_CONTACTS = 3;
93 
94     // The contact picker mode when max number of participants is reached.
95     public static final int MODE_PICK_MAX_PARTICIPANTS = 4;
96 
97     public interface ContactPickerFragmentHost {
onGetOrCreateNewConversation(String conversationId)98         void onGetOrCreateNewConversation(String conversationId);
onBackButtonPressed()99         void onBackButtonPressed();
onInitiateAddMoreParticipants()100         void onInitiateAddMoreParticipants();
onParticipantCountChanged(boolean canAddMoreParticipants)101         void onParticipantCountChanged(boolean canAddMoreParticipants);
invalidateActionBar()102         void invalidateActionBar();
103     }
104 
105     @VisibleForTesting
106     final Binding<ContactPickerData> mBinding = BindingBase.createBinding(this);
107 
108     private ContactPickerFragmentHost mHost;
109     private ContactRecipientAutoCompleteView mRecipientTextView;
110     private CustomHeaderViewPager mCustomHeaderViewPager;
111     private AllContactsListViewHolder mAllContactsListViewHolder;
112     private FrequentContactsListViewHolder mFrequentContactsListViewHolder;
113     private View mRootView;
114     private View mPendingExplodeView;
115     private View mComposeDivider;
116     private Toolbar mToolbar;
117     private int mContactPickingMode = MODE_UNDEFINED;
118 
119     // Keeps track of the currently selected phone numbers in the chips view to enable fast lookup.
120     private Set<String> mSelectedPhoneNumbers = null;
121 
122     /**
123      * {@inheritDoc} from Fragment
124      */
125     @Override
onCreate(final Bundle savedInstanceState)126     public void onCreate(final Bundle savedInstanceState) {
127         super.onCreate(savedInstanceState);
128         mAllContactsListViewHolder = new AllContactsListViewHolder(getActivity(), this);
129         mFrequentContactsListViewHolder = new FrequentContactsListViewHolder(getActivity(), this);
130 
131         if (ContactUtil.hasReadContactsPermission()) {
132             mBinding.bind(DataModel.get().createContactPickerData(getActivity(), this));
133             mBinding.getData().init(getLoaderManager(), mBinding);
134         }
135     }
136 
137     /**
138      * {@inheritDoc} from Fragment
139      */
140     @Override
onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState)141     public View onCreateView(final LayoutInflater inflater, final ViewGroup container,
142             final Bundle savedInstanceState) {
143         final View view = inflater.inflate(R.layout.contact_picker_fragment, container, false);
144         mRecipientTextView = (ContactRecipientAutoCompleteView)
145                 view.findViewById(R.id.recipient_text_view);
146         mRecipientTextView.setThreshold(0);
147         mRecipientTextView.setDropDownAnchor(R.id.compose_contact_divider);
148 
149         mRecipientTextView.setContactChipsListener(this);
150         mRecipientTextView.setDropdownChipLayouter(new ContactDropdownLayouter(inflater,
151                 getActivity(), this));
152         mRecipientTextView.setAdapter(new ContactRecipientAdapter(getActivity(), this));
153         mRecipientTextView.addTextChangedListener(new TextWatcher() {
154             @Override
155             public void onTextChanged(final CharSequence s, final int start, final int before,
156                     final int count) {
157             }
158 
159             @Override
160             public void beforeTextChanged(final CharSequence s, final int start, final int count,
161                     final int after) {
162             }
163 
164             @Override
165             public void afterTextChanged(final Editable s) {
166                 updateTextInputButtonsVisibility();
167             }
168         });
169 
170         final CustomHeaderPagerViewHolder[] viewHolders = {
171                 mFrequentContactsListViewHolder,
172                 mAllContactsListViewHolder };
173 
174         mCustomHeaderViewPager = (CustomHeaderViewPager) view.findViewById(R.id.contact_pager);
175         mCustomHeaderViewPager.setViewHolders(viewHolders);
176         mCustomHeaderViewPager.setViewPagerTabHeight(CustomHeaderViewPager.DEFAULT_TAB_STRIP_SIZE);
177         mCustomHeaderViewPager.setBackgroundColor(getResources()
178                 .getColor(R.color.contact_picker_background));
179 
180         // The view pager defaults to the frequent contacts page.
181         mCustomHeaderViewPager.setCurrentItem(0);
182 
183         mToolbar = (Toolbar) view.findViewById(R.id.toolbar);
184         mToolbar.setNavigationIcon(R.drawable.ic_arrow_back_light);
185         mToolbar.setNavigationContentDescription(R.string.back);
186         mToolbar.setNavigationOnClickListener(new OnClickListener() {
187             @Override
188             public void onClick(final View v) {
189                 mHost.onBackButtonPressed();
190             }
191         });
192 
193         mToolbar.inflateMenu(R.menu.compose_menu);
194         mToolbar.setOnMenuItemClickListener(this);
195 
196         mComposeDivider = view.findViewById(R.id.compose_contact_divider);
197         mRootView = view;
198         return view;
199     }
200 
201     /**
202      * {@inheritDoc}
203      *
204      * Called when the host activity has been created. At this point, the host activity should
205      * have set the contact picking mode for us so that we may update our visuals.
206      */
207     @Override
onActivityCreated(final Bundle savedInstanceState)208     public void onActivityCreated(final Bundle savedInstanceState) {
209         super.onActivityCreated(savedInstanceState);
210         Assert.isTrue(mContactPickingMode != MODE_UNDEFINED);
211         updateVisualsForContactPickingMode(false /* animate */);
212         mHost.invalidateActionBar();
213     }
214 
215     @Override
onDestroy()216     public void onDestroy() {
217         super.onDestroy();
218         // We could not have bound to the data if the permission was denied.
219         if (mBinding.isBound()) {
220             mBinding.unbind();
221         }
222 
223         if (mMonitor != null) {
224             mMonitor.unregister();
225         }
226         mMonitor = null;
227     }
228 
229     @Override
onMenuItemClick(final MenuItem menuItem)230     public boolean onMenuItemClick(final MenuItem menuItem) {
231         switch (menuItem.getItemId()) {
232             case R.id.action_ime_dialpad_toggle:
233                 final int baseInputType = InputType.TYPE_TEXT_FLAG_MULTI_LINE;
234                 if ((mRecipientTextView.getInputType() & InputType.TYPE_CLASS_PHONE) !=
235                         InputType.TYPE_CLASS_PHONE) {
236                     mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_PHONE);
237                     menuItem.setIcon(R.drawable.ic_ime_light);
238                 } else {
239                     mRecipientTextView.setInputType(baseInputType | InputType.TYPE_CLASS_TEXT);
240                     menuItem.setIcon(R.drawable.ic_numeric_dialpad);
241                 }
242                 ImeUtil.get().showImeKeyboard(getActivity(), mRecipientTextView);
243                 return true;
244 
245             case R.id.action_add_more_participants:
246                 mHost.onInitiateAddMoreParticipants();
247                 return true;
248 
249             case R.id.action_confirm_participants:
250                 maybeGetOrCreateConversation();
251                 return true;
252 
253             case R.id.action_delete_text:
254                 Assert.equals(MODE_PICK_INITIAL_CONTACT, mContactPickingMode);
255                 mRecipientTextView.setText("");
256                 return true;
257         }
258         return false;
259     }
260 
261     @Override // From ContactPickerDataListener
onAllContactsCursorUpdated(final Cursor data)262     public void onAllContactsCursorUpdated(final Cursor data) {
263         mBinding.ensureBound();
264         mAllContactsListViewHolder.onContactsCursorUpdated(data);
265     }
266 
267     @Override // From ContactPickerDataListener
onFrequentContactsCursorUpdated(final Cursor data)268     public void onFrequentContactsCursorUpdated(final Cursor data) {
269         mBinding.ensureBound();
270         mFrequentContactsListViewHolder.onContactsCursorUpdated(data);
271         if (data != null && data.getCount() == 0) {
272             // Show the all contacts list when there's no frequents.
273             mCustomHeaderViewPager.setCurrentItem(1);
274         }
275     }
276 
277     @Override // From ContactListItemView.HostInterface
onContactListItemClicked(final ContactListItemData item, final ContactListItemView view)278     public void onContactListItemClicked(final ContactListItemData item,
279             final ContactListItemView view) {
280         if (!isContactSelected(item)) {
281             if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
282                 mPendingExplodeView = view;
283             }
284             mRecipientTextView.appendRecipientEntry(item.getRecipientEntry());
285         } else if (mContactPickingMode != MODE_PICK_INITIAL_CONTACT) {
286             mRecipientTextView.removeRecipientEntry(item.getRecipientEntry());
287         }
288     }
289 
290     @Override // From ContactListItemView.HostInterface
isContactSelected(final ContactListItemData item)291     public boolean isContactSelected(final ContactListItemData item) {
292         return mSelectedPhoneNumbers != null &&
293                 mSelectedPhoneNumbers.contains(PhoneUtils.getDefault().getCanonicalBySystemLocale(
294                         item.getRecipientEntry().getDestination()));
295     }
296 
297     /**
298      * Call this immediately after attaching the fragment, or when there's a ui state change that
299      * changes our host (i.e. restore from saved instance state).
300      */
setHost(final ContactPickerFragmentHost host)301     public void setHost(final ContactPickerFragmentHost host) {
302         mHost = host;
303     }
304 
setContactPickingMode(final int mode, final boolean animate)305     public void setContactPickingMode(final int mode, final boolean animate) {
306         if (mContactPickingMode != mode) {
307             // Guard against impossible transitions.
308             Assert.isTrue(
309                     // We may start from undefined mode to any mode when we are restoring state.
310                     (mContactPickingMode == MODE_UNDEFINED) ||
311                     (mContactPickingMode == MODE_PICK_INITIAL_CONTACT && mode == MODE_CHIPS_ONLY) ||
312                     (mContactPickingMode == MODE_CHIPS_ONLY && mode == MODE_PICK_MORE_CONTACTS) ||
313                     (mContactPickingMode == MODE_PICK_MORE_CONTACTS
314                             && mode == MODE_PICK_MAX_PARTICIPANTS) ||
315                     (mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS
316                             && mode == MODE_PICK_MORE_CONTACTS));
317 
318             mContactPickingMode = mode;
319             updateVisualsForContactPickingMode(animate);
320         }
321     }
322 
showImeKeyboard()323     private void showImeKeyboard() {
324         Assert.notNull(mRecipientTextView);
325         mRecipientTextView.requestFocus();
326 
327         // showImeKeyboard() won't work until the layout is ready, so wait until layout is complete
328         // before showing the soft keyboard.
329         UiUtils.doOnceAfterLayoutChange(mRootView, new Runnable() {
330             @Override
331             public void run() {
332                 final Activity activity = getActivity();
333                 if (activity != null) {
334                     ImeUtil.get().showImeKeyboard(activity, mRecipientTextView);
335                 }
336             }
337         });
338         mRecipientTextView.invalidate();
339     }
340 
updateVisualsForContactPickingMode(final boolean animate)341     private void updateVisualsForContactPickingMode(final boolean animate) {
342         // Don't update visuals if the visuals haven't been inflated yet.
343         if (mRootView != null) {
344             final Menu menu = mToolbar.getMenu();
345             final MenuItem addMoreParticipantsItem = menu.findItem(
346                     R.id.action_add_more_participants);
347             final MenuItem confirmParticipantsItem = menu.findItem(
348                     R.id.action_confirm_participants);
349             switch (mContactPickingMode) {
350                 case MODE_PICK_INITIAL_CONTACT:
351                     addMoreParticipantsItem.setVisible(false);
352                     confirmParticipantsItem.setVisible(false);
353                     mCustomHeaderViewPager.setVisibility(View.VISIBLE);
354                     mComposeDivider.setVisibility(View.INVISIBLE);
355                     mRecipientTextView.setEnabled(true);
356                     showImeKeyboard();
357                     break;
358 
359                 case MODE_CHIPS_ONLY:
360                     if (animate) {
361                         if (mPendingExplodeView == null) {
362                             // The user didn't click on any contact item, so use the toolbar as
363                             // the view to "explode."
364                             mPendingExplodeView = mToolbar;
365                         }
366                         startExplodeTransitionForContactLists(false /* show */);
367 
368                         ViewGroupItemVerticalExplodeAnimation.startAnimationForView(
369                                 mCustomHeaderViewPager, mPendingExplodeView, mRootView,
370                                 true /* snapshotView */, UiUtils.COMPOSE_TRANSITION_DURATION);
371                         showHideContactPagerWithAnimation(false /* show */);
372                     } else {
373                         mCustomHeaderViewPager.setVisibility(View.GONE);
374                     }
375 
376                     addMoreParticipantsItem.setVisible(true);
377                     confirmParticipantsItem.setVisible(false);
378                     mComposeDivider.setVisibility(View.VISIBLE);
379                     mRecipientTextView.setEnabled(true);
380                     break;
381 
382                 case MODE_PICK_MORE_CONTACTS:
383                     if (animate) {
384                         // Correctly set the start visibility state for the view pager and
385                         // individual list items (hidden initially), so that the transition
386                         // manager can properly track the visibility change for the explode.
387                         mCustomHeaderViewPager.setVisibility(View.VISIBLE);
388                         toggleContactListItemsVisibilityForPendingTransition(false /* show */);
389                         startExplodeTransitionForContactLists(true /* show */);
390                     }
391                     addMoreParticipantsItem.setVisible(false);
392                     confirmParticipantsItem.setVisible(true);
393                     mCustomHeaderViewPager.setVisibility(View.VISIBLE);
394                     mComposeDivider.setVisibility(View.INVISIBLE);
395                     mRecipientTextView.setEnabled(true);
396                     showImeKeyboard();
397                     break;
398 
399                 case MODE_PICK_MAX_PARTICIPANTS:
400                     addMoreParticipantsItem.setVisible(false);
401                     confirmParticipantsItem.setVisible(true);
402                     mCustomHeaderViewPager.setVisibility(View.VISIBLE);
403                     mComposeDivider.setVisibility(View.INVISIBLE);
404                     // TODO: Verify that this is okay for accessibility
405                     mRecipientTextView.setEnabled(false);
406                     break;
407 
408                 default:
409                     Assert.fail("Unsupported contact picker mode!");
410                     break;
411             }
412             updateTextInputButtonsVisibility();
413         }
414     }
415 
updateTextInputButtonsVisibility()416     private void updateTextInputButtonsVisibility() {
417         final Menu menu = mToolbar.getMenu();
418         final MenuItem keypadToggleItem = menu.findItem(R.id.action_ime_dialpad_toggle);
419         final MenuItem deleteTextItem = menu.findItem(R.id.action_delete_text);
420         if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
421             if (TextUtils.isEmpty(mRecipientTextView.getText())) {
422                 deleteTextItem.setVisible(false);
423                 keypadToggleItem.setVisible(true);
424             } else {
425                 deleteTextItem.setVisible(true);
426                 keypadToggleItem.setVisible(false);
427             }
428         } else {
429             deleteTextItem.setVisible(false);
430             keypadToggleItem.setVisible(false);
431         }
432     }
433 
maybeGetOrCreateConversation()434     private void maybeGetOrCreateConversation() {
435         final ArrayList<ParticipantData> participants =
436                 mRecipientTextView.getRecipientParticipantDataForConversationCreation();
437         if (ContactPickerData.isTooManyParticipants(participants.size())) {
438             UiUtils.showToast(R.string.too_many_participants);
439         } else if (participants.size() > 0 && mMonitor == null) {
440             mMonitor = GetOrCreateConversationAction.getOrCreateConversation(participants,
441                     null, this);
442         }
443     }
444 
445     /**
446      * Watches changes in contact chips to determine possible state transitions (e.g. creating
447      * the initial conversation, adding more participants or finish the current conversation)
448      */
449     @Override
onContactChipsChanged(final int oldCount, final int newCount)450     public void onContactChipsChanged(final int oldCount, final int newCount) {
451         Assert.isTrue(oldCount != newCount);
452         if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT) {
453             // Initial picking mode. Start a conversation once a recipient has been picked.
454             maybeGetOrCreateConversation();
455         } else if (mContactPickingMode == MODE_CHIPS_ONLY) {
456             // oldCount == 0 means we are restoring from savedInstanceState to add the existing
457             // chips, don't switch to "add more participants" mode in this case.
458             if (oldCount > 0 && mRecipientTextView.isFocused()) {
459                 // Chips only mode. The user may have picked an additional contact or deleted the
460                 // only existing contact. Either way, switch to picking more participants mode.
461                 mHost.onInitiateAddMoreParticipants();
462             }
463         }
464         mHost.onParticipantCountChanged(ContactPickerData.getCanAddMoreParticipants(newCount));
465 
466         // Refresh our local copy of the selected chips set to keep it up-to-date.
467         mSelectedPhoneNumbers =  mRecipientTextView.getSelectedDestinations();
468         invalidateContactLists();
469     }
470 
471     /**
472      * Listens for notification that invalid contacts have been removed during resolving them.
473      * These contacts were not local contacts, valid email, or valid phone numbers
474      */
475     @Override
onInvalidContactChipsPruned(final int prunedCount)476     public void onInvalidContactChipsPruned(final int prunedCount) {
477         Assert.isTrue(prunedCount > 0);
478         UiUtils.showToast(R.plurals.add_invalid_contact_error, prunedCount);
479     }
480 
481     /**
482      * Listens for notification that the user has pressed enter/done on the keyboard with all
483      * contacts in place and we should create or go to the existing conversation now
484      */
485     @Override
onEntryComplete()486     public void onEntryComplete() {
487         if (mContactPickingMode == MODE_PICK_INITIAL_CONTACT ||
488                 mContactPickingMode == MODE_PICK_MORE_CONTACTS ||
489                 mContactPickingMode == MODE_PICK_MAX_PARTICIPANTS) {
490             // Avoid multiple calls to create in race cases (hit done right after selecting contact)
491             maybeGetOrCreateConversation();
492         }
493     }
494 
invalidateContactLists()495     private void invalidateContactLists() {
496         mAllContactsListViewHolder.invalidateList();
497         mFrequentContactsListViewHolder.invalidateList();
498     }
499 
500     /**
501      * Kicks off a scene transition that animates visibility changes of individual contact list
502      * items via explode animation.
503      * @param show whether the contact lists are to be shown or hidden.
504      */
startExplodeTransitionForContactLists(final boolean show)505     private void startExplodeTransitionForContactLists(final boolean show) {
506         if (!OsUtil.isAtLeastL()) {
507             // Explode animation is not supported pre-L.
508             return;
509         }
510         final Explode transition = new Explode();
511         final Rect epicenter = mPendingExplodeView == null ? null :
512             UiUtils.getMeasuredBoundsOnScreen(mPendingExplodeView);
513         transition.setDuration(UiUtils.COMPOSE_TRANSITION_DURATION);
514         transition.setInterpolator(UiUtils.EASE_IN_INTERPOLATOR);
515         transition.setEpicenterCallback(new EpicenterCallback() {
516             @Override
517             public Rect onGetEpicenter(final Transition transition) {
518                 return epicenter;
519             }
520         });
521 
522         // Kick off the delayed scene explode transition. Anything happens after this line in this
523         // method before the next frame will be tracked by the transition manager for visibility
524         // changes and animated accordingly.
525         TransitionManager.beginDelayedTransition(mCustomHeaderViewPager,
526                 transition);
527 
528         toggleContactListItemsVisibilityForPendingTransition(show);
529     }
530 
531     /**
532      * Toggle the visibility of contact list items in the contact lists for them to be tracked by
533      * the transition manager for pending explode transition.
534      */
toggleContactListItemsVisibilityForPendingTransition(final boolean show)535     private void toggleContactListItemsVisibilityForPendingTransition(final boolean show) {
536         if (!OsUtil.isAtLeastL()) {
537             // Explode animation is not supported pre-L.
538             return;
539         }
540         mAllContactsListViewHolder.toggleVisibilityForPendingTransition(show, mPendingExplodeView);
541         mFrequentContactsListViewHolder.toggleVisibilityForPendingTransition(show,
542                 mPendingExplodeView);
543     }
544 
showHideContactPagerWithAnimation(final boolean show)545     private void showHideContactPagerWithAnimation(final boolean show) {
546         final boolean isPagerVisible = (mCustomHeaderViewPager.getVisibility() == View.VISIBLE);
547         if (show == isPagerVisible) {
548             return;
549         }
550 
551         mCustomHeaderViewPager.animate().alpha(show ? 1F : 0F)
552             .setStartDelay(!show ? UiUtils.COMPOSE_TRANSITION_DURATION : 0)
553             .withStartAction(new Runnable() {
554                 @Override
555                 public void run() {
556                     mCustomHeaderViewPager.setVisibility(View.VISIBLE);
557                     mCustomHeaderViewPager.setAlpha(show ? 0F : 1F);
558                 }
559             })
560             .withEndAction(new Runnable() {
561                 @Override
562                 public void run() {
563                     mCustomHeaderViewPager.setVisibility(show ? View.VISIBLE : View.GONE);
564                     mCustomHeaderViewPager.setAlpha(1F);
565                 }
566             });
567     }
568 
569     @Override
onContactCustomColorLoaded(final ContactPickerData data)570     public void onContactCustomColorLoaded(final ContactPickerData data) {
571         mBinding.ensureBound(data);
572         invalidateContactLists();
573     }
574 
updateActionBar(final ActionBar actionBar)575     public void updateActionBar(final ActionBar actionBar) {
576         // Hide the action bar for contact picker mode. The custom ToolBar containing chips UI
577         // etc. will take the spot of the action bar.
578         actionBar.hide();
579         UiUtils.setStatusBarColor(getActivity(),
580                 getResources().getColor(R.color.compose_notification_bar_background));
581     }
582 
583     private GetOrCreateConversationActionMonitor mMonitor;
584 
585     @Override
586     @RunsOnMainThread
onGetOrCreateConversationSucceeded(final ActionMonitor monitor, final Object data, final String conversationId)587     public void onGetOrCreateConversationSucceeded(final ActionMonitor monitor,
588             final Object data, final String conversationId) {
589         Assert.isTrue(monitor == mMonitor);
590         Assert.isTrue(conversationId != null);
591 
592         mRecipientTextView.setInputType(InputType.TYPE_TEXT_FLAG_MULTI_LINE |
593                 InputType.TYPE_CLASS_TEXT);
594         mHost.onGetOrCreateNewConversation(conversationId);
595 
596         mMonitor = null;
597     }
598 
599     @Override
600     @RunsOnMainThread
onGetOrCreateConversationFailed(final ActionMonitor monitor, final Object data)601     public void onGetOrCreateConversationFailed(final ActionMonitor monitor,
602             final Object data) {
603         Assert.isTrue(monitor == mMonitor);
604         LogUtil.e(LogUtil.BUGLE_TAG, "onGetOrCreateConversationFailed");
605         mMonitor = null;
606     }
607 }
608