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 { 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