blob: 02bba3cbe2d771d6bc71146a5f910c44aa2bde82 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.browser;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Patterns;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import com.android.browser.SuggestionsAdapter.CompletionListener;
import com.android.browser.SuggestionsAdapter.SuggestItem;
import com.android.browser.search.SearchEngine;
import com.android.browser.search.SearchEngineInfo;
import com.android.browser.search.SearchEngines;
import com.android.internal.R;
import java.util.List;
/**
* url/search input view
* handling suggestions
*/
public class UrlInputView extends AutoCompleteTextView
implements OnEditorActionListener,
CompletionListener, OnItemClickListener, TextWatcher {
static final String TYPED = "browser-type";
static final String SUGGESTED = "browser-suggest";
static final int POST_DELAY = 100;
static interface StateListener {
static final int STATE_NORMAL = 0;
static final int STATE_HIGHLIGHTED = 1;
static final int STATE_EDITED = 2;
public void onStateChanged(int state);
}
private UrlInputListener mListener;
private InputMethodManager mInputManager;
private SuggestionsAdapter mAdapter;
private View mContainer;
private boolean mLandscape;
private boolean mIncognitoMode;
private boolean mNeedsUpdate;
private int mState;
private StateListener mStateListener;
private Rect mPopupPadding;
public UrlInputView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
TypedArray a = context.obtainStyledAttributes(
attrs, com.android.internal.R.styleable.PopupWindow,
R.attr.autoCompleteTextViewStyle, 0);
Drawable popupbg = a.getDrawable(R.styleable.PopupWindow_popupBackground);
a.recycle();
mPopupPadding = new Rect();
popupbg.getPadding(mPopupPadding);
init(context);
}
public UrlInputView(Context context, AttributeSet attrs) {
this(context, attrs, R.attr.autoCompleteTextViewStyle);
}
public UrlInputView(Context context) {
this(context, null);
}
private void init(Context ctx) {
mInputManager = (InputMethodManager) ctx.getSystemService(Context.INPUT_METHOD_SERVICE);
setOnEditorActionListener(this);
mAdapter = new SuggestionsAdapter(ctx, this);
setAdapter(mAdapter);
setSelectAllOnFocus(true);
onConfigurationChanged(ctx.getResources().getConfiguration());
setThreshold(1);
setOnItemClickListener(this);
mNeedsUpdate = false;
addTextChangedListener(this);
mState = StateListener.STATE_NORMAL;
}
protected void onFocusChanged(boolean focused, int direction, Rect prevRect) {
super.onFocusChanged(focused, direction, prevRect);
int state = -1;
if (focused) {
if (hasSelection()) {
state = StateListener.STATE_HIGHLIGHTED;
} else {
state = StateListener.STATE_EDITED;
}
} else {
// reset the selection state
state = StateListener.STATE_NORMAL;
}
final int s = state;
post(new Runnable() {
public void run() {
changeState(s);
}
});
}
@Override
public boolean onTouchEvent(MotionEvent evt) {
boolean hasSelection = hasSelection();
boolean res = super.onTouchEvent(evt);
if ((MotionEvent.ACTION_DOWN == evt.getActionMasked())
&& hasSelection) {
postDelayed(new Runnable() {
public void run() {
changeState(StateListener.STATE_EDITED);
}}, POST_DELAY);
}
return res;
}
/**
* check if focus change requires a title bar update
*/
boolean needsUpdate() {
return mNeedsUpdate;
}
/**
* clear the focus change needs title bar update flag
*/
void clearNeedsUpdate() {
mNeedsUpdate = false;
}
void setController(UiController controller) {
UrlSelectionActionMode urlSelectionMode
= new UrlSelectionActionMode(controller);
setCustomSelectionActionModeCallback(urlSelectionMode);
}
void setContainer(View container) {
mContainer = container;
}
public void setUrlInputListener(UrlInputListener listener) {
mListener = listener;
}
public void setStateListener(StateListener listener) {
mStateListener = listener;
// update listener
changeState(mState);
}
private void changeState(int newState) {
mState = newState;
if (mStateListener != null) {
mStateListener.onStateChanged(mState);
}
}
int getState() {
return mState;
}
@Override
protected void onConfigurationChanged(Configuration config) {
super.onConfigurationChanged(config);
mLandscape = (config.orientation &
Configuration.ORIENTATION_LANDSCAPE) != 0;
mAdapter.setLandscapeMode(mLandscape);
if (isPopupShowing() && (getVisibility() == View.VISIBLE)) {
setupDropDown();
performFiltering(getText(), 0);
}
}
@Override
public void showDropDown() {
setupDropDown();
super.showDropDown();
}
@Override
public void dismissDropDown() {
super.dismissDropDown();
mAdapter.clearCache();
}
private void setupDropDown() {
int width = mContainer != null ? mContainer.getWidth() : getWidth();
width += mPopupPadding.left + mPopupPadding.right;
if (width != getDropDownWidth()) {
setDropDownWidth(width);
}
int left = getLeft();
left += mPopupPadding.left;
if (left != -getDropDownHorizontalOffset()) {
setDropDownHorizontalOffset(-left);
}
}
@Override
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
finishInput(getText().toString(), null, TYPED);
return true;
}
void forceFilter() {
showDropDown();
}
void hideIME() {
mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
}
void showIME() {
mInputManager.focusIn(this);
mInputManager.showSoftInput(this, 0);
}
private void finishInput(String url, String extra, String source) {
mNeedsUpdate = true;
dismissDropDown();
mInputManager.hideSoftInputFromWindow(getWindowToken(), 0);
if (TextUtils.isEmpty(url)) {
mListener.onDismiss();
} else {
if (mIncognitoMode && isSearch(url)) {
// To prevent logging, intercept this request
// TODO: This is a quick hack, refactor this
SearchEngine searchEngine = BrowserSettings.getInstance()
.getSearchEngine();
if (searchEngine == null) return;
SearchEngineInfo engineInfo = SearchEngines
.getSearchEngineInfo(mContext, searchEngine.getName());
if (engineInfo == null) return;
url = engineInfo.getSearchUriForQuery(url);
// mLister.onAction can take it from here without logging
}
mListener.onAction(url, extra, source);
}
}
boolean isSearch(String inUrl) {
String url = UrlUtils.fixUrl(inUrl).trim();
if (TextUtils.isEmpty(url)) return false;
if (Patterns.WEB_URL.matcher(url).matches()
|| UrlUtils.ACCEPTED_URI_SCHEMA.matcher(url).matches()) {
return false;
}
return true;
}
// Completion Listener
@Override
public void onSearch(String search) {
mListener.onCopySuggestion(search);
}
@Override
public void onSelect(String url, int type, String extra) {
finishInput(url, extra, SUGGESTED);
}
@Override
public void onItemClick(
AdapterView<?> parent, View view, int position, long id) {
SuggestItem item = mAdapter.getItem(position);
onSelect(SuggestionsAdapter.getSuggestionUrl(item), item.type, item.extra);
}
interface UrlInputListener {
public void onDismiss();
public void onAction(String text, String extra, String source);
public void onCopySuggestion(String text);
}
public void setIncognitoMode(boolean incognito) {
mIncognitoMode = incognito;
mAdapter.setIncognitoMode(mIncognitoMode);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent evt) {
if (keyCode == KeyEvent.KEYCODE_ESCAPE && !isInTouchMode()) {
finishInput(null, null, null);
return true;
}
return super.onKeyDown(keyCode, evt);
}
public SuggestionsAdapter getAdapter() {
return mAdapter;
}
/*
* no-op to prevent scrolling of webview when embedded titlebar
* gets edited
*/
@Override
public boolean requestRectangleOnScreen(Rect rect, boolean immediate) {
return false;
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) { }
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
if (StateListener.STATE_HIGHLIGHTED == mState) {
changeState(StateListener.STATE_EDITED);
}
}
@Override
public void afterTextChanged(Editable s) { }
}