blob: 9cdc4233786ede924b6020c25bcf1d89407cae3b [file] [log] [blame]
Michael Kolbfe251992010-07-08 15:41:55 -07001/*
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
Bijan Amirzada41242f22014-03-21 12:12:18 -070017package com.android.browser;
Michael Kolbfe251992010-07-08 15:41:55 -070018
Michael Kolbfe251992010-07-08 15:41:55 -070019import android.content.Context;
Michael Kolb21ce4d22010-09-15 14:55:05 -070020import android.content.res.Configuration;
Narayan Kamath5119edd2011-02-23 15:49:17 +000021import android.graphics.Rect;
John Reck8d021aa2011-09-06 15:30:11 -070022import android.graphics.drawable.Drawable;
Narayan Kamathf3174a52011-11-17 14:43:32 +000023import android.text.Editable;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070024import android.text.InputFilter;
25import android.text.InputFilter.LengthFilter;
26import android.text.Spanned;
John Reckdcda1d52010-11-29 10:05:54 -080027import android.text.TextUtils;
Narayan Kamathf3174a52011-11-17 14:43:32 +000028import android.text.TextWatcher;
Michael Kolbfe251992010-07-08 15:41:55 -070029import android.util.AttributeSet;
John Reckb77bdc42011-01-24 13:24:48 -080030import android.util.Patterns;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070031import android.view.Gravity;
Michael Kolbfe251992010-07-08 15:41:55 -070032import android.view.KeyEvent;
Michael Kolb305b1c52011-06-21 16:16:22 -070033import android.view.MotionEvent;
Michael Kolbfe251992010-07-08 15:41:55 -070034import android.view.View;
Tarun Nainani244dc462014-04-03 17:39:20 -070035import android.view.inputmethod.EditorInfo;
36import android.view.inputmethod.InputConnection;
Michael Kolbfe251992010-07-08 15:41:55 -070037import android.view.inputmethod.InputMethodManager;
Tarun Nainani244dc462014-04-03 17:39:20 -070038import android.text.InputType;
John Reck87369ea2011-01-23 15:20:38 -080039import android.widget.AdapterView;
40import android.widget.AdapterView.OnItemClickListener;
Narayan Kamathf3174a52011-11-17 14:43:32 +000041import android.widget.AutoCompleteTextView;
Michael Kolbfe251992010-07-08 15:41:55 -070042import android.widget.TextView;
Michael Kolbed217742010-08-10 17:52:34 -070043import android.widget.TextView.OnEditorActionListener;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070044import android.widget.Toast;
Michael Kolbfe251992010-07-08 15:41:55 -070045
Bijan Amirzada41242f22014-03-21 12:12:18 -070046import com.android.browser.SuggestionsAdapter.CompletionListener;
47import com.android.browser.SuggestionsAdapter.SuggestItem;
48import com.android.browser.reflect.ReflectHelper;
49import com.android.browser.search.SearchEngine;
50import com.android.browser.search.SearchEngineInfo;
51import com.android.browser.search.SearchEngines;
Michael Kolb305b1c52011-06-21 16:16:22 -070052
Michael Kolbfe251992010-07-08 15:41:55 -070053/**
54 * url/search input view
55 * handling suggestions
56 */
Narayan Kamathf3174a52011-11-17 14:43:32 +000057public class UrlInputView extends AutoCompleteTextView
Michael Kolbdc2ee1b2011-02-14 14:34:40 -080058 implements OnEditorActionListener,
Narayan Kamathf3174a52011-11-17 14:43:32 +000059 CompletionListener, OnItemClickListener, TextWatcher {
Michael Kolb257cc2c2010-12-09 09:45:52 -080060
61 static final String TYPED = "browser-type";
62 static final String SUGGESTED = "browser-suggest";
63
Michael Kolb94ec5272011-08-31 16:23:57 -070064 static final int POST_DELAY = 100;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070065 static final int URL_MAX_LENGTH = 2048;
Michael Kolb94ec5272011-08-31 16:23:57 -070066
Michael Kolb305b1c52011-06-21 16:16:22 -070067 static interface StateListener {
68 static final int STATE_NORMAL = 0;
69 static final int STATE_HIGHLIGHTED = 1;
70 static final int STATE_EDITED = 2;
71
72 public void onStateChanged(int state);
73 }
74
Michael Kolbfe251992010-07-08 15:41:55 -070075 private UrlInputListener mListener;
76 private InputMethodManager mInputManager;
77 private SuggestionsAdapter mAdapter;
Michael Kolb21ce4d22010-09-15 14:55:05 -070078 private View mContainer;
79 private boolean mLandscape;
John Reckb77bdc42011-01-24 13:24:48 -080080 private boolean mIncognitoMode;
Michael Kolbdc2ee1b2011-02-14 14:34:40 -080081 private boolean mNeedsUpdate;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070082 private Context mContext;
Michael Kolbfe251992010-07-08 15:41:55 -070083
Michael Kolb305b1c52011-06-21 16:16:22 -070084 private int mState;
85 private StateListener mStateListener;
John Reck8d021aa2011-09-06 15:30:11 -070086 private Rect mPopupPadding;
Michael Kolb305b1c52011-06-21 16:16:22 -070087
Michael Kolbfe251992010-07-08 15:41:55 -070088 public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
89 super(context, attrs, defStyle);
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080090 // SWE_TODO : HARDCODED a random background - clean up
91 /*
John Reck8d021aa2011-09-06 15:30:11 -070092 TypedArray a = context.obtainStyledAttributes(
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080093 attrs, R.styleable.PopupWindow,
John Reck8d021aa2011-09-06 15:30:11 -070094 R.attr.autoCompleteTextViewStyle, 0);
95
96 Drawable popupbg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080097 a.recycle(); */
98 Drawable popupbg = context.getResources().getDrawable(android.R.drawable.editbox_background);
John Reck8d021aa2011-09-06 15:30:11 -070099 mPopupPadding = new Rect();
100 popupbg.getPadding(mPopupPadding);
Michael Kolbfe251992010-07-08 15:41:55 -0700101 init(context);
102 }
103
104 public UrlInputView(Context context, AttributeSet attrs) {
Bijan Amirzada9b1e9882014-02-26 17:15:46 -0800105 // SWE_TODO : Needs Fix
106 //this(context, attrs, R.attr.autoCompleteTextViewStyle);
107 this(context, attrs, 0);
Michael Kolbfe251992010-07-08 15:41:55 -0700108 }
109
Tarun Nainani244dc462014-04-03 17:39:20 -0700110 @Override
111 public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
112 BrowserInputConnection browserInputConnection =
113 new BrowserInputConnection(this, false);
114 outAttrs.actionLabel = null;
115 outAttrs.inputType = InputType.TYPE_NULL;
116 outAttrs.imeOptions = EditorInfo.IME_ACTION_GO;
117 return browserInputConnection;
118 }
119
Michael Kolbfe251992010-07-08 15:41:55 -0700120 public UrlInputView(Context context) {
John Reck8d021aa2011-09-06 15:30:11 -0700121 this(context, null);
Michael Kolbfe251992010-07-08 15:41:55 -0700122 }
123
124 private void init(Context ctx) {
Michael Kolbfe251992010-07-08 15:41:55 -0700125 mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
Michael Kolbed217742010-08-10 17:52:34 -0700126 setOnEditorActionListener(this);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700127 mAdapter = new SuggestionsAdapter(ctx, this);
Michael Kolbfe251992010-07-08 15:41:55 -0700128 setAdapter(mAdapter);
Michael Kolbba99c5d2010-11-29 14:57:41 -0800129 setSelectAllOnFocus(true);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700130 onConfigurationChanged(ctx.getResources().getConfiguration());
John Reck35defff2010-11-11 14:06:45 -0800131 setThreshold(1);
John Reck87369ea2011-01-23 15:20:38 -0800132 setOnItemClickListener(this);
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800133 mNeedsUpdate = false;
Narayan Kamathf3174a52011-11-17 14:43:32 +0000134 addTextChangedListener(this);
Narayan Kamath5119edd2011-02-23 15:49:17 +0000135
Michael Kolb305b1c52011-06-21 16:16:22 -0700136 mState = StateListener.STATE_NORMAL;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -0700137 mContext = ctx;
138
139 this.setFilters(new InputFilter[] {
140 new UrlLengthFilter(URL_MAX_LENGTH)
141 });
Michael Kolb305b1c52011-06-21 16:16:22 -0700142 }
143
144 protected void onFocusChanged(boolean focused, int direction, Rect prevRect) {
145 super.onFocusChanged(focused, direction, prevRect);
Michael Kolb94ec5272011-08-31 16:23:57 -0700146 int state = -1;
Michael Kolb305b1c52011-06-21 16:16:22 -0700147 if (focused) {
148 if (hasSelection()) {
Michael Kolb94ec5272011-08-31 16:23:57 -0700149 state = StateListener.STATE_HIGHLIGHTED;
Michael Kolb305b1c52011-06-21 16:16:22 -0700150 } else {
Michael Kolb94ec5272011-08-31 16:23:57 -0700151 state = StateListener.STATE_EDITED;
Michael Kolb305b1c52011-06-21 16:16:22 -0700152 }
153 } else {
154 // reset the selection state
Michael Kolb94ec5272011-08-31 16:23:57 -0700155 state = StateListener.STATE_NORMAL;
Michael Kolb305b1c52011-06-21 16:16:22 -0700156 }
Michael Kolb94ec5272011-08-31 16:23:57 -0700157 final int s = state;
158 post(new Runnable() {
159 public void run() {
160 changeState(s);
161 }
162 });
Michael Kolb305b1c52011-06-21 16:16:22 -0700163 }
164
165 @Override
166 public boolean onTouchEvent(MotionEvent evt) {
167 boolean hasSelection = hasSelection();
168 boolean res = super.onTouchEvent(evt);
169 if ((MotionEvent.ACTION_DOWN == evt.getActionMasked())
170 && hasSelection) {
Michael Kolb94ec5272011-08-31 16:23:57 -0700171 postDelayed(new Runnable() {
172 public void run() {
173 changeState(StateListener.STATE_EDITED);
174 }}, POST_DELAY);
Michael Kolb305b1c52011-06-21 16:16:22 -0700175 }
176 return res;
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800177 }
178
179 /**
180 * check if focus change requires a title bar update
181 */
182 boolean needsUpdate() {
183 return mNeedsUpdate;
184 }
185
186 /**
187 * clear the focus change needs title bar update flag
188 */
189 void clearNeedsUpdate() {
190 mNeedsUpdate = false;
Michael Kolb21ce4d22010-09-15 14:55:05 -0700191 }
192
Michael Kolbba99c5d2010-11-29 14:57:41 -0800193 void setController(UiController controller) {
194 UrlSelectionActionMode urlSelectionMode
195 = new UrlSelectionActionMode(controller);
196 setCustomSelectionActionModeCallback(urlSelectionMode);
197 }
198
Michael Kolb21ce4d22010-09-15 14:55:05 -0700199 void setContainer(View container) {
200 mContainer = container;
201 }
202
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800203 public void setUrlInputListener(UrlInputListener listener) {
204 mListener = listener;
205 }
206
Michael Kolb305b1c52011-06-21 16:16:22 -0700207 public void setStateListener(StateListener listener) {
208 mStateListener = listener;
209 // update listener
210 changeState(mState);
211 }
212
213 private void changeState(int newState) {
214 mState = newState;
215 if (mStateListener != null) {
216 mStateListener.onStateChanged(mState);
217 }
218 }
219
Michael Kolb5e8f2b92011-07-19 13:22:32 -0700220 int getState() {
221 return mState;
222 }
223
Michael Kolb21ce4d22010-09-15 14:55:05 -0700224 @Override
225 protected void onConfigurationChanged(Configuration config) {
John Reck35defff2010-11-11 14:06:45 -0800226 super.onConfigurationChanged(config);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700227 mLandscape = (config.orientation &
Michael Kolb31d469b2010-12-09 20:49:54 -0800228 Configuration.ORIENTATION_LANDSCAPE) != 0;
John Reck35defff2010-11-11 14:06:45 -0800229 mAdapter.setLandscapeMode(mLandscape);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700230 if (isPopupShowing() && (getVisibility() == View.VISIBLE)) {
John Reck35defff2010-11-11 14:06:45 -0800231 setupDropDown();
Narayan Kamathf3174a52011-11-17 14:43:32 +0000232 performFiltering(getText(), 0);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700233 }
234 }
235
236 @Override
237 public void showDropDown() {
John Reck35defff2010-11-11 14:06:45 -0800238 setupDropDown();
239 super.showDropDown();
240 }
241
242 @Override
243 public void dismissDropDown() {
244 super.dismissDropDown();
245 mAdapter.clearCache();
246 }
247
248 private void setupDropDown() {
John Reck92026732011-02-15 10:12:30 -0800249 int width = mContainer != null ? mContainer.getWidth() : getWidth();
John Reck8d021aa2011-09-06 15:30:11 -0700250 width += mPopupPadding.left + mPopupPadding.right;
Michael Kolbc5998c22010-10-10 16:04:35 -0700251 if (width != getDropDownWidth()) {
252 setDropDownWidth(width);
253 }
John Reck8d021aa2011-09-06 15:30:11 -0700254 int left = getLeft();
255 left += mPopupPadding.left;
256 if (left != -getDropDownHorizontalOffset()) {
257 setDropDownHorizontalOffset(-left);
Michael Kolbc5998c22010-10-10 16:04:35 -0700258 }
Michael Kolb513286f2010-09-09 12:55:12 -0700259 }
260
261 @Override
Michael Kolbed217742010-08-10 17:52:34 -0700262 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Narayan Kamathf3174a52011-11-17 14:43:32 +0000263 finishInput(getText().toString(), null, TYPED);
Michael Kolbed217742010-08-10 17:52:34 -0700264 return true;
265 }
Michael Kolb21ce4d22010-09-15 14:55:05 -0700266
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800267 void forceFilter() {
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800268 showDropDown();
Michael Kolba2b2ba82010-08-04 17:54:03 -0700269 }
270
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800271 void hideIME() {
272 mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
273 }
274
Michael Kolb4bb6fcb2012-04-13 14:25:27 -0700275 void showIME() {
Bijan Amirzada9b1e9882014-02-26 17:15:46 -0800276 //mInputManager.focusIn(this);
277 Object[] params = {this};
278 Class[] type = new Class[] {View.class};
279 ReflectHelper.invokeMethod(mInputManager, "focusIn", type, params);
Michael Kolb4bb6fcb2012-04-13 14:25:27 -0700280 mInputManager.showSoftInput(this, 0);
281 }
282
Michael Kolb257cc2c2010-12-09 09:45:52 -0800283 private void finishInput(String url, String extra, String source) {
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800284 mNeedsUpdate = true;
285 dismissDropDown();
Michael Kolbfe251992010-07-08 15:41:55 -0700286 mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
John Reckdcda1d52010-11-29 10:05:54 -0800287 if (TextUtils.isEmpty(url)) {
Michael Kolbfe251992010-07-08 15:41:55 -0700288 mListener.onDismiss();
289 } else {
John Reckb77bdc42011-01-24 13:24:48 -0800290 if (mIncognitoMode && isSearch(url)) {
291 // To prevent logging, intercept this request
292 // TODO: This is a quick hack, refactor this
293 SearchEngine searchEngine = BrowserSettings.getInstance()
294 .getSearchEngine();
295 if (searchEngine == null) return;
296 SearchEngineInfo engineInfo = SearchEngines
Bijan Amirzada9b1e9882014-02-26 17:15:46 -0800297 .getSearchEngineInfo(getContext(), searchEngine.getName());
John Reckb77bdc42011-01-24 13:24:48 -0800298 if (engineInfo == null) return;
299 url = engineInfo.getSearchUriForQuery(url);
300 // mLister.onAction can take it from here without logging
301 }
Michael Kolb257cc2c2010-12-09 09:45:52 -0800302 mListener.onAction(url, extra, source);
Michael Kolbfe251992010-07-08 15:41:55 -0700303 }
Michael Kolbfe251992010-07-08 15:41:55 -0700304 }
305
John Reckb77bdc42011-01-24 13:24:48 -0800306 boolean isSearch(String inUrl) {
307 String url = UrlUtils.fixUrl(inUrl).trim();
308 if (TextUtils.isEmpty(url)) return false;
309
310 if (Patterns.WEB_URL.matcher(url).matches()
311 || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
312 return false;
313 }
314 return true;
315 }
316
Michael Kolb21ce4d22010-09-15 14:55:05 -0700317 // Completion Listener
318
319 @Override
320 public void onSearch(String search) {
Michael Kolb7cdc4902011-02-03 17:54:40 -0800321 mListener.onCopySuggestion(search);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700322 }
323
324 @Override
Michael Kolbbd2dd642011-01-13 13:01:30 -0800325 public void onSelect(String url, int type, String extra) {
Michael Kolb5ff5c8b2012-05-03 11:37:58 -0700326 finishInput(url, extra, SUGGESTED);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700327 }
328
Michael Kolbfe251992010-07-08 15:41:55 -0700329 @Override
John Reck87369ea2011-01-23 15:20:38 -0800330 public void onItemClick(
331 AdapterView<?> parent, View view, int position, long id) {
332 SuggestItem item = mAdapter.getItem(position);
Narayan Kamath5119edd2011-02-23 15:49:17 +0000333 onSelect(SuggestionsAdapter.getSuggestionUrl(item), item.type, item.extra);
John Reck87369ea2011-01-23 15:20:38 -0800334 }
335
Michael Kolbfe251992010-07-08 15:41:55 -0700336 interface UrlInputListener {
Michael Kolb21ce4d22010-09-15 14:55:05 -0700337
Michael Kolbfe251992010-07-08 15:41:55 -0700338 public void onDismiss();
Michael Kolb21ce4d22010-09-15 14:55:05 -0700339
Michael Kolb257cc2c2010-12-09 09:45:52 -0800340 public void onAction(String text, String extra, String source);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700341
Michael Kolb7cdc4902011-02-03 17:54:40 -0800342 public void onCopySuggestion(String text);
Michael Kolbfe251992010-07-08 15:41:55 -0700343
344 }
345
John Reck117f07d2011-01-24 09:39:03 -0800346 public void setIncognitoMode(boolean incognito) {
John Reckb77bdc42011-01-24 13:24:48 -0800347 mIncognitoMode = incognito;
348 mAdapter.setIncognitoMode(mIncognitoMode);
John Reck117f07d2011-01-24 09:39:03 -0800349 }
350
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800351 @Override
352 public boolean onKeyDown(int keyCode, KeyEvent evt) {
353 if (keyCode == KeyEvent.KEYCODE_ESCAPE && !isInTouchMode()) {
354 finishInput(null, null, null);
355 return true;
356 }
357 return super.onKeyDown(keyCode, evt);
358 }
359
Narayan Kamath80aad8d2011-02-23 12:01:13 +0000360 public SuggestionsAdapter getAdapter() {
361 return mAdapter;
362 }
Narayan Kamath5119edd2011-02-23 15:49:17 +0000363
Michael Kolb74e60c22011-04-30 16:43:47 -0700364 /*
365 * no-op to prevent scrolling of webview when embedded titlebar
366 * gets edited
367 */
368 @Override
369 public boolean requestRectangleOnScreen(Rect rect, boolean immediate) {
370 return false;
371 }
Michael Kolb305b1c52011-06-21 16:16:22 -0700372
373 @Override
Narayan Kamathf3174a52011-11-17 14:43:32 +0000374 public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
375
376 @Override
377 public void onTextChanged(CharSequence s, int start, int before, int count) {
Michael Kolb305b1c52011-06-21 16:16:22 -0700378 if (StateListener.STATE_HIGHLIGHTED == mState) {
379 changeState(StateListener.STATE_EDITED);
380 }
381 }
382
Narayan Kamathf3174a52011-11-17 14:43:32 +0000383 @Override
384 public void afterTextChanged(Editable s) { }
385
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -0700386 /**
387 * It will prompt the toast if the text length greater than the given length.
388 */
389 private class UrlLengthFilter extends LengthFilter {
390 public UrlLengthFilter(int max) {
391 super(max);
392 }
393
394 @Override
395 public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
396 int dstart, int dend) {
397 CharSequence result = super.filter(source, start, end, dest, dstart, dend);
398 if (result != null) {
399 // If the result is not null, it means the text length is greater than
400 // the given length. We will prompt the toast to alert the user.
401 CharSequence alert = getResources().getString(R.string.max_url_character_limit_msg);
402 Toast t = Toast.makeText(mContext , alert, Toast.LENGTH_SHORT);
403 t.setGravity(Gravity.CENTER, 0, 0);
404 t.show();
405 }
406 return result;
407 }
408 }
409
Michael Kolbfe251992010-07-08 15:41:55 -0700410}