1818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes/*
21b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes * Copyright (C) 2014 The Android Open Source Project
3818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes *
4818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Licensed under the Apache License, Version 2.0 (the "License");
5818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * you may not use this file except in compliance with the License.
6818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * You may obtain a copy of the License at
7818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes *
8818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes *      http://www.apache.org/licenses/LICENSE-2.0
9818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes *
10818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * Unless required by applicable law or agreed to in writing, software
11818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * distributed under the License is distributed on an "AS IS" BASIS,
12818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * See the License for the specific language governing permissions and
14818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * limitations under the License.
15818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */
16818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
17818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banespackage android.support.v7.widget;
18818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
19c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport static android.support.annotation.RestrictTo.Scope.GROUP_ID;
206c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikasimport static android.support.v7.widget.SuggestionsAdapter.getColumnString;
216c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
221b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banesimport android.annotation.TargetApi;
23818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.app.PendingIntent;
24818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.app.SearchManager;
25818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.app.SearchableInfo;
26818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.ActivityNotFoundException;
27818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.ComponentName;
28818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.Context;
29818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.Intent;
30818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.pm.PackageManager;
31818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.pm.ResolveInfo;
32818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.res.Configuration;
33818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.content.res.Resources;
34818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.database.Cursor;
35818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.graphics.Rect;
36818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.graphics.drawable.Drawable;
37818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.net.Uri;
38818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.os.Build;
39818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.os.Bundle;
403de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikasimport android.os.Parcel;
413de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikasimport android.os.Parcelable;
422e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banesimport android.os.ResultReceiver;
43818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.speech.RecognizerIntent;
44d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banesimport android.support.annotation.Nullable;
45c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viveretteimport android.support.annotation.RestrictTo;
460cbe64051d4241ad38cb4a232a86e6f26a3c8259Chris Banesimport android.support.v4.content.res.ConfigurationHelper;
4705f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banesimport android.support.v4.os.ParcelableCompat;
4805f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banesimport android.support.v4.os.ParcelableCompatCreatorCallbacks;
4905f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banesimport android.support.v4.view.AbsSavedState;
50818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.support.v4.view.KeyEventCompat;
5147082c30c630c34829439a9eecd1cf7e8d255a86Aurimas Liutikasimport android.support.v4.view.ViewCompat;
52818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.support.v4.widget.CursorAdapter;
53818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.support.v7.appcompat.R;
542e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banesimport android.support.v7.view.CollapsibleActionView;
55818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.Editable;
56818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.InputType;
57818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.Spannable;
58818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.SpannableStringBuilder;
59818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.TextUtils;
60818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.TextWatcher;
61818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.text.style.ImageSpan;
62818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.util.AttributeSet;
638dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynskiimport android.util.DisplayMetrics;
64818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.util.Log;
658dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynskiimport android.util.TypedValue;
66818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.KeyEvent;
67818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.LayoutInflater;
686c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikasimport android.view.MotionEvent;
696c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikasimport android.view.TouchDelegate;
70818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.View;
716c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikasimport android.view.ViewConfiguration;
72818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.ViewTreeObserver;
73818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.inputmethod.EditorInfo;
74818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.view.inputmethod.InputMethodManager;
75818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.AdapterView;
76818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.AdapterView.OnItemClickListener;
77818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.AdapterView.OnItemSelectedListener;
78818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.AutoCompleteTextView;
79818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.ImageView;
80818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.ListView;
81818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.TextView;
82818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport android.widget.TextView.OnEditorActionListener;
83818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
84818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport java.lang.reflect.Method;
85818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banesimport java.util.WeakHashMap;
86818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
87818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes/**
88818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * A widget that provides a user interface for the user to enter a search query and submit a request
89818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * to a search provider. Shows a list of query suggestions or results, if available, and allows the
90818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * user to pick a suggestion or result to launch into.
91818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes *
929dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * <p class="note"><strong>Note:</strong> This class is included in the <a
939dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * href="{@docRoot}tools/extras/support-library.html">support library</a> for compatibility
949dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * with API level 7 and higher. If you're developing your app for API level 11 and higher
959dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * <em>only</em>, you should instead use the framework {@link android.widget.SearchView} class.</p>
969dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main *
97818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * <p>
989dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * When the SearchView is used in an {@link android.support.v7.app.ActionBar}
999dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * as an action view, it's collapsed by default, so you must provide an icon for the action.
100818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * </p>
101818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * <p>
1029dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * If you want the search field to always be visible, then call
1039dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * {@link #setIconifiedByDefault(boolean) setIconifiedByDefault(false)}.
104818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * </p>
105818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes *
106818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * <div class="special reference">
107818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * <h3>Developer Guides</h3>
108818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * <p>For information about using {@code SearchView}, read the
1099dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * <a href="{@docRoot}guide/topics/search/index.html">Search</a> API guide.
1109dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * Additional information about action views is also available in the <<a
1119dcd2e58138ca4eb4b18f80b50e8979329e859d6Scott Main * href="{@docRoot}guide/topics/ui/actionbar.html#ActionView">Action Bar</a> API guide</p>
112818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * </div>
113818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes *
114818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes * @see android.support.v4.view.MenuItemCompat#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
115818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes */
1161b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banespublic class SearchView extends LinearLayoutCompat implements CollapsibleActionView {
117818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1182c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    static final boolean DBG = false;
1192c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    static final String LOG_TAG = "SearchView";
120818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
121818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
122818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Private constant for removing the microphone in the keyboard.
123818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
124818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private static final String IME_OPTION_NO_MICROPHONE = "nm";
125818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1262c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    final SearchAutoComplete mSearchSrcTextView;
1271b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final View mSearchEditFrame;
1281b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final View mSearchPlate;
1291b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final View mSubmitArea;
1302c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    final ImageView mSearchButton;
1312c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    final ImageView mGoButton;
1322c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    final ImageView mCloseButton;
1332c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    final ImageView mVoiceButton;
1341b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final View mDropDownAnchor;
135cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
1366c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private UpdatableTouchDelegate mTouchDelegate;
1376c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private Rect mSearchSrcTextViewBounds = new Rect();
1386c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private Rect mSearchSrtTextViewBoundsExpanded = new Rect();
1396c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private int[] mTemp = new int[2];
1406c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private int[] mTemp2 = new int[2];
1416c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
142cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes    /** Icon optionally displayed when the SearchView is collapsed. */
143cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes    private final ImageView mCollapsedIcon;
144cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
145cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes    /** Drawable used as an EditText hint. */
146cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes    private final Drawable mSearchHintIcon;
1471b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes
1481b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    // Resources used by SuggestionsAdapter to display suggestions.
1491b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final int mSuggestionRowLayout;
1501b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final int mSuggestionCommitIconResId;
1511b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes
1521b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    // Intents used for voice searching.
1531b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final Intent mVoiceWebSearchIntent;
1541b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final Intent mVoiceAppSearchIntent;
155cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
156ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes    private final CharSequence mDefaultQueryHint;
157ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes
158818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private OnQueryTextListener mOnQueryChangeListener;
159818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private OnCloseListener mOnCloseListener;
1602c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    OnFocusChangeListener mOnQueryTextFocusChangeListener;
161818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private OnSuggestionListener mOnSuggestionListener;
162818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private OnClickListener mOnSearchClickListener;
163818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
164818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean mIconifiedByDefault;
165818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean mIconified;
1662c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    CursorAdapter mSuggestionsAdapter;
167818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean mSubmitButtonEnabled;
168818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private CharSequence mQueryHint;
169818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean mQueryRefinement;
170818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean mClearingFocus;
171818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private int mMaxWidth;
172818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean mVoiceButtonEnabled;
173818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private CharSequence mOldQueryText;
174818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private CharSequence mUserQuery;
175818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean mExpandedInActionView;
176818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private int mCollapsedImeOptions;
177818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1782c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    SearchableInfo mSearchable;
179818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private Bundle mAppSearchData;
180818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
181818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    static final AutoCompleteTextViewReflector HIDDEN_METHOD_INVOKER = new AutoCompleteTextViewReflector();
182818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
183818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /*
184818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * SearchView can be set expanded before the IME is ready to be shown during
185818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * initial UI setup. The show operation is asynchronous to account for this.
186818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
187818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private Runnable mShowImeRunnable = new Runnable() {
188dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
189818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void run() {
190818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            InputMethodManager imm = (InputMethodManager)
191818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
192818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
193818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (imm != null) {
1942e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                HIDDEN_METHOD_INVOKER.showSoftInputUnchecked(imm, SearchView.this, 0);
195818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
196818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
197818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
198818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1991b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    private final Runnable mUpdateDrawableStateRunnable = new Runnable() {
200dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
201818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void run() {
202818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            updateFocusedState();
203818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
204818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
205818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
206818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private Runnable mReleaseCursorRunnable = new Runnable() {
207dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
208818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void run() {
209818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) {
210818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                mSuggestionsAdapter.changeCursor(null);
211818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
212818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
213818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
214818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
215818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    // A weak map of drawables we've gotten from other packages, so we don't load them
216818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    // more than once.
217818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
218818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            new WeakHashMap<String, Drawable.ConstantState>();
219818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
220818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
221818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Callbacks for changes to the query text.
222818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
223818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public interface OnQueryTextListener {
224818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
225818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
226818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Called when the user submits the query. This could be due to a key press on the
227818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * keyboard or due to pressing a submit button.
228818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * The listener can override the standard behavior by returning true
229818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * to indicate that it has handled the submit request. Otherwise return false to
230818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * let the SearchView handle the submission by launching any associated intent.
231818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         *
232818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @param query the query text that is to be submitted
233818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         *
234818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @return true if the query has been handled by the listener, false to let the
235818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * SearchView perform the default action.
236818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
237818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        boolean onQueryTextSubmit(String query);
238818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
239818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
240818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Called when the query text is changed by the user.
241818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         *
242818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @param newText the new content of the query text field.
243818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         *
244818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @return false if the SearchView should perform the default action of showing any
245818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * suggestions if available, true if the action was handled by the listener.
246818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
247818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        boolean onQueryTextChange(String newText);
248818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
249818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
250818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public interface OnCloseListener {
251818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
252818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
253818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * The user is attempting to close the SearchView.
254818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         *
255818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @return true if the listener wants to override the default behavior of clearing the
256818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * text field and dismissing it, false otherwise.
257818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
258818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        boolean onClose();
259818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
260818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
261818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
262818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Callback interface for selection events on suggestions. These callbacks
263818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * are only relevant when a SearchableInfo has been specified by {@link #setSearchableInfo}.
264818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
265818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public interface OnSuggestionListener {
266818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
267818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
268818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Called when a suggestion was selected by navigating to it.
269818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @param position the absolute position in the list of suggestions.
270818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         *
271818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @return true if the listener handles the event and wants to override the default
272818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * behavior of possibly rewriting the query based on the selected item, false otherwise.
273818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
274818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        boolean onSuggestionSelect(int position);
275818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
276818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
277818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Called when a suggestion was clicked.
278818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @param position the absolute position of the clicked item in the list of suggestions.
279818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         *
280818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * @return true if the listener handles the event and wants to override the default
281818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * behavior of launching any intent or submitting a search query specified on that item.
282818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Return false otherwise.
283818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
284818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        boolean onSuggestionClick(int position);
285818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
286818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
287818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public SearchView(Context context) {
288818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        this(context, null);
289818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
290818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
291818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public SearchView(Context context, AttributeSet attrs) {
2921b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        this(context, attrs, R.attr.searchViewStyle);
2931b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    }
294818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
2951b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    public SearchView(Context context, AttributeSet attrs, int defStyleAttr) {
2961b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        super(context, attrs, defStyleAttr);
297818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
298469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes        final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
2991b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes                attrs, R.styleable.SearchView, defStyleAttr, 0);
300469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
301cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final LayoutInflater inflater = LayoutInflater.from(context);
302cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final int layoutResId = a.getResourceId(
303cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                R.styleable.SearchView_layout, R.layout.abc_search_view);
3041b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        inflater.inflate(layoutResId, this, true);
305cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
306cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
307cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setSearchView(this);
308818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
309818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSearchEditFrame = findViewById(R.id.search_edit_frame);
310818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSearchPlate = findViewById(R.id.search_plate);
311818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSubmitArea = findViewById(R.id.submit_area);
3121b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        mSearchButton = (ImageView) findViewById(R.id.search_button);
313cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mGoButton = (ImageView) findViewById(R.id.search_go_btn);
314818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mCloseButton = (ImageView) findViewById(R.id.search_close_btn);
3151b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        mVoiceButton = (ImageView) findViewById(R.id.search_voice_btn);
316cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mCollapsedIcon = (ImageView) findViewById(R.id.search_mag_icon);
317cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
3181b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        // Set up icons and backgrounds.
31947082c30c630c34829439a9eecd1cf7e8d255a86Aurimas Liutikas        ViewCompat.setBackground(mSearchPlate,
32047082c30c630c34829439a9eecd1cf7e8d255a86Aurimas Liutikas                a.getDrawable(R.styleable.SearchView_queryBackground));
32147082c30c630c34829439a9eecd1cf7e8d255a86Aurimas Liutikas        ViewCompat.setBackground(mSubmitArea,
32247082c30c630c34829439a9eecd1cf7e8d255a86Aurimas Liutikas                a.getDrawable(R.styleable.SearchView_submitBackground));
323cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
324cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mGoButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_goIcon));
3251b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        mCloseButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_closeIcon));
3261b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        mVoiceButton.setImageDrawable(a.getDrawable(R.styleable.SearchView_voiceIcon));
327cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mCollapsedIcon.setImageDrawable(a.getDrawable(R.styleable.SearchView_searchIcon));
328cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
329cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchHintIcon = a.getDrawable(R.styleable.SearchView_searchHintIcon);
3301b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes
3311b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        // Extract dropdown layout resource IDs for later use.
332cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSuggestionRowLayout = a.getResourceId(R.styleable.SearchView_suggestionRowLayout,
333cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                R.layout.abc_search_dropdown_item_icons_2line);
3341b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        mSuggestionCommitIconResId = a.getResourceId(R.styleable.SearchView_commitIcon, 0);
335818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
336818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSearchButton.setOnClickListener(mOnClickListener);
337818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mCloseButton.setOnClickListener(mOnClickListener);
338cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mGoButton.setOnClickListener(mOnClickListener);
339818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mVoiceButton.setOnClickListener(mOnClickListener);
340cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setOnClickListener(mOnClickListener);
341cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
342cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.addTextChangedListener(mTextWatcher);
343cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setOnEditorActionListener(mOnEditorActionListener);
344cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setOnItemClickListener(mOnItemClickListener);
345cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setOnItemSelectedListener(mOnItemSelectedListener);
346cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setOnKeyListener(mTextKeyListener);
347818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
348818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Inform any listener of focus changes
349cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
350dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas            @Override
351818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            public void onFocusChange(View v, boolean hasFocus) {
352818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (mOnQueryTextFocusChangeListener != null) {
353818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    mOnQueryTextFocusChangeListener.onFocusChange(SearchView.this, hasFocus);
354818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
355818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
356818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        });
357818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true));
3581b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes
3591b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        final int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_android_maxWidth, -1);
360818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (maxWidth != -1) {
361818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            setMaxWidth(maxWidth);
362818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
363cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
364ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes        mDefaultQueryHint = a.getText(R.styleable.SearchView_defaultQueryHint);
365ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes        mQueryHint = a.getText(R.styleable.SearchView_queryHint);
366cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
3671b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        final int imeOptions = a.getInt(R.styleable.SearchView_android_imeOptions, -1);
368818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (imeOptions != -1) {
369818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            setImeOptions(imeOptions);
370818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
371cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes
3721b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        final int inputType = a.getInt(R.styleable.SearchView_android_inputType, -1);
373818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (inputType != -1) {
374818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            setInputType(inputType);
375818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
376818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
3776912ef40f2cbf2a449c492cbe81ca96deee67ca0Adam Powell        boolean focusable = true;
3786912ef40f2cbf2a449c492cbe81ca96deee67ca0Adam Powell        focusable = a.getBoolean(R.styleable.SearchView_android_focusable, focusable);
3796912ef40f2cbf2a449c492cbe81ca96deee67ca0Adam Powell        setFocusable(focusable);
3806912ef40f2cbf2a449c492cbe81ca96deee67ca0Adam Powell
381818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        a.recycle();
382818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
383818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Save voice intent for later queries/launching
384818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
385818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
386818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
387818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
388818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
389818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
390818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
391818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
392cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mDropDownAnchor = findViewById(mSearchSrcTextView.getDropDownAnchor());
393818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mDropDownAnchor != null) {
394818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
395818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                addOnLayoutChangeListenerToDropDownAnchorSDK11();
396818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } else {
397818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                addOnLayoutChangeListenerToDropDownAnchorBase();
398818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
399818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
400818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
401818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateViewsVisibility(mIconifiedByDefault);
402818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateQueryHint();
403818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
404818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
4051b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
406818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void addOnLayoutChangeListenerToDropDownAnchorSDK11() {
407818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mDropDownAnchor.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
408818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            @Override
409818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            public void onLayoutChange(View v, int left, int top, int right, int bottom,
410818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    int oldLeft, int oldTop, int oldRight, int oldBottom) {
411818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                adjustDropDownSizeAndPosition();
412818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
413818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        });
414818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
415818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
416818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void addOnLayoutChangeListenerToDropDownAnchorBase() {
417818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mDropDownAnchor.getViewTreeObserver()
418818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                .addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
419818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    @Override
420818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    public void onGlobalLayout() {
421818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        adjustDropDownSizeAndPosition();
422818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    }
423818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                });
424818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
425818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
4261b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    int getSuggestionRowLayout() {
4271b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        return mSuggestionRowLayout;
4281b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    }
4291b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes
4301b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    int getSuggestionCommitIconResId() {
4311b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        return mSuggestionCommitIconResId;
4321b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes    }
4331b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes
434818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
435818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used
436818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * to display labels, hints, suggestions, create intents for launching search results screens
437818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * and controlling other affordances such as a voice button.
438818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
439818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific
440818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * activity or a global search provider.
441818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
442818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setSearchableInfo(SearchableInfo searchable) {
443818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSearchable = searchable;
444818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSearchable != null) {
44564dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            updateSearchAutoComplete();
446818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            updateQueryHint();
447818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
448818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Cache the voice search capability
44964dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        mVoiceButtonEnabled = hasVoiceSearch();
450818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
451818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mVoiceButtonEnabled) {
452818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // Disable the microphone on the keyboard, as a mic is displayed near the text box
453818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // TODO: use imeOptions to disable voice input when the new API will be available
454cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
455818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
456818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateViewsVisibility(isIconified());
457818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
458818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
459818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
460818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets the APP_DATA for legacy SearchDialog use.
461818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param appSearchData bundle provided by the app when launching the search dialog
462818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @hide
463818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
464c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette    @RestrictTo(GROUP_ID)
465818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setAppSearchData(Bundle appSearchData) {
466818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mAppSearchData = appSearchData;
467818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
468818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
469818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
470818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets the IME options on the query text field.
471818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
472818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @see TextView#setImeOptions(int)
473818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param imeOptions the options to set on the query text field
474d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
475d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_imeOptions
476818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
477818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setImeOptions(int imeOptions) {
478cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setImeOptions(imeOptions);
479818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
480818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
481818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
482818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns the IME options set on the query text field.
483818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return the ime options
484818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @see TextView#setImeOptions(int)
485d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
486d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_imeOptions
487818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
488818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public int getImeOptions() {
489cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        return mSearchSrcTextView.getImeOptions();
490818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
491818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
492818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
493818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets the input type on the query text field.
494818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
495818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @see TextView#setInputType(int)
496818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param inputType the input type to set on the query text field
497d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
498d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_inputType
499818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
500818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setInputType(int inputType) {
501cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setInputType(inputType);
502818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
503818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
504818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
505818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns the input type set on the query text field.
506818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return the input type
507d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
508d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_inputType
509818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
510818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public int getInputType() {
511cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        return mSearchSrcTextView.getInputType();
512818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
513818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
514818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /** @hide */
515c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette    @RestrictTo(GROUP_ID)
516818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    @Override
517818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
518818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Don't accept focus if in the middle of clearing focus
519818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mClearingFocus) return false;
520818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Check if SearchView is focusable.
521818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (!isFocusable()) return false;
522818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // If it is not iconified, then give the focus to the text field
523818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (!isIconified()) {
524cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            boolean result = mSearchSrcTextView.requestFocus(direction, previouslyFocusedRect);
525818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (result) {
526818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                updateViewsVisibility(false);
527818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
528818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return result;
529818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } else {
530818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return super.requestFocus(direction, previouslyFocusedRect);
531818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
532818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
533818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
534818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /** @hide */
535c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette    @RestrictTo(GROUP_ID)
536818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    @Override
537818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void clearFocus() {
538818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mClearingFocus = true;
539818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        setImeVisibility(false);
540818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        super.clearFocus();
541cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.clearFocus();
542818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mClearingFocus = false;
543818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
544818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
545818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
546818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets a listener for user actions within the SearchView.
547818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
548818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param listener the listener object that receives callbacks when the user performs
549818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * actions in the SearchView such as clicking on buttons or typing a query.
550818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
551818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setOnQueryTextListener(OnQueryTextListener listener) {
552818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mOnQueryChangeListener = listener;
553818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
554818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
555818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
556818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets a listener to inform when the user closes the SearchView.
557818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
558818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param listener the listener to call when the user closes the SearchView.
559818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
560818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setOnCloseListener(OnCloseListener listener) {
561818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mOnCloseListener = listener;
562818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
563818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
564818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
565818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets a listener to inform when the focus of the query text field changes.
566818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
567818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param listener the listener to inform of focus changes.
568818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
569818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setOnQueryTextFocusChangeListener(OnFocusChangeListener listener) {
570818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mOnQueryTextFocusChangeListener = listener;
571818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
572818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
573818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
574818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets a listener to inform when a suggestion is focused or clicked.
575818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
576818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param listener the listener to inform of suggestion selection events.
577818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
578818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setOnSuggestionListener(OnSuggestionListener listener) {
579818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mOnSuggestionListener = listener;
580818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
581818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
582818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
583818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets a listener to inform when the search button is pressed. This is only
584818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * relevant when the text field is not visible by default. Calling {@link #setIconified
585818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * setIconified(false)} can also cause this listener to be informed.
586818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
587818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param listener the listener to inform when the search button is clicked or
588818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * the text field is programmatically de-iconified.
589818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
590818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setOnSearchClickListener(OnClickListener listener) {
591818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mOnSearchClickListener = listener;
592818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
593818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
594818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
595818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns the query string currently in the text field.
596818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
597818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return the query string
598818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
599818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public CharSequence getQuery() {
600cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        return mSearchSrcTextView.getText();
601818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
602818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
603818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
604818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets a query string in the text field and optionally submits the query as well.
605818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
606818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param query the query string. This replaces any query text already present in the
607818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * text field.
608818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param submit whether to submit the query right now or only update the contents of
609818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * text field.
610818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
611818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setQuery(CharSequence query, boolean submit) {
612cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setText(query);
613818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (query != null) {
614cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.setSelection(mSearchSrcTextView.length());
615818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mUserQuery = query;
616818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
617818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
618818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // If the query is not empty and submit is requested, submit the query
619818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (submit && !TextUtils.isEmpty(query)) {
620818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            onSubmitQuery();
621818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
622818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
623818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
624818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
625ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * Sets the hint text to display in the query text field. This overrides
626ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * any hint specified in the {@link SearchableInfo}.
627ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * <p>
628ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * This value may be specified as an empty string to prevent any query hint
629ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * from being displayed.
630818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
631ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * @param hint the hint text to display or {@code null} to clear
632d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_queryHint
633818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
634d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes    public void setQueryHint(@Nullable CharSequence hint) {
635818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mQueryHint = hint;
636818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateQueryHint();
637818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
638818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
639818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
640ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * Returns the hint text that will be displayed in the query text field.
641ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * <p>
642ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * The displayed query hint is chosen in the following order:
643ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * <ol>
644ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * <li>Non-null value set with {@link #setQueryHint(CharSequence)}
645ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * <li>Value specified in XML using {@code app:queryHint}
646ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * <li>Valid string resource ID exposed by the {@link SearchableInfo} via
647ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     *     {@link SearchableInfo#getHintId()}
648ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * <li>Default hint provided by the theme against which the view was
649ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     *     inflated
650ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * </ol>
651ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     *
652d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
653d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
654ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes     * @return the displayed query hint text, or {@code null} if none set
655d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_queryHint
656818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
657d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes    @Nullable
658818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public CharSequence getQueryHint() {
659ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes        final CharSequence hint;
660818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mQueryHint != null) {
661ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes            hint = mQueryHint;
66264dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        } else if (mSearchable != null && mSearchable.getHintId() != 0) {
663ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes            hint = getContext().getText(mSearchable.getHintId());
664ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes        } else {
665ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes            hint = mDefaultQueryHint;
666818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
667ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes        return hint;
668818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
669818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
670818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
671818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets the default or resting state of the search field. If true, a single search icon is
672818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * shown by default and expands to show the text field and other buttons when pressed. Also,
673818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * if the default state is iconified, then it collapses to that state when the close button
674818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * is pressed. Changes to this property will take effect immediately.
675818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
676818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * <p>The default value is true.</p>
677818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
678818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param iconified whether the search field should be iconified by default
679d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
680d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_iconifiedByDefault
681818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
682818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setIconifiedByDefault(boolean iconified) {
683818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mIconifiedByDefault == iconified) return;
684818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mIconifiedByDefault = iconified;
685818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateViewsVisibility(iconified);
686818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateQueryHint();
687818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
688818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
689818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
690818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns the default iconified state of the search field.
691818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return
692d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
693d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_iconifiedByDefault
694818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
695818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public boolean isIconfiedByDefault() {
696818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return mIconifiedByDefault;
697818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
698818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
699818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
700818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is
701818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * a temporary state and does not override the default iconified state set by
702818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then
703818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * a false here will only be valid until the user closes the field. And if the default
704818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * state is expanded, then a true here will only clear the text field and not close it.
705818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
706818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param iconify a true value will collapse the SearchView to an icon, while a false will
707818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * expand it.
708818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
709818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setIconified(boolean iconify) {
710818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (iconify) {
711818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            onCloseClicked();
712818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } else {
713818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            onSearchClicked();
714818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
715818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
716818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
717818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
718818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns the current iconified state of the SearchView.
719818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
720818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return true if the SearchView is currently iconified, false if the search field is
721818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * fully visible.
722818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
723818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public boolean isIconified() {
724818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return mIconified;
725818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
726818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
727818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
728818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Enables showing a submit button when the query is non-empty. In cases where the SearchView
729818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * is being used to filter the contents of the current activity and doesn't launch a separate
730818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * results activity, then the submit button should be disabled.
731818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
732818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param enabled true to show a submit button for submitting queries, false if a submit
733818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * button is not required.
734818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
735818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setSubmitButtonEnabled(boolean enabled) {
736818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSubmitButtonEnabled = enabled;
737818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateViewsVisibility(isIconified());
738818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
739818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
740818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
741818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns whether the submit button is enabled when necessary or never displayed.
742818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
743818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return whether the submit button is enabled automatically when necessary
744818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
745818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public boolean isSubmitButtonEnabled() {
746818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return mSubmitButtonEnabled;
747818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
748818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
749818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
750818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Specifies if a query refinement button should be displayed alongside each suggestion
751818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * or if it should depend on the flags set in the individual items retrieved from the
752818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * suggestions provider. Clicking on the query refinement button will replace the text
753818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * in the query text field with the text from the suggestion. This flag only takes effect
754818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)}
755818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * and not when using a custom adapter.
756818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
757818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param enable true if all items should have a query refinement button, false if only
758818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * those items that have a query refinement flag set should have the button.
759818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
760818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @see SearchManager#SUGGEST_COLUMN_FLAGS
761818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @see SearchManager#FLAG_QUERY_REFINEMENT
762818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
763818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setQueryRefinementEnabled(boolean enable) {
764818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mQueryRefinement = enable;
765818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSuggestionsAdapter instanceof SuggestionsAdapter) {
766818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
767818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    enable ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY);
768818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
769818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
770818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
771818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
772818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns whether query refinement is enabled for all items or only specific ones.
773818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return true if enabled for all items, false otherwise.
774818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
775818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public boolean isQueryRefinementEnabled() {
776818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return mQueryRefinement;
777818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
778818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
779818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
780818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * You can set a custom adapter if you wish. Otherwise the default adapter is used to
781818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * display the suggestions from the suggestions provider associated with the SearchableInfo.
782818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
783818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @see #setSearchableInfo(SearchableInfo)
784818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
785818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setSuggestionsAdapter(CursorAdapter adapter) {
786818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSuggestionsAdapter = adapter;
787818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
788cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setAdapter(mSuggestionsAdapter);
789818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
790818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
791818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
792818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Returns the adapter used for suggestions, if any.
793818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return the suggestions adapter
794818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
795818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public CursorAdapter getSuggestionsAdapter() {
796818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return mSuggestionsAdapter;
797818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
798818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
799818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
800818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Makes the view at most this many pixels wide
801d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
802d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_maxWidth
803818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
804818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void setMaxWidth(int maxpixels) {
805818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mMaxWidth = maxpixels;
806818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
807818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        requestLayout();
808818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
809818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
810818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
811818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Gets the specified maximum width in pixels, if set. Returns zero if
812818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * no maximum width was specified.
813818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return the maximum width of the view
814d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     *
815d6e47228c44aaadb0d4518da6db5c3f5dffda1abChris Banes     * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_maxWidth
816818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
817818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public int getMaxWidth() {
818818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return mMaxWidth;
819818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
820818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
821818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    @Override
822818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
823818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Let the standard measurements take effect in iconified state.
824818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (isIconified()) {
825818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
826818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return;
827818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
828818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
829818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
830818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        int width = MeasureSpec.getSize(widthMeasureSpec);
831818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
832818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        switch (widthMode) {
833818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            case MeasureSpec.AT_MOST:
834818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // If there is an upper limit, don't exceed maximum width (explicit or implicit)
835818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (mMaxWidth > 0) {
836818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    width = Math.min(mMaxWidth, width);
837818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                } else {
838818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    width = Math.min(getPreferredWidth(), width);
839818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
840818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                break;
841818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            case MeasureSpec.EXACTLY:
842818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // If an exact width is specified, still don't exceed any specified maximum width
843818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (mMaxWidth > 0) {
844818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    width = Math.min(mMaxWidth, width);
845818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
846818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                break;
847818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            case MeasureSpec.UNSPECIFIED:
848818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // Use maximum width, if specified, else preferred width
849818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                width = mMaxWidth > 0 ? mMaxWidth : getPreferredWidth();
850818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                break;
851818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
852818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        widthMode = MeasureSpec.EXACTLY;
8536c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
8546c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
8556c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        int height = MeasureSpec.getSize(heightMeasureSpec);
8566c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
8576c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        switch (heightMode) {
8586c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            case MeasureSpec.AT_MOST:
8596c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                height = Math.min(getPreferredHeight(), height);
8606c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                break;
861d8ee8fab3fab996b6dfba84ef10ee4ed09a37cb0Aurimas Liutikas            case MeasureSpec.UNSPECIFIED:
862d8ee8fab3fab996b6dfba84ef10ee4ed09a37cb0Aurimas Liutikas                height = getPreferredHeight();
863d8ee8fab3fab996b6dfba84ef10ee4ed09a37cb0Aurimas Liutikas                break;
8646c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        }
8656c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        heightMode = MeasureSpec.EXACTLY;
8666c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
8676c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode),
8686c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                MeasureSpec.makeMeasureSpec(height, heightMode));
8696c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    }
8706c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
8716c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    @Override
8726c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
8736c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        super.onLayout(changed, left, top, right, bottom);
8746c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
8756c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        if (changed) {
8766c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            // Expand mSearchSrcTextView touch target to be the height of the parent in order to
8776c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            // allow it to be up to 48dp.
8786c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            getChildBoundsWithinSearchView(mSearchSrcTextView, mSearchSrcTextViewBounds);
8796c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mSearchSrtTextViewBoundsExpanded.set(
8806c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    mSearchSrcTextViewBounds.left, 0, mSearchSrcTextViewBounds.right, bottom - top);
8816c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            if (mTouchDelegate == null) {
8826c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                mTouchDelegate = new UpdatableTouchDelegate(mSearchSrtTextViewBoundsExpanded,
8836c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                        mSearchSrcTextViewBounds, mSearchSrcTextView);
8846c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                setTouchDelegate(mTouchDelegate);
8856c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            } else {
8866c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                mTouchDelegate.setBounds(mSearchSrtTextViewBoundsExpanded, mSearchSrcTextViewBounds);
8876c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            }
8886c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        }
8896c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    }
8906c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
8916c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private void getChildBoundsWithinSearchView(View view, Rect rect) {
8926c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        view.getLocationInWindow(mTemp);
8936c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        getLocationInWindow(mTemp2);
8946c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        final int top = mTemp[1] - mTemp2[1];
8956c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        final int left = mTemp[0] - mTemp2[0];
8966c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        rect.set(left, top, left + view.getWidth(), top + view.getHeight());
897818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
898818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
899818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private int getPreferredWidth() {
900818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return getContext().getResources()
90107a07ce59efb770e9fb9ca53a0133e5e64a63bbcChris Banes                .getDimensionPixelSize(R.dimen.abc_search_view_preferred_width);
902818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
903818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
9046c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private int getPreferredHeight() {
9056c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        return getContext().getResources()
9066c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                .getDimensionPixelSize(R.dimen.abc_search_view_preferred_height);
9076c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    }
9086c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
909818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void updateViewsVisibility(final boolean collapsed) {
910818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mIconified = collapsed;
911818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Visibility of views that are visible when collapsed
912818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        final int visCollapsed = collapsed ? VISIBLE : GONE;
913818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Is there text in the query
914cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText());
915818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
916818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSearchButton.setVisibility(visCollapsed);
917818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateSubmitButton(hasText);
918818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
9194ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes
9204ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes        final int iconVisibility;
9214ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes        if (mCollapsedIcon.getDrawable() == null || mIconifiedByDefault) {
9224ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes            iconVisibility = GONE;
9234ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes        } else {
9244ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes            iconVisibility = VISIBLE;
9254ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes        }
9264ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes        mCollapsedIcon.setVisibility(iconVisibility);
9274ae9d79a932a0518a41d6cbc67cb0ad69ce7dca3Chris Banes
928818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateCloseButton();
929818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateVoiceButton(!hasText);
930818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateSubmitArea();
931818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
932818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
933818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean hasVoiceSearch() {
934cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) {
935818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Intent testIntent = null;
936818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (mSearchable.getVoiceSearchLaunchWebSearch()) {
937818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                testIntent = mVoiceWebSearchIntent;
938818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
939818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                testIntent = mVoiceAppSearchIntent;
940818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
941818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (testIntent != null) {
942818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                ResolveInfo ri = getContext().getPackageManager().resolveActivity(testIntent,
943818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        PackageManager.MATCH_DEFAULT_ONLY);
944818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                return ri != null;
945818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
946818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
947818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return false;
948818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
949818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
950818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean isSubmitAreaEnabled() {
951818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return (mSubmitButtonEnabled || mVoiceButtonEnabled) && !isIconified();
952818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
953818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
954818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void updateSubmitButton(boolean hasText) {
955818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        int visibility = GONE;
956818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus()
957818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                && (hasText || !mVoiceButtonEnabled)) {
958818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            visibility = VISIBLE;
959818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
960cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mGoButton.setVisibility(visibility);
961818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
962818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
963818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void updateSubmitArea() {
964818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        int visibility = GONE;
965818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (isSubmitAreaEnabled()
966cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                && (mGoButton.getVisibility() == VISIBLE
967cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                        || mVoiceButton.getVisibility() == VISIBLE)) {
968818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            visibility = VISIBLE;
969818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
970818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mSubmitArea.setVisibility(visibility);
971818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
972818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
973818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void updateCloseButton() {
974cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final boolean hasText = !TextUtils.isEmpty(mSearchSrcTextView.getText());
975818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Should we show the close button? It is not shown if there's no focus,
976818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // field is not iconified by default and there is no text in it.
977818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView);
978818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mCloseButton.setVisibility(showClose ? VISIBLE : GONE);
979cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final Drawable closeButtonImg = mCloseButton.getDrawable();
980cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        if (closeButtonImg != null){
981cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            closeButtonImg.setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
982cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        }
983818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
984818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
985818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void postUpdateFocusedState() {
986818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        post(mUpdateDrawableStateRunnable);
987818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
988818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
9892c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void updateFocusedState() {
990cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final boolean focused = mSearchSrcTextView.hasFocus();
991cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final int[] stateSet = focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET;
992cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final Drawable searchPlateBg = mSearchPlate.getBackground();
993cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        if (searchPlateBg != null) {
994cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            searchPlateBg.setState(stateSet);
995cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        }
996cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final Drawable submitAreaBg = mSubmitArea.getBackground();
997cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        if (submitAreaBg != null) {
998cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            submitAreaBg.setState(stateSet);
999cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        }
1000818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        invalidate();
1001818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1002818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1003818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    @Override
1004818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    protected void onDetachedFromWindow() {
1005818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        removeCallbacks(mUpdateDrawableStateRunnable);
1006818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        post(mReleaseCursorRunnable);
1007818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        super.onDetachedFromWindow();
1008818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1009818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
10102c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void setImeVisibility(final boolean visible) {
1011818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (visible) {
1012818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            post(mShowImeRunnable);
1013818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } else {
1014818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            removeCallbacks(mShowImeRunnable);
1015818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            InputMethodManager imm = (InputMethodManager)
1016818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
1017818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1018818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (imm != null) {
1019818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                imm.hideSoftInputFromWindow(getWindowToken(), 0);
1020818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1021818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1022818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1023818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1024818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1025818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Called by the SuggestionsAdapter
1026818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1027c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette    void onQueryRefine(CharSequence queryText) {
1028818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        setQuery(queryText);
1029818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1030818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1031818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private final OnClickListener mOnClickListener = new OnClickListener() {
1032dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1033818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void onClick(View v) {
1034818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (v == mSearchButton) {
1035818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                onSearchClicked();
1036818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } else if (v == mCloseButton) {
1037818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                onCloseClicked();
1038cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            } else if (v == mGoButton) {
1039818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                onSubmitQuery();
1040818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } else if (v == mVoiceButton) {
1041cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                onVoiceClicked();
1042cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            } else if (v == mSearchSrcTextView) {
1043818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                forceSuggestionQuery();
1044818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1045818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1046818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
1047818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1048818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1049818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * React to the user typing "enter" or other hardwired keys while typing in
1050818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * the search box. This handles these special keys while the edit box has
1051818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * focus.
1052818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1053818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
1054dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1055818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public boolean onKey(View v, int keyCode, KeyEvent event) {
1056818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // guard against possible race conditions
1057818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (mSearchable == null) {
1058818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                return false;
1059818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1060818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1061818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (DBG) {
1062818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: "
1063cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                        + mSearchSrcTextView.getListSelection());
1064818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1065818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1066818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // If a suggestion is selected, handle enter, search key, and action keys
1067818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // as presses on the selected suggestion
1068cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            if (mSearchSrcTextView.isPopupShowing()
1069cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                    && mSearchSrcTextView.getListSelection() != ListView.INVALID_POSITION) {
1070818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                return onSuggestionsKey(v, keyCode, event);
1071818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1072818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1073818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // If there is text in the query box, handle enter, and action keys
1074818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // The search key is handled by the dialog's onKeyDown().
1075cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            if (!mSearchSrcTextView.isEmpty() && KeyEventCompat.hasNoModifiers(event)) {
1076818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (event.getAction() == KeyEvent.ACTION_UP) {
1077818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    if (keyCode == KeyEvent.KEYCODE_ENTER) {
1078818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        v.cancelLongPress();
1079818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1080818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        // Launch as a regular search.
1081cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mSearchSrcTextView.getText()
1082818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                                .toString());
1083818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        return true;
1084818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    }
1085818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
1086818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1087818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return false;
1088818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1089818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
1090818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1091818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1092818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * React to the user typing while in the suggestions list. First, check for
1093818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * action keys. If not handled, try refocusing regular characters into the
1094818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * EditText.
1095818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
10962c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
1097818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // guard against possible race conditions (late arrival after dismiss)
1098818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSearchable == null) {
1099818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return false;
1100818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1101818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSuggestionsAdapter == null) {
1102818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return false;
1103818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1104818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (event.getAction() == KeyEvent.ACTION_DOWN && KeyEventCompat.hasNoModifiers(event)) {
1105818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // First, check for enter or search (both of which we'll treat as a
1106818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // "click")
1107818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH
1108818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    || keyCode == KeyEvent.KEYCODE_TAB) {
1109cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                int position = mSearchSrcTextView.getListSelection();
1110818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
1111818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1112818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1113818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // Next, check for left/right moves, which we use to "return" the
1114818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // user to the edit view
1115818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
1116818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // give "focus" to text editor, with cursor at the beginning if
1117818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // left key, at end if right key
11181b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes                // TODO: Reverse left/right for right-to-left languages, e.g.
11191b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes                // Arabic
1120cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mSearchSrcTextView
1121818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        .length();
1122cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                mSearchSrcTextView.setSelection(selPoint);
1123cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                mSearchSrcTextView.setListSelection(0);
1124cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                mSearchSrcTextView.clearListSelection();
1125cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                HIDDEN_METHOD_INVOKER.ensureImeVisible(mSearchSrcTextView, true);
1126818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1127818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                return true;
1128818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1129818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1130818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // Next, check for an "up and out" move
1131cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchSrcTextView.getListSelection()) {
1132818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // TODO: restoreUserQuery();
1133818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // let ACTV complete the move
1134818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                return false;
1135818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1136818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1137818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return false;
1138818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1139818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1140818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private CharSequence getDecoratedHint(CharSequence hintText) {
1141cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        // If the field is always expanded or we don't have a search hint icon,
1142cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        // then don't add the search icon to the hint.
1143cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        if (!mIconifiedByDefault || mSearchHintIcon == null) {
11441b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes            return hintText;
11451b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        }
1146818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1147cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final int textSize = (int) (mSearchSrcTextView.getTextSize() * 1.25);
1148cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchHintIcon.setBounds(0, 0, textSize, textSize);
11491b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes
1150cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        final SpannableStringBuilder ssb = new SpannableStringBuilder("   ");
1151cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        ssb.setSpan(new ImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
11521b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        ssb.append(hintText);
1153818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return ssb;
1154818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1155818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1156818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void updateQueryHint() {
1157ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes        final CharSequence hint = getQueryHint();
1158ecd75f2a7ec12bcbec7a9f0382cbd63b5c903188Chris Banes        mSearchSrcTextView.setHint(getDecoratedHint(hint == null ? "" : hint));
1159818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1160818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1161818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1162818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Updates the auto-complete text view.
1163818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1164818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void updateSearchAutoComplete() {
1165cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setThreshold(mSearchable.getSuggestThreshold());
1166cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setImeOptions(mSearchable.getImeOptions());
1167818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        int inputType = mSearchable.getInputType();
1168818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // We only touch this if the input type is set up for text (which it almost certainly
1169818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // should be, in the case of search!)
1170818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
1171818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // The existence of a suggestions authority is the proxy for "suggestions
1172818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // are available here"
1173818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
1174818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (mSearchable.getSuggestAuthority() != null) {
1175818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
1176818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // TYPE_TEXT_FLAG_AUTO_COMPLETE means that the text editor is performing
1177818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // auto-completion based on its own semantics, which it will present to the user
1178818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // as they type. This generally means that the input method should not show its
1179818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // own candidates, and the spell checker should not be in action. The text editor
1180818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // supplies its candidates by calling InputMethodManager.displayCompletions(),
1181818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // which in turn will call InputMethodSession.displayCompletions().
1182818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
1183818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1184818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1185cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setInputType(inputType);
1186818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSuggestionsAdapter != null) {
1187818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mSuggestionsAdapter.changeCursor(null);
1188818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1189818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // attach the suggestions adapter, if suggestions are available
1190818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // The existence of a suggestions authority is the proxy for "suggestions available here"
1191818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSearchable.getSuggestAuthority() != null) {
1192818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
1193818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    this, mSearchable, mOutsideDrawablesCache);
1194cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.setAdapter(mSuggestionsAdapter);
1195818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
1196818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
1197cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                    : SuggestionsAdapter.REFINE_BY_ENTRY);
1198818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1199818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1200818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1201818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1202818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Update the visibility of the voice button.  There are actually two voice search modes,
1203818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * either of which will activate the button.
1204818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param empty whether the search query text field is empty. If it is, then the other
1205818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * criteria apply to make the voice button visible.
1206818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1207818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void updateVoiceButton(boolean empty) {
1208818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        int visibility = GONE;
1209818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mVoiceButtonEnabled && !isIconified() && empty) {
1210818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            visibility = VISIBLE;
1211cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mGoButton.setVisibility(GONE);
1212818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1213818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mVoiceButton.setVisibility(visibility);
1214818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1215818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1216818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() {
1217818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1218818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1219818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Called when the input method default action key is pressed.
1220818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1221dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1222818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
1223818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            onSubmitQuery();
1224818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return true;
1225818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1226818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
1227818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
12282c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void onTextChanged(CharSequence newText) {
1229cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        CharSequence text = mSearchSrcTextView.getText();
1230818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mUserQuery = text;
1231818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        boolean hasText = !TextUtils.isEmpty(text);
1232818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateSubmitButton(hasText);
1233818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateVoiceButton(!hasText);
1234818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateCloseButton();
1235818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateSubmitArea();
1236818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
1237818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mOnQueryChangeListener.onQueryTextChange(newText.toString());
1238818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1239818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mOldQueryText = newText.toString();
1240818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1241818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
12422c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void onSubmitQuery() {
1243cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        CharSequence query = mSearchSrcTextView.getText();
1244818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (query != null && TextUtils.getTrimmedLength(query) > 0) {
1245818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (mOnQueryChangeListener == null
1246818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
1247818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (mSearchable != null) {
1248818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
1249818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
12501b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes                setImeVisibility(false);
1251818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                dismissSuggestions();
1252818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1253818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1254818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1255818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1256818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void dismissSuggestions() {
1257cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.dismissDropDown();
1258818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1259818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
12602c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void onCloseClicked() {
1261cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        CharSequence text = mSearchSrcTextView.getText();
1262818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (TextUtils.isEmpty(text)) {
1263818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (mIconifiedByDefault) {
1264818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // If the app doesn't override the close behavior
1265818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
1266818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    // hide the keyboard and remove focus
1267818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    clearFocus();
1268818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    // collapse the search field
1269818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    updateViewsVisibility(true);
1270818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
1271818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1272818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } else {
1273cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.setText("");
1274cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.requestFocus();
1275818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            setImeVisibility(true);
1276818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1277818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1278818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1279818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
12802c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void onSearchClicked() {
1281818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateViewsVisibility(false);
1282cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.requestFocus();
1283818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        setImeVisibility(true);
1284818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mOnSearchClickListener != null) {
1285818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mOnSearchClickListener.onClick(this);
1286818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1287818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1288818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
12892c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void onVoiceClicked() {
1290818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // guard against possible race conditions
1291818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mSearchable == null) {
1292818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return;
1293818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1294818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        SearchableInfo searchable = mSearchable;
1295818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        try {
1296818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (searchable.getVoiceSearchLaunchWebSearch()) {
1297818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent,
1298818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        searchable);
1299818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                getContext().startActivity(webSearchIntent);
1300818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } else if (searchable.getVoiceSearchLaunchRecognizer()) {
1301818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent,
1302818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        searchable);
1303818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                getContext().startActivity(appSearchIntent);
1304818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1305818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } catch (ActivityNotFoundException e) {
1306818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // Should not happen, since we check the availability of
1307818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // voice search before showing the button. But just in case...
1308818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Log.w(LOG_TAG, "Could not find voice search activity");
1309818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1310818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1311818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1312818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    void onTextFocusChanged() {
1313818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateViewsVisibility(isIconified());
1314818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Delayed update to make sure that the focus has settled down and window focus changes
1315818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // don't affect it. A synchronous update was not working.
1316818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        postUpdateFocusedState();
1317cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        if (mSearchSrcTextView.hasFocus()) {
1318818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            forceSuggestionQuery();
1319818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1320818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1321818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1322818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    @Override
1323818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void onWindowFocusChanged(boolean hasWindowFocus) {
1324818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        super.onWindowFocusChanged(hasWindowFocus);
1325818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1326818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        postUpdateFocusedState();
1327818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1328818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1329818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1330818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * {@inheritDoc}
1331818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1332818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    @Override
1333818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void onActionViewCollapsed() {
13341b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes        setQuery("", false);
1335818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        clearFocus();
1336818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        updateViewsVisibility(true);
1337cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setImeOptions(mCollapsedImeOptions);
1338818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mExpandedInActionView = false;
1339818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1340818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1341818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1342818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * {@inheritDoc}
1343818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1344818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    @Override
1345818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    public void onActionViewExpanded() {
1346818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mExpandedInActionView) return;
1347818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1348818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        mExpandedInActionView = true;
1349cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mCollapsedImeOptions = mSearchSrcTextView.getImeOptions();
1350cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
1351cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setText("");
1352818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        setIconified(false);
1353818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1354818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
135505f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes    static class SavedState extends AbsSavedState {
13563de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        boolean isIconified;
13573de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas
13583de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        SavedState(Parcelable superState) {
13593de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas            super(superState);
13603de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        }
13613de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas
136205f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes        public SavedState(Parcel source, ClassLoader loader) {
136305f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes            super(source, loader);
13643de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas            isIconified = (Boolean) source.readValue(null);
13653de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        }
13663de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas
13673de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        @Override
13683de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        public void writeToParcel(Parcel dest, int flags) {
13693de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas            super.writeToParcel(dest, flags);
13703de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas            dest.writeValue(isIconified);
13713de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        }
13723de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas
13733de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        @Override
13743de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        public String toString() {
13753de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas            return "SearchView.SavedState{"
13763de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas                    + Integer.toHexString(System.identityHashCode(this))
13773de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas                    + " isIconified=" + isIconified + "}";
13783de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        }
13799950da639cb16f776b9cd39669b416ec6d91bce5Aurimas Liutikas
138005f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes        public static final Parcelable.Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
138105f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes                new ParcelableCompatCreatorCallbacks<SavedState>() {
138205f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes                    @Override
138305f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes                    public SavedState createFromParcel(Parcel in, ClassLoader loader) {
138405f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes                        return new SavedState(in, loader);
13859950da639cb16f776b9cd39669b416ec6d91bce5Aurimas Liutikas                    }
13869950da639cb16f776b9cd39669b416ec6d91bce5Aurimas Liutikas
138705f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes                    @Override
13889950da639cb16f776b9cd39669b416ec6d91bce5Aurimas Liutikas                    public SavedState[] newArray(int size) {
13899950da639cb16f776b9cd39669b416ec6d91bce5Aurimas Liutikas                        return new SavedState[size];
13909950da639cb16f776b9cd39669b416ec6d91bce5Aurimas Liutikas                    }
139105f5ba020fa6caa658c75b6d77436aa980ca0fccChris Banes                });
13923de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas    }
13933de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas
13943de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas    @Override
13953de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas    protected Parcelable onSaveInstanceState() {
13963de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        Parcelable superState = super.onSaveInstanceState();
13973de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        SavedState ss = new SavedState(superState);
13983de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        ss.isIconified = isIconified();
13993de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        return ss;
14003de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas    }
14013de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas
14023de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas    @Override
14033de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas    protected void onRestoreInstanceState(Parcelable state) {
1404bb58a82daf11bf3c056d1cd5887aa26435d37b69Aurimas Liutikas        if (!(state instanceof SavedState)) {
1405bb58a82daf11bf3c056d1cd5887aa26435d37b69Aurimas Liutikas            super.onRestoreInstanceState(state);
1406bb58a82daf11bf3c056d1cd5887aa26435d37b69Aurimas Liutikas            return;
1407bb58a82daf11bf3c056d1cd5887aa26435d37b69Aurimas Liutikas        }
14083de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        SavedState ss = (SavedState) state;
14093de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        super.onRestoreInstanceState(ss.getSuperState());
14103de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        updateViewsVisibility(ss.isIconified);
14113de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas        requestLayout();
14123de9a134c5b81bbe150c081209595d82f2bbf19fAurimas Liutikas    }
1413818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
14142c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void adjustDropDownSizeAndPosition() {
1415818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mDropDownAnchor.getWidth() > 1) {
1416818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Resources res = getContext().getResources();
1417818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            int anchorPadding = mSearchPlate.getPaddingLeft();
1418818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Rect dropDownPadding = new Rect();
14191b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes            final boolean isLayoutRtl = ViewUtils.isLayoutRtl(this);
1420818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            int iconOffset = mIconifiedByDefault
142107a07ce59efb770e9fb9ca53a0133e5e64a63bbcChris Banes                    ? res.getDimensionPixelSize(R.dimen.abc_dropdownitem_icon_width)
142207a07ce59efb770e9fb9ca53a0133e5e64a63bbcChris Banes                    + res.getDimensionPixelSize(R.dimen.abc_dropdownitem_text_padding_left)
1423818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    : 0;
1424cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.getDropDownBackground().getPadding(dropDownPadding);
14251b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes            int offset;
14261b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes            if (isLayoutRtl) {
14271b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes                offset = - dropDownPadding.left;
14281b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes            } else {
14291b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes                offset = anchorPadding - (dropDownPadding.left + iconOffset);
14301b01ce23fa533d467ca2ccbc65c980a59662aa8dChris Banes            }
1431cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.setDropDownHorizontalOffset(offset);
1432818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            final int width = mDropDownAnchor.getWidth() + dropDownPadding.left
1433818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    + dropDownPadding.right + iconOffset - anchorPadding;
1434cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes            mSearchSrcTextView.setDropDownWidth(width);
1435818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1436818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1437818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
14382c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    boolean onItemClicked(int position, int actionKey, String actionMsg) {
1439818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mOnSuggestionListener == null
1440818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                || !mOnSuggestionListener.onSuggestionClick(position)) {
1441818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
1442818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            setImeVisibility(false);
1443818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            dismissSuggestions();
1444818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return true;
1445818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1446818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return false;
1447818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1448818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
14492c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    boolean onItemSelected(int position) {
1450818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mOnSuggestionListener == null
1451818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                || !mOnSuggestionListener.onSuggestionSelect(position)) {
1452818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            rewriteQueryFromSuggestion(position);
1453818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return true;
1454818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1455818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return false;
1456818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1457818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1458818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
1459818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1460818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1461818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Implements OnItemClickListener
1462818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1463dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1464818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1465818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
1466818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
1467818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1468818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
1469818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1470818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
1471818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1472818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1473818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Implements OnItemSelectedListener
1474818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1475dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1476818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1477818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
1478818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            SearchView.this.onItemSelected(position);
1479818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1480818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1481818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1482818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Implements OnItemSelectedListener
1483818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1484dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1485818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void onNothingSelected(AdapterView<?> parent) {
1486818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (DBG)
1487818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                Log.d(LOG_TAG, "onNothingSelected()");
1488818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1489818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
1490818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1491818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1492818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Query rewriting.
1493818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1494818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void rewriteQueryFromSuggestion(int position) {
1495cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        CharSequence oldQuery = mSearchSrcTextView.getText();
1496818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Cursor c = mSuggestionsAdapter.getCursor();
1497818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (c == null) {
1498818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return;
1499818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1500818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (c.moveToPosition(position)) {
1501818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // Get the new query from the suggestion.
1502818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
1503818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (newQuery != null) {
1504818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // The suggestion rewrites the query.
1505818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // Update the text field, without getting new suggestions.
1506818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                setQuery(newQuery);
1507818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } else {
1508818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // The suggestion does not rewrite the query, restore the user's query.
1509818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                setQuery(oldQuery);
1510818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1511818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } else {
1512818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // We got a bad position, restore the user's query.
1513818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            setQuery(oldQuery);
1514818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1515818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1516818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1517818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1518818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Launches an intent based on a suggestion.
1519818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
1520818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param position The index of the suggestion to create the intent from.
1521818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param actionKey The key code of the action key that was pressed,
1522818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1523818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param actionMsg The message for the action key that was pressed,
1524818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *        or <code>null</code> if none.
1525818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return true if a successful launch, false if could not (e.g. bad position).
1526818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1527818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private boolean launchSuggestion(int position, int actionKey, String actionMsg) {
1528818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Cursor c = mSuggestionsAdapter.getCursor();
1529818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if ((c != null) && c.moveToPosition(position)) {
1530818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1531818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
1532818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1533818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // launch the intent
1534818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            launchIntent(intent);
1535818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1536818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return true;
1537818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1538818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return false;
1539818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1540818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1541818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1542818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Launches an intent, including any special intent handling.
1543818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1544818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void launchIntent(Intent intent) {
1545818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (intent == null) {
1546818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return;
1547818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1548818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        try {
1549818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // If the intent was created from a suggestion, it will always have an explicit
1550818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // component here.
1551818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            getContext().startActivity(intent);
1552818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } catch (RuntimeException ex) {
1553818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
1554818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1555818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1556818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1557818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1558818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Sets the text in the query box, without updating the suggestions.
1559818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1560818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private void setQuery(CharSequence query) {
1561cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setText(query);
1562818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Move the cursor to the end
1563cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        mSearchSrcTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
1564818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1565818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
15662c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void launchQuerySearch(int actionKey, String actionMsg, String query) {
1567818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        String action = Intent.ACTION_SEARCH;
1568818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
1569818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        getContext().startActivity(intent);
1570818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1571818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1572818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1573818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Constructs an intent from the given information and the search dialog state.
1574818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
1575818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param action Intent action.
1576818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param data Intent data, or <code>null</code>.
1577818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
1578818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param query Intent query, or <code>null</code>.
1579818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param actionKey The key code of the action key that was pressed,
1580818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1581818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param actionMsg The message for the action key that was pressed,
1582818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *        or <code>null</code> if none.
1583818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return The intent.
1584818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1585818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private Intent createIntent(String action, Uri data, String extraData, String query,
1586818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            int actionKey, String actionMsg) {
1587818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Now build the Intent
1588818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Intent intent = new Intent(action);
1589818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1590818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // We need CLEAR_TOP to avoid reusing an old task that has other activities
1591818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // on top of the one we want. We don't want to do this in in-app search though,
1592818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // as it can be destructive to the activity stack.
1593818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (data != null) {
1594818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            intent.setData(data);
1595818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1596818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
1597818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (query != null) {
1598818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            intent.putExtra(SearchManager.QUERY, query);
1599818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1600818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (extraData != null) {
1601818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
1602818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1603818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mAppSearchData != null) {
1604818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
1605818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1606818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
1607818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            intent.putExtra(SearchManager.ACTION_KEY, actionKey);
1608818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
1609818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
161064dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        intent.setComponent(mSearchable.getSearchActivity());
1611818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return intent;
1612818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1613818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1614818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1615818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Create and return an Intent that can launch the voice search activity for web search.
1616818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1617818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) {
1618818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Intent voiceIntent = new Intent(baseIntent);
1619818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        ComponentName searchActivity = searchable.getSearchActivity();
1620818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
1621818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                : searchActivity.flattenToShortString());
1622818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return voiceIntent;
1623818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1624818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1625818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1626818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Create and return an Intent that can launch the voice search activity, perform a specific
1627818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * voice transcription, and forward the results to the searchable activity.
1628818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
1629818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param baseIntent The voice app search intent to start from
1630818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return A completely-configured intent ready to send to the voice search activity
1631818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1632818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) {
1633818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        ComponentName searchActivity = searchable.getSearchActivity();
1634818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1635818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // create the necessary intent to set up a search-and-forward operation
1636818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // in the voice search system.   We have to keep the bundle separate,
1637818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // because it becomes immutable once it enters the PendingIntent
1638818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
1639818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        queryIntent.setComponent(searchActivity);
1640818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        PendingIntent pending = PendingIntent.getActivity(getContext(), 0, queryIntent,
1641818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                PendingIntent.FLAG_ONE_SHOT);
1642818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1643818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Now set up the bundle that will be inserted into the pending intent
1644818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // when it's time to do the search.  We always build it here (even if empty)
1645818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // because the voice search activity will always need to insert "QUERY" into
1646818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // it anyway.
1647818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Bundle queryExtras = new Bundle();
1648818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        if (mAppSearchData != null) {
1649818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            queryExtras.putParcelable(SearchManager.APP_DATA, mAppSearchData);
1650818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1651818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1652818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Now build the intent to launch the voice search.  Add all necessary
1653818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // extras to launch the voice recognizer, and then all the necessary extras
1654818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // to forward the results to the searchable activity
1655818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        Intent voiceIntent = new Intent(baseIntent);
1656818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1657818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Add all of the configuration options supplied by the searchable's metadata
1658818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
1659818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        String prompt = null;
1660818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        String language = null;
1661818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        int maxResults = 1;
1662818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
166364dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        Resources resources = getResources();
166464dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        if (searchable.getVoiceLanguageModeId() != 0) {
166564dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            languageModel = resources.getString(searchable.getVoiceLanguageModeId());
166664dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        }
166764dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        if (searchable.getVoicePromptTextId() != 0) {
166864dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            prompt = resources.getString(searchable.getVoicePromptTextId());
166964dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        }
167064dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        if (searchable.getVoiceLanguageId() != 0) {
167164dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            language = resources.getString(searchable.getVoiceLanguageId());
1672818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
167364dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        if (searchable.getVoiceMaxResults() != 0) {
167464dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            maxResults = searchable.getVoiceMaxResults();
167564dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov        }
167664dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov
1677818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
1678818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
1679818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
1680818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
1681818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
1682818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                : searchActivity.flattenToShortString());
1683818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1684818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        // Add the values that configure forwarding the results
1685818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
1686818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
1687818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1688818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return voiceIntent;
1689818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1690818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1691818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1692818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * When a particular suggestion has been selected, perform the various lookups required
1693818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * to use the suggestion.  This includes checking the cursor for suggestion-specific data,
1694818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * and/or falling back to the XML for defaults;  It also creates REST style Uri data when
1695818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * the suggestion includes a data id.
1696818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *
1697818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param c The suggestions cursor, moved to the row of the user's selection
1698818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param actionKey The key code of the action key that was pressed,
1699818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1700818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @param actionMsg The message for the action key that was pressed,
1701818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     *        or <code>null</code> if none.
1702818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @return An intent for the suggestion at the cursor's position.
1703818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1704818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
1705818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        try {
1706818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // use specific action if supplied, or default action if supplied, or fixed default
1707818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
1708818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
170964dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            if (action == null) {
1710818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                action = mSearchable.getSuggestIntentAction();
1711818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1712818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (action == null) {
1713818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                action = Intent.ACTION_SEARCH;
1714818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1715818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1716818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // use specific data if supplied, or default data if supplied
1717818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
171864dbe1d454f1190b3cd8426d09b9119949a10709Kirill Grouchnikov            if (data == null) {
1719818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                data = mSearchable.getSuggestIntentData();
1720818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1721818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            // then, if an ID was provided, append it.
1722818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (data != null) {
1723818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
1724818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (id != null) {
1725818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    data = data + "/" + Uri.encode(id);
1726818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
1727818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1728818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Uri dataUri = (data == null) ? null : Uri.parse(data);
1729818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1730818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
1731818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
1732818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1733818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
1734818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        } catch (RuntimeException e ) {
1735818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            int rowNum;
1736818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            try {                       // be really paranoid now
1737818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                rowNum = c.getPosition();
1738818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } catch (RuntimeException e2 ) {
1739818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                rowNum = -1;
1740818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1741818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            Log.w(LOG_TAG, "Search suggestions cursor at row " + rowNum +
1742cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes                            " returned exception.", e);
1743818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return null;
1744818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1745818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1746818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
17472c1bad7ecd5879bf0f29ce2ce1bc5bd67a3f4682Aurimas Liutikas    void forceSuggestionQuery() {
1748cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        HIDDEN_METHOD_INVOKER.doBeforeTextChanged(mSearchSrcTextView);
1749cc489cd904db41ba9f8efaec0fde0b2d259123d7Chris Banes        HIDDEN_METHOD_INVOKER.doAfterTextChanged(mSearchSrcTextView);
1750818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1751818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1752818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    static boolean isLandscapeMode(Context context) {
1753818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        return context.getResources().getConfiguration().orientation
1754818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                == Configuration.ORIENTATION_LANDSCAPE;
1755818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
1756818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1757818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1758818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Callback to watch the text field for empty/non-empty
1759818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1760818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private TextWatcher mTextWatcher = new TextWatcher() {
1761dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1762818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
1763818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1764dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1765818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void onTextChanged(CharSequence s, int start,
1766818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                int before, int after) {
1767818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            SearchView.this.onTextChanged(s);
1768818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1769818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1770dc8e099ed130434c0238f558277c5bcb6a533121Aurimas Liutikas        @Override
1771818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void afterTextChanged(Editable s) {
1772818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1773818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    };
1774818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
17756c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    private static class UpdatableTouchDelegate extends TouchDelegate {
17766c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        /**
17776c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * View that should receive forwarded touch events
17786c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         */
17796c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        private final View mDelegateView;
17806c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
17816c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        /**
17826c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * Bounds in local coordinates of the containing view that should be mapped to the delegate
17836c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * view. This rect is used for initial hit testing.
17846c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         */
17856c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        private final Rect mTargetBounds;
17866c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
17876c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        /**
17886c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * Bounds in local coordinates of the containing view that are actual bounds of the delegate
17896c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * view. This rect is used for event coordinate mapping.
17906c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         */
17916c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        private final Rect mActualBounds;
17926c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
17936c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        /**
17946c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * mTargetBounds inflated to include some slop. This rect is to track whether the motion events
17956c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * should be considered to be be within the delegate view.
17966c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         */
17976c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        private final Rect mSlopBounds;
17986c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
17996c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        private final int mSlop;
18006c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
18016c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        /**
18026c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         * True if the delegate had been targeted on a down event (intersected mTargetBounds).
18036c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas         */
18046c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        private boolean mDelegateTargeted;
18056c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
18066c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        public UpdatableTouchDelegate(Rect targetBounds, Rect actualBounds, View delegateView) {
18076c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            super(targetBounds, delegateView);
18086c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
18096c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mTargetBounds = new Rect();
18106c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mSlopBounds = new Rect();
18116c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mActualBounds = new Rect();
18126c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            setBounds(targetBounds, actualBounds);
18136c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mDelegateView = delegateView;
18146c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        }
18156c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
18166c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        public void setBounds(Rect desiredBounds, Rect actualBounds) {
18176c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mTargetBounds.set(desiredBounds);
18186c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mSlopBounds.set(desiredBounds);
18196c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mSlopBounds.inset(-mSlop, -mSlop);
18206c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            mActualBounds.set(actualBounds);
18216c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        }
18226c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
18236c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        @Override
18246c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        public boolean onTouchEvent(MotionEvent event) {
18256c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            final int x = (int) event.getX();
18266c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            final int y = (int) event.getY();
18276c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            boolean sendToDelegate = false;
18286c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            boolean hit = true;
18296c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            boolean handled = false;
18306c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
18316c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            switch (event.getAction()) {
18326c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                case MotionEvent.ACTION_DOWN:
18336c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    if (mTargetBounds.contains(x, y)) {
18346c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                        mDelegateTargeted = true;
18356c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                        sendToDelegate = true;
18366c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    }
18376c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    break;
18386c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                case MotionEvent.ACTION_UP:
18396c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                case MotionEvent.ACTION_MOVE:
18406c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    sendToDelegate = mDelegateTargeted;
18416c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    if (sendToDelegate) {
18426c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                        if (!mSlopBounds.contains(x, y)) {
18436c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                            hit = false;
18446c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                        }
18456c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    }
18466c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    break;
18476c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                case MotionEvent.ACTION_CANCEL:
18486c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    sendToDelegate = mDelegateTargeted;
18496c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    mDelegateTargeted = false;
18506c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    break;
18516c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            }
18526c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            if (sendToDelegate) {
18536c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                if (hit && !mActualBounds.contains(x, y)) {
18546c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    // Offset event coordinates to be in the center of the target view since we
18556c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    // are within the targetBounds, but not inside the actual bounds of
18566c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    // mDelegateView
18576c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    event.setLocation(mDelegateView.getWidth() / 2,
18586c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                            mDelegateView.getHeight() / 2);
18596c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                } else {
18606c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    // Offset event coordinates to the target view coordinates.
18616c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                    event.setLocation(x - mActualBounds.left, y - mActualBounds.top);
18626c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                }
18636c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
18646c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas                handled = mDelegateView.dispatchTouchEvent(event);
18656c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            }
18666c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas            return handled;
18676c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas        }
18686c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas    }
18696c46b615beb0f05cc168f01bbc4dfa95a6eadddcAurimas Liutikas
1870818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    /**
1871818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * Local subclass for AutoCompleteTextView.
1872818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     * @hide
1873818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes     */
1874c39d9c75590eca86a5e7e32a8824ba04a0d42e9bAlan Viverette    @RestrictTo(GROUP_ID)
18752cccf609662389d6a23dbc0711d5fb2e826e8c63Chris Banes    public static class SearchAutoComplete extends AppCompatAutoCompleteTextView {
1876469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes
1877818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        private int mThreshold;
1878818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        private SearchView mSearchView;
1879818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1880818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public SearchAutoComplete(Context context) {
1881469286122bcbbecbdd0bef74fb50f9d8920e77b9Chris Banes            this(context, null);
1882818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1883818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1884818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public SearchAutoComplete(Context context, AttributeSet attrs) {
1885e68db207928c55c3c4941aab5ed6ec9dccc3c8fcChris Banes            this(context, attrs, R.attr.autoCompleteTextViewStyle);
1886818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1887818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1888818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
1889818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            super(context, attrs, defStyle);
1890818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mThreshold = getThreshold();
1891818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1892818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
18938dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski        @Override
18948dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski        protected void onFinishInflate() {
18958dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski            super.onFinishInflate();
18968dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski            DisplayMetrics metrics = getResources().getDisplayMetrics();
18978dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski            setMinWidth((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
18988dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski                    getSearchViewTextMinWidthDp(), metrics));
18998dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski        }
19008dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski
1901818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        void setSearchView(SearchView searchView) {
1902818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mSearchView = searchView;
1903818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1904818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1905818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        @Override
1906818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void setThreshold(int threshold) {
1907818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            super.setThreshold(threshold);
1908818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mThreshold = threshold;
1909818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1910818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1911818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1912818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * Returns true if the text field is empty, or contains only whitespace.
1913818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1914818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        private boolean isEmpty() {
1915818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return TextUtils.getTrimmedLength(getText()) == 0;
1916818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1917818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1918818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1919818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * We override this method to avoid replacing the query box text when a
1920818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * suggestion is clicked.
1921818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1922818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        @Override
1923818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        protected void replaceText(CharSequence text) {
1924818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1925818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1926818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1927818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * We override this method to avoid an extra onItemClick being called on
1928818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * the drop-down's OnItemClickListener by
1929818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is
1930818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * clicked with the trackball.
1931818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1932818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        @Override
1933818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void performCompletion() {
1934818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1935818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1936818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1937818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * We override this method to be sure and show the soft keyboard if
1938818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * appropriate when the TextView has focus.
1939818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1940818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        @Override
1941818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public void onWindowFocusChanged(boolean hasWindowFocus) {
1942818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            super.onWindowFocusChanged(hasWindowFocus);
1943818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1944818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
1945818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                InputMethodManager inputManager = (InputMethodManager) getContext()
1946818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        .getSystemService(Context.INPUT_METHOD_SERVICE);
1947818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                inputManager.showSoftInput(this, 0);
1948818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // If in landscape mode, then make sure that
1949818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // the ime is in front of the dropdown.
1950818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (isLandscapeMode(getContext())) {
1951818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    HIDDEN_METHOD_INVOKER.ensureImeVisible(this, true);
1952818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
1953818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1954818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1955818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1956818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        @Override
1957818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
1958818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            super.onFocusChanged(focused, direction, previouslyFocusedRect);
1959818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            mSearchView.onTextFocusChanged();
1960818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1961818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1962818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        /**
1963818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * We override this method so that we can allow a threshold of zero,
1964818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         * which ACTV does not.
1965818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes         */
1966818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        @Override
1967818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public boolean enoughToFilter() {
1968818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return mThreshold <= 0 || super.enoughToFilter();
1969818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
1970818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
1971818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        @Override
1972818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        public boolean onKeyPreIme(int keyCode, KeyEvent event) {
1973818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (keyCode == KeyEvent.KEYCODE_BACK) {
1974818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // special case for the back key, we do not even try to send it
1975818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // to the drop down list but instead, consume it immediately
1976818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
1977818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1978818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    if (state != null) {
1979818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        state.startTracking(event, this);
1980818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    }
1981818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    return true;
1982818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                } else if (event.getAction() == KeyEvent.ACTION_UP) {
1983818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1984818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    if (state != null) {
1985818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        state.handleUpEvent(event);
1986818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    }
1987818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    if (event.isTracking() && !event.isCanceled()) {
1988818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        mSearchView.clearFocus();
1989818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        mSearchView.setImeVisibility(false);
1990818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        return true;
1991818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    }
1992818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
1993818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
1994818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            return super.onKeyPreIme(keyCode, event);
1995818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
19968dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski
19978dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski        /**
19988dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski         * Get minimum width of the search view text entry area.
19998dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski         */
20008dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski        private int getSearchViewTextMinWidthDp() {
2001ed1096ebffac8f3cac5e5b98f54b96ae2ce8579eChris Banes            final Configuration config = getResources().getConfiguration();
20020cbe64051d4241ad38cb4a232a86e6f26a3c8259Chris Banes            final int widthDp = ConfigurationHelper.getScreenWidthDp(getResources());
20030cbe64051d4241ad38cb4a232a86e6f26a3c8259Chris Banes            final int heightDp = ConfigurationHelper.getScreenHeightDp(getResources());
2004ed1096ebffac8f3cac5e5b98f54b96ae2ce8579eChris Banes
2005ed1096ebffac8f3cac5e5b98f54b96ae2ce8579eChris Banes            if (widthDp >= 960 && heightDp >= 720
2006ed1096ebffac8f3cac5e5b98f54b96ae2ce8579eChris Banes                    && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
20078dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski                return 256;
2008ed1096ebffac8f3cac5e5b98f54b96ae2ce8579eChris Banes            } else if (widthDp >= 600 || (widthDp >= 640 && heightDp >= 480)) {
20098dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski                return 192;
2010ed1096ebffac8f3cac5e5b98f54b96ae2ce8579eChris Banes            }
20118dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski            return 160;
20128dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski        }
2013818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
2014818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
2015818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    private static class AutoCompleteTextViewReflector {
2016818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        private Method doBeforeTextChanged, doAfterTextChanged;
2017818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        private Method ensureImeVisible;
20182e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes        private Method showSoftInputUnchecked;
2019818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
2020818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        AutoCompleteTextViewReflector() {
2021818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            try {
2022818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                doBeforeTextChanged = AutoCompleteTextView.class
2023818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        .getDeclaredMethod("doBeforeTextChanged");
2024818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                doBeforeTextChanged.setAccessible(true);
2025818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } catch (NoSuchMethodException e) {
2026818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // Ah well.
2027818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
2028818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            try {
2029818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                doAfterTextChanged = AutoCompleteTextView.class
2030818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                        .getDeclaredMethod("doAfterTextChanged");
2031818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                doAfterTextChanged.setAccessible(true);
2032818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } catch (NoSuchMethodException e) {
2033818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // Ah well.
2034818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
2035818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            try {
2036818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                ensureImeVisible = AutoCompleteTextView.class
20372e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                        .getMethod("ensureImeVisible", boolean.class);
2038818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                ensureImeVisible.setAccessible(true);
2039818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            } catch (NoSuchMethodException e) {
2040818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                // Ah well.
2041818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
20422e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes            try {
20432e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                showSoftInputUnchecked = InputMethodManager.class.getMethod(
20442e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                        "showSoftInputUnchecked", int.class, ResultReceiver.class);
20452e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                showSoftInputUnchecked.setAccessible(true);
20462e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes            } catch (NoSuchMethodException e) {
20472e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                // Ah well.
20482e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes            }
2049818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
2050818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
2051818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        void doBeforeTextChanged(AutoCompleteTextView view) {
2052818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (doBeforeTextChanged != null) {
2053818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                try {
2054818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    doBeforeTextChanged.invoke(view);
20552e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                } catch (Exception e) {
2056818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
2057818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
2058818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
2059818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
2060818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        void doAfterTextChanged(AutoCompleteTextView view) {
2061818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (doAfterTextChanged != null) {
2062818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                try {
2063818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    doAfterTextChanged.invoke(view);
20642e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                } catch (Exception e) {
2065818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
2066818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
2067818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
2068818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes
2069818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        void ensureImeVisible(AutoCompleteTextView view, boolean visible) {
2070818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            if (ensureImeVisible != null) {
2071818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                try {
2072818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                    ensureImeVisible.invoke(view, visible);
20732e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                } catch (Exception e) {
2074818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes                }
2075818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes            }
2076818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes        }
20772e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes
20782e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes        void showSoftInputUnchecked(InputMethodManager imm, View view, int flags) {
20792e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes            if (showSoftInputUnchecked != null) {
20802e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                try {
20812e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                    showSoftInputUnchecked.invoke(imm, flags, null);
20822e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                    return;
20832e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                } catch (Exception e) {
20842e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes                }
20852e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes            }
20862e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes
20872e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes            // Hidden method failed, call public version instead
20882e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes            imm.showSoftInput(view, flags);
20892e21b5e22c320fd7e6af86a7cc05b4b11d7a0f64Chris Banes        }
2090818041428e0aca4943fe5adc0d54dad0d36a75f4Chris Banes    }
20918dd9fe7e5a46645cc7a0ec950df7ff7d76b90abaFilip Gruszczynski}
2092