blob: c719a0e5145c48aadbc75179688582b8ac6f7d0a [file] [log] [blame]
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001/*
2 * Copyright (C) 2008 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.app;
18
Amith Yamasani479ae0a2010-02-04 14:52:32 -080019
Amith Yamasanid25eb352010-03-10 21:09:28 -080020import android.content.BroadcastReceiver;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.ComponentName;
Bjorn Bringert444c7272009-07-06 21:32:50 +010022import android.content.Context;
23import android.content.Intent;
Amith Yamasanid25eb352010-03-10 21:09:28 -080024import android.content.IntentFilter;
Karl Rosaen875d50a2009-04-23 19:00:21 -070025import android.content.pm.ActivityInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080026import android.content.pm.PackageManager;
Bjorn Bringertc1f40962009-04-29 13:08:39 +010027import android.content.pm.PackageManager.NameNotFoundException;
Amith Yamasanid25eb352010-03-10 21:09:28 -080028import android.content.res.Configuration;
Bjorn Bringert444c7272009-07-06 21:32:50 +010029import android.graphics.drawable.Drawable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080030import android.net.Uri;
31import android.os.Bundle;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.speech.RecognizerIntent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080033import android.text.InputType;
34import android.text.TextUtils;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.util.AttributeSet;
36import android.util.Log;
Adam Powell8abebcd2011-05-19 18:46:47 -070037import android.util.TypedValue;
38import android.view.ActionMode;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.view.Gravity;
40import android.view.KeyEvent;
Karl Rosaen875d50a2009-04-23 19:00:21 -070041import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080042import android.view.View;
Karl Rosaen875d50a2009-04-23 19:00:21 -070043import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080044import android.view.ViewGroup;
45import android.view.Window;
46import android.view.WindowManager;
Dianne Hackborna8f556e2009-03-24 20:47:50 -070047import android.view.inputmethod.InputMethodManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080048import android.widget.AutoCompleteTextView;
Bjorn Bringertda5c1bd2010-02-04 15:11:25 +000049import android.widget.ImageView;
Bjorn Bringert077357c2009-09-23 13:57:24 +010050import android.widget.LinearLayout;
Amith Yamasani968ec932010-12-02 14:00:47 -080051import android.widget.SearchView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.widget.TextView;
Amith Yamasani968ec932010-12-02 14:00:47 -080053
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080054/**
Amith Yamasanie9ce3f02010-01-25 09:15:50 -080055 * Search dialog. This is controlled by the
56 * SearchManager and runs in the current foreground process.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057 *
58 * @hide
59 */
Amith Yamasani968ec932010-12-02 14:00:47 -080060public class SearchDialog extends Dialog {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080061
62 // Debugging support
Karl Rosaen875d50a2009-04-23 19:00:21 -070063 private static final boolean DBG = false;
64 private static final String LOG_TAG = "SearchDialog";
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080066 private static final String INSTANCE_KEY_COMPONENT = "comp";
67 private static final String INSTANCE_KEY_APPDATA = "data";
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +010068 private static final String INSTANCE_KEY_USER_QUERY = "uQry";
Amith Yamasani968ec932010-12-02 14:00:47 -080069
Mike LeBeau7df84612009-10-30 16:11:40 -070070 // The string used for privateImeOptions to identify to the IME that it should not show
71 // a microphone button since one already exists in the search dialog.
72 private static final String IME_OPTION_NO_MICROPHONE = "nm";
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +010073
Mike LeBeau1fd73232009-04-27 19:12:05 -070074 private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
Bjorn Bringert444c7272009-07-06 21:32:50 +010075
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080076 // views & widgets
77 private TextView mBadgeLabel;
Bjorn Bringertda5c1bd2010-02-04 15:11:25 +000078 private ImageView mAppIcon;
Amith Yamasani968ec932010-12-02 14:00:47 -080079 private AutoCompleteTextView mSearchAutoComplete;
Karl Rosaen875d50a2009-04-23 19:00:21 -070080 private View mSearchPlate;
Amith Yamasani968ec932010-12-02 14:00:47 -080081 private SearchView mSearchView;
Romain Guyf4f70462009-06-26 16:55:54 -070082 private Drawable mWorkingSpinner;
Amith Yamasani24652982011-06-23 16:16:05 -070083 private View mCloseSearch;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080084
85 // interaction with searchable application
Karl Rosaen875d50a2009-04-23 19:00:21 -070086 private SearchableInfo mSearchable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080087 private ComponentName mLaunchComponent;
88 private Bundle mAppSearchData;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 private Context mActivityContext;
90
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080091 // For voice searching
Bryan Mawhinneyf36740b2009-09-30 15:20:22 +010092 private final Intent mVoiceWebSearchIntent;
93 private final Intent mVoiceAppSearchIntent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080094
Karl Rosaen875d50a2009-04-23 19:00:21 -070095 // The query entered by the user. This is not changed when selecting a suggestion
96 // that modifies the contents of the text field. But if the user then edits
97 // the suggestion, the resulting string is saved.
98 private String mUserQuery;
Satish Sampath662df0b2009-06-22 23:16:07 +010099
100 // Last known IME options value for the search edit text.
101 private int mSearchAutoCompleteImeOptions;
102
Amith Yamasani49860442010-03-17 20:54:10 -0700103 private BroadcastReceiver mConfChangeListener = new BroadcastReceiver() {
104 @Override
105 public void onReceive(Context context, Intent intent) {
106 if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
107 onConfigurationChanged();
108 }
109 }
110 };
111
Adam Powell8abebcd2011-05-19 18:46:47 -0700112 static int resolveDialogTheme(Context context) {
113 TypedValue outValue = new TypedValue();
114 context.getTheme().resolveAttribute(com.android.internal.R.attr.searchDialogTheme,
115 outValue, true);
116 return outValue.resourceId;
117 }
118
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800119 /**
120 * Constructor - fires it up and makes it look like the search UI.
121 *
122 * @param context Application Context we can use for system acess
123 */
Amith Yamasanie9ce3f02010-01-25 09:15:50 -0800124 public SearchDialog(Context context, SearchManager searchManager) {
Adam Powell8abebcd2011-05-19 18:46:47 -0700125 super(context, resolveDialogTheme(context));
Bryan Mawhinneyf36740b2009-09-30 15:20:22 +0100126
127 // Save voice intent for later queries/launching
128 mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
129 mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
130 mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
131 RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
132
133 mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
134 mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800135 }
136
137 /**
Bryan Mawhinneyf36740b2009-09-30 15:20:22 +0100138 * Create the search dialog and any resources that are used for the
139 * entire lifetime of the dialog.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800140 */
141 @Override
142 protected void onCreate(Bundle savedInstanceState) {
143 super.onCreate(savedInstanceState);
144
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100145 Window theWindow = getWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800146 WindowManager.LayoutParams lp = theWindow.getAttributes();
Romain Guy980a9382010-01-08 15:06:28 -0800147 lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100148 // taking up the whole window (even when transparent) is less than ideal,
149 // but necessary to show the popup window until the window manager supports
150 // having windows anchored by their parent but not clipped by them.
Romain Guy980a9382010-01-08 15:06:28 -0800151 lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100152 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
Mike LeBeau98acd542009-05-07 19:04:39 -0700153 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800154 theWindow.setAttributes(lp);
155
Bryan Mawhinneyf36740b2009-09-30 15:20:22 +0100156 // Touching outside of the search dialog will dismiss it
157 setCanceledOnTouchOutside(true);
158 }
159
160 /**
161 * We recreate the dialog view each time it becomes visible so as to limit
162 * the scope of any problems with the contained resources.
163 */
164 private void createContentView() {
165 setContentView(com.android.internal.R.layout.search_bar);
166
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800167 // get the view elements for local access
Bjorn Bringert077357c2009-09-23 13:57:24 +0100168 SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar);
169 searchBar.setSearchDialog(this);
Amith Yamasani968ec932010-12-02 14:00:47 -0800170 mSearchView = (SearchView) findViewById(com.android.internal.R.id.search_view);
Amith Yamasanib4569fb2011-07-08 15:25:39 -0700171 mSearchView.setIconified(false);
Amith Yamasani968ec932010-12-02 14:00:47 -0800172 mSearchView.setOnCloseListener(mOnCloseListener);
Adam Powell01f21352011-01-20 18:30:10 -0800173 mSearchView.setOnQueryTextListener(mOnQueryChangeListener);
174 mSearchView.setOnSuggestionListener(mOnSuggestionSelectionListener);
Amith Yamasani6a7421b2011-07-27 11:55:53 -0700175 mSearchView.onActionViewExpanded();
Bjorn Bringert077357c2009-09-23 13:57:24 +0100176
Amith Yamasani24652982011-06-23 16:16:05 -0700177 mCloseSearch = findViewById(com.android.internal.R.id.closeButton);
178 mCloseSearch.setOnClickListener(new View.OnClickListener() {
179 @Override
180 public void onClick(View v) {
181 dismiss();
182 }
183 });
184
Amith Yamasani968ec932010-12-02 14:00:47 -0800185 // TODO: Move the badge logic to SearchView or move the badge to search_bar.xml
186 mBadgeLabel = (TextView) mSearchView.findViewById(com.android.internal.R.id.search_badge);
187 mSearchAutoComplete = (AutoCompleteTextView)
188 mSearchView.findViewById(com.android.internal.R.id.search_src_text);
Bjorn Bringertda5c1bd2010-02-04 15:11:25 +0000189 mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
Amith Yamasani968ec932010-12-02 14:00:47 -0800190 mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate);
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800191 mWorkingSpinner = getContext().getDrawable(com.android.internal.R.drawable.search_spinner);
Amith Yamasania40d7ec2011-07-01 13:50:04 -0700192 // TODO: Restore the spinner for slow suggestion lookups
193 // mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
194 // null, null, mWorkingSpinner, null);
Bryan Mawhinneydfcbc042009-09-17 21:31:16 +0100195 setWorking(false);
196
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800197 // pre-hide all the extraneous elements
198 mBadgeLabel.setVisibility(View.GONE);
199
200 // Additional adjustments to make Dialog work for Search
Satish Sampath662df0b2009-06-22 23:16:07 +0100201 mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800202 }
203
204 /**
205 * Set up the search dialog
206 *
Karl Rosaen875d50a2009-04-23 19:00:21 -0700207 * @return true if search dialog launched, false if not
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800208 */
209 public boolean show(String initialQuery, boolean selectInitialQuery,
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800210 ComponentName componentName, Bundle appSearchData) {
211 boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData);
Satish Sampathfef8d3e2009-07-01 17:48:42 +0100212 if (success) {
213 // Display the drop down as soon as possible instead of waiting for the rest of the
214 // pending UI stuff to get done, so that things appear faster to the user.
215 mSearchAutoComplete.showDropDownAfterLayout();
216 }
217 return success;
Mike LeBeaub3aab692009-04-30 02:09:09 -0700218 }
Bjorn Bringertda1f4b62009-07-28 15:15:26 +0100219
Mike LeBeaub3aab692009-04-30 02:09:09 -0700220 /**
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800221 * Does the rest of the work required to show the search dialog. Called by
222 * {@link #show(String, boolean, ComponentName, Bundle)} and
223 *
Mike LeBeaub3aab692009-04-30 02:09:09 -0700224 * @return true if search dialog showed, false if not
225 */
226 private boolean doShow(String initialQuery, boolean selectInitialQuery,
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800227 ComponentName componentName, Bundle appSearchData) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700228 // set up the searchable and show the dialog
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800229 if (!show(componentName, appSearchData)) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700230 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800231 }
Mike LeBeaub3aab692009-04-30 02:09:09 -0700232
Karl Rosaen875d50a2009-04-23 19:00:21 -0700233 // finally, load the user's initial text (which may trigger suggestions)
234 setUserQuery(initialQuery);
235 if (selectInitialQuery) {
236 mSearchAutoComplete.selectAll();
237 }
Mike LeBeaub3aab692009-04-30 02:09:09 -0700238
Karl Rosaen875d50a2009-04-23 19:00:21 -0700239 return true;
240 }
Mike LeBeaub3aab692009-04-30 02:09:09 -0700241
Karl Rosaen875d50a2009-04-23 19:00:21 -0700242 /**
243 * Sets up the search dialog and shows it.
244 *
245 * @return <code>true</code> if search dialog launched
246 */
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800247 private boolean show(ComponentName componentName, Bundle appSearchData) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700248
249 if (DBG) {
250 Log.d(LOG_TAG, "show(" + componentName + ", "
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800251 + appSearchData + ")");
Karl Rosaen875d50a2009-04-23 19:00:21 -0700252 }
253
Bjorn Bringert8d153822009-06-22 10:31:44 +0100254 SearchManager searchManager = (SearchManager)
255 mContext.getSystemService(Context.SEARCH_SERVICE);
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800256 // Try to get the searchable info for the provided component.
Bjorn Bringert6cf7a322010-02-23 13:17:06 +0000257 mSearchable = searchManager.getSearchableInfo(componentName);
Bjorn Bringertee716fa2009-07-16 09:15:37 +0100258
Bjorn Bringertee716fa2009-07-16 09:15:37 +0100259 if (mSearchable == null) {
Bjorn Bringertee716fa2009-07-16 09:15:37 +0100260 return false;
261 }
262
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800263 mLaunchComponent = componentName;
264 mAppSearchData = appSearchData;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700265 mActivityContext = mSearchable.getActivityContext(getContext());
Bryan Mawhinneyf36740b2009-09-30 15:20:22 +0100266
Karl Rosaen875d50a2009-04-23 19:00:21 -0700267 // show the dialog. this will call onStart().
Bjorn Bringert11f09bb2009-10-05 21:00:53 +0100268 if (!isShowing()) {
269 // Recreate the search bar view every time the dialog is shown, to get rid
270 // of any bad state in the AutoCompleteTextView etc
271 createContentView();
Amith Yamasani968ec932010-12-02 14:00:47 -0800272 mSearchView.setSearchableInfo(mSearchable);
Amith Yamasani940ef382011-03-02 18:43:23 -0800273 mSearchView.setAppSearchData(mAppSearchData);
Bjorn Bringert11f09bb2009-10-05 21:00:53 +0100274
Karl Rosaen875d50a2009-04-23 19:00:21 -0700275 show();
276 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700277 updateUI();
Amith Yamasani49860442010-03-17 20:54:10 -0700278
Karl Rosaen875d50a2009-04-23 19:00:21 -0700279 return true;
280 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800281
Amith Yamasani49860442010-03-17 20:54:10 -0700282 @Override
283 public void onStart() {
284 super.onStart();
285
286 // Register a listener for configuration change events.
287 IntentFilter filter = new IntentFilter();
288 filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
289 getContext().registerReceiver(mConfChangeListener, filter);
290 }
291
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800292 /**
293 * The search dialog is being dismissed, so handle all of the local shutdown operations.
294 *
295 * This function is designed to be idempotent so that dismiss() can be safely called at any time
296 * (even if already closed) and more likely to really dump any memory. No leaks!
297 */
298 @Override
299 public void onStop() {
300 super.onStop();
Bjorn Bringert444c7272009-07-06 21:32:50 +0100301
Amith Yamasani49860442010-03-17 20:54:10 -0700302 getContext().unregisterReceiver(mConfChangeListener);
303
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800304 // dump extra memory we're hanging on to
305 mLaunchComponent = null;
306 mAppSearchData = null;
307 mSearchable = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800308 mUserQuery = null;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700309 }
Mike LeBeau1c690752009-05-20 20:20:26 -0700310
Mike LeBeau1c690752009-05-20 20:20:26 -0700311 /**
Mike LeBeau1480eb22009-05-20 17:22:13 -0700312 * Sets the search dialog to the 'working' state, which shows a working spinner in the
313 * right hand size of the text field.
314 *
315 * @param working true to show spinner, false to hide spinner
316 */
317 public void setWorking(boolean working) {
Bryan Mawhinneydfcbc042009-09-17 21:31:16 +0100318 mWorkingSpinner.setAlpha(working ? 255 : 0);
319 mWorkingSpinner.setVisible(working, false);
320 mWorkingSpinner.invalidateSelf();
Mike LeBeau1480eb22009-05-20 17:22:13 -0700321 }
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800322
Mike LeBeau1480eb22009-05-20 17:22:13 -0700323 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800324 * Save the minimal set of data necessary to recreate the search
325 *
Bjorn Bringert444c7272009-07-06 21:32:50 +0100326 * @return A bundle with the state of the dialog, or {@code null} if the search
327 * dialog is not showing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 */
329 @Override
330 public Bundle onSaveInstanceState() {
Bjorn Bringert444c7272009-07-06 21:32:50 +0100331 if (!isShowing()) return null;
332
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800333 Bundle bundle = new Bundle();
Bjorn Bringert444c7272009-07-06 21:32:50 +0100334
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800335 // setup info so I can recreate this particular search
336 bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
337 bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +0100338 bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
339
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800340 return bundle;
341 }
342
343 /**
344 * Restore the state of the dialog from a previously saved bundle.
Karl Rosaen875d50a2009-04-23 19:00:21 -0700345 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800346 * @param savedInstanceState The state of the dialog previously saved by
347 * {@link #onSaveInstanceState()}.
348 */
349 @Override
350 public void onRestoreInstanceState(Bundle savedInstanceState) {
Bjorn Bringert444c7272009-07-06 21:32:50 +0100351 if (savedInstanceState == null) return;
352
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800353 ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
354 Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +0100355 String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
356
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +0100357 // show the dialog.
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800358 if (!doShow(userQuery, false, launchComponent, appSearchData)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800359 // for some reason, we couldn't re-instantiate
360 return;
361 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800362 }
Amith Yamasani968ec932010-12-02 14:00:47 -0800363
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800364 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700365 * Called after resources have changed, e.g. after screen rotation or locale change.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800366 */
Bjorn Bringert444c7272009-07-06 21:32:50 +0100367 public void onConfigurationChanged() {
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800368 if (mSearchable != null && isShowing()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800369 // Redraw (resources may have changed)
Mike LeBeau1fd73232009-04-27 19:12:05 -0700370 updateSearchAppIcon();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800371 updateSearchBadge();
Amith Yamasanid25eb352010-03-10 21:09:28 -0800372 if (isLandscapeMode(getContext())) {
373 mSearchAutoComplete.ensureImeVisible(true);
374 }
Amith Yamasanid25eb352010-03-10 21:09:28 -0800375 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800376 }
Amith Yamasanid25eb352010-03-10 21:09:28 -0800377
378 static boolean isLandscapeMode(Context context) {
379 return context.getResources().getConfiguration().orientation
380 == Configuration.ORIENTATION_LANDSCAPE;
381 }
382
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700384 * Update the UI according to the info in the current value of {@link #mSearchable}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800385 */
Karl Rosaen875d50a2009-04-23 19:00:21 -0700386 private void updateUI() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800387 if (mSearchable != null) {
Karl Rosaenea52d292009-07-20 09:26:10 -0700388 mDecor.setVisibility(View.VISIBLE);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700389 updateSearchAutoComplete();
Mike LeBeau1fd73232009-04-27 19:12:05 -0700390 updateSearchAppIcon();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800391 updateSearchBadge();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800392
393 // In order to properly configure the input method (if one is being used), we
394 // need to let it know if we'll be providing suggestions. Although it would be
395 // difficult/expensive to know if every last detail has been configured properly, we
396 // can at least see if a suggestions provider has been configured, and use that
397 // as our trigger.
398 int inputType = mSearchable.getInputType();
399 // We only touch this if the input type is set up for text (which it almost certainly
400 // should be, in the case of search!)
401 if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
402 // The existence of a suggestions authority is the proxy for "suggestions
403 // are available here"
404 inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
405 if (mSearchable.getSuggestAuthority() != null) {
406 inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
407 }
408 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700409 mSearchAutoComplete.setInputType(inputType);
Satish Sampath662df0b2009-06-22 23:16:07 +0100410 mSearchAutoCompleteImeOptions = mSearchable.getImeOptions();
411 mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions);
Mike LeBeau7df84612009-10-30 16:11:40 -0700412
413 // If the search dialog is going to show a voice search button, then don't let
414 // the soft keyboard display a microphone button if it would have otherwise.
415 if (mSearchable.getVoiceSearchEnabled()) {
416 mSearchAutoComplete.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
417 } else {
418 mSearchAutoComplete.setPrivateImeOptions(null);
419 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700420 }
421 }
422
423 /**
424 * Updates the auto-complete text view.
425 */
426 private void updateSearchAutoComplete() {
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100427 // we dismiss the entire dialog instead
428 mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
Amith Yamasani968ec932010-12-02 14:00:47 -0800429 mSearchAutoComplete.setForceIgnoreOutsideTouch(false);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800430 }
431
Mike LeBeau1fd73232009-04-27 19:12:05 -0700432 private void updateSearchAppIcon() {
Amith Yamasani968ec932010-12-02 14:00:47 -0800433 PackageManager pm = getContext().getPackageManager();
434 Drawable icon;
435 try {
436 ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
437 icon = pm.getApplicationIcon(info.applicationInfo);
438 if (DBG)
439 Log.d(LOG_TAG, "Using app-specific icon");
440 } catch (NameNotFoundException e) {
441 icon = pm.getDefaultActivityIcon();
442 Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
Mike LeBeau1fd73232009-04-27 19:12:05 -0700443 }
Amith Yamasani968ec932010-12-02 14:00:47 -0800444 mAppIcon.setImageDrawable(icon);
445 mAppIcon.setVisibility(View.VISIBLE);
446 mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL, mSearchPlate.getPaddingTop(), mSearchPlate.getPaddingRight(), mSearchPlate.getPaddingBottom());
Mike LeBeau1fd73232009-04-27 19:12:05 -0700447 }
448
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800449 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700450 * Setup the search "Badge" if requested by mode flags.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800451 */
452 private void updateSearchBadge() {
453 // assume both hidden
454 int visibility = View.GONE;
455 Drawable icon = null;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700456 CharSequence text = null;
Amith Yamasani968ec932010-12-02 14:00:47 -0800457
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800458 // optionally show one or the other.
Bjorn Bringerta9204132009-05-05 14:06:35 +0100459 if (mSearchable.useBadgeIcon()) {
Alan Viverette8eea3ea2014-02-03 18:40:20 -0800460 icon = mActivityContext.getDrawable(mSearchable.getIconId());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800461 visibility = View.VISIBLE;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700462 if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId());
Bjorn Bringerta9204132009-05-05 14:06:35 +0100463 } else if (mSearchable.useBadgeLabel()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800464 text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
465 visibility = View.VISIBLE;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700466 if (DBG) Log.d(LOG_TAG, "Using badge label: " + mSearchable.getLabelId());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800467 }
Amith Yamasani968ec932010-12-02 14:00:47 -0800468
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800469 mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
470 mBadgeLabel.setText(text);
471 mBadgeLabel.setVisibility(visibility);
472 }
473
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800474 /*
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800475 * Listeners of various types
476 */
477
478 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700479 * {@link Dialog#onTouchEvent(MotionEvent)} will cancel the dialog only when the
480 * touch is outside the window. But the window includes space for the drop-down,
481 * so we also cancel on taps outside the search bar when the drop-down is not showing.
482 */
483 @Override
484 public boolean onTouchEvent(MotionEvent event) {
485 // cancel if the drop-down is not showing and the touch event was outside the search plate
486 if (!mSearchAutoComplete.isPopupShowing() && isOutOfBounds(mSearchPlate, event)) {
487 if (DBG) Log.d(LOG_TAG, "Pop-up not showing and outside of search plate.");
488 cancel();
489 return true;
490 }
491 // Let Dialog handle events outside the window while the pop-up is showing.
492 return super.onTouchEvent(event);
493 }
Amith Yamasani968ec932010-12-02 14:00:47 -0800494
Karl Rosaen875d50a2009-04-23 19:00:21 -0700495 private boolean isOutOfBounds(View v, MotionEvent event) {
496 final int x = (int) event.getX();
497 final int y = (int) event.getY();
498 final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
499 return (x < -slop) || (y < -slop)
500 || (x > (v.getWidth()+slop))
501 || (y > (v.getHeight()+slop));
502 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800503
Dianne Hackborna8f556e2009-03-24 20:47:50 -0700504 @Override
Karl Rosaenea52d292009-07-20 09:26:10 -0700505 public void hide() {
Bjorn Bringert444c7272009-07-06 21:32:50 +0100506 if (!isShowing()) return;
507
Dianne Hackborna8f556e2009-03-24 20:47:50 -0700508 // We made sure the IME was displayed, so also make sure it is closed
509 // when we go away.
510 InputMethodManager imm = (InputMethodManager)getContext()
511 .getSystemService(Context.INPUT_METHOD_SERVICE);
512 if (imm != null) {
513 imm.hideSoftInputFromWindow(
514 getWindow().getDecorView().getWindowToken(), 0);
515 }
Karl Rosaenea52d292009-07-20 09:26:10 -0700516
517 super.hide();
Dianne Hackborna8f556e2009-03-24 20:47:50 -0700518 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700519
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800520 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700521 * Launch a search for the text in the query text field.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800522 */
Amith Yamasani968ec932010-12-02 14:00:47 -0800523 public void launchQuerySearch() {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700524 launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800525 }
526
527 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700528 * Launch a search for the text in the query text field.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800529 *
Karl Rosaen875d50a2009-04-23 19:00:21 -0700530 * @param actionKey The key code of the action key that was pressed,
531 * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
532 * @param actionMsg The message for the action key that was pressed,
533 * or <code>null</code> if none.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800534 */
Amith Yamasani968ec932010-12-02 14:00:47 -0800535 protected void launchQuerySearch(int actionKey, String actionMsg) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700536 String query = mSearchAutoComplete.getText().toString();
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800537 String action = Intent.ACTION_SEARCH;
Amith Yamasani968ec932010-12-02 14:00:47 -0800538 Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700539 launchIntent(intent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800540 }
Karl Rosaena058f022009-06-01 23:11:44 +0100541
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800542 /**
Amith Yamasanie9ce3f02010-01-25 09:15:50 -0800543 * Launches an intent, including any special intent handling.
Karl Rosaen875d50a2009-04-23 19:00:21 -0700544 */
545 private void launchIntent(Intent intent) {
546 if (intent == null) {
547 return;
548 }
Karl Rosaenea52d292009-07-20 09:26:10 -0700549 Log.d(LOG_TAG, "launching " + intent);
Bjorn Bringert4899e382009-07-22 10:25:25 +0100550 try {
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800551 // If the intent was created from a suggestion, it will always have an explicit
552 // component here.
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800553 getContext().startActivity(intent);
554 // If the search switches to a different activity,
555 // SearchDialogWrapper#performActivityResuming
556 // will handle hiding the dialog when the next activity starts, but for
557 // real in-app search, we still need to dismiss the dialog.
558 dismiss();
Bjorn Bringert4899e382009-07-22 10:25:25 +0100559 } catch (RuntimeException ex) {
560 Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
Karl Rosaen876627d2009-07-20 14:30:55 -0700561 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700562 }
Bjorn Bringert4899e382009-07-22 10:25:25 +0100563
Bjorn Bringert4899e382009-07-22 10:25:25 +0100564 /**
Mike LeBeauae9760b2009-06-01 21:53:09 +0100565 * Sets the list item selection in the AutoCompleteTextView's ListView.
566 */
567 public void setListSelection(int index) {
568 mSearchAutoComplete.setListSelection(index);
569 }
Karl Rosaena058f022009-06-01 23:11:44 +0100570
Mike LeBeauae9760b2009-06-01 21:53:09 +0100571 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700572 * Constructs an intent from the given information and the search dialog state.
573 *
574 * @param action Intent action.
575 * @param data Intent data, or <code>null</code>.
Karl Rosaen875d50a2009-04-23 19:00:21 -0700576 * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
Satish Sampathbf23fe02009-06-15 23:47:56 +0100577 * @param query Intent query, or <code>null</code>.
Karl Rosaen875d50a2009-04-23 19:00:21 -0700578 * @param actionKey The key code of the action key that was pressed,
579 * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
580 * @param actionMsg The message for the action key that was pressed,
581 * or <code>null</code> if none.
Bjorn Bringertbe5b73c2009-09-21 23:05:23 +0100582 * @param mode The search mode, one of the acceptable values for
583 * {@link SearchManager#SEARCH_MODE}, or {@code null}.
Karl Rosaen875d50a2009-04-23 19:00:21 -0700584 * @return The intent.
585 */
Satish Sampathbf23fe02009-06-15 23:47:56 +0100586 private Intent createIntent(String action, Uri data, String extraData, String query,
Amith Yamasanie678f462010-09-15 16:13:43 -0700587 int actionKey, String actionMsg) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700588 // Now build the Intent
589 Intent intent = new Intent(action);
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100590 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bjorn Bringert442da482009-09-22 13:15:33 +0100591 // We need CLEAR_TOP to avoid reusing an old task that has other activities
Evan Millardc1396c2009-11-06 11:59:39 -0800592 // on top of the one we want. We don't want to do this in in-app search though,
593 // as it can be destructive to the activity stack.
Karl Rosaen875d50a2009-04-23 19:00:21 -0700594 if (data != null) {
595 intent.setData(data);
596 }
Bjorn Bringert5f806052009-06-24 12:02:26 +0100597 intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700598 if (query != null) {
599 intent.putExtra(SearchManager.QUERY, query);
600 }
601 if (extraData != null) {
602 intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
603 }
604 if (mAppSearchData != null) {
605 intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
606 }
607 if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
608 intent.putExtra(SearchManager.ACTION_KEY, actionKey);
609 intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
610 }
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800611 intent.setComponent(mSearchable.getSearchActivity());
Karl Rosaen875d50a2009-04-23 19:00:21 -0700612 return intent;
613 }
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800614
Karl Rosaen875d50a2009-04-23 19:00:21 -0700615 /**
Bjorn Bringert077357c2009-09-23 13:57:24 +0100616 * The root element in the search bar layout. This is a custom view just to override
617 * the handling of the back button.
618 */
619 public static class SearchBar extends LinearLayout {
620
621 private SearchDialog mSearchDialog;
622
623 public SearchBar(Context context, AttributeSet attrs) {
624 super(context, attrs);
625 }
626
627 public SearchBar(Context context) {
628 super(context);
629 }
630
631 public void setSearchDialog(SearchDialog searchDialog) {
632 mSearchDialog = searchDialog;
633 }
634
635 /**
Adam Powell8abebcd2011-05-19 18:46:47 -0700636 * Don't allow action modes in a SearchBar, it looks silly.
637 */
638 @Override
639 public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
640 return null;
641 }
Clara Bayarri4423d912015-03-02 19:42:48 +0000642
643 @Override
644 public ActionMode startActionModeForChild(
645 View child, ActionMode.Callback callback, int type) {
646 return null;
647 }
Bjorn Bringert077357c2009-09-23 13:57:24 +0100648 }
649
Amith Yamasani968ec932010-12-02 14:00:47 -0800650 private boolean isEmpty(AutoCompleteTextView actv) {
651 return TextUtils.getTrimmedLength(actv.getText()) == 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800652 }
Bjorn Bringert87d61f22009-09-16 23:17:16 +0100653
654 @Override
655 public void onBackPressed() {
Bjorn Bringerta6309c32009-10-01 10:30:25 +0100656 // If the input method is covering the search dialog completely,
657 // e.g. in landscape mode with no hard keyboard, dismiss just the input method
658 InputMethodManager imm = (InputMethodManager)getContext()
659 .getSystemService(Context.INPUT_METHOD_SERVICE);
660 if (imm != null && imm.isFullscreenMode() &&
661 imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) {
662 return;
663 }
Amith Yamasani479ae0a2010-02-04 14:52:32 -0800664 // Close search dialog
665 cancel();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800666 }
Bjorn Bringert87d61f22009-09-16 23:17:16 +0100667
Amith Yamasani968ec932010-12-02 14:00:47 -0800668 private boolean onClosePressed() {
669 // Dismiss the dialog if close button is pressed when there's no query text
670 if (isEmpty(mSearchAutoComplete)) {
671 dismiss();
672 return true;
673 }
674
675 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800676 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700677
Amith Yamasani968ec932010-12-02 14:00:47 -0800678 private final SearchView.OnCloseListener mOnCloseListener = new SearchView.OnCloseListener() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800679
Amith Yamasani968ec932010-12-02 14:00:47 -0800680 public boolean onClose() {
681 return onClosePressed();
682 }
683 };
Karl Rosaen875d50a2009-04-23 19:00:21 -0700684
Adam Powell01f21352011-01-20 18:30:10 -0800685 private final SearchView.OnQueryTextListener mOnQueryChangeListener =
686 new SearchView.OnQueryTextListener() {
Amith Yamasani968ec932010-12-02 14:00:47 -0800687
Adam Powell01f21352011-01-20 18:30:10 -0800688 public boolean onQueryTextSubmit(String query) {
Amith Yamasani968ec932010-12-02 14:00:47 -0800689 dismiss();
690 return false;
691 }
692
Adam Powell01f21352011-01-20 18:30:10 -0800693 public boolean onQueryTextChange(String newText) {
Amith Yamasani968ec932010-12-02 14:00:47 -0800694 return false;
695 }
696 };
697
Adam Powell01f21352011-01-20 18:30:10 -0800698 private final SearchView.OnSuggestionListener mOnSuggestionSelectionListener =
699 new SearchView.OnSuggestionListener() {
Amith Yamasani968ec932010-12-02 14:00:47 -0800700
Adam Powell01f21352011-01-20 18:30:10 -0800701 public boolean onSuggestionSelect(int position) {
Amith Yamasani968ec932010-12-02 14:00:47 -0800702 return false;
703 }
704
Adam Powell01f21352011-01-20 18:30:10 -0800705 public boolean onSuggestionClick(int position) {
Amith Yamasani968ec932010-12-02 14:00:47 -0800706 dismiss();
707 return false;
708 }
709 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800710
711 /**
Amith Yamasani968ec932010-12-02 14:00:47 -0800712 * Sets the text in the query box, updating the suggestions.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800713 */
Amith Yamasani968ec932010-12-02 14:00:47 -0800714 private void setUserQuery(String query) {
715 if (query == null) {
716 query = "";
717 }
718 mUserQuery = query;
719 mSearchAutoComplete.setText(query);
720 mSearchAutoComplete.setSelection(query.length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800721 }
722}