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
17package com.android.messaging.ui.contact;
18
19import android.app.Activity;
20import android.app.Fragment;
21import android.database.Cursor;
22import android.graphics.Rect;
23import android.os.Bundle;
24import android.support.v7.app.ActionBar;
25import android.support.v7.widget.Toolbar;
26import android.support.v7.widget.Toolbar.OnMenuItemClickListener;
27import android.text.Editable;
28import android.text.InputType;
29import android.text.TextUtils;
30import android.text.TextWatcher;
31import android.transition.Explode;
32import android.transition.Transition;
33import android.transition.Transition.EpicenterCallback;
34import android.transition.TransitionManager;
35import android.view.LayoutInflater;
36import android.view.Menu;
37import android.view.MenuItem;
38import android.view.View;
39import android.view.View.OnClickListener;
40import android.view.ViewGroup;
41
42import com.android.messaging.R;
43import com.android.messaging.datamodel.DataModel;
44import com.android.messaging.datamodel.action.ActionMonitor;
45import com.android.messaging.datamodel.action.GetOrCreateConversationAction;
46import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionListener;
47import com.android.messaging.datamodel.action.GetOrCreateConversationAction.GetOrCreateConversationActionMonitor;
48import com.android.messaging.datamodel.binding.Binding;
49import com.android.messaging.datamodel.binding.BindingBase;
50import com.android.messaging.datamodel.data.ContactListItemData;
51import com.android.messaging.datamodel.data.ContactPickerData;
52import com.android.messaging.datamodel.data.ContactPickerData.ContactPickerDataListener;
53import com.android.messaging.datamodel.data.ParticipantData;
54import com.android.messaging.ui.CustomHeaderPagerViewHolder;
55import com.android.messaging.ui.CustomHeaderViewPager;
56import com.android.messaging.ui.animation.ViewGroupItemVerticalExplodeAnimation;
57import com.android.messaging.ui.contact.ContactRecipientAutoCompleteView.ContactChipsChangeListener;
58import com.android.messaging.util.Assert;
59import com.android.messaging.util.Assert.RunsOnMainThread;
60import com.android.messaging.util.ContactUtil;
61import com.android.messaging.util.ImeUtil;
62import com.android.messaging.util.LogUtil;
63import com.android.messaging.util.OsUtil;
64import com.android.messaging.util.PhoneUtils;
65import com.android.messaging.util.UiUtils;
66import com.google.common.annotations.VisibleForTesting;
67
68import java.util.ArrayList;
69import java.util.Set;
70
71
72/**
73 * Shows lists of contacts to start conversations with.
74 */
75public 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 {
98        void onGetOrCreateNewConversation(String conversationId);
99        void onBackButtonPressed();
100        void onInitiateAddMoreParticipants();
101        void onParticipantCountChanged(boolean canAddMoreParticipants);
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
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
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
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
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
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
262    public void onAllContactsCursorUpdated(final Cursor data) {
263        mBinding.ensureBound();
264        mAllContactsListViewHolder.onContactsCursorUpdated(data);
265    }
266
267    @Override // From ContactPickerDataListener
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
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
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     */
301    public void setHost(final ContactPickerFragmentHost host) {
302        mHost = host;
303    }
304
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
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
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
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
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
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
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
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
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     */
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     */
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
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
570    public void onContactCustomColorLoaded(final ContactPickerData data) {
571        mBinding.ensureBound(data);
572        invalidateContactLists();
573    }
574
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
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
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