/*
* 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.