aboutsummaryrefslogtreecommitdiffstats
path: root/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java
diff options
context:
space:
mode:
Diffstat (limited to 'libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java')
-rw-r--r--libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java1811
1 files changed, 0 insertions, 1811 deletions
diff --git a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java b/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java
deleted file mode 100644
index fb8319645..000000000
--- a/libraries/ActionBarSherlock/src/com/actionbarsherlock/widget/SearchView.java
+++ /dev/null
@@ -1,1811 +0,0 @@
-/*
- * 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.actionbarsherlock.widget;
-
-import android.app.PendingIntent;
-import android.app.SearchManager;
-import android.app.SearchableInfo;
-import android.content.ActivityNotFoundException;
-import android.content.ComponentName;
-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;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import android.net.Uri;
-import android.os.Build;
-import android.os.Bundle;
-import android.os.ResultReceiver;
-import android.speech.RecognizerIntent;
-import android.support.v4.view.KeyEventCompat;
-import android.support.v4.widget.CursorAdapter;
-import android.text.Editable;
-import android.text.InputType;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.TextUtils;
-import android.text.TextWatcher;
-import android.text.style.ImageSpan;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.util.TypedValue;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewTreeObserver;
-import android.view.accessibility.AccessibilityEvent;
-import android.view.accessibility.AccessibilityNodeInfo;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.AutoCompleteTextView;
-import android.widget.ImageView;
-import android.widget.LinearLayout;
-import android.widget.ListView;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-import com.actionbarsherlock.R;
-import com.actionbarsherlock.view.CollapsibleActionView;
-
-import java.lang.reflect.Method;
-import java.util.WeakHashMap;
-
-import static com.actionbarsherlock.widget.SuggestionsAdapter.getColumnString;
-
-/**
- * A widget that provides a user interface for the user to enter a search query and submit a request
- * to a search provider. Shows a list of query suggestions or results, if available, and allows the
- * user to pick a suggestion or result to launch into.
- *
- * <p>
- * When the SearchView is used in an ActionBar as an action view for a collapsible menu item, it
- * needs to be set to iconified by default using {@link #setIconifiedByDefault(boolean)
- * setIconifiedByDefault(true)}. This is the default, so nothing needs to be done.
- * </p>
- * <p>
- * If you want the search field to always be visible, then call setIconifiedByDefault(false).
- * </p>
- *
- * <div class="special reference">
- * <h3>Developer Guides</h3>
- * <p>For information about using {@code SearchView}, read the
- * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p>
- * </div>
- *
- * @see android.view.MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
- * @attr ref android.R.styleable#SearchView_iconifiedByDefault
- * @attr ref android.R.styleable#SearchView_imeOptions
- * @attr ref android.R.styleable#SearchView_inputType
- * @attr ref android.R.styleable#SearchView_maxWidth
- * @attr ref android.R.styleable#SearchView_queryHint
- */
-public class SearchView extends LinearLayout implements CollapsibleActionView {
-
- private static final boolean DBG = false;
- private static final String LOG_TAG = "SearchView";
-
- /**
- * Private constant for removing the microphone in the keyboard.
- */
- private static final String IME_OPTION_NO_MICROPHONE = "nm";
-
- private OnQueryTextListener mOnQueryChangeListener;
- private OnCloseListener mOnCloseListener;
- private OnFocusChangeListener mOnQueryTextFocusChangeListener;
- private OnSuggestionListener mOnSuggestionListener;
- private OnClickListener mOnSearchClickListener;
-
- private boolean mIconifiedByDefault;
- private boolean mIconified;
- private CursorAdapter mSuggestionsAdapter;
- private View mSearchButton;
- private View mSubmitButton;
- private View mSearchPlate;
- private View mSubmitArea;
- private ImageView mCloseButton;
- private View mSearchEditFrame;
- private View mVoiceButton;
- private SearchAutoComplete mQueryTextView;
- private View mDropDownAnchor;
- private ImageView mSearchHintIcon;
- private boolean mSubmitButtonEnabled;
- private CharSequence mQueryHint;
- private boolean mQueryRefinement;
- private boolean mClearingFocus;
- private int mMaxWidth;
- private boolean mVoiceButtonEnabled;
- private CharSequence mOldQueryText;
- private CharSequence mUserQuery;
- private boolean mExpandedInActionView;
- private int mCollapsedImeOptions;
-
- private SearchableInfo mSearchable;
- private Bundle mAppSearchData;
-
- /*
- * SearchView can be set expanded before the IME is ready to be shown during
- * initial UI setup. The show operation is asynchronous to account for this.
- */
- private Runnable mShowImeRunnable = new Runnable() {
- public void run() {
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
- if (imm != null) {
- showSoftInputUnchecked(SearchView.this, imm, 0);
- }
- }
- };
-
- private Runnable mUpdateDrawableStateRunnable = new Runnable() {
- public void run() {
- updateFocusedState();
- }
- };
-
- private Runnable mReleaseCursorRunnable = new Runnable() {
- public void run() {
- if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) {
- mSuggestionsAdapter.changeCursor(null);
- }
- }
- };
-
- // For voice searching
- private final Intent mVoiceWebSearchIntent;
- private final Intent mVoiceAppSearchIntent;
-
- // 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>();
-
- /**
- * Callbacks for changes to the query text.
- */
- public interface OnQueryTextListener {
-
- /**
- * Called when the user submits the query. This could be due to a key press on the
- * keyboard or due to pressing a submit button.
- * The listener can override the standard behavior by returning true
- * to indicate that it has handled the submit request. Otherwise return false to
- * let the SearchView handle the submission by launching any associated intent.
- *
- * @param query the query text that is to be submitted
- *
- * @return true if the query has been handled by the listener, false to let the
- * SearchView perform the default action.
- */
- boolean onQueryTextSubmit(String query);
-
- /**
- * Called when the query text is changed by the user.
- *
- * @param newText the new content of the query text field.
- *
- * @return false if the SearchView should perform the default action of showing any
- * suggestions if available, true if the action was handled by the listener.
- */
- boolean onQueryTextChange(String newText);
- }
-
- public interface OnCloseListener {
-
- /**
- * The user is attempting to close the SearchView.
- *
- * @return true if the listener wants to override the default behavior of clearing the
- * text field and dismissing it, false otherwise.
- */
- boolean onClose();
- }
-
- /**
- * Callback interface for selection events on suggestions. These callbacks
- * are only relevant when a SearchableInfo has been specified by {@link #setSearchableInfo}.
- */
- public interface OnSuggestionListener {
-
- /**
- * Called when a suggestion was selected by navigating to it.
- * @param position the absolute position in the list of suggestions.
- *
- * @return true if the listener handles the event and wants to override the default
- * behavior of possibly rewriting the query based on the selected item, false otherwise.
- */
- boolean onSuggestionSelect(int position);
-
- /**
- * Called when a suggestion was clicked.
- * @param position the absolute position of the clicked item in the list of suggestions.
- *
- * @return true if the listener handles the event and wants to override the default
- * behavior of launching any intent or submitting a search query specified on that item.
- * Return false otherwise.
- */
- boolean onSuggestionClick(int position);
- }
-
- public SearchView(Context context) {
- this(context, null);
- }
-
- public SearchView(Context context, AttributeSet attrs) {
- super(context, attrs);
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.FROYO) {
- throw new IllegalStateException("SearchView is API 8+ only.");
- }
-
- LayoutInflater inflater = (LayoutInflater) context
- .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
- inflater.inflate(R.layout.abs__search_view, this, true);
-
- mSearchButton = findViewById(R.id.abs__search_button);
- mQueryTextView = (SearchAutoComplete) findViewById(R.id.abs__search_src_text);
- mQueryTextView.setSearchView(this);
-
- mSearchEditFrame = findViewById(R.id.abs__search_edit_frame);
- mSearchPlate = findViewById(R.id.abs__search_plate);
- mSubmitArea = findViewById(R.id.abs__submit_area);
- mSubmitButton = findViewById(R.id.abs__search_go_btn);
- mCloseButton = (ImageView) findViewById(R.id.abs__search_close_btn);
- mVoiceButton = findViewById(R.id.abs__search_voice_btn);
- mSearchHintIcon = (ImageView) findViewById(R.id.abs__search_mag_icon);
-
- mSearchButton.setOnClickListener(mOnClickListener);
- mCloseButton.setOnClickListener(mOnClickListener);
- mSubmitButton.setOnClickListener(mOnClickListener);
- mVoiceButton.setOnClickListener(mOnClickListener);
- mQueryTextView.setOnClickListener(mOnClickListener);
-
- mQueryTextView.addTextChangedListener(mTextWatcher);
- mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
- mQueryTextView.setOnItemClickListener(mOnItemClickListener);
- mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
- mQueryTextView.setOnKeyListener(mTextKeyListener);
- // Inform any listener of focus changes
- mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
-
- public void onFocusChange(View v, boolean hasFocus) {
- if (mOnQueryTextFocusChangeListener != null) {
- mOnQueryTextFocusChangeListener.onFocusChange(SearchView.this, hasFocus);
- }
- }
- });
-
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SherlockSearchView, 0, 0);
- setIconifiedByDefault(a.getBoolean(R.styleable.SherlockSearchView_iconifiedByDefault, true));
- int maxWidth = a.getDimensionPixelSize(R.styleable.SherlockSearchView_android_maxWidth, -1);
- if (maxWidth != -1) {
- setMaxWidth(maxWidth);
- }
- CharSequence queryHint = a.getText(R.styleable.SherlockSearchView_queryHint);
- if (!TextUtils.isEmpty(queryHint)) {
- setQueryHint(queryHint);
- }
- int imeOptions = a.getInt(R.styleable.SherlockSearchView_android_imeOptions, -1);
- if (imeOptions != -1) {
- setImeOptions(imeOptions);
- }
- int inputType = a.getInt(R.styleable.SherlockSearchView_android_inputType, -1);
- if (inputType != -1) {
- setInputType(inputType);
- }
-
- a.recycle();
-
- boolean focusable = true;
-
- a = context.obtainStyledAttributes(attrs, R.styleable.SherlockView, 0, 0);
- focusable = a.getBoolean(R.styleable.SherlockView_android_focusable, focusable);
- a.recycle();
- setFocusable(focusable);
-
- // Save voice intent for later queries/launching
- mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
- mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
- RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
-
- mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
- mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
- mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor());
- if (mDropDownAnchor != null) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
- mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- adjustDropDownSizeAndPosition();
- }
- });
- } else {
- mDropDownAnchor.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
- @Override public void onGlobalLayout() {
- adjustDropDownSizeAndPosition();
- }
- });
- }
- }
-
- updateViewsVisibility(mIconifiedByDefault);
- updateQueryHint();
- }
-
- /**
- * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used
- * to display labels, hints, suggestions, create intents for launching search results screens
- * and controlling other affordances such as a voice button.
- *
- * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific
- * activity or a global search provider.
- */
- public void setSearchableInfo(SearchableInfo searchable) {
- mSearchable = searchable;
- if (mSearchable != null) {
- updateSearchAutoComplete();
- updateQueryHint();
- }
- // Cache the voice search capability
- mVoiceButtonEnabled = hasVoiceSearch();
-
- if (mVoiceButtonEnabled) {
- // Disable the microphone on the keyboard, as a mic is displayed near the text box
- // TODO: use imeOptions to disable voice input when the new API will be available
- mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
- }
- updateViewsVisibility(isIconified());
- }
-
- /**
- * Sets the APP_DATA for legacy SearchDialog use.
- * @param appSearchData bundle provided by the app when launching the search dialog
- * @hide
- */
- public void setAppSearchData(Bundle appSearchData) {
- mAppSearchData = appSearchData;
- }
-
- /**
- * Sets the IME options on the query text field.
- *
- * @see TextView#setImeOptions(int)
- * @param imeOptions the options to set on the query text field
- *
- * @attr ref android.R.styleable#SearchView_imeOptions
- */
- public void setImeOptions(int imeOptions) {
- mQueryTextView.setImeOptions(imeOptions);
- }
-
- /**
- * Returns the IME options set on the query text field.
- * @return the ime options
- * @see TextView#setImeOptions(int)
- *
- * @attr ref android.R.styleable#SearchView_imeOptions
- */
- public int getImeOptions() {
- return mQueryTextView.getImeOptions();
- }
-
- /**
- * Sets the input type on the query text field.
- *
- * @see TextView#setInputType(int)
- * @param inputType the input type to set on the query text field
- *
- * @attr ref android.R.styleable#SearchView_inputType
- */
- public void setInputType(int inputType) {
- mQueryTextView.setInputType(inputType);
- }
-
- /**
- * Returns the input type set on the query text field.
- * @return the input type
- *
- * @attr ref android.R.styleable#SearchView_inputType
- */
- public int getInputType() {
- return mQueryTextView.getInputType();
- }
-
- /** @hide */
- @Override
- public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
- // Don't accept focus if in the middle of clearing focus
- if (mClearingFocus) return false;
- // Check if SearchView is focusable.
- if (!isFocusable()) return false;
- // If it is not iconified, then give the focus to the text field
- if (!isIconified()) {
- boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect);
- if (result) {
- updateViewsVisibility(false);
- }
- return result;
- } else {
- return super.requestFocus(direction, previouslyFocusedRect);
- }
- }
-
- /** @hide */
- @Override
- public void clearFocus() {
- mClearingFocus = true;
- setImeVisibility(false);
- super.clearFocus();
- mQueryTextView.clearFocus();
- mClearingFocus = false;
- }
-
- /**
- * Sets a listener for user actions within the SearchView.
- *
- * @param listener the listener object that receives callbacks when the user performs
- * actions in the SearchView such as clicking on buttons or typing a query.
- */
- public void setOnQueryTextListener(OnQueryTextListener listener) {
- mOnQueryChangeListener = listener;
- }
-
- /**
- * Sets a listener to inform when the user closes the SearchView.
- *
- * @param listener the listener to call when the user closes the SearchView.
- */
- public void setOnCloseListener(OnCloseListener listener) {
- mOnCloseListener = listener;
- }
-
- /**
- * Sets a listener to inform when the focus of the query text field changes.
- *
- * @param listener the listener to inform of focus changes.
- */
- public void setOnQueryTextFocusChangeListener(OnFocusChangeListener listener) {
- mOnQueryTextFocusChangeListener = listener;
- }
-
- /**
- * Sets a listener to inform when a suggestion is focused or clicked.
- *
- * @param listener the listener to inform of suggestion selection events.
- */
- public void setOnSuggestionListener(OnSuggestionListener listener) {
- mOnSuggestionListener = listener;
- }
-
- /**
- * Sets a listener to inform when the search button is pressed. This is only
- * relevant when the text field is not visible by default. Calling {@link #setIconified
- * setIconified(false)} can also cause this listener to be informed.
- *
- * @param listener the listener to inform when the search button is clicked or
- * the text field is programmatically de-iconified.
- */
- public void setOnSearchClickListener(OnClickListener listener) {
- mOnSearchClickListener = listener;
- }
-
- /**
- * Returns the query string currently in the text field.
- *
- * @return the query string
- */
- public CharSequence getQuery() {
- return mQueryTextView.getText();
- }
-
- /**
- * Sets a query string in the text field and optionally submits the query as well.
- *
- * @param query the query string. This replaces any query text already present in the
- * text field.
- * @param submit whether to submit the query right now or only update the contents of
- * text field.
- */
- public void setQuery(CharSequence query, boolean submit) {
- mQueryTextView.setText(query);
- if (query != null) {
- mQueryTextView.setSelection(mQueryTextView.length());
- mUserQuery = query;
- }
-
- // If the query is not empty and submit is requested, submit the query
- if (submit && !TextUtils.isEmpty(query)) {
- onSubmitQuery();
- }
- }
-
- /**
- * Sets the hint text to display in the query text field. This overrides any hint specified
- * in the SearchableInfo.
- *
- * @param hint the hint text to display
- *
- * @attr ref android.R.styleable#SearchView_queryHint
- */
- public void setQueryHint(CharSequence hint) {
- mQueryHint = hint;
- updateQueryHint();
- }
-
- /**
- * Gets the hint text to display in the query text field.
- * @return the query hint text, if specified, null otherwise.
- *
- * @attr ref android.R.styleable#SearchView_queryHint
- */
- public CharSequence getQueryHint() {
- if (mQueryHint != null) {
- return mQueryHint;
- } else if (mSearchable != null) {
- CharSequence hint = null;
- int hintId = mSearchable.getHintId();
- if (hintId != 0) {
- hint = getContext().getString(hintId);
- }
- return hint;
- }
- return null;
- }
-
- /**
- * Sets the default or resting state of the search field. If true, a single search icon is
- * shown by default and expands to show the text field and other buttons when pressed. Also,
- * if the default state is iconified, then it collapses to that state when the close button
- * is pressed. Changes to this property will take effect immediately.
- *
- * <p>The default value is true.</p>
- *
- * @param iconified whether the search field should be iconified by default
- *
- * @attr ref android.R.styleable#SearchView_iconifiedByDefault
- */
- public void setIconifiedByDefault(boolean iconified) {
- if (mIconifiedByDefault == iconified) return;
- mIconifiedByDefault = iconified;
- updateViewsVisibility(iconified);
- updateQueryHint();
- }
-
- /**
- * Returns the default iconified state of the search field.
- * @return
- *
- * @attr ref android.R.styleable#SearchView_iconifiedByDefault
- */
- public boolean isIconfiedByDefault() {
- return mIconifiedByDefault;
- }
-
- /**
- * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is
- * a temporary state and does not override the default iconified state set by
- * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then
- * a false here will only be valid until the user closes the field. And if the default
- * state is expanded, then a true here will only clear the text field and not close it.
- *
- * @param iconify a true value will collapse the SearchView to an icon, while a false will
- * expand it.
- */
- public void setIconified(boolean iconify) {
- if (iconify) {
- onCloseClicked();
- } else {
- onSearchClicked();
- }
- }
-
- /**
- * Returns the current iconified state of the SearchView.
- *
- * @return true if the SearchView is currently iconified, false if the search field is
- * fully visible.
- */
- public boolean isIconified() {
- return mIconified;
- }
-
- /**
- * Enables showing a submit button when the query is non-empty. In cases where the SearchView
- * is being used to filter the contents of the current activity and doesn't launch a separate
- * results activity, then the submit button should be disabled.
- *
- * @param enabled true to show a submit button for submitting queries, false if a submit
- * button is not required.
- */
- public void setSubmitButtonEnabled(boolean enabled) {
- mSubmitButtonEnabled = enabled;
- updateViewsVisibility(isIconified());
- }
-
- /**
- * Returns whether the submit button is enabled when necessary or never displayed.
- *
- * @return whether the submit button is enabled automatically when necessary
- */
- public boolean isSubmitButtonEnabled() {
- return mSubmitButtonEnabled;
- }
-
- /**
- * Specifies if a query refinement button should be displayed alongside each suggestion
- * or if it should depend on the flags set in the individual items retrieved from the
- * suggestions provider. Clicking on the query refinement button will replace the text
- * in the query text field with the text from the suggestion. This flag only takes effect
- * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)}
- * and not when using a custom adapter.
- *
- * @param enable true if all items should have a query refinement button, false if only
- * those items that have a query refinement flag set should have the button.
- *
- * @see SearchManager#SUGGEST_COLUMN_FLAGS
- * @see SearchManager#FLAG_QUERY_REFINEMENT
- */
- public void setQueryRefinementEnabled(boolean enable) {
- mQueryRefinement = enable;
- if (mSuggestionsAdapter instanceof SuggestionsAdapter) {
- ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
- enable ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY);
- }
- }
-
- /**
- * Returns whether query refinement is enabled for all items or only specific ones.
- * @return true if enabled for all items, false otherwise.
- */
- public boolean isQueryRefinementEnabled() {
- return mQueryRefinement;
- }
-
- /**
- * You can set a custom adapter if you wish. Otherwise the default adapter is used to
- * display the suggestions from the suggestions provider associated with the SearchableInfo.
- *
- * @see #setSearchableInfo(SearchableInfo)
- */
- public void setSuggestionsAdapter(CursorAdapter adapter) {
- mSuggestionsAdapter = adapter;
-
- mQueryTextView.setAdapter(mSuggestionsAdapter);
- }
-
- /**
- * Returns the adapter used for suggestions, if any.
- * @return the suggestions adapter
- */
- public CursorAdapter getSuggestionsAdapter() {
- return mSuggestionsAdapter;
- }
-
- /**
- * Makes the view at most this many pixels wide
- *
- * @attr ref android.R.styleable#SearchView_maxWidth
- */
- public void setMaxWidth(int maxpixels) {
- mMaxWidth = maxpixels;
-
- requestLayout();
- }
-
- /**
- * Gets the specified maximum width in pixels, if set. Returns zero if
- * no maximum width was specified.
- * @return the maximum width of the view
- *
- * @attr ref android.R.styleable#SearchView_maxWidth
- */
- public int getMaxWidth() {
- return mMaxWidth;
- }
-
- @Override
- protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- // Let the standard measurements take effect in iconified state.
- if (isIconified()) {
- super.onMeasure(widthMeasureSpec, heightMeasureSpec);
- return;
- }
-
- int widthMode = MeasureSpec.getMode(widthMeasureSpec);
- int width = MeasureSpec.getSize(widthMeasureSpec);
-
- switch (widthMode) {
- case MeasureSpec.AT_MOST:
- // If there is an upper limit, don't exceed maximum width (explicit or implicit)
- if (mMaxWidth > 0) {
- width = Math.min(mMaxWidth, width);
- } else {
- width = Math.min(getPreferredWidth(), width);
- }
- break;
- case MeasureSpec.EXACTLY:
- // If an exact width is specified, still don't exceed any specified maximum width
- if (mMaxWidth > 0) {
- width = Math.min(mMaxWidth, width);
- }
- break;
- case MeasureSpec.UNSPECIFIED:
- // Use maximum width, if specified, else preferred width
- width = mMaxWidth > 0 ? mMaxWidth : getPreferredWidth();
- break;
- }
- widthMode = MeasureSpec.EXACTLY;
- super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec);
- }
-
- private int getPreferredWidth() {
- return getContext().getResources()
- .getDimensionPixelSize(R.dimen.abs__search_view_preferred_width);
- }
-
- private void updateViewsVisibility(final boolean collapsed) {
- mIconified = collapsed;
- // Visibility of views that are visible when collapsed
- final int visCollapsed = collapsed ? VISIBLE : GONE;
- // Is there text in the query
- final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
-
- mSearchButton.setVisibility(visCollapsed);
- updateSubmitButton(hasText);
- mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
- mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
- updateCloseButton();
- updateVoiceButton(!hasText);
- updateSubmitArea();
- }
-
- private boolean hasVoiceSearch() {
- if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) {
- 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);
- return ri != null;
- }
- }
- return false;
- }
-
- private boolean isSubmitAreaEnabled() {
- return (mSubmitButtonEnabled || mVoiceButtonEnabled) && !isIconified();
- }
-
- private void updateSubmitButton(boolean hasText) {
- int visibility = GONE;
- if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus()
- && (hasText || !mVoiceButtonEnabled)) {
- visibility = VISIBLE;
- }
- mSubmitButton.setVisibility(visibility);
- }
-
- private void updateSubmitArea() {
- int visibility = GONE;
- if (isSubmitAreaEnabled()
- && (mSubmitButton.getVisibility() == VISIBLE
- || mVoiceButton.getVisibility() == VISIBLE)) {
- visibility = VISIBLE;
- }
- mSubmitArea.setVisibility(visibility);
- }
-
- private void updateCloseButton() {
- final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
- // Should we show the close button? It is not shown if there's no focus,
- // field is not iconified by default and there is no text in it.
- final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView);
- mCloseButton.setVisibility(showClose ? VISIBLE : GONE);
- mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
- }
-
- private void postUpdateFocusedState() {
- post(mUpdateDrawableStateRunnable);
- }
-
- private void updateFocusedState() {
- boolean focused = mQueryTextView.hasFocus();
- mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
- mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
- invalidate();
- }
-
- @Override
- protected void onDetachedFromWindow() {
- removeCallbacks(mUpdateDrawableStateRunnable);
- post(mReleaseCursorRunnable);
- super.onDetachedFromWindow();
- }
-
- private void setImeVisibility(final boolean visible) {
- if (visible) {
- post(mShowImeRunnable);
- } else {
- removeCallbacks(mShowImeRunnable);
- InputMethodManager imm = (InputMethodManager)
- getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
-
- if (imm != null) {
- imm.hideSoftInputFromWindow(getWindowToken(), 0);
- }
- }
- }
-
- /**
- * Called by the SuggestionsAdapter
- * @hide
- */
- /* package */void onQueryRefine(CharSequence queryText) {
- setQuery(queryText);
- }
-
- private final OnClickListener mOnClickListener = new OnClickListener() {
-
- public void onClick(View v) {
- if (v == mSearchButton) {
- onSearchClicked();
- } else if (v == mCloseButton) {
- onCloseClicked();
- } else if (v == mSubmitButton) {
- onSubmitQuery();
- } else if (v == mVoiceButton) {
- onVoiceClicked();
- } else if (v == mQueryTextView) {
- forceSuggestionQuery();
- }
- }
- };
-
- /**
- * Handles the key down event for dealing with action keys.
- *
- * @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 true if the event was handled here, or false if not.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (mSearchable == null) {
- return false;
- }
-
- // if it's an action specified by the searchable activity, launch the
- // entered query with the action key
- // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- // TODO if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- // TODO launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText()
- // TODO .toString());
- // TODO return true;
- // TODO }
-
- 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() && KeyEventCompat.hasNoModifiers(event)) {
- if (event.getAction() == KeyEvent.ACTION_UP) {
- if (keyCode == KeyEvent.KEYCODE_ENTER) {
- v.cancelLongPress();
-
- // Launch as a regular search.
- launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText()
- .toString());
- return true;
- }
- }
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- // TODO if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
- // TODO launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView
- // TODO .getText().toString());
- // TODO return true;
- // TODO }
- }
- }
- 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 && KeyEventCompat.hasNoModifiers(event)) {
- // First, check for enter or search (both of which we'll treat as a
- // "click")
- if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH
- || keyCode == KeyEvent.KEYCODE_TAB) {
- 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();
- ensureImeVisible(mQueryTextView, 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"
- // TODO SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
- // TODO if ((actionKey != null)
- // TODO && ((actionKey.getSuggestActionMsg() != null) || (actionKey
- // TODO .getSuggestActionMsgColumn() != null))) {
- // TODO // launch suggestion using action key column
- // TODO int position = mQueryTextView.getListSelection();
- // TODO if (position != ListView.INVALID_POSITION) {
- // TODO Cursor c = mSuggestionsAdapter.getCursor();
- // TODO if (c.moveToPosition(position)) {
- // TODO final String actionMsg = getActionKeyMessage(c, actionKey);
- // TODO if (actionMsg != null && (actionMsg.length() > 0)) {
- // TODO return onItemClicked(position, keyCode, actionMsg);
- // TODO }
- // TODO }
- // TODO }
- // TODO }
- }
- 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
- */
- // TODO private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
- // TODO String result = null;
- // TODO // check first in the cursor data, for a suggestion-specific message
- // TODO final String column = actionKey.getSuggestActionMsgColumn();
- // TODO if (column != null) {
- // TODO result = SuggestionsAdapter.getColumnString(c, column);
- // TODO }
- // TODO // If the cursor didn't give us a message, see if there's a single
- // TODO // message defined
- // TODO // for the actionkey (for all suggestions)
- // TODO if (result == null) {
- // TODO result = actionKey.getSuggestActionMsg();
- // TODO }
- // TODO return result;
- // TODO }
-
- private int getSearchIconId() {
- TypedValue outValue = new TypedValue();
- getContext().getTheme().resolveAttribute(R.attr.searchViewSearchIcon,
- outValue, true);
- return outValue.resourceId;
- }
-
- private CharSequence getDecoratedHint(CharSequence hintText) {
- // If the field is always expanded, then don't add the search icon to the hint
- if (!mIconifiedByDefault) return hintText;
-
- SpannableStringBuilder ssb = new SpannableStringBuilder(" "); // for the icon
- ssb.append(hintText);
- Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId());
- int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
- searchIcon.setBounds(0, 0, textSize, textSize);
- ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- return ssb;
- }
-
- private void updateQueryHint() {
- if (mQueryHint != null) {
- mQueryTextView.setHint(getDecoratedHint(mQueryHint));
- } else if (mSearchable != null) {
- CharSequence hint = null;
- int hintId = mSearchable.getHintId();
- if (hintId != 0) {
- hint = getContext().getString(hintId);
- }
- if (hint != null) {
- mQueryTextView.setHint(getDecoratedHint(hint));
- }
- } else {
- mQueryTextView.setHint(getDecoratedHint(""));
- }
- }
-
- /**
- * Updates the auto-complete text view.
- */
- private void updateSearchAutoComplete() {
- // TODO mQueryTextView.setDropDownAnimationStyle(0); // no animation
- mQueryTextView.setThreshold(mSearchable.getSuggestThreshold());
- mQueryTextView.setImeOptions(mSearchable.getImeOptions());
- int inputType = mSearchable.getInputType();
- // We only touch this if the input type is set up for text (which it almost certainly
- // should be, in the case of search!)
- if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
- // The existence of a suggestions authority is the proxy for "suggestions
- // are available here"
- inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
- if (mSearchable.getSuggestAuthority() != null) {
- inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
- // TYPE_TEXT_FLAG_AUTO_COMPLETE means that the text editor is performing
- // auto-completion based on its own semantics, which it will present to the user
- // as they type. This generally means that the input method should not show its
- // own candidates, and the spell checker should not be in action. The text editor
- // supplies its candidates by calling InputMethodManager.displayCompletions(),
- // which in turn will call InputMethodSession.displayCompletions().
- inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
- }
- }
- mQueryTextView.setInputType(inputType);
- if (mSuggestionsAdapter != null) {
- mSuggestionsAdapter.changeCursor(null);
- }
- // 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);
- mQueryTextView.setAdapter(mSuggestionsAdapter);
- ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
- mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
- : SuggestionsAdapter.REFINE_BY_ENTRY);
- }
- }
-
- /**
- * 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.
- */
- private void updateVoiceButton(boolean empty) {
- int visibility = GONE;
- if (mVoiceButtonEnabled && !isIconified() && empty) {
- visibility = VISIBLE;
- mSubmitButton.setVisibility(GONE);
- }
- mVoiceButton.setVisibility(visibility);
- }
-
- private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() {
-
- /**
- * Called when the input method default action key is pressed.
- */
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- onSubmitQuery();
- return true;
- }
- };
-
- private void onTextChanged(CharSequence newText) {
- CharSequence text = mQueryTextView.getText();
- mUserQuery = text;
- boolean hasText = !TextUtils.isEmpty(text);
- updateSubmitButton(hasText);
- updateVoiceButton(!hasText);
- updateCloseButton();
- updateSubmitArea();
- if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
- mOnQueryChangeListener.onQueryTextChange(newText.toString());
- }
- mOldQueryText = newText.toString();
- }
-
- private void onSubmitQuery() {
- CharSequence query = mQueryTextView.getText();
- if (query != null && TextUtils.getTrimmedLength(query) > 0) {
- if (mOnQueryChangeListener == null
- || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
- if (mSearchable != null) {
- launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
- setImeVisibility(false);
- }
- dismissSuggestions();
- }
- }
- }
-
- private void dismissSuggestions() {
- mQueryTextView.dismissDropDown();
- }
-
- private void onCloseClicked() {
- CharSequence text = mQueryTextView.getText();
- if (TextUtils.isEmpty(text)) {
- if (mIconifiedByDefault) {
- // If the app doesn't override the close behavior
- if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
- // hide the keyboard and remove focus
- clearFocus();
- // collapse the search field
- updateViewsVisibility(true);
- }
- }
- } else {
- mQueryTextView.setText("");
- mQueryTextView.requestFocus();
- setImeVisibility(true);
- }
-
- }
-
- private void onSearchClicked() {
- updateViewsVisibility(false);
- mQueryTextView.requestFocus();
- setImeVisibility(true);
- if (mOnSearchClickListener != null) {
- mOnSearchClickListener.onClick(this);
- }
- }
-
- private void onVoiceClicked() {
- // 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");
- }
- }
-
- void onTextFocusChanged() {
- updateViewsVisibility(isIconified());
- // Delayed update to make sure that the focus has settled down and window focus changes
- // don't affect it. A synchronous update was not working.
- postUpdateFocusedState();
- if (mQueryTextView.hasFocus()) {
- forceSuggestionQuery();
- }
- }
-
- @Override
- public void onWindowFocusChanged(boolean hasWindowFocus) {
- super.onWindowFocusChanged(hasWindowFocus);
-
- postUpdateFocusedState();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onActionViewCollapsed() {
- clearFocus();
- updateViewsVisibility(true);
- mQueryTextView.setImeOptions(mCollapsedImeOptions);
- mExpandedInActionView = false;
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onActionViewExpanded() {
- if (mExpandedInActionView) return;
-
- mExpandedInActionView = true;
- mCollapsedImeOptions = mQueryTextView.getImeOptions();
- mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
- mQueryTextView.setText("");
- setIconified(false);
- }
-
- @Override
- public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
- super.onInitializeAccessibilityEvent(event);
- event.setClassName(SearchView.class.getName());
- }
-
- @Override
- public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
- super.onInitializeAccessibilityNodeInfo(info);
- info.setClassName(SearchView.class.getName());
- }
-
- private void adjustDropDownSizeAndPosition() {
- if (mDropDownAnchor.getWidth() > 1) {
- Resources res = getContext().getResources();
- int anchorPadding = mSearchPlate.getPaddingLeft();
- Rect dropDownPadding = new Rect();
- int iconOffset = mIconifiedByDefault
- ? res.getDimensionPixelSize(R.dimen.abs__dropdownitem_icon_width)
- + res.getDimensionPixelSize(R.dimen.abs__dropdownitem_text_padding_left)
- : 0;
- mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
- mQueryTextView.setDropDownHorizontalOffset(-(dropDownPadding.left + iconOffset)
- + anchorPadding);
- mQueryTextView.setDropDownWidth(mDropDownAnchor.getWidth() + dropDownPadding.left
- + dropDownPadding.right + iconOffset - (anchorPadding));
- }
- }
-
- private boolean onItemClicked(int position, int actionKey, String actionMsg) {
- if (mOnSuggestionListener == null
- || !mOnSuggestionListener.onSuggestionClick(position)) {
- launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
- setImeVisibility(false);
- dismissSuggestions();
- return true;
- }
- return false;
- }
-
- private boolean onItemSelected(int position) {
- if (mOnSuggestionListener == null
- || !mOnSuggestionListener.onSuggestionSelect(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);
- onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
- }
- };
-
- private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
- if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
- SearchView.this.onItemSelected(position);
- }
-
- /**
- * Implements OnItemSelectedListener
- */
- public void onNothingSelected(AdapterView<?> parent) {
- if (DBG)
- Log.d(LOG_TAG, "onNothingSelected()");
- }
- };
-
- /**
- * Query rewriting.
- */
- private void rewriteQueryFromSuggestion(int position) {
- CharSequence oldQuery = mQueryTextView.getText();
- 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.
- // Update the text field, without getting new suggestions.
- setQuery(newQuery);
- } else {
- // The suggestion does not rewrite the query, restore the user's query.
- setQuery(oldQuery);
- }
- } else {
- // We got a bad position, restore the user's query.
- setQuery(oldQuery);
- }
- }
-
- /**
- * 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).
- */
- private boolean launchSuggestion(int position, int actionKey, String actionMsg) {
- 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.
- */
- private void launchIntent(Intent intent) {
- if (intent == null) {
- return;
- }
- try {
- // If the intent was created from a suggestion, it will always have an explicit
- // component here.
- getContext().startActivity(intent);
- } catch (RuntimeException ex) {
- Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
- }
- }
-
- /**
- * Sets the text in the query box, without updating the suggestions.
- */
- private void setQuery(CharSequence query) {
- setText(mQueryTextView, query, true);
- // Move the cursor to the end
- mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
- }
-
- private void launchQuerySearch(int actionKey, String actionMsg, String query) {
- String action = Intent.ACTION_SEARCH;
- Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
- getContext().startActivity(intent);
- }
-
- /**
- * Constructs an intent from the given information and the search dialog state.
- *
- * @param action Intent action.
- * @param data Intent data, or <code>null</code>.
- * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
- * @param query Intent query, or <code>null</code>.
- * @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 The intent.
- */
- private Intent createIntent(String action, Uri data, String extraData, String query,
- int actionKey, String actionMsg) {
- // Now build the Intent
- Intent intent = new Intent(action);
- intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- // We need CLEAR_TOP to avoid reusing an old task that has other activities
- // on top of the one we want. We don't want to do this in in-app search though,
- // as it can be destructive to the activity stack.
- if (data != null) {
- intent.setData(data);
- }
- intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
- if (query != null) {
- intent.putExtra(SearchManager.QUERY, query);
- }
- if (extraData != null) {
- intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
- }
- if (mAppSearchData != null) {
- intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
- }
- if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
- intent.putExtra(SearchManager.ACTION_KEY, actionKey);
- intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
- }
- intent.setComponent(mSearchable.getSearchActivity());
- return intent;
- }
-
- /**
- * 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();
-
- // 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 = 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;
- }
-
- /**
- * 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);
- return null;
- }
- }
-
- private void forceSuggestionQuery() {
- try {
- Method before = AutoCompleteTextView.class.getDeclaredMethod("doBeforeTextChanged");
- Method after = AutoCompleteTextView.class.getDeclaredMethod("doAfterTextChanged");
- before.setAccessible(true);
- after.setAccessible(true);
- before.invoke(mQueryTextView);
- after.invoke(mQueryTextView);
- } catch (Exception e) {
- // Oh well...
- }
- }
-
- static boolean isLandscapeMode(Context context) {
- return context.getResources().getConfiguration().orientation
- == Configuration.ORIENTATION_LANDSCAPE;
- }
-
- /**
- * Callback to watch the text 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) {
- SearchView.this.onTextChanged(s);
- }
-
- 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 && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
- 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(this, true);
- }
- }
- }
-
- @Override
- protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
- super.onFocusChanged(focused, direction, previouslyFocusedRect);
- mSearchView.onTextFocusChanged();
- }
-
- /**
- * 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();
- }
-
- @Override
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK) {
- // special case for the back key, we do not even try to send it
- // to the drop down list but instead, consume it immediately
- if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- state.startTracking(event, this);
- }
- return true;
- } else if (event.getAction() == KeyEvent.ACTION_UP) {
- KeyEvent.DispatcherState state = getKeyDispatcherState();
- if (state != null) {
- state.handleUpEvent(event);
- }
- if (event.isTracking() && !event.isCanceled()) {
- mSearchView.clearFocus();
- mSearchView.setImeVisibility(false);
- return true;
- }
- }
- }
- return super.onKeyPreIme(keyCode, event);
- }
-
- }
-
- private static void ensureImeVisible(AutoCompleteTextView view, boolean visible) {
- try {
- Method method = AutoCompleteTextView.class.getMethod("ensureImeVisible", boolean.class);
- method.setAccessible(true);
- method.invoke(view, visible);
- } catch (Exception e) {
- //Oh well...
- }
- }
-
- private static void showSoftInputUnchecked(View view, InputMethodManager imm, int flags) {
- try {
- Method method = imm.getClass().getMethod("showSoftInputUnchecked", int.class, ResultReceiver.class);
- method.setAccessible(true);
- method.invoke(imm, flags, null);
- } catch (Exception e) {
- //Fallback to public API which hopefully does mostly the same thing
- imm.showSoftInput(view, flags);
- }
- }
-
- private static void setText(AutoCompleteTextView view, CharSequence text, boolean filter) {
- try {
- Method method = AutoCompleteTextView.class.getMethod("setText", CharSequence.class, boolean.class);
- method.setAccessible(true);
- method.invoke(view, text, filter);
- } catch (Exception e) {
- //Fallback to public API which hopefully does mostly the same thing
- view.setText(text);
- }
- }
-}