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 
UrlInputView(Context context, AttributeSet attrs, int defStyle)80     public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
81         super(context, attrs, defStyle);
82         init(context);
83     }
84 
UrlInputView(Context context, AttributeSet attrs)85     public UrlInputView(Context context, AttributeSet attrs) {
86         this(context, attrs, R.attr.autoCompleteTextViewStyle);
87     }
88 
UrlInputView(Context context)89     public UrlInputView(Context context) {
90         this(context, null);
91     }
92 
init(Context ctx)93     private void init(Context ctx) {
94         mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
95         setOnEditorActionListener(this);
96         mAdapter = new SuggestionsAdapter(ctx, this);
97         setAdapter(mAdapter);
98         setSelectAllOnFocus(true);
99         onConfigurationChanged(ctx.getResources().getConfiguration());
100         setThreshold(1);
101         setOnItemClickListener(this);
102         mNeedsUpdate = false;
103         addTextChangedListener(this);
104         setDropDownAnchor(com.android.browser.R.id.taburlbar);
105         mState = StateListener.STATE_NORMAL;
106     }
107 
onFocusChanged(boolean focused, int direction, Rect prevRect)108     protected void onFocusChanged(boolean focused, int direction, Rect prevRect) {
109         super.onFocusChanged(focused, direction, prevRect);
110         int state = -1;
111         if (focused) {
112             if (hasSelection()) {
113                 state = StateListener.STATE_HIGHLIGHTED;
114             } else {
115                 state = StateListener.STATE_EDITED;
116             }
117         } else {
118             // reset the selection state
119             state = StateListener.STATE_NORMAL;
120         }
121         final int s = state;
122         post(new Runnable() {
123             public void run() {
124                 changeState(s);
125             }
126         });
127     }
128 
129     @Override
onTouchEvent(MotionEvent evt)130     public boolean onTouchEvent(MotionEvent evt) {
131         boolean hasSelection = hasSelection();
132         boolean res = super.onTouchEvent(evt);
133         if ((MotionEvent.ACTION_DOWN == evt.getActionMasked())
134               && hasSelection) {
135             postDelayed(new Runnable() {
136                 public void run() {
137                     changeState(StateListener.STATE_EDITED);
138                 }}, POST_DELAY);
139         }
140         return res;
141     }
142 
143     /**
144      * check if focus change requires a title bar update
145      */
needsUpdate()146     boolean needsUpdate() {
147         return mNeedsUpdate;
148     }
149 
150     /**
151      * clear the focus change needs title bar update flag
152      */
clearNeedsUpdate()153     void clearNeedsUpdate() {
154         mNeedsUpdate = false;
155     }
156 
setController(UiController controller)157     void setController(UiController controller) {
158         UrlSelectionActionMode urlSelectionMode
159                 = new UrlSelectionActionMode(controller);
160         setCustomSelectionActionModeCallback(urlSelectionMode);
161     }
162 
setContainer(View container)163     void setContainer(View container) {
164         mContainer = container;
165     }
166 
setUrlInputListener(UrlInputListener listener)167     public void setUrlInputListener(UrlInputListener listener) {
168         mListener = listener;
169     }
170 
setStateListener(StateListener listener)171     public void setStateListener(StateListener listener) {
172         mStateListener = listener;
173         // update listener
174         changeState(mState);
175     }
176 
changeState(int newState)177     private void changeState(int newState) {
178         mState = newState;
179         if (mStateListener != null) {
180             mStateListener.onStateChanged(mState);
181         }
182     }
183 
getState()184     int getState() {
185         return mState;
186     }
187 
188     @Override
onConfigurationChanged(Configuration config)189     protected void onConfigurationChanged(Configuration config) {
190         super.onConfigurationChanged(config);
191         mLandscape = (config.orientation &
192                 Configuration.ORIENTATION_LANDSCAPE) != 0;
193         mAdapter.setLandscapeMode(mLandscape);
194         if (isPopupShowing() && (getVisibility() == View.VISIBLE)) {
195             dismissDropDown();
196             showDropDown();
197             performFiltering(getText(), 0);
198         }
199     }
200 
201     @Override
dismissDropDown()202     public void dismissDropDown() {
203         super.dismissDropDown();
204         mAdapter.clearCache();
205     }
206 
207     @Override
onEditorAction(TextView v, int actionId, KeyEvent event)208     public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
209         finishInput(getText().toString(), null, TYPED);
210         return true;
211     }
212 
forceFilter()213     void forceFilter() {
214         showDropDown();
215     }
216 
hideIME()217     void hideIME() {
218         mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
219     }
220 
showIME()221     void showIME() {
222         mInputManager.focusIn(this);
223         mInputManager.showSoftInput(this, 0);
224     }
225 
finishInput(String url, String extra, String source)226     private void finishInput(String url, String extra, String source) {
227         mNeedsUpdate = true;
228         dismissDropDown();
229         mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
230         if (TextUtils.isEmpty(url)) {
231             mListener.onDismiss();
232         } else {
233             if (mIncognitoMode && isSearch(url)) {
234                 // To prevent logging, intercept this request
235                 // TODO: This is a quick hack, refactor this
236                 SearchEngine searchEngine = BrowserSettings.getInstance()
237                         .getSearchEngine();
238                 if (searchEngine == null) return;
239                 SearchEngineInfo engineInfo = SearchEngines
240                         .getSearchEngineInfo(mContext, searchEngine.getName());
241                 if (engineInfo == null) return;
242                 url = engineInfo.getSearchUriForQuery(url);
243                 // mLister.onAction can take it from here without logging
244             }
245             mListener.onAction(url, extra, source);
246         }
247     }
248 
isSearch(String inUrl)249     boolean isSearch(String inUrl) {
250         String url = UrlUtils.fixUrl(inUrl).trim();
251         if (TextUtils.isEmpty(url)) return false;
252 
253         if (Patterns.WEB_URL.matcher(url).matches()
254                 || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
255             return false;
256         }
257         return true;
258     }
259 
260     // Completion Listener
261 
262     @Override
onSearch(String search)263     public void onSearch(String search) {
264         mListener.onCopySuggestion(search);
265     }
266 
267     @Override
onSelect(String url, int type, String extra)268     public void onSelect(String url, int type, String extra) {
269         finishInput(url, extra, SUGGESTED);
270     }
271 
272     @Override
onItemClick( AdapterView<?> parent, View view, int position, long id)273     public void onItemClick(
274             AdapterView<?> parent, View view, int position, long id) {
275         SuggestItem item = mAdapter.getItem(position);
276         onSelect(SuggestionsAdapter.getSuggestionUrl(item), item.type, item.extra);
277     }
278 
279     interface UrlInputListener {
280 
onDismiss()281         public void onDismiss();
282 
onAction(String text, String extra, String source)283         public void onAction(String text, String extra, String source);
284 
onCopySuggestion(String text)285         public void onCopySuggestion(String text);
286 
287     }
288 
setIncognitoMode(boolean incognito)289     public void setIncognitoMode(boolean incognito) {
290         mIncognitoMode = incognito;
291         mAdapter.setIncognitoMode(mIncognitoMode);
292     }
293 
294     @Override
onKeyDown(int keyCode, KeyEvent evt)295     public boolean onKeyDown(int keyCode, KeyEvent evt) {
296         if (keyCode == KeyEvent.KEYCODE_ESCAPE && !isInTouchMode()) {
297             finishInput(null, null, null);
298             return true;
299         }
300         return super.onKeyDown(keyCode, evt);
301     }
302 
getAdapter()303     public SuggestionsAdapter getAdapter() {
304         return mAdapter;
305     }
306 
307     /*
308      * no-op to prevent scrolling of webview when embedded titlebar
309      * gets edited
310      */
311     @Override
requestRectangleOnScreen(Rect rect, boolean immediate)312     public boolean requestRectangleOnScreen(Rect rect, boolean immediate) {
313         return false;
314     }
315 
316     @Override
beforeTextChanged(CharSequence s, int start, int count, int after)317     public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
318 
319     @Override
onTextChanged(CharSequence s, int start, int before, int count)320     public void onTextChanged(CharSequence s, int start, int before, int count) {
321         if (StateListener.STATE_HIGHLIGHTED == mState) {
322             changeState(StateListener.STATE_EDITED);
323         }
324     }
325 
326     @Override
afterTextChanged(Editable s)327     public void afterTextChanged(Editable s) { }
328 
329 }
330