summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAmith Yamasani <yamasani@google.com>2010-12-03 11:22:48 -0800
committerAndroid (Google) Code Review <android-gerrit@google.com>2010-12-03 11:22:48 -0800
commit3b7fec8d56e0634d4c7795258f03023f4885f723 (patch)
tree29fdf5527041d1d3fc8d6428471b177a9d38af3a
parentc99821d01ab0c1cee54ccb0556e621dfe2ddb55f (diff)
parent968ec938399033d280b1648123104ac567f2a093 (diff)
downloadframeworks_base-3b7fec8d56e0634d4c7795258f03023f4885f723.zip
frameworks_base-3b7fec8d56e0634d4c7795258f03023f4885f723.tar.gz
frameworks_base-3b7fec8d56e0634d4c7795258f03023f4885f723.tar.bz2
Merge "Use SearchView in SearchDialog for legacy apps using the old model."
-rw-r--r--core/java/android/app/SearchDialog.java1018
-rw-r--r--core/java/android/app/SuggestionsAdapter.java653
-rw-r--r--core/java/android/widget/SearchView.java288
-rw-r--r--core/res/res/layout/search_bar.xml95
-rw-r--r--core/res/res/layout/search_view.xml2
-rw-r--r--core/res/res/values/themes.xml17
6 files changed, 405 insertions, 1668 deletions
diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java
index 0c280f9..4d19b62 100644
--- a/core/java/android/app/SearchDialog.java
+++ b/core/java/android/app/SearchDialog.java
@@ -17,12 +17,6 @@
package android.app;
-import static android.app.SuggestionsAdapter.getColumnString;
-
-import java.util.WeakHashMap;
-import java.util.concurrent.atomic.AtomicLong;
-
-import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
@@ -30,21 +24,15 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.res.Configuration;
-import android.content.res.Resources;
-import android.database.Cursor;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.SystemClock;
-import android.provider.Browser;
import android.speech.RecognizerIntent;
-import android.text.Editable;
import android.text.InputType;
import android.text.TextUtils;
-import android.text.TextWatcher;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
@@ -55,18 +43,15 @@ import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
-import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
import android.widget.AutoCompleteTextView;
-import android.widget.Button;
-import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.ListView;
+import android.widget.SearchView;
import android.widget.TextView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
+
+import java.util.WeakHashMap;
+import java.util.concurrent.atomic.AtomicLong;
/**
* Search dialog. This is controlled by the
@@ -74,32 +59,28 @@ import android.widget.AdapterView.OnItemSelectedListener;
*
* @hide
*/
-public class SearchDialog extends Dialog implements OnItemClickListener, OnItemSelectedListener {
+public class SearchDialog extends Dialog {
// Debugging support
private static final boolean DBG = false;
private static final String LOG_TAG = "SearchDialog";
- private static final boolean DBG_LOG_TIMING = false;
private static final String INSTANCE_KEY_COMPONENT = "comp";
private static final String INSTANCE_KEY_APPDATA = "data";
- private static final String INSTANCE_KEY_STORED_APPDATA = "sData";
private static final String INSTANCE_KEY_USER_QUERY = "uQry";
-
+
// The string used for privateImeOptions to identify to the IME that it should not show
// a microphone button since one already exists in the search dialog.
private static final String IME_OPTION_NO_MICROPHONE = "nm";
- private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12;
private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
// views & widgets
private TextView mBadgeLabel;
private ImageView mAppIcon;
- private SearchAutoComplete mSearchAutoComplete;
- private Button mGoButton;
- private ImageButton mVoiceButton;
+ private AutoCompleteTextView mSearchAutoComplete;
private View mSearchPlate;
+ private SearchView mSearchView;
private Drawable mWorkingSpinner;
// interaction with searchable application
@@ -107,30 +88,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
private ComponentName mLaunchComponent;
private Bundle mAppSearchData;
private Context mActivityContext;
- private SearchManager mSearchManager;
// For voice searching
private final Intent mVoiceWebSearchIntent;
private final Intent mVoiceAppSearchIntent;
- // support for AutoCompleteTextView suggestions display
- private SuggestionsAdapter mSuggestionsAdapter;
-
- // Whether to rewrite queries when selecting suggestions
- private static final boolean REWRITE_QUERIES = true;
-
// The query entered by the user. This is not changed when selecting a suggestion
// that modifies the contents of the text field. But if the user then edits
// the suggestion, the resulting string is saved.
private String mUserQuery;
- // The query passed in when opening the SearchDialog. Used in the browser
- // case to determine whether the user has edited the query.
- private String mInitialQuery;
-
- // A weak map of drawables we've gotten from other packages, so we don't load them
- // more than once.
- private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
- new WeakHashMap<String, Drawable.ConstantState>();
// Last known IME options value for the search edit text.
private int mSearchAutoCompleteImeOptions;
@@ -160,7 +126,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mSearchManager = searchManager;
}
/**
@@ -196,30 +161,24 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// get the view elements for local access
SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar);
searchBar.setSearchDialog(this);
-
- mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
- mSearchAutoComplete = (SearchAutoComplete)
- findViewById(com.android.internal.R.id.search_src_text);
+ mSearchView = (SearchView) findViewById(com.android.internal.R.id.search_view);
+ mSearchView.setSubmitButtonEnabled(true);
+ mSearchView.setOnCloseListener(mOnCloseListener);
+ mSearchView.setOnQueryChangeListener(mOnQueryChangeListener);
+ mSearchView.setOnSuggestionSelectionListener(mOnSuggestionSelectionListener);
+
+ // TODO: Move the badge logic to SearchView or move the badge to search_bar.xml
+ mBadgeLabel = (TextView) mSearchView.findViewById(com.android.internal.R.id.search_badge);
+ mSearchAutoComplete = (AutoCompleteTextView)
+ mSearchView.findViewById(com.android.internal.R.id.search_src_text);
mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
- mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
- mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
- mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
+ mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate);
mWorkingSpinner = getContext().getResources().
getDrawable(com.android.internal.R.drawable.search_spinner);
mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
null, null, mWorkingSpinner, null);
setWorking(false);
- // attach listeners
- mSearchAutoComplete.addTextChangedListener(mTextWatcher);
- mSearchAutoComplete.setOnKeyListener(mTextKeyListener);
- mSearchAutoComplete.setOnItemClickListener(this);
- mSearchAutoComplete.setOnItemSelectedListener(this);
- mGoButton.setOnClickListener(mGoButtonClickListener);
- mGoButton.setOnKeyListener(mButtonsKeyListener);
- mVoiceButton.setOnClickListener(mVoiceButtonClickListener);
- mVoiceButton.setOnKeyListener(mButtonsKeyListener);
-
// pre-hide all the extraneous elements
mBadgeLabel.setVisibility(View.GONE);
@@ -256,7 +215,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return false;
}
- mInitialQuery = initialQuery == null ? "" : initialQuery;
// finally, load the user's initial text (which may trigger suggestions)
setUserQuery(initialQuery);
if (selectInitialQuery) {
@@ -296,6 +254,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Recreate the search bar view every time the dialog is shown, to get rid
// of any bad state in the AutoCompleteTextView etc
createContentView();
+ mSearchView.setSearchableInfo(mSearchable);
show();
}
@@ -326,14 +285,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
getContext().unregisterReceiver(mConfChangeListener);
- closeSuggestionsAdapter();
-
// dump extra memory we're hanging on to
mLaunchComponent = null;
mAppSearchData = null;
mSearchable = null;
mUserQuery = null;
- mInitialQuery = null;
}
/**
@@ -349,20 +305,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Closes and gets rid of the suggestions adapter.
- */
- private void closeSuggestionsAdapter() {
- // remove the adapter from the autocomplete first, to avoid any updates
- // when we drop the cursor
- mSearchAutoComplete.setAdapter((SuggestionsAdapter)null);
- // close any leftover cursor
- if (mSuggestionsAdapter != null) {
- mSuggestionsAdapter.close();
- }
- mSuggestionsAdapter = null;
- }
-
- /**
* Save the minimal set of data necessary to recreate the search
*
* @return A bundle with the state of the dialog, or {@code null} if the search
@@ -385,8 +327,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
/**
* Restore the state of the dialog from a previously saved bundle.
*
- * TODO: go through this and make sure that it saves everything that is saved
- *
* @param savedInstanceState The state of the dialog previously saved by
* {@link #onSaveInstanceState()}.
*/
@@ -404,21 +344,18 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
return;
}
}
-
+
/**
* Called after resources have changed, e.g. after screen rotation or locale change.
*/
public void onConfigurationChanged() {
if (mSearchable != null && isShowing()) {
// Redraw (resources may have changed)
- updateSearchButton();
updateSearchAppIcon();
updateSearchBadge();
- updateQueryHint();
if (isLandscapeMode(getContext())) {
mSearchAutoComplete.ensureImeVisible(true);
}
- mSearchAutoComplete.showDropDownAfterLayout();
}
}
@@ -434,11 +371,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
if (mSearchable != null) {
mDecor.setVisibility(View.VISIBLE);
updateSearchAutoComplete();
- updateSearchButton();
updateSearchAppIcon();
updateSearchBadge();
- updateQueryHint();
- updateVoiceButton(TextUtils.isEmpty(mUserQuery));
// In order to properly configure the input method (if one is being used), we
// need to let it know if we'll be providing suggestions. Although it would be
@@ -474,68 +408,26 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* Updates the auto-complete text view.
*/
private void updateSearchAutoComplete() {
- // close any existing suggestions adapter
- closeSuggestionsAdapter();
-
- mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
- mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold());
// we dismiss the entire dialog instead
mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
-
- mSearchAutoComplete.setForceIgnoreOutsideTouch(true);
-
- // attach the suggestions adapter, if suggestions are available
- // The existence of a suggestions authority is the proxy for "suggestions available here"
- if (mSearchable.getSuggestAuthority() != null) {
- mSuggestionsAdapter = new SuggestionsAdapter(getContext(), this, mSearchable,
- mOutsideDrawablesCache);
- mSearchAutoComplete.setAdapter(mSuggestionsAdapter);
- }
- }
-
- private void updateSearchButton() {
- String textLabel = null;
- Drawable iconLabel = null;
- int textId = mSearchable.getSearchButtonText();
- if (isBrowserSearch()){
- iconLabel = getContext().getResources()
- .getDrawable(com.android.internal.R.drawable.ic_btn_search_go);
- } else if (textId != 0) {
- textLabel = mActivityContext.getResources().getString(textId);
- } else {
- iconLabel = getContext().getResources().
- getDrawable(com.android.internal.R.drawable.ic_btn_search);
- }
- mGoButton.setText(textLabel);
- mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null);
+ mSearchAutoComplete.setForceIgnoreOutsideTouch(false);
}
private void updateSearchAppIcon() {
- if (isBrowserSearch()) {
- mAppIcon.setImageResource(0);
- mAppIcon.setVisibility(View.GONE);
- mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_GLOBAL,
- mSearchPlate.getPaddingTop(),
- mSearchPlate.getPaddingRight(),
- mSearchPlate.getPaddingBottom());
- } else {
- PackageManager pm = getContext().getPackageManager();
- Drawable icon;
- try {
- ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
- icon = pm.getApplicationIcon(info.applicationInfo);
- if (DBG) Log.d(LOG_TAG, "Using app-specific icon");
- } catch (NameNotFoundException e) {
- icon = pm.getDefaultActivityIcon();
- Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
- }
- mAppIcon.setImageDrawable(icon);
- mAppIcon.setVisibility(View.VISIBLE);
- mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL,
- mSearchPlate.getPaddingTop(),
- mSearchPlate.getPaddingRight(),
- mSearchPlate.getPaddingBottom());
+ PackageManager pm = getContext().getPackageManager();
+ Drawable icon;
+ try {
+ ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
+ icon = pm.getApplicationIcon(info.applicationInfo);
+ if (DBG)
+ Log.d(LOG_TAG, "Using app-specific icon");
+ } catch (NameNotFoundException e) {
+ icon = pm.getDefaultActivityIcon();
+ Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
}
+ mAppIcon.setImageDrawable(icon);
+ mAppIcon.setVisibility(View.VISIBLE);
+ mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL, mSearchPlate.getPaddingTop(), mSearchPlate.getPaddingRight(), mSearchPlate.getPaddingBottom());
}
/**
@@ -546,7 +438,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
int visibility = View.GONE;
Drawable icon = null;
CharSequence text = null;
-
+
// optionally show one or the other.
if (mSearchable.useBadgeIcon()) {
icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId());
@@ -557,71 +449,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
visibility = View.VISIBLE;
if (DBG) Log.d(LOG_TAG, "Using badge label: " + mSearchable.getLabelId());
}
-
+
mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
mBadgeLabel.setText(text);
mBadgeLabel.setVisibility(visibility);
}
- /**
- * Update the hint in the query text field.
- */
- private void updateQueryHint() {
- if (isShowing()) {
- String hint = null;
- if (mSearchable != null) {
- int hintId = mSearchable.getHintId();
- if (hintId != 0) {
- hint = mActivityContext.getString(hintId);
- }
- }
- mSearchAutoComplete.setHint(hint);
- }
- }
-
- /**
- * Update the visibility of the voice button. There are actually two voice search modes,
- * either of which will activate the button.
- * @param empty whether the search query text field is empty. If it is, then the other
- * criteria apply to make the voice button visible. Otherwise the voice button will not
- * be visible - i.e., if the user has typed a query, remove the voice button.
- */
- private void updateVoiceButton(boolean empty) {
- int visibility = View.GONE;
- if ((mAppSearchData == null || !mAppSearchData.getBoolean(
- SearchManager.DISABLE_VOICE_SEARCH, false))
- && mSearchable.getVoiceSearchEnabled() && empty) {
- Intent testIntent = null;
- if (mSearchable.getVoiceSearchLaunchWebSearch()) {
- testIntent = mVoiceWebSearchIntent;
- } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
- testIntent = mVoiceAppSearchIntent;
- }
- if (testIntent != null) {
- ResolveInfo ri = getContext().getPackageManager().
- resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
- if (ri != null) {
- visibility = View.VISIBLE;
- }
- }
- }
- mVoiceButton.setVisibility(visibility);
- }
-
- /** Called by SuggestionsAdapter when the cursor contents changed. */
- void onDataSetChanged() {
- if (mSearchAutoComplete != null && mSuggestionsAdapter != null) {
- mSearchAutoComplete.onFilterComplete(mSuggestionsAdapter.getCount());
- }
- }
-
- /**
- * Hack to determine whether this is the browser, so we can adjust the UI.
- */
- private boolean isBrowserSearch() {
- return mLaunchComponent.flattenToShortString().startsWith("com.android.browser/");
- }
-
/*
* Listeners of various types
*/
@@ -642,7 +475,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
// Let Dialog handle events outside the window while the pop-up is showing.
return super.onTouchEvent(event);
}
-
+
private boolean isOutOfBounds(View v, MotionEvent event) {
final int x = (int) event.getX();
final int y = (int) event.getY();
@@ -651,336 +484,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
|| (x > (v.getWidth()+slop))
|| (y > (v.getHeight()+slop));
}
-
- /**
- * Dialog's OnKeyListener implements various search-specific functionality
- *
- * @param keyCode This is the keycode of the typed key, and is the same value as
- * found in the KeyEvent parameter.
- * @param event The complete event record for the typed key
- *
- * @return Return true if the event was handled here, or false if not.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (DBG) Log.d(LOG_TAG, "onKeyDown(" + keyCode + "," + event + ")");
- if (mSearchable == null) {
- return false;
- }
-
- // if it's an action specified by the searchable activity, launch the
- // entered query with the action key
- SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- launchQuerySearch(keyCode, actionKey.getQueryActionMsg());
- return true;
- }
-
- return super.onKeyDown(keyCode, event);
- }
-
- /**
- * Callback to watch the textedit field for empty/non-empty
- */
- private TextWatcher mTextWatcher = new TextWatcher() {
-
- public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
-
- public void onTextChanged(CharSequence s, int start,
- int before, int after) {
- if (DBG_LOG_TIMING) {
- dbgLogTiming("onTextChanged()");
- }
- if (mSearchable == null) {
- return;
- }
- if (!mSearchAutoComplete.isPerformingCompletion()) {
- // The user changed the query, remember it.
- mUserQuery = s == null ? "" : s.toString();
- }
- updateWidgetState();
- // Always want to show the microphone if the context is voice.
- // Also show the microphone if this is a browser search and the
- // query matches the initial query.
- updateVoiceButton(mSearchAutoComplete.isEmpty()
- || (isBrowserSearch() && mInitialQuery.equals(mUserQuery))
- || (mAppSearchData != null && mAppSearchData.getBoolean(
- SearchManager.CONTEXT_IS_VOICE)));
- }
-
- public void afterTextChanged(Editable s) {
- if (mSearchable == null) {
- return;
- }
- if (mSearchable.autoUrlDetect() && !mSearchAutoComplete.isPerformingCompletion()) {
- // The user changed the query, check if it is a URL and if so change the search
- // button in the soft keyboard to the 'Go' button.
- int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION))
- | EditorInfo.IME_ACTION_GO;
- if (options != mSearchAutoCompleteImeOptions) {
- mSearchAutoCompleteImeOptions = options;
- mSearchAutoComplete.setImeOptions(options);
- // This call is required to update the soft keyboard UI with latest IME flags.
- mSearchAutoComplete.setInputType(mSearchAutoComplete.getInputType());
- }
- }
- }
- };
-
- /**
- * Enable/Disable the go button based on edit text state (any text?)
- */
- private void updateWidgetState() {
- // enable the button if we have one or more non-space characters
- boolean enabled = !mSearchAutoComplete.isEmpty();
- if (isBrowserSearch()) {
- // In the browser, we hide the search button when there is no text,
- // or if the text matches the initial query.
- if (enabled && !mInitialQuery.equals(mUserQuery)) {
- mSearchAutoComplete.setBackgroundResource(
- com.android.internal.R.drawable.textfield_search);
- mGoButton.setVisibility(View.VISIBLE);
- // Just to be sure
- mGoButton.setEnabled(true);
- mGoButton.setFocusable(true);
- } else {
- mSearchAutoComplete.setBackgroundResource(
- com.android.internal.R.drawable.textfield_search_empty);
- mGoButton.setVisibility(View.GONE);
- }
- } else {
- // Elsewhere we just disable the button
- mGoButton.setEnabled(enabled);
- mGoButton.setFocusable(enabled);
- }
- }
-
- /**
- * React to typing in the GO search button by refocusing to EditText.
- * Continue typing the query.
- */
- View.OnKeyListener mButtonsKeyListener = new View.OnKeyListener() {
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- // guard against possible race conditions
- if (mSearchable == null) {
- return false;
- }
-
- if (!event.isSystem() &&
- (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
- (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
- // restore focus and give key to EditText ...
- if (mSearchAutoComplete.requestFocus()) {
- return mSearchAutoComplete.dispatchKeyEvent(event);
- }
- }
-
- return false;
- }
- };
-
- /**
- * React to a click in the GO button by launching a search.
- */
- View.OnClickListener mGoButtonClickListener = new View.OnClickListener() {
- public void onClick(View v) {
- // guard against possible race conditions
- if (mSearchable == null) {
- return;
- }
- launchQuerySearch();
- }
- };
-
- /**
- * React to a click in the voice search button.
- */
- View.OnClickListener mVoiceButtonClickListener = new View.OnClickListener() {
- public void onClick(View v) {
- // guard against possible race conditions
- if (mSearchable == null) {
- return;
- }
- SearchableInfo searchable = mSearchable;
- try {
- if (searchable.getVoiceSearchLaunchWebSearch()) {
- Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent,
- searchable);
- getContext().startActivity(webSearchIntent);
- } else if (searchable.getVoiceSearchLaunchRecognizer()) {
- Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent,
- searchable);
- getContext().startActivity(appSearchIntent);
- }
- } catch (ActivityNotFoundException e) {
- // Should not happen, since we check the availability of
- // voice search before showing the button. But just in case...
- Log.w(LOG_TAG, "Could not find voice search activity");
- }
- dismiss();
- }
- };
-
- /**
- * Create and return an Intent that can launch the voice search activity for web search.
- */
- private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) {
- Intent voiceIntent = new Intent(baseIntent);
- ComponentName searchActivity = searchable.getSearchActivity();
- voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
- searchActivity == null ? null : searchActivity.flattenToShortString());
- return voiceIntent;
- }
-
- /**
- * Create and return an Intent that can launch the voice search activity, perform a specific
- * voice transcription, and forward the results to the searchable activity.
- *
- * @param baseIntent The voice app search intent to start from
- * @return A completely-configured intent ready to send to the voice search activity
- */
- private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) {
- ComponentName searchActivity = searchable.getSearchActivity();
-
- // create the necessary intent to set up a search-and-forward operation
- // in the voice search system. We have to keep the bundle separate,
- // because it becomes immutable once it enters the PendingIntent
- Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
- queryIntent.setComponent(searchActivity);
- PendingIntent pending = PendingIntent.getActivity(
- getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
-
- // Now set up the bundle that will be inserted into the pending intent
- // when it's time to do the search. We always build it here (even if empty)
- // because the voice search activity will always need to insert "QUERY" into
- // it anyway.
- Bundle queryExtras = new Bundle();
- if (mAppSearchData != null) {
- queryExtras.putBundle(SearchManager.APP_DATA, mAppSearchData);
- }
-
- // Now build the intent to launch the voice search. Add all necessary
- // extras to launch the voice recognizer, and then all the necessary extras
- // to forward the results to the searchable activity
- Intent voiceIntent = new Intent(baseIntent);
-
- // Add all of the configuration options supplied by the searchable's metadata
- String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
- String prompt = null;
- String language = null;
- int maxResults = 1;
- Resources resources = mActivityContext.getResources();
- if (searchable.getVoiceLanguageModeId() != 0) {
- languageModel = resources.getString(searchable.getVoiceLanguageModeId());
- }
- if (searchable.getVoicePromptTextId() != 0) {
- prompt = resources.getString(searchable.getVoicePromptTextId());
- }
- if (searchable.getVoiceLanguageId() != 0) {
- language = resources.getString(searchable.getVoiceLanguageId());
- }
- if (searchable.getVoiceMaxResults() != 0) {
- maxResults = searchable.getVoiceMaxResults();
- }
- voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE,
- searchActivity == null ? null : searchActivity.flattenToShortString());
-
- // Add the values that configure forwarding the results
- voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
- voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
-
- return voiceIntent;
- }
-
- /**
- * Corrects http/https typo errors in the given url string, and if the protocol specifier was
- * not present defaults to http.
- *
- * @param inUrl URL to check and fix
- * @return fixed URL string.
- */
- private String fixUrl(String inUrl) {
- if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
- return inUrl;
-
- if (inUrl.startsWith("http:") || inUrl.startsWith("https:")) {
- if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
- inUrl = inUrl.replaceFirst("/", "//");
- } else {
- inUrl = inUrl.replaceFirst(":", "://");
- }
- }
-
- if (inUrl.indexOf("://") == -1) {
- inUrl = "http://" + inUrl;
- }
-
- return inUrl;
- }
-
- /**
- * React to the user typing "enter" or other hardwired keys while typing in the search box.
- * This handles these special keys while the edit box has focus.
- */
- View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- // guard against possible race conditions
- if (mSearchable == null) {
- return false;
- }
-
- if (DBG_LOG_TIMING) dbgLogTiming("doTextKey()");
- if (DBG) {
- Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event
- + "), selection: " + mSearchAutoComplete.getListSelection());
- }
-
- // If a suggestion is selected, handle enter, search key, and action keys
- // as presses on the selected suggestion
- if (mSearchAutoComplete.isPopupShowing() &&
- mSearchAutoComplete.getListSelection() != ListView.INVALID_POSITION) {
- return onSuggestionsKey(v, keyCode, event);
- }
-
- // If there is text in the query box, handle enter, and action keys
- // The search key is handled by the dialog's onKeyDown().
- if (!mSearchAutoComplete.isEmpty()) {
- if (keyCode == KeyEvent.KEYCODE_ENTER
- && event.getAction() == KeyEvent.ACTION_UP) {
- v.cancelLongPress();
-
- // If this is a url entered by the user & we displayed the 'Go' button which
- // the user clicked, launch the url instead of using it as a search query.
- if (mSearchable.autoUrlDetect() &&
- (mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION)
- == EditorInfo.IME_ACTION_GO) {
- Uri uri = Uri.parse(fixUrl(mSearchAutoComplete.getText().toString()));
- Intent intent = new Intent(Intent.ACTION_VIEW, uri);
- intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- launchIntent(intent);
- } else {
- // Launch as a regular search.
- launchQuerySearch();
- }
- return true;
- }
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- launchQuerySearch(keyCode, actionKey.getQueryActionMsg());
- return true;
- }
- }
- }
- return false;
- }
- };
@Override
public void hide() {
@@ -997,78 +500,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
super.hide();
}
-
- /**
- * React to the user typing while in the suggestions list. First, check for action
- * keys. If not handled, try refocusing regular characters into the EditText.
- */
- private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
- // guard against possible race conditions (late arrival after dismiss)
- if (mSearchable == null) {
- return false;
- }
- if (mSuggestionsAdapter == null) {
- return false;
- }
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- if (DBG_LOG_TIMING) {
- dbgLogTiming("onSuggestionsKey()");
- }
-
- // First, check for enter or search (both of which we'll treat as a "click")
- if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
- int position = mSearchAutoComplete.getListSelection();
- return launchSuggestion(position);
- }
-
- // Next, check for left/right moves, which we use to "return" the user to the edit view
- if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
- // give "focus" to text editor, with cursor at the beginning if
- // left key, at end if right key
- // TODO: Reverse left/right for right-to-left languages, e.g. Arabic
- int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ?
- 0 : mSearchAutoComplete.length();
- mSearchAutoComplete.setSelection(selPoint);
- mSearchAutoComplete.setListSelection(0);
- mSearchAutoComplete.clearListSelection();
- mSearchAutoComplete.ensureImeVisible(true);
-
- return true;
- }
-
- // Next, check for an "up and out" move
- if (keyCode == KeyEvent.KEYCODE_DPAD_UP
- && 0 == mSearchAutoComplete.getListSelection()) {
- restoreUserQuery();
- // let ACTV complete the move
- return false;
- }
-
- // Next, check for an "action key"
- SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- if ((actionKey != null) &&
- ((actionKey.getSuggestActionMsg() != null) ||
- (actionKey.getSuggestActionMsgColumn() != null))) {
- // launch suggestion using action key column
- int position = mSearchAutoComplete.getListSelection();
- if (position != ListView.INVALID_POSITION) {
- Cursor c = mSuggestionsAdapter.getCursor();
- if (c.moveToPosition(position)) {
- final String actionMsg = getActionKeyMessage(c, actionKey);
- if (actionMsg != null && (actionMsg.length() > 0)) {
- return launchSuggestion(position, keyCode, actionMsg);
- }
- }
- }
- }
- }
- return false;
- }
/**
* Launch a search for the text in the query text field.
*/
- public void launchQuerySearch() {
+ public void launchQuerySearch() {
launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
}
@@ -1080,50 +516,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
* @param actionMsg The message for the action key that was pressed,
* or <code>null</code> if none.
*/
- protected void launchQuerySearch(int actionKey, String actionMsg) {
+ protected void launchQuerySearch(int actionKey, String actionMsg) {
String query = mSearchAutoComplete.getText().toString();
String action = Intent.ACTION_SEARCH;
- Intent intent = createIntent(action, null, null, query,
- actionKey, actionMsg);
+ Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
launchIntent(intent);
}
-
- /**
- * Launches an intent based on a suggestion.
- *
- * @param position The index of the suggestion to create the intent from.
- * @return true if a successful launch, false if could not (e.g. bad position).
- */
- protected boolean launchSuggestion(int position) {
- return launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
- }
-
- /**
- * Launches an intent based on a suggestion.
- *
- * @param position The index of the suggestion to create the intent from.
- * @param actionKey The key code of the action key that was pressed,
- * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
- * @param actionMsg The message for the action key that was pressed,
- * or <code>null</code> if none.
- * @return true if a successful launch, false if could not (e.g. bad position).
- */
- protected boolean launchSuggestion(int position, int actionKey, String actionMsg) {
- if (mSuggestionsAdapter == null) {
- return false;
- }
- Cursor c = mSuggestionsAdapter.getCursor();
- if ((c != null) && c.moveToPosition(position)) {
-
- Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
-
- // launch the intent
- launchIntent(intent);
-
- return true;
- }
- return false;
- }
/**
* Launches an intent, including any special intent handling.
@@ -1149,21 +547,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * If the intent is to open an HTTP or HTTPS URL, we set
- * {@link Browser#EXTRA_APPLICATION_ID} so that any existing browser window that
- * has been opened by us for the same URL will be reused.
- */
- private void setBrowserApplicationId(Intent intent) {
- Uri data = intent.getData();
- if (Intent.ACTION_VIEW.equals(intent.getAction()) && data != null) {
- String scheme = data.getScheme();
- if (scheme != null && scheme.startsWith("http")) {
- intent.putExtra(Browser.EXTRA_APPLICATION_ID, data.toString());
- }
- }
- }
-
- /**
* Sets the list item selection in the AutoCompleteTextView's ListView.
*/
public void setListSelection(int index) {
@@ -1171,62 +554,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * When a particular suggestion has been selected, perform the various lookups required
- * to use the suggestion. This includes checking the cursor for suggestion-specific data,
- * and/or falling back to the XML for defaults; It also creates REST style Uri data when
- * the suggestion includes a data id.
- *
- * @param c The suggestions cursor, moved to the row of the user's selection
- * @param actionKey The key code of the action key that was pressed,
- * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
- * @param actionMsg The message for the action key that was pressed,
- * or <code>null</code> if none.
- * @return An intent for the suggestion at the cursor's position.
- */
- private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
- try {
- // use specific action if supplied, or default action if supplied, or fixed default
- String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
-
- if (action == null) {
- action = mSearchable.getSuggestIntentAction();
- }
- if (action == null) {
- action = Intent.ACTION_SEARCH;
- }
-
- // use specific data if supplied, or default data if supplied
- String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
- if (data == null) {
- data = mSearchable.getSuggestIntentData();
- }
- // then, if an ID was provided, append it.
- if (data != null) {
- String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
- if (id != null) {
- data = data + "/" + Uri.encode(id);
- }
- }
- Uri dataUri = (data == null) ? null : Uri.parse(data);
-
- String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
- String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
-
- return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
- } catch (RuntimeException e ) {
- int rowNum;
- try { // be really paranoid now
- rowNum = c.getPosition();
- } catch (RuntimeException e2 ) {
- rowNum = -1;
- }
- Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum +
- " returned exception" + e.toString());
- return null;
- }
- }
-
- /**
* Constructs an intent from the given information and the search dialog state.
*
* @param action Intent action.
@@ -1271,30 +598,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * For a given suggestion and a given cursor row, get the action message. If not provided
- * by the specific row/column, also check for a single definition (for the action key).
- *
- * @param c The cursor providing suggestions
- * @param actionKey The actionkey record being examined
- *
- * @return Returns a string, or null if no action key message for this suggestion
- */
- private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
- String result = null;
- // check first in the cursor data, for a suggestion-specific message
- final String column = actionKey.getSuggestActionMsgColumn();
- if (column != null) {
- result = SuggestionsAdapter.getColumnString(c, column);
- }
- // If the cursor didn't give us a message, see if there's a single message defined
- // for the actionkey (for all suggestions)
- if (result == null) {
- result = actionKey.getSuggestActionMsg();
- }
- return result;
- }
-
- /**
* The root element in the search bar layout. This is a custom view just to override
* the handling of the back button.
*/
@@ -1315,21 +618,22 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
/**
- * Overrides the handling of the back key to move back to the previous sources or dismiss
- * the search dialog, instead of dismissing the input method.
+ * Overrides the handling of the back key to move back to the previous
+ * sources or dismiss the search dialog, instead of dismissing the input
+ * method.
*/
@Override
public boolean dispatchKeyEventPreIme(KeyEvent event) {
- if (DBG) Log.d(LOG_TAG, "onKeyPreIme(" + event + ")");
+ if (DBG)
+ Log.d(LOG_TAG, "onKeyPreIme(" + event + ")");
if (mSearchDialog != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
KeyEvent.DispatcherState state = getKeyDispatcherState();
if (state != null) {
- if (event.getAction() == KeyEvent.ACTION_DOWN
- && event.getRepeatCount() == 0) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
state.startTracking(event, this);
return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP
- && !event.isCanceled() && state.isTracking(event)) {
+ } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()
+ && state.isTracking(event)) {
mSearchDialog.onBackPressed();
return true;
}
@@ -1339,86 +643,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
}
}
- /**
- * Local subclass for AutoCompleteTextView.
- */
- public static class SearchAutoComplete extends AutoCompleteTextView {
-
- private int mThreshold;
-
- public SearchAutoComplete(Context context) {
- super(context);
- mThreshold = getThreshold();
- }
-
- public SearchAutoComplete(Context context, AttributeSet attrs) {
- super(context, attrs);
- mThreshold = getThreshold();
- }
-
- public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mThreshold = getThreshold();
- }
-
- @Override
- public void setThreshold(int threshold) {
- super.setThreshold(threshold);
- mThreshold = threshold;
- }
-
- /**
- * Returns true if the text field is empty, or contains only whitespace.
- */
- private boolean isEmpty() {
- return TextUtils.getTrimmedLength(getText()) == 0;
- }
-
- /**
- * We override this method to avoid replacing the query box text
- * when a suggestion is clicked.
- */
- @Override
- protected void replaceText(CharSequence text) {
- }
-
- /**
- * We override this method to avoid an extra onItemClick being called on the
- * drop-down's OnItemClickListener by {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)}
- * when an item is clicked with the trackball.
- */
- @Override
- public void performCompletion() {
- }
-
- /**
- * We override this method to be sure and show the soft keyboard if appropriate when
- * the TextView has focus.
- */
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
-
- if (hasWindowFocus) {
- InputMethodManager inputManager = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- inputManager.showSoftInput(this, 0);
- // If in landscape mode, then make sure that
- // the ime is in front of the dropdown.
- if (isLandscapeMode(getContext())) {
- ensureImeVisible(true);
- }
- }
- }
-
- /**
- * We override this method so that we can allow a threshold of zero, which ACTV does not.
- */
- @Override
- public boolean enoughToFilter() {
- return mThreshold <= 0 || super.enoughToFilter();
- }
-
+ private boolean isEmpty(AutoCompleteTextView actv) {
+ return TextUtils.getTrimmedLength(actv.getText()) == 0;
}
@Override
@@ -1435,106 +661,58 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS
cancel();
}
- /**
- * Implements OnItemClickListener
- */
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
- launchSuggestion(position);
+ private boolean onClosePressed() {
+ // Dismiss the dialog if close button is pressed when there's no query text
+ if (isEmpty(mSearchAutoComplete)) {
+ dismiss();
+ return true;
+ }
+
+ return false;
}
- /**
- * Implements OnItemSelectedListener
- */
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
- // A suggestion has been selected, rewrite the query if possible,
- // otherwise the restore the original query.
- if (REWRITE_QUERIES) {
- rewriteQueryFromSuggestion(position);
- }
- }
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onNothingSelected(AdapterView<?> parent) {
- if (DBG) Log.d(LOG_TAG, "onNothingSelected()");
- }
-
- /**
- * Query rewriting.
- */
-
- private void rewriteQueryFromSuggestion(int position) {
- Cursor c = mSuggestionsAdapter.getCursor();
- if (c == null) {
- return;
- }
- if (c.moveToPosition(position)) {
- // Get the new query from the suggestion.
- CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
- if (newQuery != null) {
- // The suggestion rewrites the query.
- if (DBG) Log.d(LOG_TAG, "Rewriting query to '" + newQuery + "'");
- // Update the text field, without getting new suggestions.
- setQuery(newQuery);
- } else {
- // The suggestion does not rewrite the query, restore the user's query.
- if (DBG) Log.d(LOG_TAG, "Suggestion gives no rewrite, restoring user query.");
- restoreUserQuery();
- }
- } else {
- // We got a bad position, restore the user's query.
- Log.w(LOG_TAG, "Bad suggestion position: " + position);
- restoreUserQuery();
- }
- }
-
- /**
- * Restores the query entered by the user if needed.
- */
- private void restoreUserQuery() {
- if (DBG) Log.d(LOG_TAG, "Restoring query to '" + mUserQuery + "'");
- setQuery(mUserQuery);
- }
-
- /**
- * Sets the text in the query box, without updating the suggestions.
- */
- private void setQuery(CharSequence query) {
- mSearchAutoComplete.setText(query, false);
- if (query != null) {
- mSearchAutoComplete.setSelection(query.length());
- }
- }
-
- /**
- * Sets the text in the query box, updating the suggestions.
- */
- private void setUserQuery(String query) {
- if (query == null) {
- query = "";
- }
- mUserQuery = query;
- mSearchAutoComplete.setText(query);
- mSearchAutoComplete.setSelection(query.length());
- }
+ private final SearchView.OnCloseListener mOnCloseListener = new SearchView.OnCloseListener() {
- /**
- * Debugging Support
- */
+ public boolean onClose() {
+ return onClosePressed();
+ }
+ };
+
+ private final SearchView.OnQueryChangeListener mOnQueryChangeListener =
+ new SearchView.OnQueryChangeListener() {
+
+ public boolean onSubmitQuery(String query) {
+ dismiss();
+ return false;
+ }
+
+ public boolean onQueryTextChanged(String newText) {
+ return false;
+ }
+ };
+
+ private final SearchView.OnSuggestionSelectionListener mOnSuggestionSelectionListener =
+ new SearchView.OnSuggestionSelectionListener() {
+
+ public boolean onSuggestionSelected(int position) {
+ return false;
+ }
+
+ public boolean onSuggestionClicked(int position) {
+ dismiss();
+ return false;
+ }
+ };
/**
- * For debugging only, sample the millisecond clock and log it.
- * Uses AtomicLong so we can use in multiple threads
+ * Sets the text in the query box, updating the suggestions.
*/
- private AtomicLong mLastLogTime = new AtomicLong(SystemClock.uptimeMillis());
- private void dbgLogTiming(final String caller) {
- long millis = SystemClock.uptimeMillis();
- long oldTime = mLastLogTime.getAndSet(millis);
- long delta = millis - oldTime;
- final String report = millis + " (+" + delta + ") ticks for Search keystroke in " + caller;
- Log.d(LOG_TAG,report);
+ private void setUserQuery(String query) {
+ if (query == null) {
+ query = "";
+ }
+ mUserQuery = query;
+ mSearchAutoComplete.setText(query);
+ mSearchAutoComplete.setSelection(query.length());
}
}
diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java
deleted file mode 100644
index 5705bff..0000000
--- a/core/java/android/app/SuggestionsAdapter.java
+++ /dev/null
@@ -1,653 +0,0 @@
-/*
- * Copyright (C) 2009 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 android.app;
-
-import com.android.internal.R;
-
-import android.content.ComponentName;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.ContentResolver.OpenResourceIdResult;
-import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.content.res.ColorStateList;
-import android.content.res.Resources;
-import android.database.Cursor;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
-import android.graphics.drawable.StateListDrawable;
-import android.net.Uri;
-import android.os.Bundle;
-import android.text.Spannable;
-import android.text.SpannableString;
-import android.text.TextUtils;
-import android.text.style.TextAppearanceSpan;
-import android.util.Log;
-import android.util.SparseArray;
-import android.util.TypedValue;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Filter;
-import android.widget.ImageView;
-import android.widget.ResourceCursorAdapter;
-import android.widget.TextView;
-
-import java.io.FileNotFoundException;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.WeakHashMap;
-
-/**
- * Provides the contents for the suggestion drop-down list.in {@link SearchDialog}.
- *
- * @hide
- */
-class SuggestionsAdapter extends ResourceCursorAdapter {
-
- private static final boolean DBG = false;
- private static final String LOG_TAG = "SuggestionsAdapter";
- private static final int QUERY_LIMIT = 50;
-
- private SearchManager mSearchManager;
- private SearchDialog mSearchDialog;
- private SearchableInfo mSearchable;
- private Context mProviderContext;
- private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache;
- private boolean mClosed = false;
-
- // URL color
- private ColorStateList mUrlColor;
-
- // Cached column indexes, updated when the cursor changes.
- private int mText1Col;
- private int mText2Col;
- private int mText2UrlCol;
- private int mIconName1Col;
- private int mIconName2Col;
-
- static final int NONE = -1;
-
- private final Runnable mStartSpinnerRunnable;
- private final Runnable mStopSpinnerRunnable;
-
- /**
- * The amount of time we delay in the filter when the user presses the delete key.
- * @see Filter#setDelayer(android.widget.Filter.Delayer).
- */
- private static final long DELETE_KEY_POST_DELAY = 500L;
-
- public SuggestionsAdapter(Context context, SearchDialog searchDialog,
- SearchableInfo searchable,
- WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache) {
- super(context,
- com.android.internal.R.layout.search_dropdown_item_icons_2line,
- null, // no initial cursor
- true); // auto-requery
- mSearchManager = (SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE);
- mSearchDialog = searchDialog;
- mSearchable = searchable;
-
- // set up provider resources (gives us icons, etc.)
- Context activityContext = mSearchable.getActivityContext(mContext);
- mProviderContext = mSearchable.getProviderContext(mContext, activityContext);
-
- mOutsideDrawablesCache = outsideDrawablesCache;
-
- mStartSpinnerRunnable = new Runnable() {
- public void run() {
- mSearchDialog.setWorking(true);
- }
- };
-
- mStopSpinnerRunnable = new Runnable() {
- public void run() {
- mSearchDialog.setWorking(false);
- }
- };
-
- // delay 500ms when deleting
- getFilter().setDelayer(new Filter.Delayer() {
-
- private int mPreviousLength = 0;
-
- public long getPostingDelay(CharSequence constraint) {
- if (constraint == null) return 0;
-
- long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0;
- mPreviousLength = constraint.length();
- return delay;
- }
- });
- }
-
- /**
- * Overridden to always return <code>false</code>, since we cannot be sure that
- * suggestion sources return stable IDs.
- */
- @Override
- public boolean hasStableIds() {
- return false;
- }
-
- /**
- * Use the search suggestions provider to obtain a live cursor. This will be called
- * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
- * The results will be processed in the UI thread and changeCursor() will be called.
- */
- @Override
- public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
- if (DBG) Log.d(LOG_TAG, "runQueryOnBackgroundThread(" + constraint + ")");
- String query = (constraint == null) ? "" : constraint.toString();
- /**
- * for in app search we show the progress spinner until the cursor is returned with
- * the results.
- */
- Cursor cursor = null;
- mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
- try {
- cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT);
- // trigger fill window so the spinner stays up until the results are copied over and
- // closer to being ready
- if (cursor != null) {
- cursor.getCount();
- return cursor;
- }
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "Search suggestions query threw an exception.", e);
- }
- // If cursor is null or an exception was thrown, stop the spinner and return null.
- // changeCursor doesn't get called if cursor is null
- mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
- return null;
- }
-
- public void close() {
- if (DBG) Log.d(LOG_TAG, "close()");
- changeCursor(null);
- mClosed = true;
- }
-
- @Override
- public void notifyDataSetChanged() {
- if (DBG) Log.d(LOG_TAG, "notifyDataSetChanged");
- super.notifyDataSetChanged();
-
- mSearchDialog.onDataSetChanged();
-
- updateSpinnerState(getCursor());
- }
-
- @Override
- public void notifyDataSetInvalidated() {
- if (DBG) Log.d(LOG_TAG, "notifyDataSetInvalidated");
- super.notifyDataSetInvalidated();
-
- updateSpinnerState(getCursor());
- }
-
- private void updateSpinnerState(Cursor cursor) {
- Bundle extras = cursor != null ? cursor.getExtras() : null;
- if (DBG) {
- Log.d(LOG_TAG, "updateSpinnerState - extra = "
- + (extras != null
- ? extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)
- : null));
- }
- // Check if the Cursor indicates that the query is not complete and show the spinner
- if (extras != null
- && extras.getBoolean(SearchManager.CURSOR_EXTRA_KEY_IN_PROGRESS)) {
- mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable);
- return;
- }
- // If cursor is null or is done, stop the spinner
- mSearchDialog.getWindow().getDecorView().post(mStopSpinnerRunnable);
- }
-
- /**
- * Cache columns.
- */
- @Override
- public void changeCursor(Cursor c) {
- if (DBG) Log.d(LOG_TAG, "changeCursor(" + c + ")");
-
- if (mClosed) {
- Log.w(LOG_TAG, "Tried to change cursor after adapter was closed.");
- if (c != null) c.close();
- return;
- }
-
- try {
- super.changeCursor(c);
-
- if (c != null) {
- mText1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1);
- mText2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2);
- mText2UrlCol = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2_URL);
- mIconName1Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1);
- mIconName2Col = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2);
- }
- } catch (Exception e) {
- Log.e(LOG_TAG, "error changing cursor and caching columns", e);
- }
- }
-
- /**
- * Tags the view with cached child view look-ups.
- */
- @Override
- public View newView(Context context, Cursor cursor, ViewGroup parent) {
- View v = super.newView(context, cursor, parent);
- v.setTag(new ChildViewCache(v));
- return v;
- }
-
- /**
- * Cache of the child views of drop-drown list items, to avoid looking up the children
- * each time the contents of a list item are changed.
- */
- private final static class ChildViewCache {
- public final TextView mText1;
- public final TextView mText2;
- public final ImageView mIcon1;
- public final ImageView mIcon2;
-
- public ChildViewCache(View v) {
- mText1 = (TextView) v.findViewById(com.android.internal.R.id.text1);
- mText2 = (TextView) v.findViewById(com.android.internal.R.id.text2);
- mIcon1 = (ImageView) v.findViewById(com.android.internal.R.id.icon1);
- mIcon2 = (ImageView) v.findViewById(com.android.internal.R.id.icon2);
- }
- }
-
- @Override
- public void bindView(View view, Context context, Cursor cursor) {
- ChildViewCache views = (ChildViewCache) view.getTag();
-
- if (views.mText1 != null) {
- String text1 = getStringOrNull(cursor, mText1Col);
- setViewText(views.mText1, text1);
- }
- if (views.mText2 != null) {
- // First check TEXT_2_URL
- CharSequence text2 = getStringOrNull(cursor, mText2UrlCol);
- if (text2 != null) {
- text2 = formatUrl(text2);
- } else {
- text2 = getStringOrNull(cursor, mText2Col);
- }
-
- // If no second line of text is indicated, allow the first line of text
- // to be up to two lines if it wants to be.
- if (TextUtils.isEmpty(text2)) {
- if (views.mText1 != null) {
- views.mText1.setSingleLine(false);
- views.mText1.setMaxLines(2);
- }
- } else {
- if (views.mText1 != null) {
- views.mText1.setSingleLine(true);
- views.mText1.setMaxLines(1);
- }
- }
- setViewText(views.mText2, text2);
- }
-
- if (views.mIcon1 != null) {
- setViewDrawable(views.mIcon1, getIcon1(cursor));
- }
- if (views.mIcon2 != null) {
- setViewDrawable(views.mIcon2, getIcon2(cursor));
- }
- }
-
- private CharSequence formatUrl(CharSequence url) {
- if (mUrlColor == null) {
- // Lazily get the URL color from the current theme.
- TypedValue colorValue = new TypedValue();
- mContext.getTheme().resolveAttribute(R.attr.textColorSearchUrl, colorValue, true);
- mUrlColor = mContext.getResources().getColorStateList(colorValue.resourceId);
- }
-
- SpannableString text = new SpannableString(url);
- text.setSpan(new TextAppearanceSpan(null, 0, 0, mUrlColor, null),
- 0, url.length(),
- Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return text;
- }
-
- private void setViewText(TextView v, CharSequence text) {
- // Set the text even if it's null, since we need to clear any previous text.
- v.setText(text);
-
- if (TextUtils.isEmpty(text)) {
- v.setVisibility(View.GONE);
- } else {
- v.setVisibility(View.VISIBLE);
- }
- }
-
- private Drawable getIcon1(Cursor cursor) {
- if (mIconName1Col < 0) {
- return null;
- }
- String value = cursor.getString(mIconName1Col);
- Drawable drawable = getDrawableFromResourceValue(value);
- if (drawable != null) {
- return drawable;
- }
- return getDefaultIcon1(cursor);
- }
-
- private Drawable getIcon2(Cursor cursor) {
- if (mIconName2Col < 0) {
- return null;
- }
- String value = cursor.getString(mIconName2Col);
- return getDrawableFromResourceValue(value);
- }
-
- /**
- * Sets the drawable in an image view, makes sure the view is only visible if there
- * is a drawable.
- */
- private void setViewDrawable(ImageView v, Drawable drawable) {
- // Set the icon even if the drawable is null, since we need to clear any
- // previous icon.
- v.setImageDrawable(drawable);
-
- if (drawable == null) {
- v.setVisibility(View.GONE);
- } else {
- v.setVisibility(View.VISIBLE);
-
- // This is a hack to get any animated drawables (like a 'working' spinner)
- // to animate. You have to setVisible true on an AnimationDrawable to get
- // it to start animating, but it must first have been false or else the
- // call to setVisible will be ineffective. We need to clear up the story
- // about animated drawables in the future, see http://b/1878430.
- drawable.setVisible(false, false);
- drawable.setVisible(true, false);
- }
- }
-
- /**
- * Gets the text to show in the query field when a suggestion is selected.
- *
- * @param cursor The Cursor to read the suggestion data from. The Cursor should already
- * be moved to the suggestion that is to be read from.
- * @return The text to show, or <code>null</code> if the query should not be
- * changed when selecting this suggestion.
- */
- @Override
- public CharSequence convertToString(Cursor cursor) {
- if (cursor == null) {
- return null;
- }
-
- String query = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_QUERY);
- if (query != null) {
- return query;
- }
-
- if (mSearchable.shouldRewriteQueryFromData()) {
- String data = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
- if (data != null) {
- return data;
- }
- }
-
- if (mSearchable.shouldRewriteQueryFromText()) {
- String text1 = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_TEXT_1);
- if (text1 != null) {
- return text1;
- }
- }
-
- return null;
- }
-
- /**
- * This method is overridden purely to provide a bit of protection against
- * flaky content providers.
- *
- * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
- */
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- try {
- return super.getView(position, convertView, parent);
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "Search suggestions cursor threw exception.", e);
- // Put exception string in item title
- View v = newView(mContext, mCursor, parent);
- if (v != null) {
- ChildViewCache views = (ChildViewCache) v.getTag();
- TextView tv = views.mText1;
- tv.setText(e.toString());
- }
- return v;
- }
- }
-
- /**
- * Gets a drawable given a value provided by a suggestion provider.
- *
- * This value could be just the string value of a resource id
- * (e.g., "2130837524"), in which case we will try to retrieve a drawable from
- * the provider's resources. If the value is not an integer, it is
- * treated as a Uri and opened with
- * {@link ContentResolver#openOutputStream(android.net.Uri, String)}.
- *
- * All resources and URIs are read using the suggestion provider's context.
- *
- * If the string is not formatted as expected, or no drawable can be found for
- * the provided value, this method returns null.
- *
- * @param drawableId a string like "2130837524",
- * "android.resource://com.android.alarmclock/2130837524",
- * or "content://contacts/photos/253".
- * @return a Drawable, or null if none found
- */
- private Drawable getDrawableFromResourceValue(String drawableId) {
- if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) {
- return null;
- }
- try {
- // First, see if it's just an integer
- int resourceId = Integer.parseInt(drawableId);
- // It's an int, look for it in the cache
- String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE
- + "://" + mProviderContext.getPackageName() + "/" + resourceId;
- // Must use URI as cache key, since ints are app-specific
- Drawable drawable = checkIconCache(drawableUri);
- if (drawable != null) {
- return drawable;
- }
- // Not cached, find it by resource ID
- drawable = mProviderContext.getResources().getDrawable(resourceId);
- // Stick it in the cache, using the URI as key
- storeInIconCache(drawableUri, drawable);
- return drawable;
- } catch (NumberFormatException nfe) {
- // It's not an integer, use it as a URI
- Drawable drawable = checkIconCache(drawableId);
- if (drawable != null) {
- return drawable;
- }
- Uri uri = Uri.parse(drawableId);
- drawable = getDrawable(uri);
- storeInIconCache(drawableId, drawable);
- return drawable;
- } catch (Resources.NotFoundException nfe) {
- // It was an integer, but it couldn't be found, bail out
- Log.w(LOG_TAG, "Icon resource not found: " + drawableId);
- return null;
- }
- }
-
- /**
- * Gets a drawable by URI, without using the cache.
- *
- * @return A drawable, or {@code null} if the drawable could not be loaded.
- */
- private Drawable getDrawable(Uri uri) {
- try {
- String scheme = uri.getScheme();
- if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) {
- // Load drawables through Resources, to get the source density information
- OpenResourceIdResult r =
- mProviderContext.getContentResolver().getResourceId(uri);
- try {
- return r.r.getDrawable(r.id);
- } catch (Resources.NotFoundException ex) {
- throw new FileNotFoundException("Resource does not exist: " + uri);
- }
- } else {
- // Let the ContentResolver handle content and file URIs.
- InputStream stream = mProviderContext.getContentResolver().openInputStream(uri);
- if (stream == null) {
- throw new FileNotFoundException("Failed to open " + uri);
- }
- try {
- return Drawable.createFromStream(stream, null);
- } finally {
- try {
- stream.close();
- } catch (IOException ex) {
- Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex);
- }
- }
- }
- } catch (FileNotFoundException fnfe) {
- Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage());
- return null;
- }
- }
-
- private Drawable checkIconCache(String resourceUri) {
- Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri);
- if (cached == null) {
- return null;
- }
- if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri);
- return cached.newDrawable();
- }
-
- private void storeInIconCache(String resourceUri, Drawable drawable) {
- if (drawable != null) {
- mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState());
- }
- }
-
- /**
- * Gets the left-hand side icon that will be used for the current suggestion
- * if the suggestion contains an icon column but no icon or a broken icon.
- *
- * @param cursor A cursor positioned at the current suggestion.
- * @return A non-null drawable.
- */
- private Drawable getDefaultIcon1(Cursor cursor) {
- // Check the component that gave us the suggestion
- Drawable drawable = getActivityIconWithCache(mSearchable.getSearchActivity());
- if (drawable != null) {
- return drawable;
- }
-
- // Fall back to a default icon
- return mContext.getPackageManager().getDefaultActivityIcon();
- }
-
- /**
- * Gets the activity or application icon for an activity.
- * Uses the local icon cache for fast repeated lookups.
- *
- * @param component Name of an activity.
- * @return A drawable, or {@code null} if neither the activity nor the application
- * has an icon set.
- */
- private Drawable getActivityIconWithCache(ComponentName component) {
- // First check the icon cache
- String componentIconKey = component.flattenToShortString();
- // Using containsKey() since we also store null values.
- if (mOutsideDrawablesCache.containsKey(componentIconKey)) {
- Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey);
- return cached == null ? null : cached.newDrawable(mProviderContext.getResources());
- }
- // Then try the activity or application icon
- Drawable drawable = getActivityIcon(component);
- // Stick it in the cache so we don't do this lookup again.
- Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState();
- mOutsideDrawablesCache.put(componentIconKey, toCache);
- return drawable;
- }
-
- /**
- * Gets the activity or application icon for an activity.
- *
- * @param component Name of an activity.
- * @return A drawable, or {@code null} if neither the acitivy or the application
- * have an icon set.
- */
- private Drawable getActivityIcon(ComponentName component) {
- PackageManager pm = mContext.getPackageManager();
- final ActivityInfo activityInfo;
- try {
- activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA);
- } catch (NameNotFoundException ex) {
- Log.w(LOG_TAG, ex.toString());
- return null;
- }
- int iconId = activityInfo.getIconResource();
- if (iconId == 0) return null;
- String pkg = component.getPackageName();
- Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo);
- if (drawable == null) {
- Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for "
- + component.flattenToShortString());
- return null;
- }
- return drawable;
- }
-
- /**
- * Gets the value of a string column by name.
- *
- * @param cursor Cursor to read the value from.
- * @param columnName The name of the column to read.
- * @return The value of the given column, or <code>null</null>
- * if the cursor does not contain the given column.
- */
- public static String getColumnString(Cursor cursor, String columnName) {
- int col = cursor.getColumnIndex(columnName);
- return getStringOrNull(cursor, col);
- }
-
- private static String getStringOrNull(Cursor cursor, int col) {
- if (col == NONE) {
- return null;
- }
- try {
- return cursor.getString(col);
- } catch (Exception e) {
- Log.e(LOG_TAG,
- "unexpected error retrieving valid column from cursor, "
- + "did the remote process die?", e);
- return null;
- }
- }
-}
diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java
index 029aebf..b296b77 100644
--- a/core/java/android/widget/SearchView.java
+++ b/core/java/android/widget/SearchView.java
@@ -18,8 +18,6 @@ package android.widget;
import static android.widget.SuggestionsAdapter.getColumnString;
-import com.android.internal.R;
-
import android.app.PendingIntent;
import android.app.SearchManager;
import android.app.SearchableInfo;
@@ -29,6 +27,7 @@ import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.database.Cursor;
@@ -50,6 +49,8 @@ import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.TextView.OnEditorActionListener;
+import com.android.internal.R;
+
import java.util.WeakHashMap;
/**
@@ -84,7 +85,7 @@ public class SearchView extends LinearLayout {
private View mCloseButton;
private View mSearchEditFrame;
private View mVoiceButton;
- private AutoCompleteTextView mQueryTextView;
+ private SearchAutoComplete mQueryTextView;
private boolean mSubmitButtonEnabled;
private CharSequence mQueryHint;
private boolean mQueryRefinement;
@@ -181,7 +182,9 @@ public class SearchView extends LinearLayout {
inflater.inflate(R.layout.search_view, this, true);
mSearchButton = findViewById(R.id.search_button);
- mQueryTextView = (AutoCompleteTextView) findViewById(R.id.search_src_text);
+ mQueryTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
+ mQueryTextView.setSearchView(this);
+
mSearchEditFrame = findViewById(R.id.search_edit_frame);
mSubmitButton = findViewById(R.id.search_go_btn);
mCloseButton = findViewById(R.id.search_close_btn);
@@ -196,6 +199,7 @@ public class SearchView extends LinearLayout {
mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
mQueryTextView.setOnItemClickListener(mOnItemClickListener);
mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
+ mQueryTextView.setOnKeyListener(mTextKeyListener);
// Inform any listener of focus changes
mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
@@ -558,6 +562,148 @@ public class SearchView extends LinearLayout {
return super.onKeyDown(keyCode, event);
}
+ /**
+ * React to the user typing "enter" or other hardwired keys while typing in
+ * the search box. This handles these special keys while the edit box has
+ * focus.
+ */
+ View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ // guard against possible race conditions
+ if (mSearchable == null) {
+ return false;
+ }
+
+ if (DBG) {
+ Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: "
+ + mQueryTextView.getListSelection());
+ }
+
+ // If a suggestion is selected, handle enter, search key, and action keys
+ // as presses on the selected suggestion
+ if (mQueryTextView.isPopupShowing()
+ && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) {
+ return onSuggestionsKey(v, keyCode, event);
+ }
+
+ // If there is text in the query box, handle enter, and action keys
+ // The search key is handled by the dialog's onKeyDown().
+ if (!mQueryTextView.isEmpty()) {
+ if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
+ v.cancelLongPress();
+
+ // Launch as a regular search.
+ launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText()
+ .toString());
+ return true;
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+ if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
+ launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView
+ .getText().toString());
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ };
+
+ /**
+ * React to the user typing while in the suggestions list. First, check for
+ * action keys. If not handled, try refocusing regular characters into the
+ * EditText.
+ */
+ private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
+ // guard against possible race conditions (late arrival after dismiss)
+ if (mSearchable == null) {
+ return false;
+ }
+ if (mSuggestionsAdapter == null) {
+ return false;
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+
+ // First, check for enter or search (both of which we'll treat as a
+ // "click")
+ if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
+ int position = mQueryTextView.getListSelection();
+ return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
+ }
+
+ // Next, check for left/right moves, which we use to "return" the
+ // user to the edit view
+ if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
+ // give "focus" to text editor, with cursor at the beginning if
+ // left key, at end if right key
+ // TODO: Reverse left/right for right-to-left languages, e.g.
+ // Arabic
+ int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView
+ .length();
+ mQueryTextView.setSelection(selPoint);
+ mQueryTextView.setListSelection(0);
+ mQueryTextView.clearListSelection();
+ mQueryTextView.ensureImeVisible(true);
+
+ return true;
+ }
+
+ // Next, check for an "up and out" move
+ if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) {
+ // TODO: restoreUserQuery();
+ // let ACTV complete the move
+ return false;
+ }
+
+ // Next, check for an "action key"
+ SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
+ if ((actionKey != null)
+ && ((actionKey.getSuggestActionMsg() != null) || (actionKey
+ .getSuggestActionMsgColumn() != null))) {
+ // launch suggestion using action key column
+ int position = mQueryTextView.getListSelection();
+ if (position != ListView.INVALID_POSITION) {
+ Cursor c = mSuggestionsAdapter.getCursor();
+ if (c.moveToPosition(position)) {
+ final String actionMsg = getActionKeyMessage(c, actionKey);
+ if (actionMsg != null && (actionMsg.length() > 0)) {
+ return onItemClicked(position, keyCode, actionMsg);
+ }
+ }
+ }
+ }
+ }
+ return false;
+ }
+
+ /**
+ * For a given suggestion and a given cursor row, get the action message. If
+ * not provided by the specific row/column, also check for a single
+ * definition (for the action key).
+ *
+ * @param c The cursor providing suggestions
+ * @param actionKey The actionkey record being examined
+ *
+ * @return Returns a string, or null if no action key message for this
+ * suggestion
+ */
+ private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
+ String result = null;
+ // check first in the cursor data, for a suggestion-specific message
+ final String column = actionKey.getSuggestActionMsgColumn();
+ if (column != null) {
+ result = SuggestionsAdapter.getColumnString(c, column);
+ }
+ // If the cursor didn't give us a message, see if there's a single
+ // message defined
+ // for the actionkey (for all suggestions)
+ if (result == null) {
+ result = actionKey.getSuggestActionMsg();
+ }
+ return result;
+ }
+
private void updateQueryHint() {
if (mQueryHint != null) {
mQueryTextView.setHint(mQueryHint);
@@ -710,20 +856,34 @@ public class SearchView extends LinearLayout {
}
}
+ private boolean onItemClicked(int position, int actionKey, String actionMsg) {
+ if (mOnSuggestionListener == null
+ || !mOnSuggestionListener.onSuggestionClicked(position)) {
+ launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
+ setImeVisibility(false);
+ dismissSuggestions();
+ return true;
+ }
+ return false;
+ }
+
+ private boolean onItemSelected(int position) {
+ if (mOnSuggestionListener == null
+ || !mOnSuggestionListener.onSuggestionSelected(position)) {
+ rewriteQueryFromSuggestion(position);
+ return true;
+ }
+ return false;
+ }
+
private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
/**
* Implements OnItemClickListener
*/
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- if (DBG)
- Log.d(LOG_TAG, "onItemClick() position " + position);
- if (mOnSuggestionListener == null
- || !mOnSuggestionListener.onSuggestionClicked(position)) {
- launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
- setImeVisibility(false);
- dismissSuggestions();
- }
+ if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
+ onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
}
};
@@ -733,14 +893,8 @@ public class SearchView extends LinearLayout {
* Implements OnItemSelectedListener
*/
public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (DBG)
- Log.d(LOG_TAG, "onItemSelected() position " + position);
- // A suggestion has been selected, rewrite the query if possible,
- // otherwise the restore the original query.
- if (mOnSuggestionListener == null
- || !mOnSuggestionListener.onSuggestionSelected(position)) {
- rewriteQueryFromSuggestion(position);
- }
+ if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
+ SearchView.this.onItemSelected(position);
}
/**
@@ -1003,6 +1157,11 @@ public class SearchView extends LinearLayout {
}
}
+ static boolean isLandscapeMode(Context context) {
+ return context.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
/**
* Callback to watch the text field for empty/non-empty
*/
@@ -1018,4 +1177,93 @@ public class SearchView extends LinearLayout {
public void afterTextChanged(Editable s) {
}
};
+
+ /**
+ * Local subclass for AutoCompleteTextView.
+ * @hide
+ */
+ public static class SearchAutoComplete extends AutoCompleteTextView {
+
+ private int mThreshold;
+ private SearchView mSearchView;
+
+ public SearchAutoComplete(Context context) {
+ super(context);
+ mThreshold = getThreshold();
+ }
+
+ public SearchAutoComplete(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mThreshold = getThreshold();
+ }
+
+ public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mThreshold = getThreshold();
+ }
+
+ void setSearchView(SearchView searchView) {
+ mSearchView = searchView;
+ }
+
+ @Override
+ public void setThreshold(int threshold) {
+ super.setThreshold(threshold);
+ mThreshold = threshold;
+ }
+
+ /**
+ * Returns true if the text field is empty, or contains only whitespace.
+ */
+ private boolean isEmpty() {
+ return TextUtils.getTrimmedLength(getText()) == 0;
+ }
+
+ /**
+ * We override this method to avoid replacing the query box text when a
+ * suggestion is clicked.
+ */
+ @Override
+ protected void replaceText(CharSequence text) {
+ }
+
+ /**
+ * We override this method to avoid an extra onItemClick being called on
+ * the drop-down's OnItemClickListener by
+ * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is
+ * clicked with the trackball.
+ */
+ @Override
+ public void performCompletion() {
+ }
+
+ /**
+ * We override this method to be sure and show the soft keyboard if
+ * appropriate when the TextView has focus.
+ */
+ @Override
+ public void onWindowFocusChanged(boolean hasWindowFocus) {
+ super.onWindowFocusChanged(hasWindowFocus);
+
+ if (hasWindowFocus) {
+ InputMethodManager inputManager = (InputMethodManager) getContext()
+ .getSystemService(Context.INPUT_METHOD_SERVICE);
+ inputManager.showSoftInput(this, 0);
+ // If in landscape mode, then make sure that
+ // the ime is in front of the dropdown.
+ if (isLandscapeMode(getContext())) {
+ ensureImeVisible(true);
+ }
+ }
+ }
+
+ /**
+ * We override this method so that we can allow a threshold of zero,
+ * which ACTV does not.
+ */
+ @Override
+ public boolean enoughToFilter() {
+ return mThreshold <= 0 || super.enoughToFilter();
+ }
+ }
}
diff --git a/core/res/res/layout/search_bar.xml b/core/res/res/layout/search_bar.xml
index 7935e2a..4bc2d1a 100644
--- a/core/res/res/layout/search_bar.xml
+++ b/core/res/res/layout/search_bar.xml
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
-/* apps/common/res/layout/SearchBar.xml
+/*
**
** Copyright 2007, The Android Open Source Project
**
@@ -23,87 +23,36 @@
android:id="@+id/search_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
+ android:orientation="horizontal"
android:focusable="true"
+ android:background="?attr/actionModeBackground"
android:descendantFocusability="afterDescendants">
- <!-- Outer layout defines the entire search bar at the top of the screen -->
- <LinearLayout
- android:id="@+id/search_plate"
+ <RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="vertical"
- android:paddingLeft="12dip"
- android:paddingRight="6dip"
- android:paddingTop="7dip"
- android:paddingBottom="16dip"
- android:background="@drawable/search_plate_global" >
+ android:layout_gravity="center_vertical"
+ >
- <!-- This is actually used for the badge icon *or* the badge label (or neither) -->
- <TextView
- android:id="@+id/search_badge"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:layout_marginBottom="2dip"
- android:drawablePadding="0dip"
- android:textAppearance="?android:attr/textAppearanceSmall"
- android:textColor="?android:attr/textColorPrimaryInverse" />
-
- <!-- Inner layout contains the app icon, button(s) and EditText -->
- <LinearLayout
- android:id="@+id/search_edit_frame"
+ <ImageView
+ android:id="@+id/search_app_icon"
+ android:layout_height="48dip"
+ android:layout_width="48dip"
+ android:layout_marginLeft="8dip"
+ android:layout_marginRight="8dip"
+ android:layout_gravity="center_vertical"
+ android:layout_alignParentLeft="true"
+ />
+ <SearchView
+ android:id="@+id/search_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:orientation="horizontal">
-
- <ImageView
- android:id="@+id/search_app_icon"
- android:layout_height="36dip"
- android:layout_width="36dip"
- android:layout_marginRight="7dip"
- android:layout_gravity="center_vertical"
- />
-
- <view class="android.app.SearchDialog$SearchAutoComplete"
- android:id="@+id/search_src_text"
- android:background="@drawable/textfield_search"
- android:layout_height="wrap_content"
- android:layout_width="0dip"
- android:layout_weight="1.0"
- android:paddingLeft="8dip"
- android:paddingRight="6dip"
- android:drawablePadding="2dip"
- android:singleLine="true"
- android:ellipsize="end"
- android:inputType="text|textAutoComplete"
- android:dropDownWidth="match_parent"
- android:dropDownHeight="match_parent"
- android:dropDownAnchor="@id/search_plate"
- android:dropDownVerticalOffset="-9dip"
- android:popupBackground="@android:drawable/search_dropdown_background"
- />
-
- <!-- This button can switch between text and icon "modes" -->
- <Button
- android:id="@+id/search_go_btn"
- android:background="@drawable/btn_search_dialog"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
+ android:maxWidth="600dip"
+ android:iconifiedByDefault="false"
+ android:layout_alignParentRight="true"
+ android:layout_gravity="center_vertical|right"
/>
- <ImageButton
- android:id="@+id/search_voice_btn"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginLeft="0dip"
- android:layout_marginTop="-6.5dip"
- android:layout_marginBottom="-7dip"
- android:layout_marginRight="-5dip"
- android:background="@drawable/btn_search_dialog_voice"
- android:src="@android:drawable/ic_btn_speak_now"
- />
- </LinearLayout>
-
- </LinearLayout>
+ </RelativeLayout>
</view>
diff --git a/core/res/res/layout/search_view.xml b/core/res/res/layout/search_view.xml
index eb0bb11..0fb824f 100644
--- a/core/res/res/layout/search_view.xml
+++ b/core/res/res/layout/search_view.xml
@@ -74,7 +74,7 @@
android:src="?android:attr/searchViewSearchIcon"
/>
- <AutoCompleteTextView
+ <view class="android.widget.SearchView$SearchAutoComplete"
android:id="@+id/search_src_text"
android:layout_height="36dip"
android:layout_width="0dp"
diff --git a/core/res/res/values/themes.xml b/core/res/res/values/themes.xml
index 8c59360..380d63b 100644
--- a/core/res/res/values/themes.xml
+++ b/core/res/res/values/themes.xml
@@ -563,6 +563,21 @@
<item name="android:windowNoTitle">true</item>
</style>
+ <!-- Default holo light theme for panel windows. This removes all extraneous
+ window decorations, so you basically have an empty rectangle in which
+ to place your content. It makes the window floating, with a transparent
+ background, and turns off dimming behind the window. -->
+ <style name="Theme.Holo.Light.Panel">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">false</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowNoTitle">true</item>
+ </style>
+
<!-- Default theme for input methods, which is used by the
{@link android.inputmethodservice.InputMethodService} class.
this inherits from Theme.NoTitleBar, but makes the background
@@ -577,7 +592,7 @@
</style>
<!-- Theme for the search input bar. -->
- <style name="Theme.SearchBar" parent="Theme.Panel">
+ <style name="Theme.SearchBar" parent="Theme.Holo.Light.Panel">
<item name="windowContentOverlay">@null</item>
</style>