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