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