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