RecipientEditTextView.java revision b86dcd5230ebcc57e5fc7a669c2304aca142dbf5
1/* 2 * Copyright (C) 2011 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.ex.chips; 18 19import android.content.Context; 20import android.graphics.Bitmap; 21import android.graphics.Canvas; 22import android.graphics.Paint; 23import android.graphics.Rect; 24import android.graphics.drawable.BitmapDrawable; 25import android.graphics.drawable.Drawable; 26import android.text.Editable; 27import android.text.Layout; 28import android.text.Spannable; 29import android.text.SpannableString; 30import android.text.Spanned; 31import android.text.TextPaint; 32import android.text.TextUtils; 33import android.text.TextWatcher; 34import android.text.method.QwertyKeyListener; 35import android.text.style.ImageSpan; 36import android.util.AttributeSet; 37import android.util.Log; 38import android.view.ActionMode; 39import android.view.KeyEvent; 40import android.view.Menu; 41import android.view.MenuItem; 42import android.view.MotionEvent; 43import android.view.View; 44import android.view.ActionMode.Callback; 45import android.widget.AdapterView; 46import android.widget.AdapterView.OnItemClickListener; 47import android.widget.ListPopupWindow; 48import android.widget.MultiAutoCompleteTextView; 49 50import java.util.Collection; 51import java.util.HashSet; 52import java.util.Set; 53 54import java.util.ArrayList; 55 56/** 57 * RecipientEditTextView is an auto complete text view for use with applications 58 * that use the new Chips UI for addressing a message to recipients. 59 */ 60public class RecipientEditTextView extends MultiAutoCompleteTextView 61 implements OnItemClickListener, Callback { 62 63 private static final String TAG = "RecipientEditTextView"; 64 65 private Drawable mChipBackground = null; 66 67 private Drawable mChipDelete = null; 68 69 private int mChipPadding; 70 71 private Tokenizer mTokenizer; 72 73 private Drawable mChipBackgroundPressed; 74 75 private RecipientChip mSelectedChip; 76 77 private int mChipDeleteWidth; 78 79 private ArrayList<RecipientChip> mRecipients; 80 81 private int mAlternatesLayout; 82 83 private int mAlternatesSelectedLayout; 84 85 public RecipientEditTextView(Context context, AttributeSet attrs) { 86 super(context, attrs); 87 mRecipients = new ArrayList<RecipientChip>(); 88 setOnItemClickListener(this); 89 setCustomSelectionActionModeCallback(this); 90 // When the user starts typing, make sure we unselect any selected 91 // chips. 92 addTextChangedListener(new TextWatcher() { 93 @Override 94 public void afterTextChanged(Editable s) { 95 // Do nothing. 96 } 97 @Override 98 public void onTextChanged(CharSequence s, int start, int before, int count) { 99 // Do nothing. 100 } 101 @Override 102 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 103 if (mSelectedChip != null) { 104 clearSelectedChip(); 105 setSelection(getText().length()); 106 } 107 } 108 }); 109 } 110 111 @Override 112 public void onSelectionChanged(int start, int end) { 113 // When selection changes, see if it is inside the chips area. 114 // If so, move the cursor back after the chips again. 115 if (mRecipients != null && mRecipients.size() > 0) { 116 Spannable span = getSpannable(); 117 RecipientChip[] chips = span.getSpans(start, getText().length(), RecipientChip.class); 118 if (chips != null && chips.length > 0) { 119 // Grab the last chip and set the cursor to after it. 120 setSelection(chips[chips.length - 1].getChipEnd() + 1); 121 } 122 } else { 123 super.onSelectionChanged(start, end); 124 } 125 } 126 127 public RecipientChip constructChipSpan(RecipientEntry contact, int offset, boolean pressed) 128 throws NullPointerException { 129 if (mChipBackground == null) { 130 throw new NullPointerException 131 ("Unable to render any chips as setChipDimensions was not called."); 132 } 133 String text = contact.getDisplayName(); 134 Layout layout = getLayout(); 135 int line = layout.getLineForOffset(offset); 136 int lineTop = layout.getLineTop(line); 137 138 TextPaint paint = getPaint(); 139 float defaultSize = paint.getTextSize(); 140 141 // Reduce the size of the text slightly so that we can get the "look" of 142 // padding. 143 paint.setTextSize((float) (paint.getTextSize() * .9)); 144 145 // Ellipsize the text so that it takes AT MOST the entire width of the 146 // autocomplete text entry area. Make sure to leave space for padding 147 // on the sides. 148 CharSequence ellipsizedText = TextUtils.ellipsize(text, paint, 149 calculateAvailableWidth(pressed), TextUtils.TruncateAt.END); 150 151 int height = getLineHeight(); 152 int width = (int) Math.floor(paint.measureText(ellipsizedText, 0, ellipsizedText.length())) 153 + (mChipPadding * 2); 154 if (pressed) { 155 // Allow the delete icon to overtake the visible recipient name. 156 // This works since when the user has entered selected mode, they 157 // will also see a popup with the recipient name. 158 width += mChipDeleteWidth/2; 159 } 160 161 // Create the background of the chip. 162 Bitmap tmpBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); 163 Canvas canvas = new Canvas(tmpBitmap); 164 if (pressed) { 165 if (mChipBackgroundPressed != null) { 166 mChipBackgroundPressed.setBounds(0, 0, width, height); 167 mChipBackgroundPressed.draw(canvas); 168 169 // Align the display text with where the user enters text. 170 canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height 171 - layout.getLineDescent(line), paint); 172 mChipDelete.setBounds(width - mChipDeleteWidth, 0, width, height); 173 mChipDelete.draw(canvas); 174 } else { 175 Log.w(TAG, 176 "Unable to draw a background for the chips as it was never set"); 177 } 178 } else { 179 if (mChipBackground != null) { 180 mChipBackground.setBounds(0, 0, width, height); 181 mChipBackground.draw(canvas); 182 183 // Align the display text with where the user enters text. 184 canvas.drawText(ellipsizedText, 0, ellipsizedText.length(), mChipPadding, height 185 - layout.getLineDescent(line), paint); 186 } else { 187 Log.w(TAG, 188 "Unable to draw a background for the chips as it was never set"); 189 } 190 } 191 192 193 // Get the location of the widget so we can properly offset 194 // the anchor for each chip. 195 int[] xy = new int[2]; 196 getLocationOnScreen(xy); 197 // Pass the full text, un-ellipsized, to the chip. 198 Drawable result = new BitmapDrawable(getResources(), tmpBitmap); 199 result.setBounds(0, 0, width, height); 200 Rect bounds = new Rect(xy[0] + offset, xy[1] + lineTop, xy[0] + width, 201 calculateLineBottom(xy[1], line)); 202 RecipientChip recipientChip = new RecipientChip(result, contact, offset, bounds); 203 204 // Return text to the original size. 205 paint.setTextSize(defaultSize); 206 207 return recipientChip; 208 } 209 210 // The bottom of the line the chip will be located on is calculated by 4 factors: 211 // 1) which line the chip appears on 212 // 2) the height of a line in the autocomplete view 213 // 3) padding built into the edit text view will move the bottom position 214 // 4) the position of the autocomplete view on the screen, taking into account 215 // that any top padding will move this down visually 216 private int calculateLineBottom(int yOffset, int line) { 217 int bottomPadding = 0; 218 if (line == getLineCount() - 1) { 219 bottomPadding += getPaddingBottom(); 220 } 221 return ((line + 1) * getLineHeight()) + (yOffset + getPaddingTop()) + bottomPadding; 222 } 223 224 // Get the max amount of space a chip can take up. The formula takes into 225 // account the width of the EditTextView, any view padding, and padding 226 // that will be added to the chip. 227 private float calculateAvailableWidth(boolean pressed) { 228 int paddingRight = 0; 229 if (pressed) { 230 paddingRight = mChipDeleteWidth; 231 } 232 return getWidth() - getPaddingLeft() - getPaddingRight() - (mChipPadding * 2) 233 - paddingRight; 234 } 235 236 /** 237 * Set all chip dimensions and resources. This has to be done from the application 238 * as this is a static library. 239 * @param chipBackground drawable 240 * @param padding Padding around the text in a chip 241 * @param offset Offset between the chip and the dropdown of alternates 242 */ 243 public void setChipDimensions(Drawable chipBackground, Drawable chipBackgroundPressed, 244 Drawable chipDelete, int alternatesLayout, int alternatesSelectedLayout, float padding) { 245 mChipBackground = chipBackground; 246 mChipBackgroundPressed = chipBackgroundPressed; 247 mChipDelete = chipDelete; 248 mChipDeleteWidth = chipDelete.getIntrinsicWidth(); 249 mChipPadding = (int) padding; 250 mAlternatesLayout = alternatesLayout; 251 mAlternatesSelectedLayout = alternatesSelectedLayout; 252 } 253 254 @Override 255 public void setTokenizer(Tokenizer tokenizer) { 256 mTokenizer = tokenizer; 257 super.setTokenizer(mTokenizer); 258 } 259 260 // We want to handle replacing text in the onItemClickListener 261 // so we can get all the associated contact information including 262 // display text, address, and id. 263 @Override 264 protected void replaceText(CharSequence text) { 265 return; 266 } 267 268 @Override 269 public boolean onKeyUp(int keyCode, KeyEvent event) { 270 switch (keyCode) { 271 case KeyEvent.KEYCODE_ENTER: 272 case KeyEvent.KEYCODE_DPAD_CENTER: 273 case KeyEvent.KEYCODE_TAB: 274 if (event.hasNoModifiers()) { 275 if (isPopupShowing()) { 276 // choose the first entry. 277 submitItemAtPosition(0); 278 dismissDropDown(); 279 return true; 280 } else { 281 int end = getSelectionEnd(); 282 int start = mTokenizer.findTokenStart(getText(), end); 283 String text = getText().toString().substring(start, end); 284 clearComposingText(); 285 286 Editable editable = getText(); 287 RecipientEntry entry = RecipientEntry.constructFakeEntry(text); 288 QwertyKeyListener.markAsReplaced(editable, start, end, ""); 289 editable.replace(start, end, createChip(entry)); 290 dismissDropDown(); 291 } 292 } 293 } 294 return super.onKeyUp(keyCode, event); 295 } 296 297 @Override 298 public boolean onKeyDown(int keyCode, KeyEvent event) { 299 if (mSelectedChip != null) { 300 mSelectedChip.onKeyDown(keyCode, event); 301 } 302 303 if (keyCode == KeyEvent.KEYCODE_ENTER && event.hasNoModifiers()) { 304 return true; 305 } 306 307 return super.onKeyDown(keyCode, event); 308 } 309 310 private Spannable getSpannable() { 311 return (Spannable) getText(); 312 } 313 314 /** 315 * Instead of filtering on the entire contents of the edit box, 316 * this subclass method filters on the range from 317 * {@link Tokenizer#findTokenStart} to {@link #getSelectionEnd} 318 * if the length of that range meets or exceeds {@link #getThreshold} 319 * and makes sure that the range is not already a Chip. 320 */ 321 @Override 322 protected void performFiltering(CharSequence text, int keyCode) { 323 if (enoughToFilter()) { 324 int end = getSelectionEnd(); 325 int start = mTokenizer.findTokenStart(text, end); 326 // If this is a RecipientChip, don't filter 327 // on its contents. 328 Spannable span = getSpannable(); 329 RecipientChip[] chips = span.getSpans(start, end, RecipientChip.class); 330 if (chips != null && chips.length > 0) { 331 return; 332 } 333 } 334 super.performFiltering(text, keyCode); 335 } 336 337 private void clearSelectedChip() { 338 if (mSelectedChip != null) { 339 mSelectedChip.unselectChip(); 340 mSelectedChip = null; 341 } 342 setCursorVisible(true); 343 } 344 345 @Override 346 public boolean onTouchEvent(MotionEvent event) { 347 int action = event.getAction(); 348 boolean handled = super.onTouchEvent(event); 349 boolean chipWasSelected = false; 350 351 if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) { 352 float x = event.getX(); 353 float y = event.getY(); 354 int offset = putOffsetInRange(getOffsetForPosition(x, y)); 355 RecipientChip currentChip = findChip(offset); 356 if (currentChip != null) { 357 if (action == MotionEvent.ACTION_UP) { 358 if (mSelectedChip != null && mSelectedChip != currentChip) { 359 clearSelectedChip(); 360 mSelectedChip = currentChip.selectChip(); 361 setCursorVisible(false); 362 } else if (mSelectedChip == null) { 363 mSelectedChip = currentChip.selectChip(); 364 setCursorVisible(false); 365 } else { 366 mSelectedChip.onClick(this, offset, x, y); 367 } 368 } 369 chipWasSelected = true; 370 } 371 } 372 if (action == MotionEvent.ACTION_UP && !chipWasSelected) { 373 clearSelectedChip(); 374 } 375 return handled; 376 } 377 378 // TODO: This algorithm will need a lot of tweaking after more people have used 379 // the chips ui. This attempts to be "forgiving" to fat finger touches by favoring 380 // what comes before the finger. 381 private int putOffsetInRange(int o) { 382 int offset = o; 383 Editable text = getText(); 384 int length = text.length(); 385 // Remove whitespace from end to find "real end" 386 int realLength = length; 387 for (int i = length - 1; i >= 0; i--) { 388 if (text.charAt(i) == ' ') { 389 realLength--; 390 } else { 391 break; 392 } 393 } 394 395 // If the offset is beyond or at the end of the text, 396 // leave it alone. 397 if (offset >= realLength) { 398 return offset; 399 } 400 Editable editable = getText(); 401 while (offset >= 0 && findText(editable, offset) == -1 && findChip(offset) == null) { 402 // Keep walking backward! 403 offset--; 404 } 405 return offset; 406 } 407 408 private int findText(Editable text, int offset) { 409 if (text.charAt(offset) != ' ') { 410 return offset; 411 } 412 return -1; 413 } 414 415 private RecipientChip findChip(int offset) { 416 RecipientChip[] chips = getSpannable().getSpans(0, getText().length(), RecipientChip.class); 417 // Find the chip that contains this offset. 418 for (int i = 0; i < chips.length; i++) { 419 RecipientChip chip = chips[i]; 420 if (chip.matchesChip(offset)) { 421 return chip; 422 } 423 } 424 return null; 425 } 426 427 private CharSequence createChip(RecipientEntry entry) { 428 CharSequence displayText = mTokenizer.terminateToken(entry.getDestination()); 429 // Always leave a blank space at the end of a chip. 430 int textLength = displayText.length(); 431 if (displayText.charAt(textLength - 1) == ' ') { 432 textLength--; 433 } else { 434 displayText = displayText.toString().concat(" "); 435 textLength = displayText.length(); 436 } 437 SpannableString chipText = new SpannableString(displayText); 438 int end = getSelectionEnd(); 439 int start = mTokenizer.findTokenStart(getText(), end); 440 try { 441 chipText.setSpan(constructChipSpan(entry, start, false), 0, textLength, 442 Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); 443 } catch (NullPointerException e) { 444 Log.e(TAG, e.getMessage(), e); 445 return null; 446 } 447 448 return chipText; 449 } 450 451 @Override 452 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 453 submitItemAtPosition(position); 454 } 455 456 private void submitItemAtPosition(int position) { 457 RecipientEntry entry = (RecipientEntry) getAdapter().getItem(position); 458 clearComposingText(); 459 460 int end = getSelectionEnd(); 461 int start = mTokenizer.findTokenStart(getText(), end); 462 463 Editable editable = getText(); 464 editable.replace(start, end, createChip(entry)); 465 QwertyKeyListener.markAsReplaced(editable, start, end, ""); 466 } 467 468 /** Returns a collection of contact Id for each chip inside this View. */ 469 /* package */ Collection<Long> getContactIds() { 470 final Set<Long> result = new HashSet<Long>(); 471 for (RecipientChip chip : mRecipients) { 472 result.add(chip.getContactId()); 473 } 474 return result; 475 } 476 477 /** Returns a collection of data Id for each chip inside this View. May be null. */ 478 /* package */ Collection<Long> getDataIds() { 479 final Set<Long> result = new HashSet<Long>(); 480 for (RecipientChip chip : mRecipients) { 481 result.add(chip.getDataId()); 482 } 483 return result; 484 } 485 486 /** 487 * RecipientChip defines an ImageSpan that contains information relevant to 488 * a particular recipient. 489 */ 490 public class RecipientChip extends ImageSpan implements OnItemClickListener { 491 private final CharSequence mDisplay; 492 493 private final CharSequence mValue; 494 495 private final int mOffset; 496 497 private ListPopupWindow mPopup; 498 499 private View mAnchorView; 500 501 private int mLeft; 502 503 private final long mContactId; 504 505 private final long mDataId; 506 507 private RecipientEntry mEntry; 508 509 private boolean mSelected = false; 510 511 private RecipientAlternatesAdapter mAlternatesAdapter; 512 513 private Rect mBounds; 514 515 public RecipientChip(Drawable drawable, RecipientEntry entry, int offset, Rect bounds) { 516 super(drawable); 517 mDisplay = entry.getDisplayName(); 518 mValue = entry.getDestination(); 519 mContactId = entry.getContactId(); 520 mDataId = entry.getDataId(); 521 mOffset = offset; 522 mEntry = entry; 523 mBounds = bounds; 524 525 mAnchorView = new View(getContext()); 526 mAnchorView.setLeft(bounds.left); 527 mAnchorView.setRight(bounds.left); 528 mAnchorView.setTop(bounds.bottom); 529 mAnchorView.setBottom(bounds.bottom); 530 mAnchorView.setVisibility(View.GONE); 531 mRecipients.add(this); 532 } 533 534 public void unselectChip() { 535 if (getChipStart() == -1 || getChipEnd() == -1) { 536 mSelectedChip = null; 537 return; 538 } 539 clearComposingText(); 540 RecipientChip newChipSpan = null; 541 try { 542 newChipSpan = constructChipSpan(mEntry, mOffset, false); 543 } catch (NullPointerException e) { 544 Log.e(TAG, e.getMessage(), e); 545 return; 546 } 547 replace(newChipSpan); 548 if (mPopup != null && mPopup.isShowing()) { 549 mPopup.dismiss(); 550 } 551 return; 552 } 553 554 public void onKeyDown(int keyCode, KeyEvent event) { 555 if (keyCode == KeyEvent.KEYCODE_DEL) { 556 if (mPopup != null && mPopup.isShowing()) { 557 mPopup.dismiss(); 558 } 559 removeChip(); 560 } 561 } 562 563 public boolean isCompletedContact() { 564 return mContactId != -1; 565 } 566 567 private void replace(RecipientChip newChip) { 568 Spannable spannable = getSpannable(); 569 int spanStart = getChipStart(); 570 int spanEnd = getChipEnd(); 571 QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, ""); 572 spannable.removeSpan(this); 573 mRecipients.remove(this); 574 spannable.setSpan(newChip, spanStart, spanEnd, 0); 575 } 576 577 public void removeChip() { 578 Spannable spannable = getSpannable(); 579 int spanStart = getChipStart(); 580 int spanEnd = getChipEnd(); 581 Editable text = getText(); 582 int toDelete = spanEnd; 583 // Always remove trailing spaces when removing a chip. 584 while (toDelete >= 0 && toDelete < text.length() - 1 && text.charAt(toDelete) == ' ') { 585 toDelete++; 586 } 587 QwertyKeyListener.markAsReplaced(getText(), spanStart, spanEnd, ""); 588 spannable.removeSpan(this); 589 mRecipients.remove(this); 590 spannable.setSpan(null, spanStart, spanEnd, 0); 591 text.delete(spanStart, toDelete); 592 if (this == mSelectedChip) { 593 mSelectedChip = null; 594 clearSelectedChip(); 595 } 596 } 597 598 public int getChipStart() { 599 return getSpannable().getSpanStart(this); 600 } 601 602 public int getChipEnd() { 603 return getSpannable().getSpanEnd(this); 604 } 605 606 public void replaceChip(RecipientEntry entry) { 607 clearComposingText(); 608 609 RecipientChip newChipSpan = null; 610 try { 611 newChipSpan = constructChipSpan(entry, mOffset, false); 612 } catch (NullPointerException e) { 613 Log.e(TAG, e.getMessage(), e); 614 return; 615 } 616 replace(newChipSpan); 617 if (mPopup != null && mPopup.isShowing()) { 618 mPopup.dismiss(); 619 } 620 } 621 622 public RecipientChip selectChip() { 623 clearComposingText(); 624 RecipientChip newChipSpan = null; 625 if (isCompletedContact()) { 626 try { 627 newChipSpan = constructChipSpan(mEntry, mOffset, true); 628 newChipSpan.setSelected(true); 629 } catch (NullPointerException e) { 630 Log.e(TAG, e.getMessage(), e); 631 return newChipSpan; 632 } 633 replace(newChipSpan); 634 if (mPopup != null && mPopup.isShowing()) { 635 mPopup.dismiss(); 636 } 637 mSelected = true; 638 // Make sure we call edit on the new chip span. 639 newChipSpan.showAlternates(); 640 } else { 641 CharSequence text = getValue(); 642 removeChip(); 643 Editable editable = getText(); 644 setSelection(editable.length()); 645 editable.append(text); 646 } 647 return newChipSpan; 648 } 649 650 private void showAlternates() { 651 mPopup = new ListPopupWindow(RecipientEditTextView.this.getContext()); 652 653 if (!mPopup.isShowing()) { 654 mAlternatesAdapter = new RecipientAlternatesAdapter( 655 RecipientEditTextView.this.getContext(), 656 mEntry.getContactId(), mEntry.getDataId(), 657 mAlternatesLayout, mAlternatesSelectedLayout); 658 mAnchorView.setLeft(mLeft); 659 mAnchorView.setRight(mLeft); 660 mPopup.setAnchorView(mAnchorView); 661 mPopup.setAdapter(mAlternatesAdapter); 662 mPopup.setWidth(getWidth()); 663 mPopup.setOnItemClickListener(this); 664 mPopup.show(); 665 } 666 } 667 668 private void setSelected(boolean selected) { 669 mSelected = selected; 670 } 671 672 public CharSequence getDisplay() { 673 return mDisplay; 674 } 675 676 public CharSequence getValue() { 677 return mValue; 678 } 679 680 private boolean isInDelete(int offset, float x, float y) { 681 // Figure out the bounds of this chip and whether or not 682 // the user clicked in the X portion. 683 return mSelected 684 && (offset == getChipEnd() 685 || (x > (mBounds.right - mChipDeleteWidth) && x < mBounds.right)); 686 } 687 688 public boolean matchesChip(int offset) { 689 int start = getChipStart(); 690 int end = getChipEnd(); 691 return (offset >= start && offset <= end); 692 } 693 694 public void onClick(View widget, int offset, float x, float y) { 695 if (mSelected) { 696 if (isInDelete(offset, x, y)) { 697 removeChip(); 698 return; 699 } 700 } 701 } 702 703 @Override 704 public void draw(Canvas canvas, CharSequence text, int start, int end, float x, int top, 705 int y, int bottom, Paint paint) { 706 // Shift the bounds of this span to where it is actually drawn on the screeen. 707 mLeft = (int) x; 708 super.draw(canvas, text, start, end, x, top, y, bottom, paint); 709 } 710 711 @Override 712 public void onItemClick(AdapterView<?> adapterView, View view, int position, long rowId) { 713 mPopup.dismiss(); 714 clearComposingText(); 715 replaceChip(mAlternatesAdapter.getRecipientEntry(position)); 716 } 717 718 public long getContactId() { 719 return mContactId; 720 } 721 722 public long getDataId() { 723 return mDataId; 724 } 725 } 726 727 @Override 728 public boolean onActionItemClicked(ActionMode mode, MenuItem item) { 729 return false; 730 } 731 732 @Override 733 public void onDestroyActionMode(ActionMode mode) { 734 } 735 736 @Override 737 public boolean onPrepareActionMode(ActionMode mode, Menu menu) { 738 return false; 739 } 740 741 // Prevent selection of chips. 742 @Override 743 public boolean onCreateActionMode(ActionMode mode, Menu menu) { 744 return false; 745 } 746} 747 748