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