blob: 0ba05ef95d127f00c1dcb78f55daa8dc4e095f99 [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;
Michael Kolbfe251992010-07-08 15:41:55 -070035import android.view.inputmethod.InputMethodManager;
John Reck87369ea2011-01-23 15:20:38 -080036import android.widget.AdapterView;
37import android.widget.AdapterView.OnItemClickListener;
Narayan Kamathf3174a52011-11-17 14:43:32 +000038import android.widget.AutoCompleteTextView;
Michael Kolbfe251992010-07-08 15:41:55 -070039import android.widget.TextView;
Michael Kolbed217742010-08-10 17:52:34 -070040import android.widget.TextView.OnEditorActionListener;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070041import android.widget.Toast;
Michael Kolbfe251992010-07-08 15:41:55 -070042
Bijan Amirzada41242f22014-03-21 12:12:18 -070043import com.android.browser.SuggestionsAdapter.CompletionListener;
44import com.android.browser.SuggestionsAdapter.SuggestItem;
45import com.android.browser.reflect.ReflectHelper;
46import com.android.browser.search.SearchEngine;
47import com.android.browser.search.SearchEngineInfo;
48import com.android.browser.search.SearchEngines;
Michael Kolb305b1c52011-06-21 16:16:22 -070049
Michael Kolbfe251992010-07-08 15:41:55 -070050/**
51 * url/search input view
52 * handling suggestions
53 */
Narayan Kamathf3174a52011-11-17 14:43:32 +000054public class UrlInputView extends AutoCompleteTextView
Michael Kolbdc2ee1b2011-02-14 14:34:40 -080055 implements OnEditorActionListener,
Narayan Kamathf3174a52011-11-17 14:43:32 +000056 CompletionListener, OnItemClickListener, TextWatcher {
Michael Kolb257cc2c2010-12-09 09:45:52 -080057
58 static final String TYPED = "browser-type";
59 static final String SUGGESTED = "browser-suggest";
Tarun Nainanid5306732014-04-28 19:38:28 -070060 static final String LATIN_INPUTMETHOD_PACKAGE_NAME = "com.android.inputmethod.latin";
Michael Kolb257cc2c2010-12-09 09:45:52 -080061
Michael Kolb94ec5272011-08-31 16:23:57 -070062 static final int POST_DELAY = 100;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070063 static final int URL_MAX_LENGTH = 2048;
Michael Kolb94ec5272011-08-31 16:23:57 -070064
Michael Kolb305b1c52011-06-21 16:16:22 -070065 static interface StateListener {
66 static final int STATE_NORMAL = 0;
67 static final int STATE_HIGHLIGHTED = 1;
68 static final int STATE_EDITED = 2;
69
70 public void onStateChanged(int state);
71 }
72
Michael Kolbfe251992010-07-08 15:41:55 -070073 private UrlInputListener mListener;
74 private InputMethodManager mInputManager;
75 private SuggestionsAdapter mAdapter;
Michael Kolb21ce4d22010-09-15 14:55:05 -070076 private View mContainer;
77 private boolean mLandscape;
John Reckb77bdc42011-01-24 13:24:48 -080078 private boolean mIncognitoMode;
Michael Kolbdc2ee1b2011-02-14 14:34:40 -080079 private boolean mNeedsUpdate;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -070080 private Context mContext;
Michael Kolbfe251992010-07-08 15:41:55 -070081
Michael Kolb305b1c52011-06-21 16:16:22 -070082 private int mState;
83 private StateListener mStateListener;
John Reck8d021aa2011-09-06 15:30:11 -070084 private Rect mPopupPadding;
Michael Kolb305b1c52011-06-21 16:16:22 -070085
Michael Kolbfe251992010-07-08 15:41:55 -070086 public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
87 super(context, attrs, defStyle);
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080088 // SWE_TODO : HARDCODED a random background - clean up
89 /*
John Reck8d021aa2011-09-06 15:30:11 -070090 TypedArray a = context.obtainStyledAttributes(
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080091 attrs, R.styleable.PopupWindow,
John Reck8d021aa2011-09-06 15:30:11 -070092 R.attr.autoCompleteTextViewStyle, 0);
93
94 Drawable popupbg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
Bijan Amirzada9b1e9882014-02-26 17:15:46 -080095 a.recycle(); */
96 Drawable popupbg = context.getResources().getDrawable(android.R.drawable.editbox_background);
John Reck8d021aa2011-09-06 15:30:11 -070097 mPopupPadding = new Rect();
98 popupbg.getPadding(mPopupPadding);
Michael Kolbfe251992010-07-08 15:41:55 -070099 init(context);
100 }
101
102 public UrlInputView(Context context, AttributeSet attrs) {
Bijan Amirzada9b1e9882014-02-26 17:15:46 -0800103 // SWE_TODO : Needs Fix
104 //this(context, attrs, R.attr.autoCompleteTextViewStyle);
105 this(context, attrs, 0);
Michael Kolbfe251992010-07-08 15:41:55 -0700106 }
107
108 public UrlInputView(Context context) {
John Reck8d021aa2011-09-06 15:30:11 -0700109 this(context, null);
Michael Kolbfe251992010-07-08 15:41:55 -0700110 }
111
112 private void init(Context ctx) {
Michael Kolbfe251992010-07-08 15:41:55 -0700113 mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
Michael Kolbed217742010-08-10 17:52:34 -0700114 setOnEditorActionListener(this);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700115 mAdapter = new SuggestionsAdapter(ctx, this);
Michael Kolbfe251992010-07-08 15:41:55 -0700116 setAdapter(mAdapter);
Michael Kolbba99c5d2010-11-29 14:57:41 -0800117 setSelectAllOnFocus(true);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700118 onConfigurationChanged(ctx.getResources().getConfiguration());
John Reck35defff2010-11-11 14:06:45 -0800119 setThreshold(1);
John Reck87369ea2011-01-23 15:20:38 -0800120 setOnItemClickListener(this);
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800121 mNeedsUpdate = false;
Narayan Kamathf3174a52011-11-17 14:43:32 +0000122 addTextChangedListener(this);
Narayan Kamath5119edd2011-02-23 15:49:17 +0000123
Michael Kolb305b1c52011-06-21 16:16:22 -0700124 mState = StateListener.STATE_NORMAL;
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -0700125 mContext = ctx;
126
127 this.setFilters(new InputFilter[] {
128 new UrlLengthFilter(URL_MAX_LENGTH)
129 });
Michael Kolb305b1c52011-06-21 16:16:22 -0700130 }
131
132 protected void onFocusChanged(boolean focused, int direction, Rect prevRect) {
133 super.onFocusChanged(focused, direction, prevRect);
Michael Kolb94ec5272011-08-31 16:23:57 -0700134 int state = -1;
Michael Kolb305b1c52011-06-21 16:16:22 -0700135 if (focused) {
136 if (hasSelection()) {
Michael Kolb94ec5272011-08-31 16:23:57 -0700137 state = StateListener.STATE_HIGHLIGHTED;
Michael Kolb305b1c52011-06-21 16:16:22 -0700138 } else {
Michael Kolb94ec5272011-08-31 16:23:57 -0700139 state = StateListener.STATE_EDITED;
Michael Kolb305b1c52011-06-21 16:16:22 -0700140 }
141 } else {
142 // reset the selection state
Michael Kolb94ec5272011-08-31 16:23:57 -0700143 state = StateListener.STATE_NORMAL;
Michael Kolb305b1c52011-06-21 16:16:22 -0700144 }
Michael Kolb94ec5272011-08-31 16:23:57 -0700145 final int s = state;
Vivek Sekhar8ee3abb2014-07-14 12:32:05 -0700146 post(new Runnable() {
Michael Kolb94ec5272011-08-31 16:23:57 -0700147 public void run() {
148 changeState(s);
149 }
Vivek Sekhar8ee3abb2014-07-14 12:32:05 -0700150 });
Michael Kolb305b1c52011-06-21 16:16:22 -0700151 }
152
153 @Override
154 public boolean onTouchEvent(MotionEvent evt) {
155 boolean hasSelection = hasSelection();
156 boolean res = super.onTouchEvent(evt);
157 if ((MotionEvent.ACTION_DOWN == evt.getActionMasked())
158 && hasSelection) {
Michael Kolb94ec5272011-08-31 16:23:57 -0700159 postDelayed(new Runnable() {
160 public void run() {
161 changeState(StateListener.STATE_EDITED);
162 }}, POST_DELAY);
Michael Kolb305b1c52011-06-21 16:16:22 -0700163 }
164 return res;
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800165 }
166
167 /**
168 * check if focus change requires a title bar update
169 */
170 boolean needsUpdate() {
171 return mNeedsUpdate;
172 }
173
174 /**
175 * clear the focus change needs title bar update flag
176 */
177 void clearNeedsUpdate() {
178 mNeedsUpdate = false;
Michael Kolb21ce4d22010-09-15 14:55:05 -0700179 }
180
Michael Kolbba99c5d2010-11-29 14:57:41 -0800181 void setController(UiController controller) {
182 UrlSelectionActionMode urlSelectionMode
183 = new UrlSelectionActionMode(controller);
184 setCustomSelectionActionModeCallback(urlSelectionMode);
185 }
186
Michael Kolb21ce4d22010-09-15 14:55:05 -0700187 void setContainer(View container) {
188 mContainer = container;
189 }
190
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800191 public void setUrlInputListener(UrlInputListener listener) {
192 mListener = listener;
193 }
194
Michael Kolb305b1c52011-06-21 16:16:22 -0700195 public void setStateListener(StateListener listener) {
196 mStateListener = listener;
197 // update listener
198 changeState(mState);
199 }
200
201 private void changeState(int newState) {
202 mState = newState;
203 if (mStateListener != null) {
204 mStateListener.onStateChanged(mState);
205 }
206 }
207
Michael Kolb5e8f2b92011-07-19 13:22:32 -0700208 int getState() {
209 return mState;
210 }
211
Michael Kolb21ce4d22010-09-15 14:55:05 -0700212 @Override
213 protected void onConfigurationChanged(Configuration config) {
John Reck35defff2010-11-11 14:06:45 -0800214 super.onConfigurationChanged(config);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700215 mLandscape = (config.orientation &
Michael Kolb31d469b2010-12-09 20:49:54 -0800216 Configuration.ORIENTATION_LANDSCAPE) != 0;
John Reck35defff2010-11-11 14:06:45 -0800217 mAdapter.setLandscapeMode(mLandscape);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700218 if (isPopupShowing() && (getVisibility() == View.VISIBLE)) {
John Reck35defff2010-11-11 14:06:45 -0800219 setupDropDown();
Narayan Kamathf3174a52011-11-17 14:43:32 +0000220 performFiltering(getText(), 0);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700221 }
222 }
223
224 @Override
225 public void showDropDown() {
John Reck35defff2010-11-11 14:06:45 -0800226 setupDropDown();
227 super.showDropDown();
228 }
229
230 @Override
231 public void dismissDropDown() {
232 super.dismissDropDown();
233 mAdapter.clearCache();
234 }
235
236 private void setupDropDown() {
John Reck92026732011-02-15 10:12:30 -0800237 int width = mContainer != null ? mContainer.getWidth() : getWidth();
John Reck8d021aa2011-09-06 15:30:11 -0700238 width += mPopupPadding.left + mPopupPadding.right;
Michael Kolbc5998c22010-10-10 16:04:35 -0700239 if (width != getDropDownWidth()) {
240 setDropDownWidth(width);
241 }
John Reck8d021aa2011-09-06 15:30:11 -0700242 int left = getLeft();
243 left += mPopupPadding.left;
244 if (left != -getDropDownHorizontalOffset()) {
245 setDropDownHorizontalOffset(-left);
Michael Kolbc5998c22010-10-10 16:04:35 -0700246 }
Michael Kolb513286f2010-09-09 12:55:12 -0700247 }
248
249 @Override
Michael Kolbed217742010-08-10 17:52:34 -0700250 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
Narayan Kamathf3174a52011-11-17 14:43:32 +0000251 finishInput(getText().toString(), null, TYPED);
Michael Kolbed217742010-08-10 17:52:34 -0700252 return true;
253 }
Michael Kolb21ce4d22010-09-15 14:55:05 -0700254
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800255 void forceFilter() {
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800256 showDropDown();
Michael Kolba2b2ba82010-08-04 17:54:03 -0700257 }
258
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800259 void hideIME() {
260 mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
261 }
262
Michael Kolb4bb6fcb2012-04-13 14:25:27 -0700263 void showIME() {
Bijan Amirzada9b1e9882014-02-26 17:15:46 -0800264 //mInputManager.focusIn(this);
265 Object[] params = {this};
266 Class[] type = new Class[] {View.class};
267 ReflectHelper.invokeMethod(mInputManager, "focusIn", type, params);
Michael Kolb4bb6fcb2012-04-13 14:25:27 -0700268 mInputManager.showSoftInput(this, 0);
269 }
270
Michael Kolb257cc2c2010-12-09 09:45:52 -0800271 private void finishInput(String url, String extra, String source) {
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800272 mNeedsUpdate = true;
273 dismissDropDown();
Michael Kolbfe251992010-07-08 15:41:55 -0700274 mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
John Reckdcda1d52010-11-29 10:05:54 -0800275 if (TextUtils.isEmpty(url)) {
Michael Kolbfe251992010-07-08 15:41:55 -0700276 mListener.onDismiss();
277 } else {
John Reckb77bdc42011-01-24 13:24:48 -0800278 if (mIncognitoMode && isSearch(url)) {
279 // To prevent logging, intercept this request
280 // TODO: This is a quick hack, refactor this
281 SearchEngine searchEngine = BrowserSettings.getInstance()
282 .getSearchEngine();
283 if (searchEngine == null) return;
284 SearchEngineInfo engineInfo = SearchEngines
Bijan Amirzada9b1e9882014-02-26 17:15:46 -0800285 .getSearchEngineInfo(getContext(), searchEngine.getName());
John Reckb77bdc42011-01-24 13:24:48 -0800286 if (engineInfo == null) return;
287 url = engineInfo.getSearchUriForQuery(url);
288 // mLister.onAction can take it from here without logging
289 }
Michael Kolb257cc2c2010-12-09 09:45:52 -0800290 mListener.onAction(url, extra, source);
Michael Kolbfe251992010-07-08 15:41:55 -0700291 }
Michael Kolbfe251992010-07-08 15:41:55 -0700292 }
293
John Reckb77bdc42011-01-24 13:24:48 -0800294 boolean isSearch(String inUrl) {
295 String url = UrlUtils.fixUrl(inUrl).trim();
296 if (TextUtils.isEmpty(url)) return false;
297
298 if (Patterns.WEB_URL.matcher(url).matches()
299 || UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
300 return false;
301 }
302 return true;
303 }
304
Michael Kolb21ce4d22010-09-15 14:55:05 -0700305 // Completion Listener
306
307 @Override
308 public void onSearch(String search) {
Michael Kolb7cdc4902011-02-03 17:54:40 -0800309 mListener.onCopySuggestion(search);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700310 }
311
312 @Override
Michael Kolbbd2dd642011-01-13 13:01:30 -0800313 public void onSelect(String url, int type, String extra) {
Michael Kolb5ff5c8b2012-05-03 11:37:58 -0700314 finishInput(url, extra, SUGGESTED);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700315 }
316
Michael Kolbfe251992010-07-08 15:41:55 -0700317 @Override
John Reck87369ea2011-01-23 15:20:38 -0800318 public void onItemClick(
319 AdapterView<?> parent, View view, int position, long id) {
320 SuggestItem item = mAdapter.getItem(position);
Narayan Kamath5119edd2011-02-23 15:49:17 +0000321 onSelect(SuggestionsAdapter.getSuggestionUrl(item), item.type, item.extra);
John Reck87369ea2011-01-23 15:20:38 -0800322 }
323
Michael Kolbfe251992010-07-08 15:41:55 -0700324 interface UrlInputListener {
Michael Kolb21ce4d22010-09-15 14:55:05 -0700325
Michael Kolbfe251992010-07-08 15:41:55 -0700326 public void onDismiss();
Michael Kolb21ce4d22010-09-15 14:55:05 -0700327
Michael Kolb257cc2c2010-12-09 09:45:52 -0800328 public void onAction(String text, String extra, String source);
Michael Kolb21ce4d22010-09-15 14:55:05 -0700329
Michael Kolb7cdc4902011-02-03 17:54:40 -0800330 public void onCopySuggestion(String text);
Michael Kolbfe251992010-07-08 15:41:55 -0700331
332 }
333
John Reck117f07d2011-01-24 09:39:03 -0800334 public void setIncognitoMode(boolean incognito) {
John Reckb77bdc42011-01-24 13:24:48 -0800335 mIncognitoMode = incognito;
336 mAdapter.setIncognitoMode(mIncognitoMode);
John Reck117f07d2011-01-24 09:39:03 -0800337 }
338
Michael Kolbdc2ee1b2011-02-14 14:34:40 -0800339 @Override
340 public boolean onKeyDown(int keyCode, KeyEvent evt) {
341 if (keyCode == KeyEvent.KEYCODE_ESCAPE && !isInTouchMode()) {
342 finishInput(null, null, null);
343 return true;
344 }
345 return super.onKeyDown(keyCode, evt);
346 }
347
Narayan Kamath80aad8d2011-02-23 12:01:13 +0000348 public SuggestionsAdapter getAdapter() {
349 return mAdapter;
350 }
Narayan Kamath5119edd2011-02-23 15:49:17 +0000351
Michael Kolb74e60c22011-04-30 16:43:47 -0700352 /*
353 * no-op to prevent scrolling of webview when embedded titlebar
354 * gets edited
355 */
356 @Override
357 public boolean requestRectangleOnScreen(Rect rect, boolean immediate) {
358 return false;
359 }
Michael Kolb305b1c52011-06-21 16:16:22 -0700360
361 @Override
Narayan Kamathf3174a52011-11-17 14:43:32 +0000362 public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
363
364 @Override
365 public void onTextChanged(CharSequence s, int start, int before, int count) {
Michael Kolb305b1c52011-06-21 16:16:22 -0700366 if (StateListener.STATE_HIGHLIGHTED == mState) {
367 changeState(StateListener.STATE_EDITED);
368 }
369 }
370
Narayan Kamathf3174a52011-11-17 14:43:32 +0000371 @Override
372 public void afterTextChanged(Editable s) { }
373
Axesh R. Ajmera9bb763e2014-04-04 15:37:34 -0700374 /**
375 * It will prompt the toast if the text length greater than the given length.
376 */
377 private class UrlLengthFilter extends LengthFilter {
378 public UrlLengthFilter(int max) {
379 super(max);
380 }
381
382 @Override
383 public CharSequence filter(CharSequence source, int start, int end, Spanned dest,
384 int dstart, int dend) {
385 CharSequence result = super.filter(source, start, end, dest, dstart, dend);
386 if (result != null) {
387 // If the result is not null, it means the text length is greater than
388 // the given length. We will prompt the toast to alert the user.
389 CharSequence alert = getResources().getString(R.string.max_url_character_limit_msg);
390 Toast t = Toast.makeText(mContext , alert, Toast.LENGTH_SHORT);
391 t.setGravity(Gravity.CENTER, 0, 0);
392 t.show();
393 }
394 return result;
395 }
396 }
397
Michael Kolbfe251992010-07-08 15:41:55 -0700398}