• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 package com.android.browser.autocomplete;
17 
18 import com.android.browser.BrowserSettings;
19 import com.android.browser.SuggestionsAdapter;
20 import com.android.browser.SuggestionsAdapter.SuggestItem;
21 import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
22 import com.android.internal.R;
23 
24 import android.content.Context;
25 import android.content.res.TypedArray;
26 import android.database.DataSetObserver;
27 import android.graphics.Rect;
28 import android.os.Parcelable;
29 import android.text.Editable;
30 import android.text.Html;
31 import android.text.Selection;
32 import android.text.TextUtils;
33 import android.util.AttributeSet;
34 import android.util.Log;
35 import android.view.AbsSavedState;
36 import android.view.KeyEvent;
37 import android.view.LayoutInflater;
38 import android.view.View;
39 import android.view.ViewGroup;
40 import android.view.WindowManager;
41 import android.view.inputmethod.CompletionInfo;
42 import android.view.inputmethod.EditorInfo;
43 import android.view.inputmethod.InputMethodManager;
44 import android.widget.AdapterView;
45 import android.widget.EditText;
46 import android.widget.Filter;
47 import android.widget.Filterable;
48 import android.widget.ListAdapter;
49 import android.widget.ListPopupWindow;
50 import android.widget.TextView;
51 
52 
53 /**
54  * This is a stripped down version of the framework AutoCompleteTextView
55  * class with added support for displaying completions in-place. Note that
56  * this cannot be implemented as a subclass of the above without making
57  * substantial changes to it and its descendants.
58  *
59  * @see android.widget.AutoCompleteTextView
60  */
61 public class SuggestiveAutoCompleteTextView extends EditText implements Filter.FilterListener {
62     private static final boolean DEBUG = false;
63     private static final String TAG = "SuggestiveAutoCompleteTextView";
64 
65     private CharSequence mHintText;
66     private TextView mHintView;
67     private int mHintResource;
68 
69     private SuggestionsAdapter mAdapter;
70     private Filter mFilter;
71     private int mThreshold;
72 
73     private ListPopupWindow mPopup;
74     private int mDropDownAnchorId;
75 
76     private AdapterView.OnItemClickListener mItemClickListener;
77 
78     private boolean mDropDownDismissedOnCompletion = true;
79 
80     private int mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
81 
82     // Set to true when text is set directly and no filtering shall be performed
83     private boolean mBlockCompletion;
84 
85     // When set, an update in the underlying adapter will update the result list popup.
86     // Set to false when the list is hidden to prevent asynchronous updates to popup the list again.
87     private boolean mPopupCanBeUpdated = true;
88 
89     private PassThroughClickListener mPassThroughClickListener;
90     private PopupDataSetObserver mObserver;
91     private SuggestedTextController mController;
92 
SuggestiveAutoCompleteTextView(Context context)93     public SuggestiveAutoCompleteTextView(Context context) {
94         this(context, null);
95     }
96 
SuggestiveAutoCompleteTextView(Context context, AttributeSet attrs)97     public SuggestiveAutoCompleteTextView(Context context, AttributeSet attrs) {
98         this(context, attrs, R.attr.autoCompleteTextViewStyle);
99     }
100 
SuggestiveAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle)101     public SuggestiveAutoCompleteTextView(Context context, AttributeSet attrs, int defStyle) {
102         super(context, attrs, defStyle);
103 
104         // The completions are always shown in the same color as the hint
105         // text.
106         mController = new SuggestedTextController(this, getHintTextColors().getDefaultColor());
107         mPopup = new ListPopupWindow(context, attrs,
108                  R.attr.autoCompleteTextViewStyle);
109         mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE);
110         mPopup.setPromptPosition(ListPopupWindow.POSITION_PROMPT_BELOW);
111 
112         TypedArray a = context.obtainStyledAttributes(
113                 attrs, R.styleable.AutoCompleteTextView, defStyle, 0);
114 
115         mThreshold = a.getInt(
116                 R.styleable.AutoCompleteTextView_completionThreshold, 2);
117 
118         mPopup.setListSelector(a.getDrawable(R.styleable.AutoCompleteTextView_dropDownSelector));
119         mPopup.setVerticalOffset((int)
120                 a.getDimension(R.styleable.AutoCompleteTextView_dropDownVerticalOffset, 0.0f));
121         mPopup.setHorizontalOffset((int)
122                 a.getDimension(R.styleable.AutoCompleteTextView_dropDownHorizontalOffset, 0.0f));
123 
124         // Get the anchor's id now, but the view won't be ready, so wait to actually get the
125         // view and store it in mDropDownAnchorView lazily in getDropDownAnchorView later.
126         // Defaults to NO_ID, in which case the getDropDownAnchorView method will simply return
127         // this TextView, as a default anchoring point.
128         mDropDownAnchorId = a.getResourceId(
129                 R.styleable.AutoCompleteTextView_dropDownAnchor, View.NO_ID);
130 
131         // For dropdown width, the developer can specify a specific width, or MATCH_PARENT
132         // (for full screen width) or WRAP_CONTENT (to match the width of the anchored view).
133         mPopup.setWidth(a.getLayoutDimension(
134                 R.styleable.AutoCompleteTextView_dropDownWidth,
135                 ViewGroup.LayoutParams.WRAP_CONTENT));
136         mPopup.setHeight(a.getLayoutDimension(
137                 R.styleable.AutoCompleteTextView_dropDownHeight,
138                 ViewGroup.LayoutParams.WRAP_CONTENT));
139 
140         mHintResource = a.getResourceId(R.styleable.AutoCompleteTextView_completionHintView,
141                 R.layout.simple_dropdown_hint);
142 
143         mPopup.setOnItemClickListener(new DropDownItemClickListener());
144         setCompletionHint(a.getText(R.styleable.AutoCompleteTextView_completionHint));
145 
146         // Always turn on the auto complete input type flag, since it
147         // makes no sense to use this widget without it.
148         int inputType = getInputType();
149         if ((inputType&EditorInfo.TYPE_MASK_CLASS)
150                 == EditorInfo.TYPE_CLASS_TEXT) {
151             inputType |= EditorInfo.TYPE_TEXT_FLAG_AUTO_COMPLETE;
152             setRawInputType(inputType);
153         }
154 
155         a.recycle();
156 
157         setFocusable(true);
158 
159         mController.addUserTextChangeWatcher(new MyWatcher());
160 
161         mPassThroughClickListener = new PassThroughClickListener();
162         super.setOnClickListener(mPassThroughClickListener);
163     }
164 
165     @Override
setOnClickListener(OnClickListener listener)166     public void setOnClickListener(OnClickListener listener) {
167         mPassThroughClickListener.mWrapped = listener;
168     }
169 
170     /**
171      * Private hook into the on click event, dispatched from {@link PassThroughClickListener}
172      */
onClickImpl()173     private void onClickImpl() {
174         // If the dropdown is showing, bring the keyboard to the front
175         // when the user touches the text field.
176         if (isPopupShowing()) {
177             ensureImeVisible(true);
178         }
179     }
180 
181     /**
182      * <p>Sets the optional hint text that is displayed at the bottom of the
183      * the matching list.  This can be used as a cue to the user on how to
184      * best use the list, or to provide extra information.</p>
185      *
186      * @param hint the text to be displayed to the user
187      *
188      * @attr ref android.R.styleable#AutoCompleteTextView_completionHint
189      */
setCompletionHint(CharSequence hint)190     private void setCompletionHint(CharSequence hint) {
191         mHintText = hint;
192         if (hint != null) {
193             if (mHintView == null) {
194                 final TextView hintView = (TextView) LayoutInflater.from(getContext()).inflate(
195                         mHintResource, null).findViewById(R.id.text1);
196                 hintView.setText(mHintText);
197                 mHintView = hintView;
198                 mPopup.setPromptView(hintView);
199             } else {
200                 mHintView.setText(hint);
201             }
202         } else {
203             mPopup.setPromptView(null);
204             mHintView = null;
205         }
206     }
207 
getDropDownWidth()208     protected int getDropDownWidth() {
209         return mPopup.getWidth();
210     }
211 
setDropDownWidth(int width)212     public void setDropDownWidth(int width) {
213         mPopup.setWidth(width);
214     }
215 
setDropDownVerticalOffset(int offset)216     protected void setDropDownVerticalOffset(int offset) {
217         mPopup.setVerticalOffset(offset);
218     }
219 
setDropDownHorizontalOffset(int offset)220     public void setDropDownHorizontalOffset(int offset) {
221         mPopup.setHorizontalOffset(offset);
222     }
223 
getDropDownHorizontalOffset()224     protected int getDropDownHorizontalOffset() {
225         return mPopup.getHorizontalOffset();
226     }
227 
setThreshold(int threshold)228     public void setThreshold(int threshold) {
229         if (threshold <= 0) {
230             threshold = 1;
231         }
232 
233         mThreshold = threshold;
234     }
235 
setOnItemClickListener(AdapterView.OnItemClickListener l)236     protected void setOnItemClickListener(AdapterView.OnItemClickListener l) {
237         mItemClickListener = l;
238     }
239 
setAdapter(SuggestionsAdapter adapter)240     public void setAdapter(SuggestionsAdapter adapter) {
241         if (mObserver == null) {
242             mObserver = new PopupDataSetObserver();
243         } else if (mAdapter != null) {
244             mAdapter.unregisterDataSetObserver(mObserver);
245         }
246         mAdapter = adapter;
247         if (mAdapter != null) {
248             mFilter = mAdapter.getFilter();
249             adapter.registerDataSetObserver(mObserver);
250         } else {
251             mFilter = null;
252         }
253 
254         mPopup.setAdapter(mAdapter);
255     }
256 
257     @Override
onKeyPreIme(int keyCode, KeyEvent event)258     public boolean onKeyPreIme(int keyCode, KeyEvent event) {
259         if (keyCode == KeyEvent.KEYCODE_BACK && isPopupShowing()
260                 && !mPopup.isDropDownAlwaysVisible()) {
261             // special case for the back key, we do not even try to send it
262             // to the drop down list but instead, consume it immediately
263             if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
264                 KeyEvent.DispatcherState state = getKeyDispatcherState();
265                 if (state != null) {
266                     state.startTracking(event, this);
267                 }
268                 return true;
269             } else if (event.getAction() == KeyEvent.ACTION_UP) {
270                 KeyEvent.DispatcherState state = getKeyDispatcherState();
271                 if (state != null) {
272                     state.handleUpEvent(event);
273                 }
274                 if (event.isTracking() && !event.isCanceled()) {
275                     dismissDropDown();
276                     return true;
277                 }
278             }
279         }
280         return super.onKeyPreIme(keyCode, event);
281     }
282 
283     @Override
onKeyUp(int keyCode, KeyEvent event)284     public boolean onKeyUp(int keyCode, KeyEvent event) {
285         boolean consumed = mPopup.onKeyUp(keyCode, event);
286         if (consumed) {
287             switch (keyCode) {
288             // if the list accepts the key events and the key event
289             // was a click, the text view gets the selected item
290             // from the drop down as its content
291             case KeyEvent.KEYCODE_ENTER:
292             case KeyEvent.KEYCODE_DPAD_CENTER:
293             case KeyEvent.KEYCODE_TAB:
294                 if (event.hasNoModifiers()) {
295                     performCompletion();
296                 }
297                 return true;
298             }
299         }
300 
301         if (isPopupShowing() && keyCode == KeyEvent.KEYCODE_TAB && event.hasNoModifiers()) {
302             performCompletion();
303             return true;
304         }
305 
306         return super.onKeyUp(keyCode, event);
307     }
308 
309     @Override
onKeyDown(int keyCode, KeyEvent event)310     public boolean onKeyDown(int keyCode, KeyEvent event) {
311         if (mPopup.onKeyDown(keyCode, event)) {
312             return true;
313         }
314 
315         if (!isPopupShowing()) {
316             switch(keyCode) {
317             case KeyEvent.KEYCODE_DPAD_DOWN:
318                 if (event.hasNoModifiers()) {
319                     performValidation();
320                 }
321             }
322         }
323 
324         if (isPopupShowing() && keyCode == KeyEvent.KEYCODE_TAB && event.hasNoModifiers()) {
325             return true;
326         }
327 
328         mLastKeyCode = keyCode;
329         boolean handled = super.onKeyDown(keyCode, event);
330         mLastKeyCode = KeyEvent.KEYCODE_UNKNOWN;
331 
332         if (handled && isPopupShowing()) {
333             clearListSelection();
334         }
335 
336         return handled;
337     }
338 
339     /**
340      * Returns <code>true</code> if the amount of text in the field meets
341      * or exceeds the {@link #getThreshold} requirement.  You can override
342      * this to impose a different standard for when filtering will be
343      * triggered.
344      */
enoughToFilter()345     private boolean enoughToFilter() {
346         if (DEBUG) Log.v(TAG, "Enough to filter: len=" + getUserText().length()
347                 + " threshold=" + mThreshold);
348         return getUserText().length() >= mThreshold;
349     }
350 
351     /**
352      * This is used to watch for edits to the text view.  Note that we call
353      * to methods on the auto complete text view class so that we can access
354      * private vars without going through thunks.
355      */
356     private class MyWatcher implements TextChangeWatcher {
357         @Override
onTextChanged(String newText)358         public void onTextChanged(String newText) {
359             doAfterTextChanged();
360         }
361     }
362 
363     /**
364      * @hide
365      */
setBlockCompletion(boolean block)366     protected void setBlockCompletion(boolean block) {
367         mBlockCompletion = block;
368     }
369 
doAfterTextChanged()370     void doAfterTextChanged() {
371         if (DEBUG) Log.d(TAG, "doAfterTextChanged(" + getText() + ")");
372         if (mBlockCompletion) return;
373 
374         // the drop down is shown only when a minimum number of characters
375         // was typed in the text view
376         if (enoughToFilter()) {
377             if (mFilter != null) {
378                 mPopupCanBeUpdated = true;
379                 performFiltering(getUserText(), mLastKeyCode);
380                 buildImeCompletions();
381             }
382         } else {
383             // drop down is automatically dismissed when enough characters
384             // are deleted from the text view
385             if (!mPopup.isDropDownAlwaysVisible()) {
386                 dismissDropDown();
387             }
388             if (mFilter != null) {
389                 performFiltering(null, mLastKeyCode);
390             }
391         }
392     }
393 
394     /**
395      * <p>Indicates whether the popup menu is showing.</p>
396      *
397      * @return true if the popup menu is showing, false otherwise
398      */
isPopupShowing()399     public boolean isPopupShowing() {
400         return mPopup.isShowing();
401     }
402 
403     /**
404      * <p>Converts the selected item from the drop down list into a sequence
405      * of character that can be used in the edit box.</p>
406      *
407      * @param selectedItem the item selected by the user for completion
408      *
409      * @return a sequence of characters representing the selected suggestion
410      */
convertSelectionToString(Object selectedItem)411     protected CharSequence convertSelectionToString(Object selectedItem) {
412         return mFilter.convertResultToString(selectedItem);
413     }
414 
415     /**
416      * <p>Clear the list selection.  This may only be temporary, as user input will often bring
417      * it back.
418      */
clearListSelection()419     private void clearListSelection() {
420         mPopup.clearListSelection();
421     }
422 
423     /**
424      * <p>Starts filtering the content of the drop down list. The filtering
425      * pattern is the content of the edit box. Subclasses should override this
426      * method to filter with a different pattern, for instance a substring of
427      * <code>text</code>.</p>
428      *
429      * @param text the filtering pattern
430      * @param keyCode the last character inserted in the edit box; beware that
431      * this will be null when text is being added through a soft input method.
432      */
433     @SuppressWarnings({ "UnusedDeclaration" })
performFiltering(CharSequence text, int keyCode)434     protected void performFiltering(CharSequence text, int keyCode) {
435         if (DEBUG) Log.d(TAG, "performFiltering(" + text + ")");
436 
437         mFilter.filter(text, this);
438     }
439 
performForcedFiltering()440     protected void performForcedFiltering() {
441         boolean wasSuspended = false;
442         if (mController.isCursorHandlingSuspended()) {
443             mController.resumeCursorMovementHandlingAndApplyChanges();
444             wasSuspended = true;
445         }
446 
447         mFilter.filter(getUserText().toString(), this);
448 
449         if (wasSuspended) {
450             mController.suspendCursorMovementHandling();
451         }
452     }
453 
454     /**
455      * <p>Performs the text completion by converting the selected item from
456      * the drop down list into a string, replacing the text box's content with
457      * this string and finally dismissing the drop down menu.</p>
458      */
performCompletion()459     private void performCompletion() {
460         performCompletion(null, -1, -1);
461     }
462 
463     @Override
onCommitCompletion(CompletionInfo completion)464     public void onCommitCompletion(CompletionInfo completion) {
465         if (isPopupShowing()) {
466             mBlockCompletion = true;
467             replaceText(completion.getText());
468             mBlockCompletion = false;
469 
470             mPopup.performItemClick(completion.getPosition());
471         }
472     }
473 
performCompletion(View selectedView, int position, long id)474     private void performCompletion(View selectedView, int position, long id) {
475         if (isPopupShowing()) {
476             Object selectedItem;
477             if (position < 0) {
478                 selectedItem = mPopup.getSelectedItem();
479             } else {
480                 selectedItem = mAdapter.getItem(position);
481             }
482             if (selectedItem == null) {
483                 Log.w(TAG, "performCompletion: no selected item");
484                 return;
485             }
486 
487             mBlockCompletion = true;
488             replaceText(convertSelectionToString(selectedItem));
489             mBlockCompletion = false;
490 
491             if (mItemClickListener != null) {
492                 final ListPopupWindow list = mPopup;
493 
494                 if (selectedView == null || position < 0) {
495                     selectedView = list.getSelectedView();
496                     position = list.getSelectedItemPosition();
497                     id = list.getSelectedItemId();
498                 }
499                 mItemClickListener.onItemClick(list.getListView(), selectedView, position, id);
500             }
501         }
502 
503         if (mDropDownDismissedOnCompletion && !mPopup.isDropDownAlwaysVisible()) {
504             dismissDropDown();
505         }
506     }
507 
508     /**
509      * <p>Performs the text completion by replacing the current text by the
510      * selected item. Subclasses should override this method to avoid replacing
511      * the whole content of the edit box.</p>
512      *
513      * @param text the selected suggestion in the drop down list
514      */
replaceText(CharSequence text)515     protected void replaceText(CharSequence text) {
516         clearComposingText();
517 
518         setText(text);
519         // make sure we keep the caret at the end of the text view
520         Editable spannable = getText();
521         Selection.setSelection(spannable, spannable.length());
522     }
523 
524     /** {@inheritDoc} */
525     @Override
onFilterComplete(int count)526     public void onFilterComplete(int count) {
527         updateDropDownForFilter(count);
528     }
529 
updateDropDownForFilter(int count)530     private void updateDropDownForFilter(int count) {
531         // Not attached to window, don't update drop-down
532         if (getWindowVisibility() == View.GONE) return;
533 
534         /*
535          * This checks enoughToFilter() again because filtering requests
536          * are asynchronous, so the result may come back after enough text
537          * has since been deleted to make it no longer appropriate
538          * to filter.
539          */
540 
541         final boolean dropDownAlwaysVisible = mPopup.isDropDownAlwaysVisible();
542         if ((count > 0 || dropDownAlwaysVisible) && enoughToFilter() &&
543                 getUserText().length() > 0) {
544             if (hasFocus() && hasWindowFocus() && mPopupCanBeUpdated) {
545                 showDropDown();
546             }
547         } else if (!dropDownAlwaysVisible && isPopupShowing()) {
548             dismissDropDown();
549             // When the filter text is changed, the first update from the adapter may show an empty
550             // count (when the query is being performed on the network). Future updates when some
551             // content has been retrieved should still be able to update the list.
552             mPopupCanBeUpdated = true;
553         }
554     }
555 
556     @Override
onWindowFocusChanged(boolean hasWindowFocus)557     public void onWindowFocusChanged(boolean hasWindowFocus) {
558         super.onWindowFocusChanged(hasWindowFocus);
559         if (!hasWindowFocus && !mPopup.isDropDownAlwaysVisible()) {
560             dismissDropDown();
561         }
562     }
563 
564     @Override
onDisplayHint(int hint)565     protected void onDisplayHint(int hint) {
566         super.onDisplayHint(hint);
567         switch (hint) {
568             case INVISIBLE:
569                 if (!mPopup.isDropDownAlwaysVisible()) {
570                     dismissDropDown();
571                 }
572                 break;
573         }
574     }
575 
576     @Override
onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect)577     protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
578         // TextView makes several cursor movements when gaining focus, and this interferes with
579         // the suggested vs user entered text. Tell the controller to temporarily ignore cursor
580         // movements while this is going on.
581         mController.suspendCursorMovementHandling();
582 
583         super.onFocusChanged(focused, direction, previouslyFocusedRect);
584         // Perform validation if the view is losing focus.
585         if (!focused) {
586             performValidation();
587         }
588         if (!focused && !mPopup.isDropDownAlwaysVisible()) {
589             dismissDropDown();
590         }
591 
592         mController.resumeCursorMovementHandlingAndApplyChanges();
593     }
594 
595     @Override
onAttachedToWindow()596     protected void onAttachedToWindow() {
597         super.onAttachedToWindow();
598     }
599 
600     @Override
onDetachedFromWindow()601     protected void onDetachedFromWindow() {
602         dismissDropDown();
603         super.onDetachedFromWindow();
604     }
605 
606     /**
607      * <p>Closes the drop down if present on screen.</p>
608      */
dismissDropDown()609     protected void dismissDropDown() {
610         InputMethodManager imm = InputMethodManager.peekInstance();
611         if (imm != null) {
612             imm.displayCompletions(this, null);
613         }
614         mPopup.dismiss();
615         mPopupCanBeUpdated = false;
616     }
617 
618     @Override
setFrame(final int l, int t, final int r, int b)619     protected boolean setFrame(final int l, int t, final int r, int b) {
620         boolean result = super.setFrame(l, t, r, b);
621 
622         if (isPopupShowing()) {
623             showDropDown();
624         }
625 
626         return result;
627     }
628 
629     /**
630      * Ensures that the drop down is not obscuring the IME.
631      * @param visible whether the ime should be in front. If false, the ime is pushed to
632      * the background.
633      * @hide internal used only here and SearchDialog
634      */
ensureImeVisible(boolean visible)635     private void ensureImeVisible(boolean visible) {
636         mPopup.setInputMethodMode(visible
637                 ? ListPopupWindow.INPUT_METHOD_NEEDED : ListPopupWindow.INPUT_METHOD_NOT_NEEDED);
638         showDropDown();
639     }
640 
641     /**
642      * <p>Displays the drop down on screen.</p>
643      */
showDropDown()644     protected void showDropDown() {
645         if (mPopup.getAnchorView() == null) {
646             if (mDropDownAnchorId != View.NO_ID) {
647                 mPopup.setAnchorView(getRootView().findViewById(mDropDownAnchorId));
648             } else {
649                 mPopup.setAnchorView(this);
650             }
651         }
652         if (!isPopupShowing()) {
653             // Make sure the list does not obscure the IME when shown for the first time.
654             mPopup.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);
655         }
656         mPopup.show();
657     }
658 
buildImeCompletions()659     private void buildImeCompletions() {
660         final ListAdapter adapter = mAdapter;
661         if (adapter != null) {
662             InputMethodManager imm = InputMethodManager.peekInstance();
663             if (imm != null) {
664                 final int count = Math.min(adapter.getCount(), 20);
665                 CompletionInfo[] completions = new CompletionInfo[count];
666                 int realCount = 0;
667 
668                 for (int i = 0; i < count; i++) {
669                     if (adapter.isEnabled(i)) {
670                         realCount++;
671                         Object item = adapter.getItem(i);
672                         long id = adapter.getItemId(i);
673                         completions[i] = new CompletionInfo(id, i,
674                                 convertSelectionToString(item));
675                     }
676                 }
677 
678                 if (realCount != count) {
679                     CompletionInfo[] tmp = new CompletionInfo[realCount];
680                     System.arraycopy(completions, 0, tmp, 0, realCount);
681                     completions = tmp;
682                 }
683 
684                 imm.displayCompletions(this, completions);
685             }
686         }
687     }
688 
performValidation()689     private void performValidation() {
690     }
691 
692     /**
693      * Returns the Filter obtained from {@link Filterable#getFilter},
694      * or <code>null</code> if {@link #setAdapter} was not called with
695      * a Filterable.
696      */
getFilter()697     protected Filter getFilter() {
698         return mFilter;
699     }
700 
701     private class DropDownItemClickListener implements AdapterView.OnItemClickListener {
702         @Override
onItemClick(AdapterView parent, View v, int position, long id)703         public void onItemClick(AdapterView parent, View v, int position, long id) {
704             performCompletion(v, position, id);
705         }
706     }
707 
708     /**
709      * Allows us a private hook into the on click event without preventing users from setting
710      * their own click listener.
711      */
712     private class PassThroughClickListener implements OnClickListener {
713 
714         private View.OnClickListener mWrapped;
715 
716         /** {@inheritDoc} */
717         @Override
onClick(View v)718         public void onClick(View v) {
719             onClickImpl();
720 
721             if (mWrapped != null) mWrapped.onClick(v);
722         }
723     }
724 
725     private class PopupDataSetObserver extends DataSetObserver {
726         @Override
onChanged()727         public void onChanged() {
728             if (mAdapter != null) {
729                 // If the popup is not showing already, showing it will cause
730                 // the list of data set observers attached to the adapter to
731                 // change. We can't do it from here, because we are in the middle
732                 // of iterating through the list of observers.
733                 post(new Runnable() {
734                     @Override
735                     public void run() {
736                         final SuggestionsAdapter adapter = mAdapter;
737                         if (adapter != null) {
738                             // This will re-layout, thus resetting mDataChanged, so that the
739                             // listView click listener stays responsive
740                             updateDropDownForFilter(adapter.getCount());
741                         }
742 
743                         updateText(adapter);
744                     }
745                 });
746             }
747         }
748     }
749 
getUserText()750     public String getUserText() {
751         return mController.getUserText();
752     }
753 
updateText(SuggestionsAdapter adapter)754     private void updateText(SuggestionsAdapter adapter) {
755         if (!BrowserSettings.getInstance().useInstantSearch()) {
756             return;
757         }
758 
759         if (!isPopupShowing()) {
760             setSuggestedText(null);
761             return;
762         }
763 
764         if (mAdapter.getCount() > 0 && !TextUtils.isEmpty(getUserText())) {
765             for (int i = 0; i < mAdapter.getCount(); ++i) {
766                 SuggestItem current = mAdapter.getItem(i);
767                 if (current.type == SuggestionsAdapter.TYPE_SUGGEST) {
768                     setSuggestedText(current.title);
769                     break;
770                 }
771             }
772         }
773     }
774 
775     @Override
setText(CharSequence text, BufferType type)776     public void setText(CharSequence text, BufferType type) {
777         Editable buffer = getEditableText();
778         if (text == null) text = "";
779         // if we already have a buffer, we must not replace it with a new one as this will break
780         // mController. Write the new text into the existing buffer instead.
781         if (buffer == null) {
782             super.setText(text, type);
783         } else {
784             buffer.replace(0, buffer.length(), text);
785             invalidate();
786         }
787     }
788 
setText(CharSequence text, boolean filter)789     public void setText(CharSequence text, boolean filter) {
790         if (filter) {
791             setText(text);
792         } else {
793             mBlockCompletion = true;
794             // If cursor movement handling was suspended (the view is
795             // not in focus), resume it and apply the pending change.
796             // Since we don't want to perform any filtering, this change
797             // is safe.
798             boolean wasSuspended = false;
799             if (mController.isCursorHandlingSuspended()) {
800                 mController.resumeCursorMovementHandlingAndApplyChanges();
801                 wasSuspended = true;
802             }
803 
804             setText(text);
805 
806             if (wasSuspended) {
807                 mController.suspendCursorMovementHandling();
808             }
809             mBlockCompletion = false;
810         }
811     }
812 
813     @Override
onSaveInstanceState()814     public Parcelable onSaveInstanceState() {
815         Parcelable superState = super.onSaveInstanceState();
816         if (superState instanceof TextView.SavedState) {
817             // get rid of TextView's saved state, we override it.
818             superState = ((TextView.SavedState) superState).getSuperState();
819         }
820         if (superState == null) {
821             superState = AbsSavedState.EMPTY_STATE;
822         }
823         return mController.saveInstanceState(superState);
824     }
825 
826     @Override
onRestoreInstanceState(Parcelable state)827     public void onRestoreInstanceState(Parcelable state) {
828         super.onRestoreInstanceState(mController.restoreInstanceState(state));
829     }
830 
addQueryTextWatcher(final SuggestedTextController.TextChangeWatcher watcher)831     public void addQueryTextWatcher(final SuggestedTextController.TextChangeWatcher watcher) {
832         mController.addUserTextChangeWatcher(watcher);
833     }
834 
setSuggestedText(String text)835     public void setSuggestedText(String text) {
836         if (!TextUtils.isEmpty(text)) {
837             String htmlStripped = Html.fromHtml(text).toString();
838             mController.setSuggestedText(htmlStripped);
839         } else {
840             mController.setSuggestedText(null);
841         }
842     }
843 
getPopupDrawableRect(Rect rect)844     public void getPopupDrawableRect(Rect rect) {
845         if (mPopup.getListView() != null) {
846             mPopup.getListView().getDrawingRect(rect);
847         }
848     }
849 }
850