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