1733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani/*
2733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * Copyright (C) 2010 The Android Open Source Project
3733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani *
4733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * Licensed under the Apache License, Version 2.0 (the "License");
5733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * you may not use this file except in compliance with the License.
6733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * You may obtain a copy of the License at
7733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani *
8733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani *      http://www.apache.org/licenses/LICENSE-2.0
9733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani *
10733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * Unless required by applicable law or agreed to in writing, software
11733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * distributed under the License is distributed on an "AS IS" BASIS,
12733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * See the License for the specific language governing permissions and
14733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani * limitations under the License.
15733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani */
16733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
17733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasanipackage android.widget;
18733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
19733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport static android.widget.SuggestionsAdapter.getColumnString;
20733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
21ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.app.PendingIntent;
22733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.app.SearchManager;
23733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.app.SearchableInfo;
24ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.content.ActivityNotFoundException;
25ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.content.ComponentName;
26733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.content.Context;
27733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.content.Intent;
28ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.content.pm.PackageManager;
29ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.content.pm.ResolveInfo;
30968ec938399033d280b1648123104ac567f2a093Amith Yamasaniimport android.content.res.Configuration;
31ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.content.res.Resources;
32733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.content.res.TypedArray;
33733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.database.Cursor;
346a81b8271d2edc1a87a3f50e8146f095be998629repo syncimport android.graphics.Rect;
35733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.graphics.drawable.Drawable;
36733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.net.Uri;
37ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.os.Bundle;
38ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasaniimport android.speech.RecognizerIntent;
39733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.text.Editable;
405607a3827172ff40196380d846128e892bedc118Amith Yamasaniimport android.text.InputType;
41b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasaniimport android.text.Spannable;
42b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasaniimport android.text.SpannableStringBuilder;
43733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.text.TextUtils;
44733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.text.TextWatcher;
45b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasaniimport android.text.style.ImageSpan;
46733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.util.AttributeSet;
47733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.util.Log;
48b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasaniimport android.util.TypedValue;
49763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasaniimport android.view.CollapsibleActionView;
50733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.view.KeyEvent;
51733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.view.LayoutInflater;
52733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.view.View;
538a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganovimport android.view.accessibility.AccessibilityEvent;
548a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganovimport android.view.accessibility.AccessibilityNodeInfo;
555607a3827172ff40196380d846128e892bedc118Amith Yamasaniimport android.view.inputmethod.EditorInfo;
56733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.view.inputmethod.InputMethodManager;
57733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.widget.AdapterView.OnItemClickListener;
58733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.widget.AdapterView.OnItemSelectedListener;
59733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport android.widget.TextView.OnEditorActionListener;
60733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
61b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasaniimport com.android.internal.R;
62b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
63733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasaniimport java.util.WeakHashMap;
64733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
65733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani/**
66763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * A widget that provides a user interface for the user to enter a search query and submit a request
67763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * to a search provider. Shows a list of query suggestions or results, if available, and allows the
68763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * user to pick a suggestion or result to launch into.
695931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani *
70763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * <p>
71763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * When the SearchView is used in an ActionBar as an action view for a collapsible menu item, it
72763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * needs to be set to iconified by default using {@link #setIconifiedByDefault(boolean)
73763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * setIconifiedByDefault(true)}. This is the default, so nothing needs to be done.
74763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * </p>
75763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * <p>
76763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * If you want the search field to always be visible, then call setIconifiedByDefault(false).
77763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * </p>
785931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani *
793aef8e1d1b2f0b87d470bcccf37ba4ebb6560c45Joe Fernandez * <div class="special reference">
803aef8e1d1b2f0b87d470bcccf37ba4ebb6560c45Joe Fernandez * <h3>Developer Guides</h3>
813aef8e1d1b2f0b87d470bcccf37ba4ebb6560c45Joe Fernandez * <p>For information about using {@code SearchView}, read the
823aef8e1d1b2f0b87d470bcccf37ba4ebb6560c45Joe Fernandez * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p>
833aef8e1d1b2f0b87d470bcccf37ba4ebb6560c45Joe Fernandez * </div>
84763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani *
85763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani * @see android.view.MenuItem#SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW
865931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani * @attr ref android.R.styleable#SearchView_iconifiedByDefault
875607a3827172ff40196380d846128e892bedc118Amith Yamasani * @attr ref android.R.styleable#SearchView_imeOptions
885607a3827172ff40196380d846128e892bedc118Amith Yamasani * @attr ref android.R.styleable#SearchView_inputType
895931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani * @attr ref android.R.styleable#SearchView_maxWidth
90abdf0d533c292709e51cee2c1213d5e44baca963Scott Main * @attr ref android.R.styleable#SearchView_queryHint
91733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani */
92763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasanipublic class SearchView extends LinearLayout implements CollapsibleActionView {
93733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
94733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private static final boolean DBG = false;
95733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private static final String LOG_TAG = "SearchView";
96733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
97535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin    /**
98535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin     * Private constant for removing the microphone in the keyboard.
99535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin     */
100535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin    private static final String IME_OPTION_NO_MICROPHONE = "nm";
101535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin
10201f21354654a315ea2a2a1668bbda50645123d4aAdam Powell    private OnQueryTextListener mOnQueryChangeListener;
103733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private OnCloseListener mOnCloseListener;
1040594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    private OnFocusChangeListener mOnQueryTextFocusChangeListener;
10501f21354654a315ea2a2a1668bbda50645123d4aAdam Powell    private OnSuggestionListener mOnSuggestionListener;
106483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani    private OnClickListener mOnSearchClickListener;
107733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
108733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private boolean mIconifiedByDefault;
1099322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    private boolean mIconified;
110733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private CursorAdapter mSuggestionsAdapter;
111733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private View mSearchButton;
112733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private View mSubmitButton;
11379f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani    private View mSearchPlate;
1149b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    private View mSubmitArea;
1154aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani    private ImageView mCloseButton;
116733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private View mSearchEditFrame;
117ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private View mVoiceButton;
118968ec938399033d280b1648123104ac567f2a093Amith Yamasani    private SearchAutoComplete mQueryTextView;
119b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    private View mDropDownAnchor;
120b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    private ImageView mSearchHintIcon;
121733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private boolean mSubmitButtonEnabled;
122733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private CharSequence mQueryHint;
123e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    private boolean mQueryRefinement;
1240594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    private boolean mClearingFocus;
1255931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani    private int mMaxWidth;
1269b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    private boolean mVoiceButtonEnabled;
127b47c4fd206ce5d057996d49228342d17ecca027eAmith Yamasani    private CharSequence mOldQueryText;
128068d73cf51305a9dfc15c96f17a18676637d3e02Amith Yamasani    private CharSequence mUserQuery;
129763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    private boolean mExpandedInActionView;
13053f56c4f51ae11d5b3afde8ce221b3fe9aea3cf1Adam Powell    private int mCollapsedImeOptions;
131733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
132733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private SearchableInfo mSearchable;
133940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani    private Bundle mAppSearchData;
134733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
135ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell    /*
136ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell     * SearchView can be set expanded before the IME is ready to be shown during
137ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell     * initial UI setup. The show operation is asynchronous to account for this.
138ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell     */
139ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell    private Runnable mShowImeRunnable = new Runnable() {
140ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell        public void run() {
141ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell            InputMethodManager imm = (InputMethodManager)
142ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
143ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell
144ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell            if (imm != null) {
145ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell                imm.showSoftInputUnchecked(0, null);
146ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell            }
147ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell        }
148ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell    };
149ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell
150a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    private Runnable mUpdateDrawableStateRunnable = new Runnable() {
151a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        public void run() {
152a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani            updateFocusedState();
153a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        }
154a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    };
155a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani
1568790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani    private Runnable mReleaseCursorRunnable = new Runnable() {
1578790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani        public void run() {
1588790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani            if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) {
1598790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani                mSuggestionsAdapter.changeCursor(null);
1608790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani            }
1618790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani        }
1628790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani    };
1638790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani
164ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    // For voice searching
165ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private final Intent mVoiceWebSearchIntent;
166ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private final Intent mVoiceAppSearchIntent;
167ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
168733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    // A weak map of drawables we've gotten from other packages, so we don't load them
169733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    // more than once.
170733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
171733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            new WeakHashMap<String, Drawable.ConstantState>();
172733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
173733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
174733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Callbacks for changes to the query text.
175733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
17601f21354654a315ea2a2a1668bbda50645123d4aAdam Powell    public interface OnQueryTextListener {
177733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
178733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        /**
179733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * Called when the user submits the query. This could be due to a key press on the
180733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * keyboard or due to pressing a submit button.
181733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * The listener can override the standard behavior by returning true
182733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * to indicate that it has handled the submit request. Otherwise return false to
183733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * let the SearchView handle the submission by launching any associated intent.
184733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         *
185733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * @param query the query text that is to be submitted
186733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         *
187733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * @return true if the query has been handled by the listener, false to let the
188733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * SearchView perform the default action.
189733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         */
19001f21354654a315ea2a2a1668bbda50645123d4aAdam Powell        boolean onQueryTextSubmit(String query);
191733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
192733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        /**
193733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * Called when the query text is changed by the user.
194733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         *
195733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * @param newText the new content of the query text field.
196733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         *
197733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * @return false if the SearchView should perform the default action of showing any
198733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * suggestions if available, true if the action was handled by the listener.
199733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         */
20001f21354654a315ea2a2a1668bbda50645123d4aAdam Powell        boolean onQueryTextChange(String newText);
201733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
202733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
203733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public interface OnCloseListener {
204733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
205733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        /**
206733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * The user is attempting to close the SearchView.
207733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         *
208733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * @return true if the listener wants to override the default behavior of clearing the
209733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * text field and dismissing it, false otherwise.
210733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         */
211733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        boolean onClose();
212733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
213733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
2140594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    /**
2150594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     * Callback interface for selection events on suggestions. These callbacks
2160594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     * are only relevant when a SearchableInfo has been specified by {@link #setSearchableInfo}.
2170594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     */
21801f21354654a315ea2a2a1668bbda50645123d4aAdam Powell    public interface OnSuggestionListener {
2190594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
2200594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        /**
2210594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * Called when a suggestion was selected by navigating to it.
2220594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * @param position the absolute position in the list of suggestions.
2230594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         *
2240594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * @return true if the listener handles the event and wants to override the default
2250594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * behavior of possibly rewriting the query based on the selected item, false otherwise.
2260594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         */
22701f21354654a315ea2a2a1668bbda50645123d4aAdam Powell        boolean onSuggestionSelect(int position);
2280594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
2290594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        /**
2300594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * Called when a suggestion was clicked.
2310594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * @param position the absolute position of the clicked item in the list of suggestions.
2320594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         *
2330594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * @return true if the listener handles the event and wants to override the default
2340594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * behavior of launching any intent or submitting a search query specified on that item.
2350594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         * Return false otherwise.
2360594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani         */
23701f21354654a315ea2a2a1668bbda50645123d4aAdam Powell        boolean onSuggestionClick(int position);
2380594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    }
2390594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
240733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public SearchView(Context context) {
241733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        this(context, null);
242733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
243733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
244733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public SearchView(Context context, AttributeSet attrs) {
245733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        super(context, attrs);
246733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
247733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        LayoutInflater inflater = (LayoutInflater) context
248733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
249733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        inflater.inflate(R.layout.search_view, this, true);
250733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
251733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSearchButton = findViewById(R.id.search_button);
252968ec938399033d280b1648123104ac567f2a093Amith Yamasani        mQueryTextView = (SearchAutoComplete) findViewById(R.id.search_src_text);
253968ec938399033d280b1648123104ac567f2a093Amith Yamasani        mQueryTextView.setSearchView(this);
254968ec938399033d280b1648123104ac567f2a093Amith Yamasani
255733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSearchEditFrame = findViewById(R.id.search_edit_frame);
25679f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        mSearchPlate = findViewById(R.id.search_plate);
2579b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        mSubmitArea = findViewById(R.id.submit_area);
258733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSubmitButton = findViewById(R.id.search_go_btn);
2594aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        mCloseButton = (ImageView) findViewById(R.id.search_close_btn);
260ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceButton = findViewById(R.id.search_voice_btn);
261b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        mSearchHintIcon = (ImageView) findViewById(R.id.search_mag_icon);
262733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
263733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSearchButton.setOnClickListener(mOnClickListener);
264733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mCloseButton.setOnClickListener(mOnClickListener);
265733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSubmitButton.setOnClickListener(mOnClickListener);
266ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceButton.setOnClickListener(mOnClickListener);
267f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani        mQueryTextView.setOnClickListener(mOnClickListener);
268ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
269733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryTextView.addTextChangedListener(mTextWatcher);
270733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryTextView.setOnEditorActionListener(mOnEditorActionListener);
271733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryTextView.setOnItemClickListener(mOnItemClickListener);
272733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryTextView.setOnItemSelectedListener(mOnItemSelectedListener);
273968ec938399033d280b1648123104ac567f2a093Amith Yamasani        mQueryTextView.setOnKeyListener(mTextKeyListener);
274535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin        // Inform any listener of focus changes
2750594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        mQueryTextView.setOnFocusChangeListener(new OnFocusChangeListener() {
2760594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
2770594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani            public void onFocusChange(View v, boolean hasFocus) {
2780594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani                if (mOnQueryTextFocusChangeListener != null) {
2790594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani                    mOnQueryTextFocusChangeListener.onFocusChange(SearchView.this, hasFocus);
2800594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani                }
2810594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani            }
2820594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        });
283733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
284733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.SearchView, 0, 0);
285733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        setIconifiedByDefault(a.getBoolean(R.styleable.SearchView_iconifiedByDefault, true));
2865931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        int maxWidth = a.getDimensionPixelSize(R.styleable.SearchView_maxWidth, -1);
2875931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        if (maxWidth != -1) {
2885931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani            setMaxWidth(maxWidth);
2895931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        }
290c0171d5e8ed1aaeaa658aa0d603860f7ada6807aAdam Powell        CharSequence queryHint = a.getText(R.styleable.SearchView_queryHint);
291c0171d5e8ed1aaeaa658aa0d603860f7ada6807aAdam Powell        if (!TextUtils.isEmpty(queryHint)) {
292c0171d5e8ed1aaeaa658aa0d603860f7ada6807aAdam Powell            setQueryHint(queryHint);
293c0171d5e8ed1aaeaa658aa0d603860f7ada6807aAdam Powell        }
2945607a3827172ff40196380d846128e892bedc118Amith Yamasani        int imeOptions = a.getInt(R.styleable.SearchView_imeOptions, -1);
2955607a3827172ff40196380d846128e892bedc118Amith Yamasani        if (imeOptions != -1) {
2965607a3827172ff40196380d846128e892bedc118Amith Yamasani            setImeOptions(imeOptions);
2975607a3827172ff40196380d846128e892bedc118Amith Yamasani        }
2985607a3827172ff40196380d846128e892bedc118Amith Yamasani        int inputType = a.getInt(R.styleable.SearchView_inputType, -1);
2995607a3827172ff40196380d846128e892bedc118Amith Yamasani        if (inputType != -1) {
3005607a3827172ff40196380d846128e892bedc118Amith Yamasani            setInputType(inputType);
3015607a3827172ff40196380d846128e892bedc118Amith Yamasani        }
3025607a3827172ff40196380d846128e892bedc118Amith Yamasani
303733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        a.recycle();
304733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
3055607a3827172ff40196380d846128e892bedc118Amith Yamasani        boolean focusable = true;
3065607a3827172ff40196380d846128e892bedc118Amith Yamasani
3077f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        a = context.obtainStyledAttributes(attrs, R.styleable.View, 0, 0);
3087f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        focusable = a.getBoolean(R.styleable.View_focusable, focusable);
3097f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        a.recycle();
3107f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        setFocusable(focusable);
3117f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani
312ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // Save voice intent for later queries/launching
313ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
314ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
315ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
316ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
317ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
318ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
319ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
320ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
321b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        mDropDownAnchor = findViewById(mQueryTextView.getDropDownAnchor());
322b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        if (mDropDownAnchor != null) {
323b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            mDropDownAnchor.addOnLayoutChangeListener(new OnLayoutChangeListener() {
324b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                @Override
325b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                public void onLayoutChange(View v, int left, int top, int right, int bottom,
326b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                        int oldLeft, int oldTop, int oldRight, int oldBottom) {
327b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    adjustDropDownSizeAndPosition();
328b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                }
329b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            });
330b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        }
331b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
332733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        updateViewsVisibility(mIconifiedByDefault);
333b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        updateQueryHint();
334733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
335733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
336733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
337733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Sets the SearchableInfo for this SearchView. Properties in the SearchableInfo are used
338733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * to display labels, hints, suggestions, create intents for launching search results screens
339733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * and controlling other affordances such as a voice button.
340733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
341733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param searchable a SearchableInfo can be retrieved from the SearchManager, for a specific
342733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * activity or a global search provider.
343733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
344733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public void setSearchableInfo(SearchableInfo searchable) {
345733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSearchable = searchable;
346733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (mSearchable != null) {
347733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            updateSearchAutoComplete();
34879f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani            updateQueryHint();
349733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
3509b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        // Cache the voice search capability
3519b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        mVoiceButtonEnabled = hasVoiceSearch();
352535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin
353535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin        if (mVoiceButtonEnabled) {
354535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin            // Disable the microphone on the keyboard, as a mic is displayed near the text box
355535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin            // TODO: use imeOptions to disable voice input when the new API will be available
356535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin            mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
357535698c57dbbd0d0eef79223293526748a07dfecLuca Zanolin        }
358b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        updateViewsVisibility(isIconified());
359733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
360733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
361940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani    /**
362940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani     * Sets the APP_DATA for legacy SearchDialog use.
363940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani     * @param appSearchData bundle provided by the app when launching the search dialog
364940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani     * @hide
365940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani     */
366940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani    public void setAppSearchData(Bundle appSearchData) {
367940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani        mAppSearchData = appSearchData;
368940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani    }
369940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani
3705607a3827172ff40196380d846128e892bedc118Amith Yamasani    /**
3715607a3827172ff40196380d846128e892bedc118Amith Yamasani     * Sets the IME options on the query text field.
3725607a3827172ff40196380d846128e892bedc118Amith Yamasani     *
3735607a3827172ff40196380d846128e892bedc118Amith Yamasani     * @see TextView#setImeOptions(int)
3745607a3827172ff40196380d846128e892bedc118Amith Yamasani     * @param imeOptions the options to set on the query text field
3755607a3827172ff40196380d846128e892bedc118Amith Yamasani     *
3765607a3827172ff40196380d846128e892bedc118Amith Yamasani     * @attr ref android.R.styleable#SearchView_imeOptions
3775607a3827172ff40196380d846128e892bedc118Amith Yamasani     */
3785607a3827172ff40196380d846128e892bedc118Amith Yamasani    public void setImeOptions(int imeOptions) {
3795607a3827172ff40196380d846128e892bedc118Amith Yamasani        mQueryTextView.setImeOptions(imeOptions);
3805607a3827172ff40196380d846128e892bedc118Amith Yamasani    }
3815607a3827172ff40196380d846128e892bedc118Amith Yamasani
3825607a3827172ff40196380d846128e892bedc118Amith Yamasani    /**
383eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * Returns the IME options set on the query text field.
384eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @return the ime options
385eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @see TextView#setImeOptions(int)
386eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     *
387eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @attr ref android.R.styleable#SearchView_imeOptions
388eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     */
389eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    public int getImeOptions() {
390eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani        return mQueryTextView.getImeOptions();
391eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    }
392eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani
393eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    /**
3945607a3827172ff40196380d846128e892bedc118Amith Yamasani     * Sets the input type on the query text field.
3955607a3827172ff40196380d846128e892bedc118Amith Yamasani     *
3965607a3827172ff40196380d846128e892bedc118Amith Yamasani     * @see TextView#setInputType(int)
3975607a3827172ff40196380d846128e892bedc118Amith Yamasani     * @param inputType the input type to set on the query text field
3985607a3827172ff40196380d846128e892bedc118Amith Yamasani     *
3995607a3827172ff40196380d846128e892bedc118Amith Yamasani     * @attr ref android.R.styleable#SearchView_inputType
4005607a3827172ff40196380d846128e892bedc118Amith Yamasani     */
4015607a3827172ff40196380d846128e892bedc118Amith Yamasani    public void setInputType(int inputType) {
4025607a3827172ff40196380d846128e892bedc118Amith Yamasani        mQueryTextView.setInputType(inputType);
4035607a3827172ff40196380d846128e892bedc118Amith Yamasani    }
4045607a3827172ff40196380d846128e892bedc118Amith Yamasani
405eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    /**
406eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * Returns the input type set on the query text field.
407eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @return the input type
408eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     *
409eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @attr ref android.R.styleable#SearchView_inputType
410eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     */
411eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    public int getInputType() {
412eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani        return mQueryTextView.getInputType();
413eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    }
414eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani
4150594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    /** @hide */
4160594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    @Override
4170594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    public boolean requestFocus(int direction, Rect previouslyFocusedRect) {
4187f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        // Don't accept focus if in the middle of clearing focus
4197f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        if (mClearingFocus) return false;
4207f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        // Check if SearchView is focusable.
4217f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        if (!isFocusable()) return false;
4227f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        // If it is not iconified, then give the focus to the text field
4237f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        if (!isIconified()) {
4247f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani            boolean result = mQueryTextView.requestFocus(direction, previouslyFocusedRect);
425f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani            if (result) {
426f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani                updateViewsVisibility(false);
427f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani            }
4287f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani            return result;
4297f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        } else {
4307f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani            return super.requestFocus(direction, previouslyFocusedRect);
4317f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        }
4320594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    }
4330594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
4340594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    /** @hide */
4350594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    @Override
4360594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    public void clearFocus() {
4370594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        mClearingFocus = true;
43810da590839e264735de8a4582021aca0dab81037Amith Yamasani        setImeVisibility(false);
4390594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        super.clearFocus();
4400594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        mQueryTextView.clearFocus();
4410594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        mClearingFocus = false;
4420594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    }
4430594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
444733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
445733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Sets a listener for user actions within the SearchView.
446733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
447733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param listener the listener object that receives callbacks when the user performs
448733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * actions in the SearchView such as clicking on buttons or typing a query.
449733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
45001f21354654a315ea2a2a1668bbda50645123d4aAdam Powell    public void setOnQueryTextListener(OnQueryTextListener listener) {
451733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mOnQueryChangeListener = listener;
452733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
453733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
454733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
4559322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * Sets a listener to inform when the user closes the SearchView.
4569322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     *
4579322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * @param listener the listener to call when the user closes the SearchView.
4589322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     */
4599322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    public void setOnCloseListener(OnCloseListener listener) {
4609322775014432ed6c87c864e98fe482f879ff233Amith Yamasani        mOnCloseListener = listener;
4619322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    }
4629322775014432ed6c87c864e98fe482f879ff233Amith Yamasani
4639322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    /**
4640594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     * Sets a listener to inform when the focus of the query text field changes.
4650594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     *
4660594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     * @param listener the listener to inform of focus changes.
4670594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     */
4680594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    public void setOnQueryTextFocusChangeListener(OnFocusChangeListener listener) {
4690594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        mOnQueryTextFocusChangeListener = listener;
4700594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    }
4710594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
4720594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    /**
4730594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     * Sets a listener to inform when a suggestion is focused or clicked.
4740594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     *
4750594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     * @param listener the listener to inform of suggestion selection events.
4760594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani     */
47701f21354654a315ea2a2a1668bbda50645123d4aAdam Powell    public void setOnSuggestionListener(OnSuggestionListener listener) {
4780594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        mOnSuggestionListener = listener;
4790594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    }
4800594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
4810594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani    /**
482483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     * Sets a listener to inform when the search button is pressed. This is only
483cccdbe9acb5cd721988b6b01a419c568fe5300b6Scott Main     * relevant when the text field is not visible by default. Calling {@link #setIconified
484cccdbe9acb5cd721988b6b01a419c568fe5300b6Scott Main     * setIconified(false)} can also cause this listener to be informed.
485483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     *
486483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     * @param listener the listener to inform when the search button is clicked or
487483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     * the text field is programmatically de-iconified.
488483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     */
489483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani    public void setOnSearchClickListener(OnClickListener listener) {
490483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani        mOnSearchClickListener = listener;
491483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani    }
492483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani
493483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani    /**
494483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     * Returns the query string currently in the text field.
495483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     *
496483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     * @return the query string
497483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani     */
498483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani    public CharSequence getQuery() {
499483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani        return mQueryTextView.getText();
500483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani    }
501483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani
502483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani    /**
503733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Sets a query string in the text field and optionally submits the query as well.
504733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
505733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param query the query string. This replaces any query text already present in the
506733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * text field.
507733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param submit whether to submit the query right now or only update the contents of
508733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * text field.
509733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
510733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public void setQuery(CharSequence query, boolean submit) {
511733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryTextView.setText(query);
51287c5025fb8a21348ead7cd7e6fa08aa5dc0f501eDmitri Plotnikov        if (query != null) {
5132427192e58b2ff31c3238b6c04440a15f4e7bf20Gilles Debunne            mQueryTextView.setSelection(mQueryTextView.length());
514068d73cf51305a9dfc15c96f17a18676637d3e02Amith Yamasani            mUserQuery = query;
51587c5025fb8a21348ead7cd7e6fa08aa5dc0f501eDmitri Plotnikov        }
51687c5025fb8a21348ead7cd7e6fa08aa5dc0f501eDmitri Plotnikov
517733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // If the query is not empty and submit is requested, submit the query
518733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (submit && !TextUtils.isEmpty(query)) {
519733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            onSubmitQuery();
520733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
521733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
522733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
523733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
524733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Sets the hint text to display in the query text field. This overrides any hint specified
525733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * in the SearchableInfo.
526733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
527733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param hint the hint text to display
528abdf0d533c292709e51cee2c1213d5e44baca963Scott Main     *
529abdf0d533c292709e51cee2c1213d5e44baca963Scott Main     * @attr ref android.R.styleable#SearchView_queryHint
530733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
531733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public void setQueryHint(CharSequence hint) {
532733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryHint = hint;
533733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        updateQueryHint();
534733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
535733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
536733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
537eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * Gets the hint text to display in the query text field.
538eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @return the query hint text, if specified, null otherwise.
539eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     *
540eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @attr ref android.R.styleable#SearchView_queryHint
541eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     */
542eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    public CharSequence getQueryHint() {
543eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani        if (mQueryHint != null) {
544eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani            return mQueryHint;
545eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani        } else if (mSearchable != null) {
546eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani            CharSequence hint = null;
547eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani            int hintId = mSearchable.getHintId();
548eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani            if (hintId != 0) {
549eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani                hint = getContext().getString(hintId);
550eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani            }
551eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani            return hint;
552eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani        }
553eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani        return null;
554eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    }
555eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani
556eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    /**
557733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Sets the default or resting state of the search field. If true, a single search icon is
558733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * shown by default and expands to show the text field and other buttons when pressed. Also,
559733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * if the default state is iconified, then it collapses to that state when the close button
5609322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * is pressed. Changes to this property will take effect immediately.
5619322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     *
562cccdbe9acb5cd721988b6b01a419c568fe5300b6Scott Main     * <p>The default value is true.</p>
563733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
5649322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * @param iconified whether the search field should be iconified by default
565abdf0d533c292709e51cee2c1213d5e44baca963Scott Main     *
566abdf0d533c292709e51cee2c1213d5e44baca963Scott Main     * @attr ref android.R.styleable#SearchView_iconifiedByDefault
567733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
568733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public void setIconifiedByDefault(boolean iconified) {
569ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        if (mIconifiedByDefault == iconified) return;
570733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mIconifiedByDefault = iconified;
571733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        updateViewsVisibility(iconified);
572b47c4fd206ce5d057996d49228342d17ecca027eAmith Yamasani        updateQueryHint();
573733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
574733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
5759322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    /**
5769322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * Returns the default iconified state of the search field.
5779322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * @return
5783d3e7a5816b5c78687bbfa4ef32abb8f1a5b83e1Amith Yamasani     *
5793d3e7a5816b5c78687bbfa4ef32abb8f1a5b83e1Amith Yamasani     * @attr ref android.R.styleable#SearchView_iconifiedByDefault
5809322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     */
581733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public boolean isIconfiedByDefault() {
582733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        return mIconifiedByDefault;
583733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
584733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
585733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
5869322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * Iconifies or expands the SearchView. Any query text is cleared when iconified. This is
5879322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * a temporary state and does not override the default iconified state set by
5889322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * {@link #setIconifiedByDefault(boolean)}. If the default state is iconified, then
5899322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * a false here will only be valid until the user closes the field. And if the default
5909322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * state is expanded, then a true here will only clear the text field and not close it.
5919322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     *
5929322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * @param iconify a true value will collapse the SearchView to an icon, while a false will
5939322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * expand it.
5949322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     */
5959322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    public void setIconified(boolean iconify) {
5969322775014432ed6c87c864e98fe482f879ff233Amith Yamasani        if (iconify) {
5979322775014432ed6c87c864e98fe482f879ff233Amith Yamasani            onCloseClicked();
5989322775014432ed6c87c864e98fe482f879ff233Amith Yamasani        } else {
5999322775014432ed6c87c864e98fe482f879ff233Amith Yamasani            onSearchClicked();
6009322775014432ed6c87c864e98fe482f879ff233Amith Yamasani        }
6019322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    }
6029322775014432ed6c87c864e98fe482f879ff233Amith Yamasani
6039322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    /**
6049322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * Returns the current iconified state of the SearchView.
6059322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     *
6069322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * @return true if the SearchView is currently iconified, false if the search field is
6079322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     * fully visible.
6089322775014432ed6c87c864e98fe482f879ff233Amith Yamasani     */
6099322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    public boolean isIconified() {
6109322775014432ed6c87c864e98fe482f879ff233Amith Yamasani        return mIconified;
6119322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    }
6129322775014432ed6c87c864e98fe482f879ff233Amith Yamasani
6139322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    /**
614733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Enables showing a submit button when the query is non-empty. In cases where the SearchView
615733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * is being used to filter the contents of the current activity and doesn't launch a separate
616733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * results activity, then the submit button should be disabled.
617733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
618733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param enabled true to show a submit button for submitting queries, false if a submit
619733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * button is not required.
620733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
621733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public void setSubmitButtonEnabled(boolean enabled) {
622733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSubmitButtonEnabled = enabled;
623ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        updateViewsVisibility(isIconified());
624733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
625733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
626733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
627733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Returns whether the submit button is enabled when necessary or never displayed.
628733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
629733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @return whether the submit button is enabled automatically when necessary
630733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
631733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public boolean isSubmitButtonEnabled() {
632733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        return mSubmitButtonEnabled;
633733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
634733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
635e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    /**
636e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * Specifies if a query refinement button should be displayed alongside each suggestion
637e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * or if it should depend on the flags set in the individual items retrieved from the
638e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * suggestions provider. Clicking on the query refinement button will replace the text
639e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * in the query text field with the text from the suggestion. This flag only takes effect
640e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * if a SearchableInfo has been specified with {@link #setSearchableInfo(SearchableInfo)}
641e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * and not when using a custom adapter.
642e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     *
643e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * @param enable true if all items should have a query refinement button, false if only
644e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * those items that have a query refinement flag set should have the button.
645e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     *
646e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * @see SearchManager#SUGGEST_COLUMN_FLAGS
647e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * @see SearchManager#FLAG_QUERY_REFINEMENT
648e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     */
649e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    public void setQueryRefinementEnabled(boolean enable) {
650e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        mQueryRefinement = enable;
651e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        if (mSuggestionsAdapter instanceof SuggestionsAdapter) {
652e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
653e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani                    enable ? SuggestionsAdapter.REFINE_ALL : SuggestionsAdapter.REFINE_BY_ENTRY);
654e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        }
655e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    }
656e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani
657e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    /**
658e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * Returns whether query refinement is enabled for all items or only specific ones.
659e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * @return true if enabled for all items, false otherwise.
660e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     */
661e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    public boolean isQueryRefinementEnabled() {
662e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        return mQueryRefinement;
663733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
664733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
665733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
666733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * You can set a custom adapter if you wish. Otherwise the default adapter is used to
667733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * display the suggestions from the suggestions provider associated with the SearchableInfo.
668733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
669733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @see #setSearchableInfo(SearchableInfo)
670733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
671733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public void setSuggestionsAdapter(CursorAdapter adapter) {
672733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSuggestionsAdapter = adapter;
673733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
674733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryTextView.setAdapter(mSuggestionsAdapter);
675733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
676733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
677733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
678733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Returns the adapter used for suggestions, if any.
679733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @return the suggestions adapter
680733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
681733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public CursorAdapter getSuggestionsAdapter() {
682733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        return mSuggestionsAdapter;
683733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
684733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
6855931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani    /**
6865931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani     * Makes the view at most this many pixels wide
6875931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani     *
6885931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani     * @attr ref android.R.styleable#SearchView_maxWidth
6895931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani     */
6905931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani    public void setMaxWidth(int maxpixels) {
6915931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        mMaxWidth = maxpixels;
6925931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani
6935931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        requestLayout();
6945931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani    }
6955931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani
696eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    /**
697eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * Gets the specified maximum width in pixels, if set. Returns zero if
698eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * no maximum width was specified.
699eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     * @return the maximum width of the view
7003d3e7a5816b5c78687bbfa4ef32abb8f1a5b83e1Amith Yamasani     *
7013d3e7a5816b5c78687bbfa4ef32abb8f1a5b83e1Amith Yamasani     * @attr ref android.R.styleable#SearchView_maxWidth
702eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani     */
703eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    public int getMaxWidth() {
704eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani        return mMaxWidth;
705eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani    }
706eca59d39c3e92815002dd50aaf176100a02e5a47Amith Yamasani
7075931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani    @Override
7085931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
709a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        // Let the standard measurements take effect in iconified state.
710a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        if (isIconified()) {
711a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
712a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani            return;
713a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        }
714a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani
7155931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
7165931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        int width = MeasureSpec.getSize(widthMeasureSpec);
7175931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani
718167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        switch (widthMode) {
719167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        case MeasureSpec.AT_MOST:
720167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            // If there is an upper limit, don't exceed maximum width (explicit or implicit)
721167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            if (mMaxWidth > 0) {
722167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani                width = Math.min(mMaxWidth, width);
723167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            } else {
724167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani                width = Math.min(getPreferredWidth(), width);
725167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            }
726167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            break;
727167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        case MeasureSpec.EXACTLY:
728167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            // If an exact width is specified, still don't exceed any specified maximum width
729167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            if (mMaxWidth > 0) {
730167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani                width = Math.min(mMaxWidth, width);
731167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            }
732167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            break;
733167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        case MeasureSpec.UNSPECIFIED:
734167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            // Use maximum width, if specified, else preferred width
735167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            width = mMaxWidth > 0 ? mMaxWidth : getPreferredWidth();
736167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani            break;
7375931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        }
738167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        widthMode = MeasureSpec.EXACTLY;
739167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec);
740167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani    }
741167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani
742167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani    private int getPreferredWidth() {
743167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        return getContext().getResources()
744167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani                .getDimensionPixelSize(R.dimen.search_view_preferred_width);
7455931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani    }
7465931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani
7479322775014432ed6c87c864e98fe482f879ff233Amith Yamasani    private void updateViewsVisibility(final boolean collapsed) {
7489322775014432ed6c87c864e98fe482f879ff233Amith Yamasani        mIconified = collapsed;
749733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // Visibility of views that are visible when collapsed
7509322775014432ed6c87c864e98fe482f879ff233Amith Yamasani        final int visCollapsed = collapsed ? VISIBLE : GONE;
7510594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        // Is there text in the query
7520594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
753733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
754733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mSearchButton.setVisibility(visCollapsed);
7559b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        updateSubmitButton(hasText);
7569b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        mSearchEditFrame.setVisibility(collapsed ? GONE : VISIBLE);
757b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        mSearchHintIcon.setVisibility(mIconifiedByDefault ? GONE : VISIBLE);
7584aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        updateCloseButton();
759ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        updateVoiceButton(!hasText);
7609b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        updateSubmitArea();
7619b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    }
7629b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani
7639b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    private boolean hasVoiceSearch() {
7649b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        if (mSearchable != null && mSearchable.getVoiceSearchEnabled()) {
7659b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            Intent testIntent = null;
7669b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            if (mSearchable.getVoiceSearchLaunchWebSearch()) {
7679b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani                testIntent = mVoiceWebSearchIntent;
7689b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
7699b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani                testIntent = mVoiceAppSearchIntent;
7709b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            }
7719b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            if (testIntent != null) {
7729b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani                ResolveInfo ri = getContext().getPackageManager().resolveActivity(testIntent,
7739b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani                        PackageManager.MATCH_DEFAULT_ONLY);
7749b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani                return ri != null;
7759b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            }
7769b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        }
7779b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        return false;
7789b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    }
7799b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani
7809b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    private boolean isSubmitAreaEnabled() {
7819b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        return (mSubmitButtonEnabled || mVoiceButtonEnabled) && !isIconified();
7829b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    }
7839b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani
7849b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    private void updateSubmitButton(boolean hasText) {
78579f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        int visibility = GONE;
786cf72ab4ddebae48e34dd2d32b7da8bf5a5d7cfa0Amith Yamasani        if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus()
787cf72ab4ddebae48e34dd2d32b7da8bf5a5d7cfa0Amith Yamasani                && (hasText || !mVoiceButtonEnabled)) {
78879f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani            visibility = VISIBLE;
78979f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        }
79079f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        mSubmitButton.setVisibility(visibility);
7919b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    }
7929b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani
7939b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani    private void updateSubmitArea() {
7949b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        int visibility = GONE;
79579f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        if (isSubmitAreaEnabled()
79679f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani                && (mSubmitButton.getVisibility() == VISIBLE
79779f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani                        || mVoiceButton.getVisibility() == VISIBLE)) {
79879f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani            visibility = VISIBLE;
7999b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        }
8009b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        mSubmitArea.setVisibility(visibility);
801733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
802733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
8034aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani    private void updateCloseButton() {
8044aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        final boolean hasText = !TextUtils.isEmpty(mQueryTextView.getText());
8054aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        // Should we show the close button? It is not shown if there's no focus,
8064aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        // field is not iconified by default and there is no text in it.
807763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani        final boolean showClose = hasText || (mIconifiedByDefault && !mExpandedInActionView);
808167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        mCloseButton.setVisibility(showClose ? VISIBLE : GONE);
8094aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        mCloseButton.getDrawable().setState(hasText ? ENABLED_STATE_SET : EMPTY_STATE_SET);
8104aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani    }
8114aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani
812a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    private void postUpdateFocusedState() {
813a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        post(mUpdateDrawableStateRunnable);
814a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    }
815a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani
816a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    private void updateFocusedState() {
817a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        boolean focused = mQueryTextView.hasFocus();
81879f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        mSearchPlate.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
81979f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        mSubmitArea.getBackground().setState(focused ? FOCUSED_STATE_SET : EMPTY_STATE_SET);
820a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        invalidate();
821a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    }
822a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani
823a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    @Override
824a465b2d4c34a24e42e5356b3632cc9c071fc9739Amith Yamasani    protected void onDetachedFromWindow() {
825a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        removeCallbacks(mUpdateDrawableStateRunnable);
8268790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani        post(mReleaseCursorRunnable);
827a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        super.onDetachedFromWindow();
82879f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani    }
82979f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani
830ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell    private void setImeVisibility(final boolean visible) {
831ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell        if (visible) {
832ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell            post(mShowImeRunnable);
833ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell        } else {
834ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell            removeCallbacks(mShowImeRunnable);
835ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell            InputMethodManager imm = (InputMethodManager)
836ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell                    getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
8370594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani
838ccdd4ee44f8cfbb45b2989cca833895fcc4c4225Adam Powell            if (imm != null) {
8390594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani                imm.hideSoftInputFromWindow(getWindowToken(), 0);
840733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
841733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
842733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
843733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
844e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    /**
845e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * Called by the SuggestionsAdapter
846e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     * @hide
847e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani     */
848e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    /* package */void onQueryRefine(CharSequence queryText) {
849e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        setQuery(queryText);
850e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani    }
851e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani
852733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private final OnClickListener mOnClickListener = new OnClickListener() {
853733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
854733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public void onClick(View v) {
855733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (v == mSearchButton) {
856733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                onSearchClicked();
857733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            } else if (v == mCloseButton) {
858733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                onCloseClicked();
859733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            } else if (v == mSubmitButton) {
860733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                onSubmitQuery();
861ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            } else if (v == mVoiceButton) {
862ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                onVoiceClicked();
863f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani            } else if (v == mQueryTextView) {
864f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani                forceSuggestionQuery();
865733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
866733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
867733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    };
868733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
869733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
870733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Handles the key down event for dealing with action keys.
871733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
872733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param keyCode This is the keycode of the typed key, and is the same value as
873733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *        found in the KeyEvent parameter.
874733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param event The complete event record for the typed key
875733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
876733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @return true if the event was handled here, or false if not.
877733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
878733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    @Override
879733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    public boolean onKeyDown(int keyCode, KeyEvent event) {
880733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (mSearchable == null) {
881733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            return false;
882733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
883733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
884733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // if it's an action specified by the searchable activity, launch the
885733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // entered query with the action key
886733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
887733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
8889322775014432ed6c87c864e98fe482f879ff233Amith Yamasani            launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView.getText()
8899322775014432ed6c87c864e98fe482f879ff233Amith Yamasani                    .toString());
890733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            return true;
891733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
892733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
893733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        return super.onKeyDown(keyCode, event);
894733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
895733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
896968ec938399033d280b1648123104ac567f2a093Amith Yamasani    /**
897968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * React to the user typing "enter" or other hardwired keys while typing in
898968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * the search box. This handles these special keys while the edit box has
899968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * focus.
900968ec938399033d280b1648123104ac567f2a093Amith Yamasani     */
901968ec938399033d280b1648123104ac567f2a093Amith Yamasani    View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
902968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public boolean onKey(View v, int keyCode, KeyEvent event) {
903968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // guard against possible race conditions
904968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if (mSearchable == null) {
905968ec938399033d280b1648123104ac567f2a093Amith Yamasani                return false;
906968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
907968ec938399033d280b1648123104ac567f2a093Amith Yamasani
908968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if (DBG) {
909968ec938399033d280b1648123104ac567f2a093Amith Yamasani                Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event + "), selection: "
910968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        + mQueryTextView.getListSelection());
911968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
912968ec938399033d280b1648123104ac567f2a093Amith Yamasani
913968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // If a suggestion is selected, handle enter, search key, and action keys
914968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // as presses on the selected suggestion
915968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if (mQueryTextView.isPopupShowing()
916968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    && mQueryTextView.getListSelection() != ListView.INVALID_POSITION) {
917968ec938399033d280b1648123104ac567f2a093Amith Yamasani                return onSuggestionsKey(v, keyCode, event);
918968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
919968ec938399033d280b1648123104ac567f2a093Amith Yamasani
920968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // If there is text in the query box, handle enter, and action keys
921968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // The search key is handled by the dialog's onKeyDown().
9224e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown            if (!mQueryTextView.isEmpty() && event.hasNoModifiers()) {
9234e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                if (event.getAction() == KeyEvent.ACTION_UP) {
9244e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                    if (keyCode == KeyEvent.KEYCODE_ENTER) {
9254e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                        v.cancelLongPress();
9264e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown
9274e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                        // Launch as a regular search.
9284e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                        launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, mQueryTextView.getText()
9294e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                                .toString());
9304e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                        return true;
9314e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                    }
932968ec938399033d280b1648123104ac567f2a093Amith Yamasani                }
933968ec938399033d280b1648123104ac567f2a093Amith Yamasani                if (event.getAction() == KeyEvent.ACTION_DOWN) {
934968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
935968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
936968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        launchQuerySearch(keyCode, actionKey.getQueryActionMsg(), mQueryTextView
937968ec938399033d280b1648123104ac567f2a093Amith Yamasani                                .getText().toString());
938968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        return true;
939968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    }
940968ec938399033d280b1648123104ac567f2a093Amith Yamasani                }
941968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
942968ec938399033d280b1648123104ac567f2a093Amith Yamasani            return false;
943968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
944968ec938399033d280b1648123104ac567f2a093Amith Yamasani    };
945968ec938399033d280b1648123104ac567f2a093Amith Yamasani
946968ec938399033d280b1648123104ac567f2a093Amith Yamasani    /**
947968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * React to the user typing while in the suggestions list. First, check for
948968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * action keys. If not handled, try refocusing regular characters into the
949968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * EditText.
950968ec938399033d280b1648123104ac567f2a093Amith Yamasani     */
951968ec938399033d280b1648123104ac567f2a093Amith Yamasani    private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
952968ec938399033d280b1648123104ac567f2a093Amith Yamasani        // guard against possible race conditions (late arrival after dismiss)
953968ec938399033d280b1648123104ac567f2a093Amith Yamasani        if (mSearchable == null) {
954968ec938399033d280b1648123104ac567f2a093Amith Yamasani            return false;
955968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
956968ec938399033d280b1648123104ac567f2a093Amith Yamasani        if (mSuggestionsAdapter == null) {
957968ec938399033d280b1648123104ac567f2a093Amith Yamasani            return false;
958968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
9594e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown        if (event.getAction() == KeyEvent.ACTION_DOWN && event.hasNoModifiers()) {
960968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // First, check for enter or search (both of which we'll treat as a
961968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // "click")
9624e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH
9634e6319b73c85082e18d1c532b86336ddd1f8cfaaJeff Brown                    || keyCode == KeyEvent.KEYCODE_TAB) {
964968ec938399033d280b1648123104ac567f2a093Amith Yamasani                int position = mQueryTextView.getListSelection();
965968ec938399033d280b1648123104ac567f2a093Amith Yamasani                return onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
966968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
967968ec938399033d280b1648123104ac567f2a093Amith Yamasani
968968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // Next, check for left/right moves, which we use to "return" the
969968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // user to the edit view
970968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
971968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // give "focus" to text editor, with cursor at the beginning if
972968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // left key, at end if right key
973968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // TODO: Reverse left/right for right-to-left languages, e.g.
974968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // Arabic
975968ec938399033d280b1648123104ac567f2a093Amith Yamasani                int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView
976968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        .length();
977968ec938399033d280b1648123104ac567f2a093Amith Yamasani                mQueryTextView.setSelection(selPoint);
978968ec938399033d280b1648123104ac567f2a093Amith Yamasani                mQueryTextView.setListSelection(0);
979968ec938399033d280b1648123104ac567f2a093Amith Yamasani                mQueryTextView.clearListSelection();
980968ec938399033d280b1648123104ac567f2a093Amith Yamasani                mQueryTextView.ensureImeVisible(true);
981968ec938399033d280b1648123104ac567f2a093Amith Yamasani
982968ec938399033d280b1648123104ac567f2a093Amith Yamasani                return true;
983968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
984968ec938399033d280b1648123104ac567f2a093Amith Yamasani
985968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // Next, check for an "up and out" move
986968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mQueryTextView.getListSelection()) {
987968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // TODO: restoreUserQuery();
988968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // let ACTV complete the move
989968ec938399033d280b1648123104ac567f2a093Amith Yamasani                return false;
990968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
991968ec938399033d280b1648123104ac567f2a093Amith Yamasani
992968ec938399033d280b1648123104ac567f2a093Amith Yamasani            // Next, check for an "action key"
993968ec938399033d280b1648123104ac567f2a093Amith Yamasani            SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
994968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if ((actionKey != null)
995968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    && ((actionKey.getSuggestActionMsg() != null) || (actionKey
996968ec938399033d280b1648123104ac567f2a093Amith Yamasani                            .getSuggestActionMsgColumn() != null))) {
997968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // launch suggestion using action key column
998968ec938399033d280b1648123104ac567f2a093Amith Yamasani                int position = mQueryTextView.getListSelection();
999968ec938399033d280b1648123104ac567f2a093Amith Yamasani                if (position != ListView.INVALID_POSITION) {
1000968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    Cursor c = mSuggestionsAdapter.getCursor();
1001968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    if (c.moveToPosition(position)) {
1002968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        final String actionMsg = getActionKeyMessage(c, actionKey);
1003968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        if (actionMsg != null && (actionMsg.length() > 0)) {
1004968ec938399033d280b1648123104ac567f2a093Amith Yamasani                            return onItemClicked(position, keyCode, actionMsg);
1005968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        }
1006968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    }
1007968ec938399033d280b1648123104ac567f2a093Amith Yamasani                }
1008968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
1009968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1010968ec938399033d280b1648123104ac567f2a093Amith Yamasani        return false;
1011968ec938399033d280b1648123104ac567f2a093Amith Yamasani    }
1012968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1013968ec938399033d280b1648123104ac567f2a093Amith Yamasani    /**
1014968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * For a given suggestion and a given cursor row, get the action message. If
1015968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * not provided by the specific row/column, also check for a single
1016968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * definition (for the action key).
1017968ec938399033d280b1648123104ac567f2a093Amith Yamasani     *
1018968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * @param c The cursor providing suggestions
1019968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * @param actionKey The actionkey record being examined
1020968ec938399033d280b1648123104ac567f2a093Amith Yamasani     *
1021968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * @return Returns a string, or null if no action key message for this
1022968ec938399033d280b1648123104ac567f2a093Amith Yamasani     *         suggestion
1023968ec938399033d280b1648123104ac567f2a093Amith Yamasani     */
1024968ec938399033d280b1648123104ac567f2a093Amith Yamasani    private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
1025968ec938399033d280b1648123104ac567f2a093Amith Yamasani        String result = null;
1026968ec938399033d280b1648123104ac567f2a093Amith Yamasani        // check first in the cursor data, for a suggestion-specific message
1027968ec938399033d280b1648123104ac567f2a093Amith Yamasani        final String column = actionKey.getSuggestActionMsgColumn();
1028968ec938399033d280b1648123104ac567f2a093Amith Yamasani        if (column != null) {
1029968ec938399033d280b1648123104ac567f2a093Amith Yamasani            result = SuggestionsAdapter.getColumnString(c, column);
1030968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1031968ec938399033d280b1648123104ac567f2a093Amith Yamasani        // If the cursor didn't give us a message, see if there's a single
1032968ec938399033d280b1648123104ac567f2a093Amith Yamasani        // message defined
1033968ec938399033d280b1648123104ac567f2a093Amith Yamasani        // for the actionkey (for all suggestions)
1034968ec938399033d280b1648123104ac567f2a093Amith Yamasani        if (result == null) {
1035968ec938399033d280b1648123104ac567f2a093Amith Yamasani            result = actionKey.getSuggestActionMsg();
1036968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1037968ec938399033d280b1648123104ac567f2a093Amith Yamasani        return result;
1038968ec938399033d280b1648123104ac567f2a093Amith Yamasani    }
1039968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1040b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    private int getSearchIconId() {
1041b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        TypedValue outValue = new TypedValue();
1042b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        getContext().getTheme().resolveAttribute(com.android.internal.R.attr.searchViewSearchIcon,
1043b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                outValue, true);
1044b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        return outValue.resourceId;
1045b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    }
1046b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
1047b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    private CharSequence getDecoratedHint(CharSequence hintText) {
1048b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        // If the field is always expanded, then don't add the search icon to the hint
1049b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        if (!mIconifiedByDefault) return hintText;
1050b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
1051b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        SpannableStringBuilder ssb = new SpannableStringBuilder("   "); // for the icon
1052b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        ssb.append(hintText);
1053b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        Drawable searchIcon = getContext().getResources().getDrawable(getSearchIconId());
1054b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        int textSize = (int) (mQueryTextView.getTextSize() * 1.25);
1055b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        searchIcon.setBounds(0, 0, textSize, textSize);
1056b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        ssb.setSpan(new ImageSpan(searchIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
1057b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        return ssb;
1058b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    }
1059b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
1060733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void updateQueryHint() {
1061733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (mQueryHint != null) {
1062b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            mQueryTextView.setHint(getDecoratedHint(mQueryHint));
1063733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        } else if (mSearchable != null) {
1064733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            CharSequence hint = null;
1065733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            int hintId = mSearchable.getHintId();
1066733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (hintId != 0) {
1067733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                hint = getContext().getString(hintId);
1068733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1069733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (hint != null) {
1070b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                mQueryTextView.setHint(getDecoratedHint(hint));
1071733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1072b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        } else {
1073b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            mQueryTextView.setHint(getDecoratedHint(""));
1074733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1075733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1076733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1077733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1078733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Updates the auto-complete text view.
1079733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1080733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void updateSearchAutoComplete() {
1081733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        mQueryTextView.setDropDownAnimationStyle(0); // no animation
10825931b1f415fdb30f429fb39238c63f7533335998Amith Yamasani        mQueryTextView.setThreshold(mSearchable.getSuggestThreshold());
10835607a3827172ff40196380d846128e892bedc118Amith Yamasani        mQueryTextView.setImeOptions(mSearchable.getImeOptions());
10845607a3827172ff40196380d846128e892bedc118Amith Yamasani        int inputType = mSearchable.getInputType();
10855607a3827172ff40196380d846128e892bedc118Amith Yamasani        // We only touch this if the input type is set up for text (which it almost certainly
10865607a3827172ff40196380d846128e892bedc118Amith Yamasani        // should be, in the case of search!)
10875607a3827172ff40196380d846128e892bedc118Amith Yamasani        if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
10885607a3827172ff40196380d846128e892bedc118Amith Yamasani            // The existence of a suggestions authority is the proxy for "suggestions
10895607a3827172ff40196380d846128e892bedc118Amith Yamasani            // are available here"
10905607a3827172ff40196380d846128e892bedc118Amith Yamasani            inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
10915607a3827172ff40196380d846128e892bedc118Amith Yamasani            if (mSearchable.getSuggestAuthority() != null) {
10925607a3827172ff40196380d846128e892bedc118Amith Yamasani                inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
10939ce1116ee844c7b03ca9c09956992b9a85b7a247Satoshi Kataoka                // TYPE_TEXT_FLAG_AUTO_COMPLETE means that the text editor is performing
10949ce1116ee844c7b03ca9c09956992b9a85b7a247Satoshi Kataoka                // auto-completion based on its own semantics, which it will present to the user
10959ce1116ee844c7b03ca9c09956992b9a85b7a247Satoshi Kataoka                // as they type. This generally means that the input method should not show its
10969ce1116ee844c7b03ca9c09956992b9a85b7a247Satoshi Kataoka                // own candidates, and the spell checker should not be in action. The text editor
10979ce1116ee844c7b03ca9c09956992b9a85b7a247Satoshi Kataoka                // supplies its candidates by calling InputMethodManager.displayCompletions(),
10989ce1116ee844c7b03ca9c09956992b9a85b7a247Satoshi Kataoka                // which in turn will call InputMethodSession.displayCompletions().
10999ce1116ee844c7b03ca9c09956992b9a85b7a247Satoshi Kataoka                inputType |= InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS;
11005607a3827172ff40196380d846128e892bedc118Amith Yamasani            }
11015607a3827172ff40196380d846128e892bedc118Amith Yamasani        }
11025607a3827172ff40196380d846128e892bedc118Amith Yamasani        mQueryTextView.setInputType(inputType);
11038790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani        if (mSuggestionsAdapter != null) {
11048790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani            mSuggestionsAdapter.changeCursor(null);
11058790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani        }
1106733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // attach the suggestions adapter, if suggestions are available
1107733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // The existence of a suggestions authority is the proxy for "suggestions available here"
1108733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (mSearchable.getSuggestAuthority() != null) {
1109733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            mSuggestionsAdapter = new SuggestionsAdapter(getContext(),
1110733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                    this, mSearchable, mOutsideDrawablesCache);
1111733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            mQueryTextView.setAdapter(mSuggestionsAdapter);
1112e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani            ((SuggestionsAdapter) mSuggestionsAdapter).setQueryRefinement(
1113e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani                    mQueryRefinement ? SuggestionsAdapter.REFINE_ALL
1114e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani                    : SuggestionsAdapter.REFINE_BY_ENTRY);
1115733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1116733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1117733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1118ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    /**
1119ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * Update the visibility of the voice button.  There are actually two voice search modes,
1120ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * either of which will activate the button.
1121ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * @param empty whether the search query text field is empty. If it is, then the other
112279f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani     * criteria apply to make the voice button visible.
1123ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     */
1124ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private void updateVoiceButton(boolean empty) {
112579f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        int visibility = GONE;
1126167d69ac41d8a1446ab216e3821ecbcccd8291bbAmith Yamasani        if (mVoiceButtonEnabled && !isIconified() && empty) {
11279b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            visibility = VISIBLE;
11289b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani            mSubmitButton.setVisibility(GONE);
1129ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1130ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mVoiceButton.setVisibility(visibility);
1131ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    }
1132ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1133733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private final OnEditorActionListener mOnEditorActionListener = new OnEditorActionListener() {
1134733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1135733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        /**
1136733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * Called when the input method default action key is pressed.
1137733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         */
1138733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
1139733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            onSubmitQuery();
1140733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            return true;
1141733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1142733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    };
1143733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1144733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void onTextChanged(CharSequence newText) {
1145733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        CharSequence text = mQueryTextView.getText();
1146068d73cf51305a9dfc15c96f17a18676637d3e02Amith Yamasani        mUserQuery = text;
1147733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        boolean hasText = !TextUtils.isEmpty(text);
1148cf72ab4ddebae48e34dd2d32b7da8bf5a5d7cfa0Amith Yamasani        updateSubmitButton(hasText);
1149ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        updateVoiceButton(!hasText);
115073e00df15e07933af06eac50901e50e07f9865bbAmith Yamasani        updateCloseButton();
11519b2e302ab8dff554fdb4a7f9d005496a655f8f03Amith Yamasani        updateSubmitArea();
1152b47c4fd206ce5d057996d49228342d17ecca027eAmith Yamasani        if (mOnQueryChangeListener != null && !TextUtils.equals(newText, mOldQueryText)) {
115301f21354654a315ea2a2a1668bbda50645123d4aAdam Powell            mOnQueryChangeListener.onQueryTextChange(newText.toString());
1154ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1155b47c4fd206ce5d057996d49228342d17ecca027eAmith Yamasani        mOldQueryText = newText.toString();
1156733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1157733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1158733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void onSubmitQuery() {
1159733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        CharSequence query = mQueryTextView.getText();
11606a7421ba94b1fca0a20ddf146054ebd771ee471eAmith Yamasani        if (query != null && TextUtils.getTrimmedLength(query) > 0) {
1161733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (mOnQueryChangeListener == null
116201f21354654a315ea2a2a1668bbda50645123d4aAdam Powell                    || !mOnQueryChangeListener.onQueryTextSubmit(query.toString())) {
1163733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                if (mSearchable != null) {
1164733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                    launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null, query.toString());
11650594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani                    setImeVisibility(false);
1166733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                }
1167ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                dismissSuggestions();
1168733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1169733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1170733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1171733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1172ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private void dismissSuggestions() {
1173ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        mQueryTextView.dismissDropDown();
1174ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    }
1175ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1176733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void onCloseClicked() {
1177246529891ee289e8393ad4a486db785ef455c778Amith Yamasani        CharSequence text = mQueryTextView.getText();
1178246529891ee289e8393ad4a486db785ef455c778Amith Yamasani        if (TextUtils.isEmpty(text)) {
1179246529891ee289e8393ad4a486db785ef455c778Amith Yamasani            if (mIconifiedByDefault) {
1180b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                // If the app doesn't override the close behavior
1181b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                if (mOnCloseListener == null || !mOnCloseListener.onClose()) {
1182b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    // hide the keyboard and remove focus
1183b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    clearFocus();
1184b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    // collapse the search field
1185b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    updateViewsVisibility(true);
1186b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                }
11870594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani            }
1188246529891ee289e8393ad4a486db785ef455c778Amith Yamasani        } else {
1189246529891ee289e8393ad4a486db785ef455c778Amith Yamasani            mQueryTextView.setText("");
1190246529891ee289e8393ad4a486db785ef455c778Amith Yamasani            mQueryTextView.requestFocus();
1191246529891ee289e8393ad4a486db785ef455c778Amith Yamasani            setImeVisibility(true);
1192246529891ee289e8393ad4a486db785ef455c778Amith Yamasani        }
1193246529891ee289e8393ad4a486db785ef455c778Amith Yamasani
1194733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1195733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1196733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void onSearchClicked() {
1197733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        updateViewsVisibility(false);
11987f8aef6d5bd26cfc7f96d059ab66abdb320ebd27Amith Yamasani        mQueryTextView.requestFocus();
11990594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani        setImeVisibility(true);
1200483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani        if (mOnSearchClickListener != null) {
1201483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani            mOnSearchClickListener.onClick(this);
1202483854820945ae8b4e4512b696cce7da92deed20Amith Yamasani        }
1203733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1204733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1205ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private void onVoiceClicked() {
1206ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // guard against possible race conditions
1207ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        if (mSearchable == null) {
1208ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            return;
1209ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1210ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        SearchableInfo searchable = mSearchable;
1211ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        try {
1212ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            if (searchable.getVoiceSearchLaunchWebSearch()) {
1213ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                Intent webSearchIntent = createVoiceWebSearchIntent(mVoiceWebSearchIntent,
1214ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                        searchable);
1215ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                getContext().startActivity(webSearchIntent);
1216ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            } else if (searchable.getVoiceSearchLaunchRecognizer()) {
1217ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent,
1218ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                        searchable);
1219ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                getContext().startActivity(appSearchIntent);
1220ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            }
1221ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        } catch (ActivityNotFoundException e) {
1222ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            // Should not happen, since we check the availability of
1223ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            // voice search before showing the button. But just in case...
1224ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            Log.w(LOG_TAG, "Could not find voice search activity");
1225ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1226ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    }
1227ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
12284aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani    void onTextFocusChanged() {
122979f7430bf93a9b5dc2b4e0f418f320affdfbf77bAmith Yamasani        updateViewsVisibility(isIconified());
1230a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        // Delayed update to make sure that the focus has settled down and window focus changes
1231a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        // don't affect it. A synchronous update was not working.
1232a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        postUpdateFocusedState();
1233f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani        if (mQueryTextView.hasFocus()) {
1234f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani            forceSuggestionQuery();
1235f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani        }
12364aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani    }
12374aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani
1238b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    @Override
1239a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani    public void onWindowFocusChanged(boolean hasWindowFocus) {
1240a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        super.onWindowFocusChanged(hasWindowFocus);
1241a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani
1242a95e488bdcef3737287227419cfe214bef0556aaAmith Yamasani        postUpdateFocusedState();
1243b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    }
1244b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
1245763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    /**
1246763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani     * {@inheritDoc}
1247763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani     */
1248763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    @Override
1249763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    public void onActionViewCollapsed() {
125010da590839e264735de8a4582021aca0dab81037Amith Yamasani        clearFocus();
125110da590839e264735de8a4582021aca0dab81037Amith Yamasani        updateViewsVisibility(true);
125253f56c4f51ae11d5b3afde8ce221b3fe9aea3cf1Adam Powell        mQueryTextView.setImeOptions(mCollapsedImeOptions);
1253763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani        mExpandedInActionView = false;
1254763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    }
1255763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani
1256763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    /**
1257763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani     * {@inheritDoc}
1258763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani     */
1259763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    @Override
1260763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    public void onActionViewExpanded() {
1261434c73ffd85369e220261bda3ac5dbe911304a4cAmith Yamasani        if (mExpandedInActionView) return;
1262434c73ffd85369e220261bda3ac5dbe911304a4cAmith Yamasani
1263763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani        mExpandedInActionView = true;
126453f56c4f51ae11d5b3afde8ce221b3fe9aea3cf1Adam Powell        mCollapsedImeOptions = mQueryTextView.getImeOptions();
126553f56c4f51ae11d5b3afde8ce221b3fe9aea3cf1Adam Powell        mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN);
12668790764e4c20737e57dafdfb3bb1c8cdb84169c5Amith Yamasani        mQueryTextView.setText("");
1267763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani        setIconified(false);
1268763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani    }
1269763bc076527b183204b8ef82711f9b404bed53dbAmith Yamasani
12708a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    @Override
12718a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    public void onInitializeAccessibilityEvent(AccessibilityEvent event) {
12728a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        super.onInitializeAccessibilityEvent(event);
12738a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        event.setClassName(SearchView.class.getName());
12748a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    }
12758a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov
12768a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    @Override
12778a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
12788a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        super.onInitializeAccessibilityNodeInfo(info);
12798a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov        info.setClassName(SearchView.class.getName());
12808a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov    }
12818a78fd4d9572dff95432fcc4ba0e87563415b728Svetoslav Ganov
1282b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    private void adjustDropDownSizeAndPosition() {
1283b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        if (mDropDownAnchor.getWidth() > 1) {
1284b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            Resources res = getContext().getResources();
1285b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            int anchorPadding = mSearchPlate.getPaddingLeft();
1286b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            Rect dropDownPadding = new Rect();
12873a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            final boolean isLayoutRtl = isLayoutRtl();
1288b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            int iconOffset = mIconifiedByDefault
1289b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    ? res.getDimensionPixelSize(R.dimen.dropdownitem_icon_width)
1290b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    + res.getDimensionPixelSize(R.dimen.dropdownitem_text_padding_left)
1291b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    : 0;
1292b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            mQueryTextView.getDropDownBackground().getPadding(dropDownPadding);
12933a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            int offset;
12943a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            if (isLayoutRtl) {
12953a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio                offset = - dropDownPadding.left;
12963a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            } else {
12973a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio                offset = anchorPadding - (dropDownPadding.left + iconOffset);
12983a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            }
12993a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            mQueryTextView.setDropDownHorizontalOffset(offset);
13003a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            final int width = mDropDownAnchor.getWidth() + dropDownPadding.left
13013a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio                    + dropDownPadding.right + iconOffset - anchorPadding;
13023a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio            mQueryTextView.setDropDownWidth(width);
1303b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        }
1304b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani    }
1305b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
1306968ec938399033d280b1648123104ac567f2a093Amith Yamasani    private boolean onItemClicked(int position, int actionKey, String actionMsg) {
1307968ec938399033d280b1648123104ac567f2a093Amith Yamasani        if (mOnSuggestionListener == null
130801f21354654a315ea2a2a1668bbda50645123d4aAdam Powell                || !mOnSuggestionListener.onSuggestionClick(position)) {
1309968ec938399033d280b1648123104ac567f2a093Amith Yamasani            launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
1310968ec938399033d280b1648123104ac567f2a093Amith Yamasani            setImeVisibility(false);
1311968ec938399033d280b1648123104ac567f2a093Amith Yamasani            dismissSuggestions();
1312968ec938399033d280b1648123104ac567f2a093Amith Yamasani            return true;
1313968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1314968ec938399033d280b1648123104ac567f2a093Amith Yamasani        return false;
1315968ec938399033d280b1648123104ac567f2a093Amith Yamasani    }
1316968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1317968ec938399033d280b1648123104ac567f2a093Amith Yamasani    private boolean onItemSelected(int position) {
1318968ec938399033d280b1648123104ac567f2a093Amith Yamasani        if (mOnSuggestionListener == null
131901f21354654a315ea2a2a1668bbda50645123d4aAdam Powell                || !mOnSuggestionListener.onSuggestionSelect(position)) {
1320968ec938399033d280b1648123104ac567f2a093Amith Yamasani            rewriteQueryFromSuggestion(position);
1321968ec938399033d280b1648123104ac567f2a093Amith Yamasani            return true;
1322968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1323968ec938399033d280b1648123104ac567f2a093Amith Yamasani        return false;
1324968ec938399033d280b1648123104ac567f2a093Amith Yamasani    }
1325968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1326733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private final OnItemClickListener mOnItemClickListener = new OnItemClickListener() {
1327733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1328733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        /**
1329733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * Implements OnItemClickListener
1330733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         */
1331733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
1332968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
1333968ec938399033d280b1648123104ac567f2a093Amith Yamasani            onItemClicked(position, KeyEvent.KEYCODE_UNKNOWN, null);
1334733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1335733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    };
1336733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1337733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private final OnItemSelectedListener mOnItemSelectedListener = new OnItemSelectedListener() {
1338733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1339733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        /**
1340733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * Implements OnItemSelectedListener
1341733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         */
1342733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
1343968ec938399033d280b1648123104ac567f2a093Amith Yamasani            if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
1344968ec938399033d280b1648123104ac567f2a093Amith Yamasani            SearchView.this.onItemSelected(position);
1345733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1346733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1347733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        /**
1348733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         * Implements OnItemSelectedListener
1349733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani         */
1350733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public void onNothingSelected(AdapterView<?> parent) {
1351733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (DBG)
1352733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                Log.d(LOG_TAG, "onNothingSelected()");
1353733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1354733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    };
1355733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
13563a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio    @Override
1357343e11345ed496003f605e1b3bba5850d3e6cf0eFabrice Di Meglio    public void onRtlPropertiesChanged(int layoutDirection) {
1358343e11345ed496003f605e1b3bba5850d3e6cf0eFabrice Di Meglio        mQueryTextView.setLayoutDirection(layoutDirection);
13593a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio    }
13603a69bea30257f32a21bb6c32aafa52982d16a401Fabrice Di Meglio
1361733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1362733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Query rewriting.
1363733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1364733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void rewriteQueryFromSuggestion(int position) {
1365733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        CharSequence oldQuery = mQueryTextView.getText();
1366733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        Cursor c = mSuggestionsAdapter.getCursor();
1367733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (c == null) {
1368733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            return;
1369733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1370733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (c.moveToPosition(position)) {
1371733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // Get the new query from the suggestion.
1372733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
1373733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (newQuery != null) {
1374733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                // The suggestion rewrites the query.
1375733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                // Update the text field, without getting new suggestions.
1376733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                setQuery(newQuery);
1377733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            } else {
1378733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                // The suggestion does not rewrite the query, restore the user's query.
1379733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                setQuery(oldQuery);
1380733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1381733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        } else {
1382733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // We got a bad position, restore the user's query.
1383733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            setQuery(oldQuery);
1384733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1385733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1386733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1387733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1388733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Launches an intent based on a suggestion.
1389733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
1390733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param position The index of the suggestion to create the intent from.
1391733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param actionKey The key code of the action key that was pressed,
1392733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1393733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param actionMsg The message for the action key that was pressed,
1394733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *        or <code>null</code> if none.
1395733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @return true if a successful launch, false if could not (e.g. bad position).
1396733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1397733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private boolean launchSuggestion(int position, int actionKey, String actionMsg) {
1398733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        Cursor c = mSuggestionsAdapter.getCursor();
1399733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if ((c != null) && c.moveToPosition(position)) {
1400733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1401733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
1402733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1403733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // launch the intent
1404733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            launchIntent(intent);
1405733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1406733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            return true;
1407733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1408733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        return false;
1409733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1410733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1411733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1412733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Launches an intent, including any special intent handling.
1413733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1414733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void launchIntent(Intent intent) {
1415733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (intent == null) {
1416733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            return;
1417733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1418733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        try {
1419733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // If the intent was created from a suggestion, it will always have an explicit
1420733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // component here.
1421733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            getContext().startActivity(intent);
1422733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        } catch (RuntimeException ex) {
1423733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
1424733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1425733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1426733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1427733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1428733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Sets the text in the query box, without updating the suggestions.
1429733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1430733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void setQuery(CharSequence query) {
1431e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        mQueryTextView.setText(query, true);
1432e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        // Move the cursor to the end
1433e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        mQueryTextView.setSelection(TextUtils.isEmpty(query) ? 0 : query.length());
1434733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1435733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1436733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private void launchQuerySearch(int actionKey, String actionMsg, String query) {
1437733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        String action = Intent.ACTION_SEARCH;
1438e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani        Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
1439733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        getContext().startActivity(intent);
1440733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1441733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1442733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1443733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Constructs an intent from the given information and the search dialog state.
1444733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
1445733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param action Intent action.
1446733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param data Intent data, or <code>null</code>.
1447733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
1448733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param query Intent query, or <code>null</code>.
1449733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param actionKey The key code of the action key that was pressed,
1450733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1451733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param actionMsg The message for the action key that was pressed,
1452733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *        or <code>null</code> if none.
1453733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param mode The search mode, one of the acceptable values for
1454733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *             {@link SearchManager#SEARCH_MODE}, or {@code null}.
1455733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @return The intent.
1456733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1457733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private Intent createIntent(String action, Uri data, String extraData, String query,
1458e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani            int actionKey, String actionMsg) {
1459733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // Now build the Intent
1460733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        Intent intent = new Intent(action);
1461733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1462733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // We need CLEAR_TOP to avoid reusing an old task that has other activities
1463733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // on top of the one we want. We don't want to do this in in-app search though,
1464733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        // as it can be destructive to the activity stack.
1465733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (data != null) {
1466733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            intent.setData(data);
1467733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1468068d73cf51305a9dfc15c96f17a18676637d3e02Amith Yamasani        intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
1469733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (query != null) {
1470733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            intent.putExtra(SearchManager.QUERY, query);
1471733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1472733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (extraData != null) {
1473733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
1474733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1475940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani        if (mAppSearchData != null) {
1476940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani            intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
1477940ef38c7c46565a8f8fdd6bb8e0183646d0abdcAmith Yamasani        }
1478733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
1479733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            intent.putExtra(SearchManager.ACTION_KEY, actionKey);
1480733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
1481733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1482733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        intent.setComponent(mSearchable.getSearchActivity());
1483733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        return intent;
1484ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    }
1485ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1486ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    /**
1487ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * Create and return an Intent that can launch the voice search activity for web search.
1488ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     */
1489ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private Intent createVoiceWebSearchIntent(Intent baseIntent, SearchableInfo searchable) {
1490ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        Intent voiceIntent = new Intent(baseIntent);
1491ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        ComponentName searchActivity = searchable.getSearchActivity();
1492ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
1493ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                : searchActivity.flattenToShortString());
1494ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        return voiceIntent;
1495ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    }
1496ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1497ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    /**
1498ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * Create and return an Intent that can launch the voice search activity, perform a specific
1499ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * voice transcription, and forward the results to the searchable activity.
1500ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     *
1501ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * @param baseIntent The voice app search intent to start from
1502ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     * @return A completely-configured intent ready to send to the voice search activity
1503ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani     */
1504ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani    private Intent createVoiceAppSearchIntent(Intent baseIntent, SearchableInfo searchable) {
1505ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        ComponentName searchActivity = searchable.getSearchActivity();
1506ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1507ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // create the necessary intent to set up a search-and-forward operation
1508ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // in the voice search system.   We have to keep the bundle separate,
1509ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // because it becomes immutable once it enters the PendingIntent
1510ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
1511ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        queryIntent.setComponent(searchActivity);
1512ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        PendingIntent pending = PendingIntent.getActivity(getContext(), 0, queryIntent,
1513ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                PendingIntent.FLAG_ONE_SHOT);
1514ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1515ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // Now set up the bundle that will be inserted into the pending intent
1516ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // when it's time to do the search.  We always build it here (even if empty)
1517ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // because the voice search activity will always need to insert "QUERY" into
1518ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // it anyway.
1519ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        Bundle queryExtras = new Bundle();
15201bcfe84179417cda57176fe8661a4a749cb843b6Jorge Ruesga        if (mAppSearchData != null) {
15211bcfe84179417cda57176fe8661a4a749cb843b6Jorge Ruesga            queryExtras.putParcelable(SearchManager.APP_DATA, mAppSearchData);
15221bcfe84179417cda57176fe8661a4a749cb843b6Jorge Ruesga        }
1523ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1524ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // Now build the intent to launch the voice search.  Add all necessary
1525ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // extras to launch the voice recognizer, and then all the necessary extras
1526ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // to forward the results to the searchable activity
1527ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        Intent voiceIntent = new Intent(baseIntent);
1528ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1529ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // Add all of the configuration options supplied by the searchable's metadata
1530ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
1531ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        String prompt = null;
1532ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        String language = null;
1533ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        int maxResults = 1;
1534ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1535ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        Resources resources = getResources();
1536ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        if (searchable.getVoiceLanguageModeId() != 0) {
1537ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            languageModel = resources.getString(searchable.getVoiceLanguageModeId());
1538ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1539ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        if (searchable.getVoicePromptTextId() != 0) {
1540ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            prompt = resources.getString(searchable.getVoicePromptTextId());
1541ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1542ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        if (searchable.getVoiceLanguageId() != 0) {
1543ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            language = resources.getString(searchable.getVoiceLanguageId());
1544ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1545ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        if (searchable.getVoiceMaxResults() != 0) {
1546ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani            maxResults = searchable.getVoiceMaxResults();
1547ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        }
1548ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
1549ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
1550ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
1551ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
1552ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_CALLING_PACKAGE, searchActivity == null ? null
1553ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani                : searchActivity.flattenToShortString());
1554ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1555ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        // Add the values that configure forwarding the results
1556ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
1557ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
1558ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani
1559ebcf5a3a50b84320528af5c2a57db99f76c08af5Amith Yamasani        return voiceIntent;
1560733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1561733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1562733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1563733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * When a particular suggestion has been selected, perform the various lookups required
1564733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * to use the suggestion.  This includes checking the cursor for suggestion-specific data,
1565733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * and/or falling back to the XML for defaults;  It also creates REST style Uri data when
1566733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * the suggestion includes a data id.
1567733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *
1568733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param c The suggestions cursor, moved to the row of the user's selection
1569733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param actionKey The key code of the action key that was pressed,
1570733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1571733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @param actionMsg The message for the action key that was pressed,
1572733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     *        or <code>null</code> if none.
1573733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * @return An intent for the suggestion at the cursor's position.
1574733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1575733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
1576733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        try {
1577733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // use specific action if supplied, or default action if supplied, or fixed default
1578733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
1579733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1580733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (action == null) {
1581733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                action = mSearchable.getSuggestIntentAction();
1582733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1583733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (action == null) {
1584733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                action = Intent.ACTION_SEARCH;
1585733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1586733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1587733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // use specific data if supplied, or default data if supplied
1588733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
1589733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (data == null) {
1590733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                data = mSearchable.getSuggestIntentData();
1591733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1592733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            // then, if an ID was provided, append it.
1593733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            if (data != null) {
1594733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
1595733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                if (id != null) {
1596733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                    data = data + "/" + Uri.encode(id);
1597733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                }
1598733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
1599733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            Uri dataUri = (data == null) ? null : Uri.parse(data);
1600733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1601733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
1602733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
1603733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1604e678f46ec45076203f6260f8a26f56d838c6b6ffAmith Yamasani            return createIntent(action, dataUri, extraData, query, actionKey, actionMsg);
1605733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        } catch (RuntimeException e ) {
1606733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            int rowNum;
1607733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            try {                       // be really paranoid now
1608733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                rowNum = c.getPosition();
1609733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            } catch (RuntimeException e2 ) {
1610733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                rowNum = -1;
1611733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            }
161273af45169476663ead1adcf1717dddd9ad4658fdJake Wharton            Log.w(LOG_TAG, "Search suggestions cursor at row " + rowNum +
161373af45169476663ead1adcf1717dddd9ad4658fdJake Wharton                            " returned exception.", e);
1614733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            return null;
1615733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1616733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    }
1617733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1618f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani    private void forceSuggestionQuery() {
1619f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani        mQueryTextView.doBeforeTextChanged();
1620f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani        mQueryTextView.doAfterTextChanged();
1621f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani    }
1622f28d1875741d1845df45808e143f3a850e66f893Amith Yamasani
1623968ec938399033d280b1648123104ac567f2a093Amith Yamasani    static boolean isLandscapeMode(Context context) {
1624968ec938399033d280b1648123104ac567f2a093Amith Yamasani        return context.getResources().getConfiguration().orientation
1625968ec938399033d280b1648123104ac567f2a093Amith Yamasani                == Configuration.ORIENTATION_LANDSCAPE;
1626968ec938399033d280b1648123104ac567f2a093Amith Yamasani    }
1627968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1628733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    /**
1629733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     * Callback to watch the text field for empty/non-empty
1630733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani     */
1631733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    private TextWatcher mTextWatcher = new TextWatcher() {
1632733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1633733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
1634733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1635733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public void onTextChanged(CharSequence s, int start,
1636733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani                int before, int after) {
1637733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani            SearchView.this.onTextChanged(s);
1638733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1639733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani
1640733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        public void afterTextChanged(Editable s) {
1641733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani        }
1642733cbd58cbc3cf438a7ac0a2902b8aeab941a6b9Amith Yamasani    };
1643968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1644968ec938399033d280b1648123104ac567f2a093Amith Yamasani    /**
1645968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * Local subclass for AutoCompleteTextView.
1646968ec938399033d280b1648123104ac567f2a093Amith Yamasani     * @hide
1647968ec938399033d280b1648123104ac567f2a093Amith Yamasani     */
1648968ec938399033d280b1648123104ac567f2a093Amith Yamasani    public static class SearchAutoComplete extends AutoCompleteTextView {
1649968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1650968ec938399033d280b1648123104ac567f2a093Amith Yamasani        private int mThreshold;
1651968ec938399033d280b1648123104ac567f2a093Amith Yamasani        private SearchView mSearchView;
1652968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1653968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public SearchAutoComplete(Context context) {
1654968ec938399033d280b1648123104ac567f2a093Amith Yamasani            super(context);
1655968ec938399033d280b1648123104ac567f2a093Amith Yamasani            mThreshold = getThreshold();
1656968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1657968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1658968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public SearchAutoComplete(Context context, AttributeSet attrs) {
1659968ec938399033d280b1648123104ac567f2a093Amith Yamasani            super(context, attrs);
1660968ec938399033d280b1648123104ac567f2a093Amith Yamasani            mThreshold = getThreshold();
1661968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1662968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1663968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
1664968ec938399033d280b1648123104ac567f2a093Amith Yamasani            super(context, attrs, defStyle);
1665968ec938399033d280b1648123104ac567f2a093Amith Yamasani            mThreshold = getThreshold();
1666968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1667968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1668968ec938399033d280b1648123104ac567f2a093Amith Yamasani        void setSearchView(SearchView searchView) {
1669968ec938399033d280b1648123104ac567f2a093Amith Yamasani            mSearchView = searchView;
1670968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1671968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1672968ec938399033d280b1648123104ac567f2a093Amith Yamasani        @Override
1673968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public void setThreshold(int threshold) {
1674968ec938399033d280b1648123104ac567f2a093Amith Yamasani            super.setThreshold(threshold);
1675968ec938399033d280b1648123104ac567f2a093Amith Yamasani            mThreshold = threshold;
1676968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1677968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1678968ec938399033d280b1648123104ac567f2a093Amith Yamasani        /**
1679968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * Returns true if the text field is empty, or contains only whitespace.
1680968ec938399033d280b1648123104ac567f2a093Amith Yamasani         */
1681968ec938399033d280b1648123104ac567f2a093Amith Yamasani        private boolean isEmpty() {
1682968ec938399033d280b1648123104ac567f2a093Amith Yamasani            return TextUtils.getTrimmedLength(getText()) == 0;
1683968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1684968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1685968ec938399033d280b1648123104ac567f2a093Amith Yamasani        /**
1686968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * We override this method to avoid replacing the query box text when a
1687968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * suggestion is clicked.
1688968ec938399033d280b1648123104ac567f2a093Amith Yamasani         */
1689968ec938399033d280b1648123104ac567f2a093Amith Yamasani        @Override
1690968ec938399033d280b1648123104ac567f2a093Amith Yamasani        protected void replaceText(CharSequence text) {
1691968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1692968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1693968ec938399033d280b1648123104ac567f2a093Amith Yamasani        /**
1694968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * We override this method to avoid an extra onItemClick being called on
1695968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * the drop-down's OnItemClickListener by
1696968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)} when an item is
1697968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * clicked with the trackball.
1698968ec938399033d280b1648123104ac567f2a093Amith Yamasani         */
1699968ec938399033d280b1648123104ac567f2a093Amith Yamasani        @Override
1700968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public void performCompletion() {
1701968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1702968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1703968ec938399033d280b1648123104ac567f2a093Amith Yamasani        /**
1704968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * We override this method to be sure and show the soft keyboard if
1705968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * appropriate when the TextView has focus.
1706968ec938399033d280b1648123104ac567f2a093Amith Yamasani         */
1707968ec938399033d280b1648123104ac567f2a093Amith Yamasani        @Override
1708968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public void onWindowFocusChanged(boolean hasWindowFocus) {
1709968ec938399033d280b1648123104ac567f2a093Amith Yamasani            super.onWindowFocusChanged(hasWindowFocus);
1710968ec938399033d280b1648123104ac567f2a093Amith Yamasani
1711acd8d2d2e7b36c9fb34c2981623d19247fa63860Amith Yamasani            if (hasWindowFocus && mSearchView.hasFocus() && getVisibility() == VISIBLE) {
1712968ec938399033d280b1648123104ac567f2a093Amith Yamasani                InputMethodManager inputManager = (InputMethodManager) getContext()
1713968ec938399033d280b1648123104ac567f2a093Amith Yamasani                        .getSystemService(Context.INPUT_METHOD_SERVICE);
1714968ec938399033d280b1648123104ac567f2a093Amith Yamasani                inputManager.showSoftInput(this, 0);
1715968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // If in landscape mode, then make sure that
1716968ec938399033d280b1648123104ac567f2a093Amith Yamasani                // the ime is in front of the dropdown.
1717968ec938399033d280b1648123104ac567f2a093Amith Yamasani                if (isLandscapeMode(getContext())) {
1718968ec938399033d280b1648123104ac567f2a093Amith Yamasani                    ensureImeVisible(true);
1719968ec938399033d280b1648123104ac567f2a093Amith Yamasani                }
1720968ec938399033d280b1648123104ac567f2a093Amith Yamasani            }
1721968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1722968ec938399033d280b1648123104ac567f2a093Amith Yamasani
17234aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        @Override
17244aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
17254aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani            super.onFocusChanged(focused, direction, previouslyFocusedRect);
17264aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani            mSearchView.onTextFocusChanged();
17274aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani        }
17284aedb39a49bda340f871c8fac2239de4fe549b03Amith Yamasani
1729968ec938399033d280b1648123104ac567f2a093Amith Yamasani        /**
1730968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * We override this method so that we can allow a threshold of zero,
1731968ec938399033d280b1648123104ac567f2a093Amith Yamasani         * which ACTV does not.
1732968ec938399033d280b1648123104ac567f2a093Amith Yamasani         */
1733968ec938399033d280b1648123104ac567f2a093Amith Yamasani        @Override
1734968ec938399033d280b1648123104ac567f2a093Amith Yamasani        public boolean enoughToFilter() {
1735968ec938399033d280b1648123104ac567f2a093Amith Yamasani            return mThreshold <= 0 || super.enoughToFilter();
1736968ec938399033d280b1648123104ac567f2a093Amith Yamasani        }
1737b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
1738b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        @Override
1739b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        public boolean onKeyPreIme(int keyCode, KeyEvent event) {
1740b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            if (keyCode == KeyEvent.KEYCODE_BACK) {
1741b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                // special case for the back key, we do not even try to send it
1742b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                // to the drop down list but instead, consume it immediately
1743b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                if (event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
1744b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1745b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    if (state != null) {
1746b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                        state.startTracking(event, this);
1747b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    }
1748b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    return true;
1749b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                } else if (event.getAction() == KeyEvent.ACTION_UP) {
1750b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    KeyEvent.DispatcherState state = getKeyDispatcherState();
1751b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    if (state != null) {
1752b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                        state.handleUpEvent(event);
1753b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    }
1754b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    if (event.isTracking() && !event.isCanceled()) {
1755b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                        mSearchView.clearFocus();
1756b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                        mSearchView.setImeVisibility(false);
1757b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                        return true;
1758b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                    }
1759b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani                }
1760b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            }
1761b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani            return super.onKeyPreIme(keyCode, event);
1762b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani        }
1763b4569fb17fada4fdc43e4f4dbfbc79bb097a1f74Amith Yamasani
1764968ec938399033d280b1648123104ac567f2a093Amith Yamasani    }
17650594476ffa39481063cf7f1546ccfce509f02c65Amith Yamasani}
1766