1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.shell.omnibox; 6 7 import android.content.Context; 8 import android.os.Handler; 9 import android.text.Editable; 10 import android.text.TextUtils; 11 import android.text.TextWatcher; 12 import android.view.View; 13 import android.view.View.OnLayoutChangeListener; 14 import android.widget.AdapterView; 15 import android.widget.AdapterView.OnItemClickListener; 16 import android.widget.ListPopupWindow; 17 import android.widget.PopupWindow.OnDismissListener; 18 import android.widget.TextView; 19 20 import org.chromium.chrome.browser.omnibox.AutocompleteController; 21 import org.chromium.chrome.browser.omnibox.AutocompleteController.OnSuggestionsReceivedListener; 22 import org.chromium.chrome.browser.omnibox.OmniboxSuggestion; 23 import org.chromium.chrome.shell.ChromeShellToolbar; 24 import org.chromium.chrome.shell.R; 25 26 import java.util.List; 27 28 /** 29 * Displays suggestions for the text that is entered to the ChromeShell URL field. 30 */ 31 public class SuggestionPopup implements OnSuggestionsReceivedListener, TextWatcher { 32 private static final long SUGGESTION_START_DELAY_MS = 30; 33 34 private final Context mContext; 35 private final TextView mUrlField; 36 private final ChromeShellToolbar mToolbar; 37 private final AutocompleteController mAutocomplete; 38 39 private boolean mHasStartedNewOmniboxEditSession; 40 private Runnable mRequestSuggestions; 41 private ListPopupWindow mSuggestionsPopup; 42 private SuggestionArrayAdapter mSuggestionArrayAdapter; 43 44 /** 45 * Initializes a suggestion popup that will track urlField value and display suggestions based 46 * on that value. 47 */ SuggestionPopup(Context context, TextView urlField, ChromeShellToolbar toolbar)48 public SuggestionPopup(Context context, TextView urlField, 49 ChromeShellToolbar toolbar) { 50 mContext = context; 51 mUrlField = urlField; 52 mToolbar = toolbar; 53 mAutocomplete = new AutocompleteController(this); 54 OnLayoutChangeListener listener = new OnLayoutChangeListener() { 55 @Override 56 public void onLayoutChange(View v, int left, int top, int right, int bottom, 57 int oldLeft, int oldTop, int oldRight, int oldBottom) { 58 if (mSuggestionsPopup == null || !mSuggestionsPopup.isShowing()) return; 59 mSuggestionsPopup.setWidth(mUrlField.getWidth()); 60 mSuggestionsPopup.show(); 61 } 62 }; 63 mUrlField.addOnLayoutChangeListener(listener); 64 } 65 navigateToSuggestion(int position)66 private void navigateToSuggestion(int position) { 67 mToolbar.getCurrentTab().loadUrlWithSanitization( 68 mSuggestionArrayAdapter.getItem(position).getUrl()); 69 mUrlField.clearFocus(); 70 mToolbar.setKeyboardVisibilityForUrl(false); 71 mToolbar.getCurrentTab().getView().requestFocus(); 72 dismissPopup(); 73 } 74 dismissPopup()75 public void dismissPopup() { 76 if (mSuggestionsPopup != null) { 77 mSuggestionsPopup.dismiss(); 78 mSuggestionsPopup = null; 79 } 80 } 81 82 /** 83 * Stops the autocomplete controller and closes the suggestion popup. 84 */ hideSuggestions()85 public void hideSuggestions() { 86 stopAutocomplete(true); 87 dismissPopup(); 88 } 89 90 /** 91 * Signals the autocomplete controller to stop generating suggestions and 92 * cancels the queued task to start the autocomplete controller, if any. 93 * 94 * @param clear Whether to clear the most recent autocomplete results. 95 */ stopAutocomplete(boolean clear)96 private void stopAutocomplete(boolean clear) { 97 if (mAutocomplete != null) mAutocomplete.stop(clear); 98 if (mRequestSuggestions != null) mRequestSuggestions = null; 99 } 100 101 // OnSuggestionsReceivedListener implementation 102 103 @Override onSuggestionsReceived(List<OmniboxSuggestion> suggestions, String inlineAutocompleteText)104 public void onSuggestionsReceived(List<OmniboxSuggestion> suggestions, 105 String inlineAutocompleteText) { 106 if (!mUrlField.isFocused() || suggestions.isEmpty()) 107 return; 108 if (mSuggestionsPopup == null) { 109 mSuggestionsPopup = new ListPopupWindow( 110 mContext, null, android.R.attr.autoCompleteTextViewStyle); 111 mSuggestionsPopup.setOnDismissListener(new OnDismissListener() { 112 @Override 113 public void onDismiss() { 114 mHasStartedNewOmniboxEditSession = false; 115 mSuggestionArrayAdapter = null; 116 } 117 }); 118 } 119 mSuggestionsPopup.setWidth(mUrlField.getWidth()); 120 mSuggestionArrayAdapter = 121 new SuggestionArrayAdapter(mContext, R.layout.dropdown_item, suggestions); 122 mSuggestionsPopup.setAdapter(mSuggestionArrayAdapter); 123 mSuggestionsPopup.setAnchorView(mUrlField); 124 mSuggestionsPopup.setOnItemClickListener(new OnItemClickListener() { 125 @Override 126 public void onItemClick(AdapterView<?> parent, View view, int position, long id) { 127 navigateToSuggestion(position); 128 } 129 }); 130 mSuggestionsPopup.show(); 131 } 132 133 // TextWatcher implementation 134 135 @Override afterTextChanged(final Editable editableText)136 public void afterTextChanged(final Editable editableText) { 137 if (!mUrlField.hasFocus()) return; 138 if (!mHasStartedNewOmniboxEditSession) { 139 mAutocomplete.resetSession(); 140 mHasStartedNewOmniboxEditSession = true; 141 } 142 143 stopAutocomplete(false); 144 if (TextUtils.isEmpty(editableText)) { 145 dismissPopup(); 146 } else { 147 assert mRequestSuggestions == null : "Multiple omnibox requests in flight."; 148 mRequestSuggestions = new Runnable() { 149 @Override 150 public void run() { 151 mRequestSuggestions = null; 152 mAutocomplete.start( 153 mToolbar.getCurrentTab().getProfile(), 154 mToolbar.getCurrentTab().getUrl(), 155 editableText.toString(), false); 156 } 157 }; 158 new Handler().postDelayed(mRequestSuggestions, SUGGESTION_START_DELAY_MS); 159 } 160 } 161 162 @Override beforeTextChanged(CharSequence s, int start, int count, int after)163 public void beforeTextChanged(CharSequence s, int start, int count, int after) { 164 mRequestSuggestions = null; 165 } 166 167 @Override onTextChanged(CharSequence s, int start, int before, int count)168 public void onTextChanged(CharSequence s, int start, int before, int count) { 169 } 170 } 171