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