• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2010 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 
17 package com.android.browser;
18 
19 import android.content.Context;
20 import android.content.res.Configuration;
21 import android.content.res.TypedArray;
22 import android.database.DataSetObserver;
23 import android.graphics.Rect;
24 import android.graphics.drawable.Drawable;
25 import android.text.TextUtils;
26 import android.util.AttributeSet;
27 import android.util.Patterns;
28 import android.view.KeyEvent;
29 import android.view.MotionEvent;
30 import android.view.View;
31 import android.view.inputmethod.EditorInfo;
32 import android.view.inputmethod.InputMethodManager;
33 import android.widget.AdapterView;
34 import android.widget.AdapterView.OnItemClickListener;
35 import android.widget.TextView;
36 import android.widget.TextView.OnEditorActionListener;
37 
38 import com.android.browser.SuggestionsAdapter.CompletionListener;
39 import com.android.browser.SuggestionsAdapter.SuggestItem;
40 import com.android.browser.UI.DropdownChangeListener;
41 import com.android.browser.autocomplete.SuggestedTextController.TextChangeWatcher;
42 import com.android.browser.autocomplete.SuggestiveAutoCompleteTextView;
43 import com.android.browser.search.SearchEngine;
44 import com.android.browser.search.SearchEngineInfo;
45 import com.android.browser.search.SearchEngines;
46 import com.android.internal.R;
47 
48 import java.util.List;
49 
50 /**
51  * url/search input view
52  * handling suggestions
53  */
54 public class UrlInputView extends SuggestiveAutoCompleteTextView
55         implements OnEditorActionListener,
56         CompletionListener, OnItemClickListener, TextChangeWatcher {
57 
58     static final String TYPED = "browser-type";
59     static final String SUGGESTED = "browser-suggest";
60     static final String VOICE = "voice-search";
61 
62     static final int POST_DELAY = 100;
63 
64     static interface StateListener {
65         static final int STATE_NORMAL = 0;
66         static final int STATE_HIGHLIGHTED = 1;
67         static final int STATE_EDITED = 2;
68 
onStateChanged(int state)69         public void onStateChanged(int state);
70     }
71 
72     private UrlInputListener   mListener;
73     private InputMethodManager mInputManager;
74     private SuggestionsAdapter mAdapter;
75     private View mContainer;
76     private boolean mLandscape;
77     private boolean mIncognitoMode;
78     private boolean mNeedsUpdate;
79     private DropdownChangeListener mDropdownListener;
80 
81     private int mState;
82     private StateListener mStateListener;
83     private Rect mPopupPadding;
84 
UrlInputView(Context context, AttributeSet attrs, int defStyle)85     public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
86         super(context, attrs, defStyle);
87         TypedArray a = context.obtainStyledAttributes(
88                 attrs, com.android.internal.R.styleable.PopupWindow,
89                 R.attr.autoCompleteTextViewStyle, 0);
90 
91         Drawable popupbg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
92         a.recycle();
93         mPopupPadding = new Rect();
94         popupbg.getPadding(mPopupPadding);
95         init(context);
96     }
97 
UrlInputView(Context context, AttributeSet attrs)98     public UrlInputView(Context context, AttributeSet attrs) {
99         this(context, attrs, R.attr.autoCompleteTextViewStyle);
100     }
101 
UrlInputView(Context context)102     public UrlInputView(Context context) {
103         this(context, null);
104     }
105 
init(Context ctx)106     private void init(Context ctx) {
107         mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
108         setOnEditorActionListener(this);
109         mAdapter = new SuggestionsAdapter(ctx, this);
110         setAdapter(mAdapter);
111         setSelectAllOnFocus(true);
112         onConfigurationChanged(ctx.getResources().getConfiguration());
113         setThreshold(1);
114         setOnItemClickListener(this);
115         mNeedsUpdate = false;
116         mDropdownListener = null;
117         addQueryTextWatcher(this);
118 
119         mAdapter.registerDataSetObserver(new DataSetObserver() {
120             @Override
121             public void onChanged() {
122                 if (!isPopupShowing()) {
123                     return;
124                 }
125                 dispatchChange();
126             }
127 
128             @Override
129             public void onInvalidated() {
130                 dispatchChange();
131             }
132         });
133         mState = StateListener.STATE_NORMAL;
134     }
135 
onFocusChanged(boolean focused, int direction, Rect prevRect)136     protected void onFocusChanged(boolean focused, int direction, Rect prevRect) {
137         super.onFocusChanged(focused, direction, prevRect);
138         int state = -1;
139         if (focused) {
140             if (hasSelection()) {
141                 state = StateListener.STATE_HIGHLIGHTED;
142             } else {
143                 state = StateListener.STATE_EDITED;
144             }
145         } else {
146             // reset the selection state
147             state = StateListener.STATE_NORMAL;
148         }
149         final int s = state;
150         post(new Runnable() {
151             public void run() {
152                 changeState(s);
153             }
154         });
155     }
156 
157     @Override
onTouchEvent(MotionEvent evt)158     public boolean onTouchEvent(MotionEvent evt) {
159         boolean hasSelection = hasSelection();
160         boolean res = super.onTouchEvent(evt);
161         if ((MotionEvent.ACTION_DOWN == evt.getActionMasked())
162               && hasSelection) {
163             postDelayed(new Runnable() {
164                 public void run() {
165                     changeState(StateListener.STATE_EDITED);
166                 }}, POST_DELAY);
167         }
168         return res;
169     }
170 
171     /**
172      * check if focus change requires a title bar update
173      */
needsUpdate()174     boolean needsUpdate() {
175         return mNeedsUpdate;
176     }
177 
178     /**
179      * clear the focus change needs title bar update flag
180      */
clearNeedsUpdate()181     void clearNeedsUpdate() {
182         mNeedsUpdate = false;
183     }
184 
setController(UiController controller)185     void setController(UiController controller) {
186         UrlSelectionActionMode urlSelectionMode
187                 = new UrlSelectionActionMode(controller);
188         setCustomSelectionActionModeCallback(urlSelectionMode);
189     }
190 
setContainer(View container)191     void setContainer(View container) {
192         mContainer = container;
193     }
194 
setUrlInputListener(UrlInputListener listener)195     public void setUrlInputListener(UrlInputListener listener) {
196         mListener = listener;
197     }
198 
setStateListener(StateListener listener)199     public void setStateListener(StateListener listener) {
200         mStateListener = listener;
201         // update listener
202         changeState(mState);
203     }
204 
changeState(int newState)205     private void changeState(int newState) {
206         mState = newState;
207         if (mStateListener != null) {
208             mStateListener.onStateChanged(mState);
209         }
210     }
211 
getState()212     int getState() {
213         return mState;
214     }
215 
setVoiceResults(List<String> voiceResults)216     void setVoiceResults(List<String> voiceResults) {
217         mAdapter.setVoiceResults(voiceResults);
218     }
219 
220     @Override
onConfigurationChanged(Configuration config)221     protected void onConfigurationChanged(Configuration config) {
222         super.onConfigurationChanged(config);
223         mLandscape = (config.orientation &
224                 Configuration.ORIENTATION_LANDSCAPE) != 0;
225         mAdapter.setLandscapeMode(mLandscape);
226         if (isPopupShowing() && (getVisibility() == View.VISIBLE)) {
227             setupDropDown();
228             performFiltering(getUserText(), 0);
229         }
230     }
231 
232     @Override
showDropDown()233     public void showDropDown() {
234         setupDropDown();
235         super.showDropDown();
236     }
237 
238     @Override
dismissDropDown()239     public void dismissDropDown() {
240         super.dismissDropDown();
241         mAdapter.clearCache();
242     }
243 
setupDropDown()244     private void setupDropDown() {
245         int width = mContainer != null ? mContainer.getWidth() : getWidth();
246         width += mPopupPadding.left + mPopupPadding.right;
247         if (width != getDropDownWidth()) {
248             setDropDownWidth(width);
249         }
250         int left = getLeft();
251         left += mPopupPadding.left;
252         if (left != -getDropDownHorizontalOffset()) {
253             setDropDownHorizontalOffset(-left);
254         }
255     }
256 
257     @Override
onEditorAction(TextView v, int actionId, KeyEvent event)258     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
259         if (BrowserSettings.getInstance().useInstantSearch() &&
260                 (actionId == EditorInfo.IME_ACTION_NEXT)) {
261             // When instant is turned on AND the user chooses to complete
262             // using the tab key, then use the completion rather than the
263             // text that the user has typed.
264             finishInput(getText().toString(), null, TYPED);
265         } else {
266             finishInput(getUserText(), null, TYPED);
267         }
268 
269         return true;
270     }
271 
forceFilter()272     void forceFilter() {
273         performForcedFiltering();
274         showDropDown();
275     }
276 
forceIme()277     void forceIme() {
278         mInputManager.focusIn(this);
279         mInputManager.showSoftInput(this, 0);
280     }
281 
hideIME()282     void hideIME() {
283         mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
284     }
285 
finishInput(String url, String extra, String source)286     private void finishInput(String url, String extra, String source) {
287         mNeedsUpdate = true;
288         dismissDropDown();
289         mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
290         if (TextUtils.isEmpty(url)) {
291             mListener.onDismiss();
292         } else {
293             if (mIncognitoMode && isSearch(url)) {
294                 // To prevent logging, intercept this request
295                 // TODO: This is a quick hack, refactor this
296                 SearchEngine searchEngine = BrowserSettings.getInstance()
297                         .getSearchEngine();
298                 if (searchEngine == null) return;
299                 SearchEngineInfo engineInfo = SearchEngines
300                         .getSearchEngineInfo(mContext, searchEngine.getName());
301                 if (engineInfo == null) return;
302                 url = engineInfo.getSearchUriForQuery(url);
303                 // mLister.onAction can take it from here without logging
304             }
305             mListener.onAction(url, extra, source);
306         }
307     }
308 
isSearch(String inUrl)309     boolean isSearch(String inUrl) {
310         String url = UrlUtils.fixUrl(inUrl).trim();
311         if (TextUtils.isEmpty(url)) return false;
312 
313         if (Patterns.WEB_URL.matcher(url).matches()
314                 || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
315             return false;
316         }
317         return true;
318     }
319 
320     // Completion Listener
321 
322     @Override
onSearch(String search)323     public void onSearch(String search) {
324         mListener.onCopySuggestion(search);
325     }
326 
327     @Override
onSelect(String url, int type, String extra)328     public void onSelect(String url, int type, String extra) {
329         finishInput(url, extra, (type == SuggestionsAdapter.TYPE_VOICE_SEARCH)
330                 ? VOICE : SUGGESTED);
331     }
332 
333     @Override
onItemClick( AdapterView<?> parent, View view, int position, long id)334     public void onItemClick(
335             AdapterView<?> parent, View view, int position, long id) {
336         SuggestItem item = mAdapter.getItem(position);
337         onSelect(SuggestionsAdapter.getSuggestionUrl(item), item.type, item.extra);
338     }
339 
340     interface UrlInputListener {
341 
onDismiss()342         public void onDismiss();
343 
onAction(String text, String extra, String source)344         public void onAction(String text, String extra, String source);
345 
onCopySuggestion(String text)346         public void onCopySuggestion(String text);
347 
348     }
349 
setIncognitoMode(boolean incognito)350     public void setIncognitoMode(boolean incognito) {
351         mIncognitoMode = incognito;
352         mAdapter.setIncognitoMode(mIncognitoMode);
353     }
354 
355     @Override
onKeyDown(int keyCode, KeyEvent evt)356     public boolean onKeyDown(int keyCode, KeyEvent evt) {
357         if (keyCode == KeyEvent.KEYCODE_ESCAPE && !isInTouchMode()) {
358             finishInput(null, null, null);
359             return true;
360         }
361         return super.onKeyDown(keyCode, evt);
362     }
363 
getAdapter()364     public SuggestionsAdapter getAdapter() {
365         return mAdapter;
366     }
367 
dispatchChange()368     private void dispatchChange() {
369         final Rect popupRect = new Rect();
370         getPopupDrawableRect(popupRect);
371 
372         if (mDropdownListener != null) {
373             mDropdownListener.onNewDropdownDimensions(popupRect.height());
374         }
375     }
376 
registerDropdownChangeListener(DropdownChangeListener d)377     void registerDropdownChangeListener(DropdownChangeListener d) {
378         mDropdownListener = d;
379     }
380 
381     /*
382      * no-op to prevent scrolling of webview when embedded titlebar
383      * gets edited
384      */
385     @Override
requestRectangleOnScreen(Rect rect, boolean immediate)386     public boolean requestRectangleOnScreen(Rect rect, boolean immediate) {
387         return false;
388     }
389 
390     @Override
onTextChanged(String newText)391     public void onTextChanged(String newText) {
392         if (StateListener.STATE_HIGHLIGHTED == mState) {
393             changeState(StateListener.STATE_EDITED);
394         }
395     }
396 
397 }
398