blob: 26b5b7ad99a752e218770b5b6a4e2f23e53392c2 [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
Karl Rosaen875d50a2009-04-23 19:00:21 -070019import static android.app.SuggestionsAdapter.getColumnString;
Bjorn Bringertc1f40962009-04-29 13:08:39 +010020
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080021import android.content.ActivityNotFoundException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080022import android.content.ComponentName;
Karl Rosaend4c98c42009-06-09 17:05:54 +010023import android.content.ContentResolver;
24import android.content.ContentValues;
Bjorn Bringert444c7272009-07-06 21:32:50 +010025import android.content.Context;
26import android.content.Intent;
Karl Rosaen875d50a2009-04-23 19:00:21 -070027import android.content.pm.ActivityInfo;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080028import android.content.pm.PackageManager;
Karl Rosaen98e333f2009-04-28 10:39:09 -070029import android.content.pm.ResolveInfo;
Bjorn Bringertc1f40962009-04-29 13:08:39 +010030import android.content.pm.PackageManager.NameNotFoundException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080031import android.content.res.Resources;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080032import android.database.Cursor;
Romain Guyb5537c42009-06-30 12:39:18 -070033import android.graphics.drawable.Animatable;
Bjorn Bringert444c7272009-07-06 21:32:50 +010034import android.graphics.drawable.Drawable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080035import android.net.Uri;
36import android.os.Bundle;
Bjorn Bringert4899e382009-07-22 10:25:25 +010037import android.os.IBinder;
38import android.os.RemoteException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080039import android.os.SystemClock;
Bjorn Bringert4899e382009-07-22 10:25:25 +010040import android.provider.Browser;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080041import android.server.search.SearchableInfo;
42import android.speech.RecognizerIntent;
43import android.text.Editable;
44import android.text.InputType;
45import android.text.TextUtils;
46import android.text.TextWatcher;
Satish Sampath662df0b2009-06-22 23:16:07 +010047import android.text.util.Regex;
Bjorn Bringert4899e382009-07-22 10:25:25 +010048import android.util.AndroidRuntimeException;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080049import android.util.AttributeSet;
50import android.util.Log;
Mike LeBeaua97f4a12009-05-23 01:19:36 -050051import android.view.ContextThemeWrapper;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080052import android.view.Gravity;
53import android.view.KeyEvent;
Karl Rosaen875d50a2009-04-23 19:00:21 -070054import android.view.MotionEvent;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080055import android.view.View;
Karl Rosaen875d50a2009-04-23 19:00:21 -070056import android.view.ViewConfiguration;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080057import android.view.ViewGroup;
58import android.view.Window;
59import android.view.WindowManager;
Satish Sampath662df0b2009-06-22 23:16:07 +010060import android.view.inputmethod.EditorInfo;
Dianne Hackborna8f556e2009-03-24 20:47:50 -070061import android.view.inputmethod.InputMethodManager;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080062import android.widget.AdapterView;
63import android.widget.AutoCompleteTextView;
64import android.widget.Button;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080065import android.widget.ImageButton;
Mike LeBeau1fd73232009-04-27 19:12:05 -070066import android.widget.ImageView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080067import android.widget.ListView;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080068import android.widget.TextView;
Bjorn Bringertc1f40962009-04-29 13:08:39 +010069import android.widget.AdapterView.OnItemClickListener;
70import android.widget.AdapterView.OnItemSelectedListener;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080071
Karl Rosaen875d50a2009-04-23 19:00:21 -070072import java.util.ArrayList;
73import java.util.WeakHashMap;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080074import java.util.concurrent.atomic.AtomicLong;
75
76/**
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +010077 * System search dialog. This is controlled by the
78 * SearchManagerService and runs in the system process.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080079 *
80 * @hide
81 */
82public class SearchDialog extends Dialog implements OnItemClickListener, OnItemSelectedListener {
83
84 // Debugging support
Karl Rosaen875d50a2009-04-23 19:00:21 -070085 private static final boolean DBG = false;
86 private static final String LOG_TAG = "SearchDialog";
87 private static final boolean DBG_LOG_TIMING = false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080088
The Android Open Source Project9066cfe2009-03-03 19:31:44 -080089 private static final String INSTANCE_KEY_COMPONENT = "comp";
90 private static final String INSTANCE_KEY_APPDATA = "data";
91 private static final String INSTANCE_KEY_GLOBALSEARCH = "glob";
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +010092 private static final String INSTANCE_KEY_STORED_COMPONENT = "sComp";
93 private static final String INSTANCE_KEY_STORED_APPDATA = "sData";
94 private static final String INSTANCE_KEY_PREVIOUS_COMPONENTS = "sPrev";
95 private static final String INSTANCE_KEY_USER_QUERY = "uQry";
Mike LeBeau260dfb52009-07-15 15:20:14 -070096
97 // The extra key used in an intent to the speech recognizer for in-app voice search.
98 private static final String EXTRA_CALLING_PACKAGE = "calling_package";
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +010099
Mike LeBeau1fd73232009-04-27 19:12:05 -0700100 private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12;
101 private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
Bjorn Bringert444c7272009-07-06 21:32:50 +0100102
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800103 // views & widgets
104 private TextView mBadgeLabel;
Mike LeBeau1fd73232009-04-27 19:12:05 -0700105 private ImageView mAppIcon;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700106 private SearchAutoComplete mSearchAutoComplete;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800107 private Button mGoButton;
108 private ImageButton mVoiceButton;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700109 private View mSearchPlate;
Romain Guyf4f70462009-06-26 16:55:54 -0700110 private Drawable mWorkingSpinner;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800111
112 // interaction with searchable application
Karl Rosaen875d50a2009-04-23 19:00:21 -0700113 private SearchableInfo mSearchable;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800114 private ComponentName mLaunchComponent;
115 private Bundle mAppSearchData;
116 private boolean mGlobalSearchMode;
117 private Context mActivityContext;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700118
Mike LeBeaub3aab692009-04-30 02:09:09 -0700119 // Values we store to allow user to toggle between in-app search and global search.
120 private ComponentName mStoredComponentName;
121 private Bundle mStoredAppSearchData;
122
Karl Rosaen875d50a2009-04-23 19:00:21 -0700123 // stack of previous searchables, to support the BACK key after
124 // SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.
125 // The top of the stack (= previous searchable) is the last element of the list,
126 // since adding and removing is efficient at the end of an ArrayList.
127 private ArrayList<ComponentName> mPreviousComponents;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800128
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800129 // For voice searching
130 private Intent mVoiceWebSearchIntent;
131 private Intent mVoiceAppSearchIntent;
132
133 // support for AutoCompleteTextView suggestions display
134 private SuggestionsAdapter mSuggestionsAdapter;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700135
136 // Whether to rewrite queries when selecting suggestions
Mike LeBeaucce7dbc2009-06-18 09:25:18 -0700137 private static final boolean REWRITE_QUERIES = true;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700138
139 // The query entered by the user. This is not changed when selecting a suggestion
140 // that modifies the contents of the text field. But if the user then edits
141 // the suggestion, the resulting string is saved.
142 private String mUserQuery;
143
144 // A weak map of drawables we've gotten from other packages, so we don't load them
145 // more than once.
Bjorn Bringertdfefa3e2009-07-17 15:06:16 +0100146 private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache =
147 new WeakHashMap<String, Drawable.ConstantState>();
Satish Sampath662df0b2009-06-22 23:16:07 +0100148
149 // Last known IME options value for the search edit text.
150 private int mSearchAutoCompleteImeOptions;
151
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800152 /**
153 * Constructor - fires it up and makes it look like the search UI.
154 *
155 * @param context Application Context we can use for system acess
156 */
157 public SearchDialog(Context context) {
Mike LeBeaua97f4a12009-05-23 01:19:36 -0500158 super(context, com.android.internal.R.style.Theme_GlobalSearchBar);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800159 }
160
161 /**
162 * We create the search dialog just once, and it stays around (hidden)
163 * until activated by the user.
164 */
165 @Override
166 protected void onCreate(Bundle savedInstanceState) {
167 super.onCreate(savedInstanceState);
168
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800169 setContentView(com.android.internal.R.layout.search_bar);
170
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100171 Window theWindow = getWindow();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800172 WindowManager.LayoutParams lp = theWindow.getAttributes();
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100173 lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR;
174 lp.width = ViewGroup.LayoutParams.FILL_PARENT;
175 // taking up the whole window (even when transparent) is less than ideal,
176 // but necessary to show the popup window until the window manager supports
177 // having windows anchored by their parent but not clipped by them.
178 lp.height = ViewGroup.LayoutParams.FILL_PARENT;
179 lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
Mike LeBeau98acd542009-05-07 19:04:39 -0700180 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800181 theWindow.setAttributes(lp);
182
183 // get the view elements for local access
184 mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700185 mSearchAutoComplete = (SearchAutoComplete)
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800186 findViewById(com.android.internal.R.id.search_src_text);
Mike LeBeau1fd73232009-04-27 19:12:05 -0700187 mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800188 mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
189 mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700190 mSearchPlate = findViewById(com.android.internal.R.id.search_plate);
Romain Guyf4f70462009-06-26 16:55:54 -0700191 mWorkingSpinner = getContext().getResources().
Mike LeBeau1480eb22009-05-20 17:22:13 -0700192 getDrawable(com.android.internal.R.drawable.search_spinner);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800193
194 // attach listeners
Karl Rosaen875d50a2009-04-23 19:00:21 -0700195 mSearchAutoComplete.addTextChangedListener(mTextWatcher);
196 mSearchAutoComplete.setOnKeyListener(mTextKeyListener);
197 mSearchAutoComplete.setOnItemClickListener(this);
198 mSearchAutoComplete.setOnItemSelectedListener(this);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800199 mGoButton.setOnClickListener(mGoButtonClickListener);
200 mGoButton.setOnKeyListener(mButtonsKeyListener);
201 mVoiceButton.setOnClickListener(mVoiceButtonClickListener);
202 mVoiceButton.setOnKeyListener(mButtonsKeyListener);
203
Karl Rosaen875d50a2009-04-23 19:00:21 -0700204 mSearchAutoComplete.setSearchDialog(this);
205
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800206 // pre-hide all the extraneous elements
207 mBadgeLabel.setVisibility(View.GONE);
208
209 // Additional adjustments to make Dialog work for Search
210
211 // Touching outside of the search dialog will dismiss it
212 setCanceledOnTouchOutside(true);
Bjorn Bringert444c7272009-07-06 21:32:50 +0100213
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800214 // Save voice intent for later queries/launching
215 mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100216 mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800217 mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
218 RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
219
220 mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100221 mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Satish Sampath662df0b2009-06-22 23:16:07 +0100222
223 mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800224 }
225
226 /**
227 * Set up the search dialog
228 *
Karl Rosaen875d50a2009-04-23 19:00:21 -0700229 * @return true if search dialog launched, false if not
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800230 */
231 public boolean show(String initialQuery, boolean selectInitialQuery,
232 ComponentName componentName, Bundle appSearchData, boolean globalSearch) {
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100233
Mike LeBeaub3aab692009-04-30 02:09:09 -0700234 // Reset any stored values from last time dialog was shown.
235 mStoredComponentName = null;
236 mStoredAppSearchData = null;
Satish Sampathfef8d3e2009-07-01 17:48:42 +0100237
238 boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData,
239 globalSearch);
240 if (success) {
241 // Display the drop down as soon as possible instead of waiting for the rest of the
242 // pending UI stuff to get done, so that things appear faster to the user.
243 mSearchAutoComplete.showDropDownAfterLayout();
244 }
245 return success;
Mike LeBeaub3aab692009-04-30 02:09:09 -0700246 }
247
Mike LeBeaub3aab692009-04-30 02:09:09 -0700248 /**
249 * Called in response to a press of the hard search button in
250 * {@link #onKeyDown(int, KeyEvent)}, this method toggles between in-app
251 * search and global search when relevant.
252 *
253 * If pressed within an in-app search context, this switches the search dialog out to
254 * global search. If pressed within a global search context that was originally an in-app
255 * search context, this switches back to the in-app search context. If pressed within a
256 * global search context that has no original in-app search context (e.g., global search
257 * from Home), this does nothing.
258 *
259 * @return false if we wanted to toggle context but could not do so successfully, true
260 * in all other cases
261 */
262 private boolean toggleGlobalSearch() {
263 String currentSearchText = mSearchAutoComplete.getText().toString();
264 if (!mGlobalSearchMode) {
265 mStoredComponentName = mLaunchComponent;
266 mStoredAppSearchData = mAppSearchData;
267 return doShow(currentSearchText, false, null, mAppSearchData, true);
268 } else {
269 if (mStoredComponentName != null) {
270 // This means we should toggle *back* to an in-app search context from
271 // global search.
272 return doShow(currentSearchText, false, mStoredComponentName,
273 mStoredAppSearchData, false);
274 } else {
275 return true;
276 }
277 }
278 }
279
280 /**
281 * Does the rest of the work required to show the search dialog. Called by both
282 * {@link #show(String, boolean, ComponentName, Bundle, boolean)} and
283 * {@link #toggleGlobalSearch()}.
284 *
285 * @return true if search dialog showed, false if not
286 */
287 private boolean doShow(String initialQuery, boolean selectInitialQuery,
288 ComponentName componentName, Bundle appSearchData,
289 boolean globalSearch) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700290 // set up the searchable and show the dialog
291 if (!show(componentName, appSearchData, globalSearch)) {
292 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800293 }
Mike LeBeaub3aab692009-04-30 02:09:09 -0700294
Karl Rosaen875d50a2009-04-23 19:00:21 -0700295 // finally, load the user's initial text (which may trigger suggestions)
296 setUserQuery(initialQuery);
297 if (selectInitialQuery) {
298 mSearchAutoComplete.selectAll();
299 }
Mike LeBeaub3aab692009-04-30 02:09:09 -0700300
Karl Rosaen875d50a2009-04-23 19:00:21 -0700301 return true;
302 }
Mike LeBeaub3aab692009-04-30 02:09:09 -0700303
Karl Rosaen875d50a2009-04-23 19:00:21 -0700304 /**
305 * Sets up the search dialog and shows it.
306 *
307 * @return <code>true</code> if search dialog launched
308 */
309 private boolean show(ComponentName componentName, Bundle appSearchData,
310 boolean globalSearch) {
311
312 if (DBG) {
313 Log.d(LOG_TAG, "show(" + componentName + ", "
314 + appSearchData + ", " + globalSearch + ")");
315 }
316
Bjorn Bringert8d153822009-06-22 10:31:44 +0100317 SearchManager searchManager = (SearchManager)
318 mContext.getSystemService(Context.SEARCH_SERVICE);
Mike LeBeaua59d7b02009-05-12 15:30:37 -0700319 // Try to get the searchable info for the provided component (or for global search,
320 // if globalSearch == true).
Bjorn Bringert8d153822009-06-22 10:31:44 +0100321 mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
Mike LeBeaua59d7b02009-05-12 15:30:37 -0700322
323 // If we got back nothing, and it wasn't a request for global search, then try again
324 // for global search, as we'll try to launch that in lieu of any component-specific search.
325 if (!globalSearch && mSearchable == null) {
326 globalSearch = true;
Bjorn Bringert8d153822009-06-22 10:31:44 +0100327 mSearchable = searchManager.getSearchableInfo(componentName, globalSearch);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800328 }
Bjorn Bringertee716fa2009-07-16 09:15:37 +0100329
330 // If there's not even a searchable info available for global search, then really give up.
331 if (mSearchable == null) {
332 Log.w(LOG_TAG, "No global search provider.");
333 return false;
334 }
335
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800336 mLaunchComponent = componentName;
337 mAppSearchData = appSearchData;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700338 // Using globalSearch here is just an optimization, just calling
339 // isDefaultSearchable() should always give the same result.
Bjorn Bringert8d153822009-06-22 10:31:44 +0100340 mGlobalSearchMode = globalSearch || searchManager.isDefaultSearchable(mSearchable);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700341 mActivityContext = mSearchable.getActivityContext(getContext());
342
343 // show the dialog. this will call onStart().
Mike LeBeau1fffbd92009-07-14 15:57:41 -0700344 if (!isShowing()) {
Mike LeBeaua97f4a12009-05-23 01:19:36 -0500345 // The Dialog uses a ContextThemeWrapper for the context; use this to change the
346 // theme out from underneath us, between the global search theme and the in-app
347 // search theme. They are identical except that the global search theme does not
348 // dim the background of the window (because global search is full screen so it's
349 // not needed and this should save a little bit of time on global search invocation).
350 Object context = getContext();
351 if (context instanceof ContextThemeWrapper) {
352 ContextThemeWrapper wrapper = (ContextThemeWrapper) context;
353 if (globalSearch) {
354 wrapper.setTheme(com.android.internal.R.style.Theme_GlobalSearchBar);
355 } else {
356 wrapper.setTheme(com.android.internal.R.style.Theme_SearchBar);
357 }
358 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700359 show();
360 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700361 updateUI();
362
363 return true;
364 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800365
366 /**
367 * The search dialog is being dismissed, so handle all of the local shutdown operations.
368 *
369 * This function is designed to be idempotent so that dismiss() can be safely called at any time
370 * (even if already closed) and more likely to really dump any memory. No leaks!
371 */
372 @Override
373 public void onStop() {
374 super.onStop();
Bjorn Bringert444c7272009-07-06 21:32:50 +0100375
Karl Rosaen875d50a2009-04-23 19:00:21 -0700376 closeSuggestionsAdapter();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800377
378 // dump extra memory we're hanging on to
379 mLaunchComponent = null;
380 mAppSearchData = null;
381 mSearchable = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800382 mActivityContext = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800383 mUserQuery = null;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700384 mPreviousComponents = null;
385 }
Mike LeBeau1c690752009-05-20 20:20:26 -0700386
Mike LeBeau1c690752009-05-20 20:20:26 -0700387 /**
Mike LeBeau1480eb22009-05-20 17:22:13 -0700388 * Sets the search dialog to the 'working' state, which shows a working spinner in the
389 * right hand size of the text field.
390 *
391 * @param working true to show spinner, false to hide spinner
392 */
393 public void setWorking(boolean working) {
394 if (working) {
395 mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
396 null, null, mWorkingSpinner, null);
Romain Guyb5537c42009-06-30 12:39:18 -0700397 ((Animatable) mWorkingSpinner).start();
Mike LeBeau1480eb22009-05-20 17:22:13 -0700398 } else {
399 mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
400 null, null, null, null);
Romain Guyb5537c42009-06-30 12:39:18 -0700401 ((Animatable) mWorkingSpinner).stop();
Mike LeBeau1480eb22009-05-20 17:22:13 -0700402 }
403 }
404
405 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700406 * Closes and gets rid of the suggestions adapter.
407 */
408 private void closeSuggestionsAdapter() {
409 // remove the adapter from the autocomplete first, to avoid any updates
410 // when we drop the cursor
411 mSearchAutoComplete.setAdapter((SuggestionsAdapter)null);
412 // close any leftover cursor
413 if (mSuggestionsAdapter != null) {
414 mSuggestionsAdapter.changeCursor(null);
415 }
416 mSuggestionsAdapter = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800417 }
418
419 /**
420 * Save the minimal set of data necessary to recreate the search
421 *
Bjorn Bringert444c7272009-07-06 21:32:50 +0100422 * @return A bundle with the state of the dialog, or {@code null} if the search
423 * dialog is not showing.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800424 */
425 @Override
426 public Bundle onSaveInstanceState() {
Bjorn Bringert444c7272009-07-06 21:32:50 +0100427 if (!isShowing()) return null;
428
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800429 Bundle bundle = new Bundle();
Bjorn Bringert444c7272009-07-06 21:32:50 +0100430
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800431 // setup info so I can recreate this particular search
432 bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
433 bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
434 bundle.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode);
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +0100435 bundle.putParcelable(INSTANCE_KEY_STORED_COMPONENT, mStoredComponentName);
436 bundle.putBundle(INSTANCE_KEY_STORED_APPDATA, mStoredAppSearchData);
437 bundle.putParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS, mPreviousComponents);
438 bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
439
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800440 return bundle;
441 }
442
443 /**
444 * Restore the state of the dialog from a previously saved bundle.
Karl Rosaen875d50a2009-04-23 19:00:21 -0700445 *
446 * TODO: go through this and make sure that it saves everything that is saved
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800447 *
448 * @param savedInstanceState The state of the dialog previously saved by
449 * {@link #onSaveInstanceState()}.
450 */
451 @Override
452 public void onRestoreInstanceState(Bundle savedInstanceState) {
Bjorn Bringert444c7272009-07-06 21:32:50 +0100453 if (savedInstanceState == null) return;
454
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800455 ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
456 Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
457 boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH);
Bjorn Bringertb0ae27f2009-06-23 13:47:31 +0100458 ComponentName storedComponentName =
459 savedInstanceState.getParcelable(INSTANCE_KEY_STORED_COMPONENT);
460 Bundle storedAppSearchData =
461 savedInstanceState.getBundle(INSTANCE_KEY_STORED_APPDATA);
462 ArrayList<ComponentName> previousComponents =
463 savedInstanceState.getParcelableArrayList(INSTANCE_KEY_PREVIOUS_COMPONENTS);
464 String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
465
466 // Set stored state
467 mStoredComponentName = storedComponentName;
468 mStoredAppSearchData = storedAppSearchData;
469 mPreviousComponents = previousComponents;
470
471 // show the dialog.
472 if (!doShow(userQuery, false, launchComponent, appSearchData, globalSearch)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800473 // for some reason, we couldn't re-instantiate
474 return;
475 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800476 }
477
478 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700479 * Called after resources have changed, e.g. after screen rotation or locale change.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800480 */
Bjorn Bringert444c7272009-07-06 21:32:50 +0100481 public void onConfigurationChanged() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800482 if (isShowing()) {
483 // Redraw (resources may have changed)
484 updateSearchButton();
Mike LeBeau1fd73232009-04-27 19:12:05 -0700485 updateSearchAppIcon();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800486 updateSearchBadge();
487 updateQueryHint();
488 }
489 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700490
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800491 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700492 * Update the UI according to the info in the current value of {@link #mSearchable}.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800493 */
Karl Rosaen875d50a2009-04-23 19:00:21 -0700494 private void updateUI() {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800495 if (mSearchable != null) {
Karl Rosaenea52d292009-07-20 09:26:10 -0700496 mDecor.setVisibility(View.VISIBLE);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700497 updateSearchAutoComplete();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800498 updateSearchButton();
Mike LeBeau1fd73232009-04-27 19:12:05 -0700499 updateSearchAppIcon();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800500 updateSearchBadge();
501 updateQueryHint();
502 updateVoiceButton();
503
504 // In order to properly configure the input method (if one is being used), we
505 // need to let it know if we'll be providing suggestions. Although it would be
506 // difficult/expensive to know if every last detail has been configured properly, we
507 // can at least see if a suggestions provider has been configured, and use that
508 // as our trigger.
509 int inputType = mSearchable.getInputType();
510 // We only touch this if the input type is set up for text (which it almost certainly
511 // should be, in the case of search!)
512 if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
513 // The existence of a suggestions authority is the proxy for "suggestions
514 // are available here"
515 inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
516 if (mSearchable.getSuggestAuthority() != null) {
517 inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
518 }
519 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700520 mSearchAutoComplete.setInputType(inputType);
Satish Sampath662df0b2009-06-22 23:16:07 +0100521 mSearchAutoCompleteImeOptions = mSearchable.getImeOptions();
522 mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700523 }
524 }
525
526 /**
527 * Updates the auto-complete text view.
528 */
529 private void updateSearchAutoComplete() {
530 // close any existing suggestions adapter
531 closeSuggestionsAdapter();
532
533 mSearchAutoComplete.setDropDownAnimationStyle(0); // no animation
Bjorn Bringert203464a2009-04-27 17:08:11 +0100534 mSearchAutoComplete.setThreshold(mSearchable.getSuggestThreshold());
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +0100535 // we dismiss the entire dialog instead
536 mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700537
538 if (mGlobalSearchMode) {
539 mSearchAutoComplete.setDropDownAlwaysVisible(true); // fill space until results come in
Karl Rosaen875d50a2009-04-23 19:00:21 -0700540 } else {
541 mSearchAutoComplete.setDropDownAlwaysVisible(false);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700542 }
543
Mike LeBeaud4760d72009-07-22 15:04:27 -0700544 mSearchAutoComplete.setForceIgnoreOutsideTouch(true);
545
Karl Rosaen875d50a2009-04-23 19:00:21 -0700546 // attach the suggestions adapter, if suggestions are available
547 // The existence of a suggestions authority is the proxy for "suggestions available here"
548 if (mSearchable.getSuggestAuthority() != null) {
Mike LeBeau1480eb22009-05-20 17:22:13 -0700549 mSuggestionsAdapter = new SuggestionsAdapter(getContext(), this, mSearchable,
550 mOutsideDrawablesCache, mGlobalSearchMode);
Karl Rosaen875d50a2009-04-23 19:00:21 -0700551 mSearchAutoComplete.setAdapter(mSuggestionsAdapter);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800552 }
553 }
554
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800555 /**
556 * Update the text in the search button. Note: This is deprecated functionality, for
557 * 1.0 compatibility only.
558 */
559 private void updateSearchButton() {
560 String textLabel = null;
561 Drawable iconLabel = null;
562 int textId = mSearchable.getSearchButtonText();
563 if (textId != 0) {
564 textLabel = mActivityContext.getResources().getString(textId);
565 } else {
566 iconLabel = getContext().getResources().
567 getDrawable(com.android.internal.R.drawable.ic_btn_search);
568 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700569 mGoButton.setText(textLabel);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800570 mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null);
571 }
572
Mike LeBeau1fd73232009-04-27 19:12:05 -0700573 private void updateSearchAppIcon() {
574 if (mGlobalSearchMode) {
575 mAppIcon.setImageResource(0);
576 mAppIcon.setVisibility(View.GONE);
577 mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_GLOBAL,
578 mSearchPlate.getPaddingTop(),
579 mSearchPlate.getPaddingRight(),
580 mSearchPlate.getPaddingBottom());
581 } else {
582 PackageManager pm = getContext().getPackageManager();
Romain Guyf4f70462009-06-26 16:55:54 -0700583 Drawable icon;
Mike LeBeau1fd73232009-04-27 19:12:05 -0700584 try {
585 ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
586 icon = pm.getApplicationIcon(info.applicationInfo);
587 if (DBG) Log.d(LOG_TAG, "Using app-specific icon");
588 } catch (NameNotFoundException e) {
589 icon = pm.getDefaultActivityIcon();
590 Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
591 }
592 mAppIcon.setImageDrawable(icon);
593 mAppIcon.setVisibility(View.VISIBLE);
594 mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL,
595 mSearchPlate.getPaddingTop(),
596 mSearchPlate.getPaddingRight(),
597 mSearchPlate.getPaddingBottom());
598 }
599 }
600
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800601 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700602 * Setup the search "Badge" if requested by mode flags.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800603 */
604 private void updateSearchBadge() {
605 // assume both hidden
606 int visibility = View.GONE;
607 Drawable icon = null;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700608 CharSequence text = null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800609
610 // optionally show one or the other.
Bjorn Bringerta9204132009-05-05 14:06:35 +0100611 if (mSearchable.useBadgeIcon()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800612 icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId());
613 visibility = View.VISIBLE;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700614 if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId());
Bjorn Bringerta9204132009-05-05 14:06:35 +0100615 } else if (mSearchable.useBadgeLabel()) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800616 text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
617 visibility = View.VISIBLE;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700618 if (DBG) Log.d(LOG_TAG, "Using badge label: " + mSearchable.getLabelId());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800619 }
620
621 mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
622 mBadgeLabel.setText(text);
623 mBadgeLabel.setVisibility(visibility);
624 }
625
626 /**
627 * Update the hint in the query text field.
628 */
629 private void updateQueryHint() {
630 if (isShowing()) {
631 String hint = null;
632 if (mSearchable != null) {
633 int hintId = mSearchable.getHintId();
634 if (hintId != 0) {
635 hint = mActivityContext.getString(hintId);
636 }
637 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700638 mSearchAutoComplete.setHint(hint);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800639 }
640 }
641
642 /**
643 * Update the visibility of the voice button. There are actually two voice search modes,
644 * either of which will activate the button.
645 */
646 private void updateVoiceButton() {
647 int visibility = View.GONE;
648 if (mSearchable.getVoiceSearchEnabled()) {
649 Intent testIntent = null;
650 if (mSearchable.getVoiceSearchLaunchWebSearch()) {
651 testIntent = mVoiceWebSearchIntent;
652 } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
653 testIntent = mVoiceAppSearchIntent;
654 }
655 if (testIntent != null) {
656 ResolveInfo ri = getContext().getPackageManager().
657 resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
658 if (ri != null) {
659 visibility = View.VISIBLE;
660 }
661 }
662 }
663 mVoiceButton.setVisibility(visibility);
664 }
665
666 /**
667 * Listeners of various types
668 */
669
670 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -0700671 * {@link Dialog#onTouchEvent(MotionEvent)} will cancel the dialog only when the
672 * touch is outside the window. But the window includes space for the drop-down,
673 * so we also cancel on taps outside the search bar when the drop-down is not showing.
674 */
675 @Override
676 public boolean onTouchEvent(MotionEvent event) {
677 // cancel if the drop-down is not showing and the touch event was outside the search plate
678 if (!mSearchAutoComplete.isPopupShowing() && isOutOfBounds(mSearchPlate, event)) {
679 if (DBG) Log.d(LOG_TAG, "Pop-up not showing and outside of search plate.");
680 cancel();
681 return true;
682 }
683 // Let Dialog handle events outside the window while the pop-up is showing.
684 return super.onTouchEvent(event);
685 }
686
687 private boolean isOutOfBounds(View v, MotionEvent event) {
688 final int x = (int) event.getX();
689 final int y = (int) event.getY();
690 final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
691 return (x < -slop) || (y < -slop)
692 || (x > (v.getWidth()+slop))
693 || (y > (v.getHeight()+slop));
694 }
695
696 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800697 * Dialog's OnKeyListener implements various search-specific functionality
698 *
699 * @param keyCode This is the keycode of the typed key, and is the same value as
Karl Rosaen875d50a2009-04-23 19:00:21 -0700700 * found in the KeyEvent parameter.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800701 * @param event The complete event record for the typed key
702 *
703 * @return Return true if the event was handled here, or false if not.
704 */
705 @Override
706 public boolean onKeyDown(int keyCode, KeyEvent event) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700707 if (DBG) Log.d(LOG_TAG, "onKeyDown(" + keyCode + "," + event + ")");
Bjorn Bringertee716fa2009-07-16 09:15:37 +0100708 if (mSearchable == null) {
709 return false;
710 }
711
Karl Rosaen875d50a2009-04-23 19:00:21 -0700712 // handle back key to go back to previous searchable, etc.
713 if (handleBackKey(keyCode, event)) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800714 return true;
Karl Rosaen875d50a2009-04-23 19:00:21 -0700715 }
716
Karl Rosaen875d50a2009-04-23 19:00:21 -0700717 if (keyCode == KeyEvent.KEYCODE_SEARCH) {
Mike LeBeaub3aab692009-04-30 02:09:09 -0700718 // If the search key is pressed, toggle between global and in-app search. If we are
719 // currently doing global search and there is no in-app search context to toggle to,
720 // just don't do anything.
721 return toggleGlobalSearch();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800722 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700723
724 // if it's an action specified by the searchable activity, launch the
725 // entered query with the action key
726 SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
Bjorn Bringerta9204132009-05-05 14:06:35 +0100727 if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
728 launchQuerySearch(keyCode, actionKey.getQueryActionMsg());
Karl Rosaen875d50a2009-04-23 19:00:21 -0700729 return true;
730 }
731
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800732 return false;
733 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700734
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800735 /**
736 * Callback to watch the textedit field for empty/non-empty
737 */
738 private TextWatcher mTextWatcher = new TextWatcher() {
739
Karl Rosaen875d50a2009-04-23 19:00:21 -0700740 public void beforeTextChanged(CharSequence s, int start, int before, int after) { }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800741
742 public void onTextChanged(CharSequence s, int start,
743 int before, int after) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700744 if (DBG_LOG_TIMING) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800745 dbgLogTiming("onTextChanged()");
746 }
Bjorn Bringertee716fa2009-07-16 09:15:37 +0100747 if (mSearchable == null) {
748 return;
749 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800750 updateWidgetState();
Karl Rosaen875d50a2009-04-23 19:00:21 -0700751 if (!mSearchAutoComplete.isPerformingCompletion()) {
752 // The user changed the query, remember it.
753 mUserQuery = s == null ? "" : s.toString();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800754 }
755 }
756
Satish Sampath662df0b2009-06-22 23:16:07 +0100757 public void afterTextChanged(Editable s) {
Dianne Hackbornb06ea702009-07-13 13:07:51 -0700758 if (mSearchable == null) {
759 return;
760 }
Satish Sampathd21572c2009-07-08 14:54:11 +0100761 if (mSearchable.autoUrlDetect() && !mSearchAutoComplete.isPerformingCompletion()) {
Satish Sampath662df0b2009-06-22 23:16:07 +0100762 // The user changed the query, check if it is a URL and if so change the search
763 // button in the soft keyboard to the 'Go' button.
764 int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION));
765 if (Regex.WEB_URL_PATTERN.matcher(mUserQuery).matches()) {
766 options = options | EditorInfo.IME_ACTION_GO;
767 } else {
768 options = options | EditorInfo.IME_ACTION_SEARCH;
769 }
770 if (options != mSearchAutoCompleteImeOptions) {
771 mSearchAutoCompleteImeOptions = options;
772 mSearchAutoComplete.setImeOptions(options);
773 // This call is required to update the soft keyboard UI with latest IME flags.
774 mSearchAutoComplete.setInputType(mSearchAutoComplete.getInputType());
775 }
776 }
777 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800778 };
779
780 /**
781 * Enable/Disable the cancel button based on edit text state (any text?)
782 */
783 private void updateWidgetState() {
784 // enable the button if we have one or more non-space characters
Karl Rosaen875d50a2009-04-23 19:00:21 -0700785 boolean enabled = !mSearchAutoComplete.isEmpty();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800786 mGoButton.setEnabled(enabled);
787 mGoButton.setFocusable(enabled);
788 }
789
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800790 /**
791 * React to typing in the GO search button by refocusing to EditText.
792 * Continue typing the query.
793 */
794 View.OnKeyListener mButtonsKeyListener = new View.OnKeyListener() {
795 public boolean onKey(View v, int keyCode, KeyEvent event) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700796 // guard against possible race conditions
797 if (mSearchable == null) {
798 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800799 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700800
801 if (!event.isSystem() &&
802 (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
803 (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
804 (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
805 (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
806 (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
807 // restore focus and give key to EditText ...
808 if (mSearchAutoComplete.requestFocus()) {
809 return mSearchAutoComplete.dispatchKeyEvent(event);
810 }
811 }
812
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800813 return false;
814 }
815 };
816
817 /**
818 * React to a click in the GO button by launching a search.
819 */
820 View.OnClickListener mGoButtonClickListener = new View.OnClickListener() {
821 public void onClick(View v) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700822 // guard against possible race conditions
823 if (mSearchable == null) {
824 return;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800825 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700826 launchQuerySearch();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800827 }
828 };
829
830 /**
831 * React to a click in the voice search button.
832 */
833 View.OnClickListener mVoiceButtonClickListener = new View.OnClickListener() {
834 public void onClick(View v) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700835 // guard against possible race conditions
836 if (mSearchable == null) {
837 return;
838 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800839 try {
840 if (mSearchable.getVoiceSearchLaunchWebSearch()) {
841 getContext().startActivity(mVoiceWebSearchIntent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800842 } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
843 Intent appSearchIntent = createVoiceAppSearchIntent(mVoiceAppSearchIntent);
844 getContext().startActivity(appSearchIntent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800845 }
846 } catch (ActivityNotFoundException e) {
847 // Should not happen, since we check the availability of
848 // voice search before showing the button. But just in case...
849 Log.w(LOG_TAG, "Could not find voice search activity");
850 }
851 }
852 };
853
854 /**
855 * Create and return an Intent that can launch the voice search activity, perform a specific
856 * voice transcription, and forward the results to the searchable activity.
857 *
858 * @param baseIntent The voice app search intent to start from
859 * @return A completely-configured intent ready to send to the voice search activity
860 */
861 private Intent createVoiceAppSearchIntent(Intent baseIntent) {
Mike LeBeau260dfb52009-07-15 15:20:14 -0700862 ComponentName searchActivity = mSearchable.getSearchActivity();
863
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800864 // create the necessary intent to set up a search-and-forward operation
865 // in the voice search system. We have to keep the bundle separate,
866 // because it becomes immutable once it enters the PendingIntent
867 Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
Mike LeBeau260dfb52009-07-15 15:20:14 -0700868 queryIntent.setComponent(searchActivity);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800869 PendingIntent pending = PendingIntent.getActivity(
870 getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
871
872 // Now set up the bundle that will be inserted into the pending intent
873 // when it's time to do the search. We always build it here (even if empty)
874 // because the voice search activity will always need to insert "QUERY" into
875 // it anyway.
876 Bundle queryExtras = new Bundle();
877 if (mAppSearchData != null) {
878 queryExtras.putBundle(SearchManager.APP_DATA, mAppSearchData);
879 }
880
881 // Now build the intent to launch the voice search. Add all necessary
882 // extras to launch the voice recognizer, and then all the necessary extras
883 // to forward the results to the searchable activity
884 Intent voiceIntent = new Intent(baseIntent);
885
886 // Add all of the configuration options supplied by the searchable's metadata
887 String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
888 String prompt = null;
889 String language = null;
890 int maxResults = 1;
891 Resources resources = mActivityContext.getResources();
892 if (mSearchable.getVoiceLanguageModeId() != 0) {
893 languageModel = resources.getString(mSearchable.getVoiceLanguageModeId());
894 }
895 if (mSearchable.getVoicePromptTextId() != 0) {
896 prompt = resources.getString(mSearchable.getVoicePromptTextId());
897 }
898 if (mSearchable.getVoiceLanguageId() != 0) {
899 language = resources.getString(mSearchable.getVoiceLanguageId());
900 }
901 if (mSearchable.getVoiceMaxResults() != 0) {
902 maxResults = mSearchable.getVoiceMaxResults();
903 }
904 voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
905 voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
906 voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
907 voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
Mike LeBeau260dfb52009-07-15 15:20:14 -0700908 voiceIntent.putExtra(EXTRA_CALLING_PACKAGE,
909 searchActivity == null ? null : searchActivity.toShortString());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800910
911 // Add the values that configure forwarding the results
912 voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
913 voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
914
915 return voiceIntent;
916 }
917
918 /**
Satish Sampath662df0b2009-06-22 23:16:07 +0100919 * Corrects http/https typo errors in the given url string, and if the protocol specifier was
920 * not present defaults to http.
921 *
922 * @param inUrl URL to check and fix
923 * @return fixed URL string.
924 */
925 private String fixUrl(String inUrl) {
926 if (inUrl.startsWith("http://") || inUrl.startsWith("https://"))
927 return inUrl;
928
929 if (inUrl.startsWith("http:") || inUrl.startsWith("https:")) {
930 if (inUrl.startsWith("http:/") || inUrl.startsWith("https:/")) {
931 inUrl = inUrl.replaceFirst("/", "//");
932 } else {
933 inUrl = inUrl.replaceFirst(":", "://");
934 }
935 }
936
937 if (inUrl.indexOf("://") == -1) {
938 inUrl = "http://" + inUrl;
939 }
940
941 return inUrl;
942 }
943
944 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800945 * React to the user typing "enter" or other hardwired keys while typing in the search box.
946 * This handles these special keys while the edit box has focus.
947 */
948 View.OnKeyListener mTextKeyListener = new View.OnKeyListener() {
949 public boolean onKey(View v, int keyCode, KeyEvent event) {
Karl Rosaen875d50a2009-04-23 19:00:21 -0700950 // guard against possible race conditions
951 if (mSearchable == null) {
952 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800953 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700954
955 if (DBG_LOG_TIMING) dbgLogTiming("doTextKey()");
956 if (DBG) {
957 Log.d(LOG_TAG, "mTextListener.onKey(" + keyCode + "," + event
958 + "), selection: " + mSearchAutoComplete.getListSelection());
959 }
960
961 // If a suggestion is selected, handle enter, search key, and action keys
962 // as presses on the selected suggestion
963 if (mSearchAutoComplete.isPopupShowing() &&
964 mSearchAutoComplete.getListSelection() != ListView.INVALID_POSITION) {
965 return onSuggestionsKey(v, keyCode, event);
966 }
967
968 // If there is text in the query box, handle enter, and action keys
969 // The search key is handled by the dialog's onKeyDown().
970 if (!mSearchAutoComplete.isEmpty()) {
971 if (keyCode == KeyEvent.KEYCODE_ENTER
972 && event.getAction() == KeyEvent.ACTION_UP) {
973 v.cancelLongPress();
Satish Sampath662df0b2009-06-22 23:16:07 +0100974
Satish Sampathb1665f22009-07-10 15:43:38 +0100975 // If this is a url entered by the user & we displayed the 'Go' button which
976 // the user clicked, launch the url instead of using it as a search query.
977 if (mSearchable.autoUrlDetect() &&
978 (mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION)
Satish Sampathd21572c2009-07-08 14:54:11 +0100979 == EditorInfo.IME_ACTION_GO) {
Satish Sampathb1665f22009-07-10 15:43:38 +0100980 Uri uri = Uri.parse(fixUrl(mSearchAutoComplete.getText().toString()));
981 Intent intent = new Intent(Intent.ACTION_VIEW, uri);
982 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
983 launchIntent(intent);
984 } else {
985 // Launch as a regular search.
986 launchQuerySearch();
Satish Sampath662df0b2009-06-22 23:16:07 +0100987 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700988 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800989 }
Karl Rosaen875d50a2009-04-23 19:00:21 -0700990 if (event.getAction() == KeyEvent.ACTION_DOWN) {
991 SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
Bjorn Bringerta9204132009-05-05 14:06:35 +0100992 if ((actionKey != null) && (actionKey.getQueryActionMsg() != null)) {
993 launchQuerySearch(keyCode, actionKey.getQueryActionMsg());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800994 return true;
995 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -0800996 }
997 }
998 return false;
999 }
1000 };
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001001
Dianne Hackborna8f556e2009-03-24 20:47:50 -07001002 @Override
Karl Rosaenea52d292009-07-20 09:26:10 -07001003 public void hide() {
Bjorn Bringert444c7272009-07-06 21:32:50 +01001004 if (!isShowing()) return;
1005
Dianne Hackborna8f556e2009-03-24 20:47:50 -07001006 // We made sure the IME was displayed, so also make sure it is closed
1007 // when we go away.
1008 InputMethodManager imm = (InputMethodManager)getContext()
1009 .getSystemService(Context.INPUT_METHOD_SERVICE);
1010 if (imm != null) {
1011 imm.hideSoftInputFromWindow(
1012 getWindow().getDecorView().getWindowToken(), 0);
1013 }
Karl Rosaenea52d292009-07-20 09:26:10 -07001014
1015 super.hide();
Dianne Hackborna8f556e2009-03-24 20:47:50 -07001016 }
Karl Rosaenea52d292009-07-20 09:26:10 -07001017
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001018 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001019 * React to the user typing while in the suggestions list. First, check for action
1020 * keys. If not handled, try refocusing regular characters into the EditText.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001021 */
Karl Rosaen875d50a2009-04-23 19:00:21 -07001022 private boolean onSuggestionsKey(View v, int keyCode, KeyEvent event) {
1023 // guard against possible race conditions (late arrival after dismiss)
1024 if (mSearchable == null) {
1025 return false;
1026 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001027 if (mSuggestionsAdapter == null) {
1028 return false;
1029 }
1030 if (event.getAction() == KeyEvent.ACTION_DOWN) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001031 if (DBG_LOG_TIMING) {
1032 dbgLogTiming("onSuggestionsKey()");
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001033 }
1034
1035 // First, check for enter or search (both of which we'll treat as a "click")
1036 if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001037 int position = mSearchAutoComplete.getListSelection();
1038 return launchSuggestion(position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001039 }
1040
1041 // Next, check for left/right moves, which we use to "return" the user to the edit view
1042 if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001043 // give "focus" to text editor, with cursor at the beginning if
1044 // left key, at end if right key
1045 // TODO: Reverse left/right for right-to-left languages, e.g. Arabic
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001046 int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ?
Karl Rosaen875d50a2009-04-23 19:00:21 -07001047 0 : mSearchAutoComplete.length();
1048 mSearchAutoComplete.setSelection(selPoint);
1049 mSearchAutoComplete.setListSelection(0);
1050 mSearchAutoComplete.clearListSelection();
Mike LeBeauffe3ecf2009-07-16 18:18:37 -07001051 mSearchAutoComplete.ensureImeVisible();
1052
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001053 return true;
1054 }
1055
1056 // Next, check for an "up and out" move
Karl Rosaen875d50a2009-04-23 19:00:21 -07001057 if (keyCode == KeyEvent.KEYCODE_DPAD_UP
1058 && 0 == mSearchAutoComplete.getListSelection()) {
1059 restoreUserQuery();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001060 // let ACTV complete the move
1061 return false;
1062 }
1063
1064 // Next, check for an "action key"
1065 SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
1066 if ((actionKey != null) &&
Bjorn Bringerta9204132009-05-05 14:06:35 +01001067 ((actionKey.getSuggestActionMsg() != null) ||
1068 (actionKey.getSuggestActionMsgColumn() != null))) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001069 // launch suggestion using action key column
1070 int position = mSearchAutoComplete.getListSelection();
1071 if (position != ListView.INVALID_POSITION) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001072 Cursor c = mSuggestionsAdapter.getCursor();
1073 if (c.moveToPosition(position)) {
1074 final String actionMsg = getActionKeyMessage(c, actionKey);
1075 if (actionMsg != null && (actionMsg.length() > 0)) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001076 return launchSuggestion(position, keyCode, actionMsg);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001077 }
1078 }
1079 }
1080 }
1081 }
1082 return false;
Karl Rosaen875d50a2009-04-23 19:00:21 -07001083 }
1084
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001085 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001086 * Launch a search for the text in the query text field.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001087 */
Karl Rosaen875d50a2009-04-23 19:00:21 -07001088 protected void launchQuerySearch() {
1089 launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001090 }
1091
1092 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001093 * Launch a search for the text in the query text field.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001094 *
Karl Rosaen875d50a2009-04-23 19:00:21 -07001095 * @param actionKey The key code of the action key that was pressed,
1096 * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1097 * @param actionMsg The message for the action key that was pressed,
1098 * or <code>null</code> if none.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001099 */
Karl Rosaen875d50a2009-04-23 19:00:21 -07001100 protected void launchQuerySearch(int actionKey, String actionMsg) {
1101 String query = mSearchAutoComplete.getText().toString();
Bjorn Bringert4899e382009-07-22 10:25:25 +01001102 String action = mGlobalSearchMode ? Intent.ACTION_WEB_SEARCH : Intent.ACTION_SEARCH;
1103 Intent intent = createIntent(action, null, null, query, null,
Karl Rosaen875d50a2009-04-23 19:00:21 -07001104 actionKey, actionMsg);
1105 launchIntent(intent);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001106 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001107
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001108 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001109 * Launches an intent based on a suggestion.
1110 *
1111 * @param position The index of the suggestion to create the intent from.
1112 * @return true if a successful launch, false if could not (e.g. bad position).
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001113 */
Karl Rosaen875d50a2009-04-23 19:00:21 -07001114 protected boolean launchSuggestion(int position) {
1115 return launchSuggestion(position, KeyEvent.KEYCODE_UNKNOWN, null);
1116 }
1117
1118 /**
1119 * Launches an intent based on a suggestion.
1120 *
1121 * @param position The index of the suggestion to create the intent from.
1122 * @param actionKey The key code of the action key that was pressed,
1123 * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1124 * @param actionMsg The message for the action key that was pressed,
1125 * or <code>null</code> if none.
1126 * @return true if a successful launch, false if could not (e.g. bad position).
1127 */
1128 protected boolean launchSuggestion(int position, int actionKey, String actionMsg) {
1129 Cursor c = mSuggestionsAdapter.getCursor();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001130 if ((c != null) && c.moveToPosition(position)) {
Karl Rosaend4c98c42009-06-09 17:05:54 +01001131
1132 Intent intent = createIntentFromSuggestion(c, actionKey, actionMsg);
1133
1134 // report back about the click
1135 if (mGlobalSearchMode) {
1136 // in global search mode, do it via cursor
1137 mSuggestionsAdapter.callCursorOnClick(c, position);
1138 } else if (intent != null
1139 && mPreviousComponents != null
1140 && !mPreviousComponents.isEmpty()) {
1141 // in-app search (and we have pivoted in as told by mPreviousComponents,
1142 // which is used for keeping track of what we pop back to when we are pivoting into
1143 // in app search.)
1144 reportInAppClickToGlobalSearch(c, intent);
1145 }
Karl Rosaena058f022009-06-01 23:11:44 +01001146
1147 // launch the intent
Karl Rosaen875d50a2009-04-23 19:00:21 -07001148 launchIntent(intent);
Karl Rosaena058f022009-06-01 23:11:44 +01001149
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001150 return true;
1151 }
1152 return false;
1153 }
Karl Rosaena058f022009-06-01 23:11:44 +01001154
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001155 /**
Karl Rosaend4c98c42009-06-09 17:05:54 +01001156 * Report a click from an in app search result back to global search for shortcutting porpoises.
1157 *
1158 * @param c The cursor that is pointing to the clicked position.
1159 * @param intent The intent that will be launched for the click.
1160 */
1161 private void reportInAppClickToGlobalSearch(Cursor c, Intent intent) {
1162 // for in app search, still tell global search via content provider
1163 Uri uri = getClickReportingUri();
1164 final ContentValues cv = new ContentValues();
1165 cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_QUERY, mUserQuery);
1166 final ComponentName source = mSearchable.getSearchActivity();
1167 cv.put(SearchManager.SEARCH_CLICK_REPORT_COLUMN_COMPONENT, source.flattenToShortString());
1168
1169 // grab the intent columns from the intent we created since it has additional
1170 // logic for falling back on the searchable default
1171 cv.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION, intent.getAction());
1172 cv.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, intent.getDataString());
Satish Sampathbf23fe02009-06-15 23:47:56 +01001173 cv.put(SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME,
1174 intent.getStringExtra(SearchManager.COMPONENT_NAME_KEY));
Karl Rosaend4c98c42009-06-09 17:05:54 +01001175
1176 // ensure the icons will work for global search
1177 cv.put(SearchManager.SUGGEST_COLUMN_ICON_1,
1178 wrapIconForPackage(
1179 source,
1180 getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_1)));
1181 cv.put(SearchManager.SUGGEST_COLUMN_ICON_2,
1182 wrapIconForPackage(
1183 source,
1184 getColumnString(c, SearchManager.SUGGEST_COLUMN_ICON_2)));
1185
1186 // the rest can be passed through directly
1187 cv.put(SearchManager.SUGGEST_COLUMN_FORMAT,
1188 getColumnString(c, SearchManager.SUGGEST_COLUMN_FORMAT));
1189 cv.put(SearchManager.SUGGEST_COLUMN_TEXT_1,
1190 getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_1));
1191 cv.put(SearchManager.SUGGEST_COLUMN_TEXT_2,
1192 getColumnString(c, SearchManager.SUGGEST_COLUMN_TEXT_2));
1193 cv.put(SearchManager.SUGGEST_COLUMN_QUERY,
1194 getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY));
1195 cv.put(SearchManager.SUGGEST_COLUMN_SHORTCUT_ID,
1196 getColumnString(c, SearchManager.SUGGEST_COLUMN_SHORTCUT_ID));
1197 // note: deliberately omitting background color since it is only for global search
1198 // "more results" entries
1199 mContext.getContentResolver().insert(uri, cv);
1200 }
1201
1202 /**
1203 * @return A URI appropriate for reporting a click.
1204 */
1205 private Uri getClickReportingUri() {
1206 Uri.Builder uriBuilder = new Uri.Builder()
1207 .scheme(ContentResolver.SCHEME_CONTENT)
1208 .authority(SearchManager.SEARCH_CLICK_REPORT_AUTHORITY);
1209
1210 uriBuilder.appendPath(SearchManager.SEARCH_CLICK_REPORT_URI_PATH);
1211
1212 return uriBuilder
1213 .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
1214 .fragment("") // TODO: Remove, workaround for a bug in Uri.writeToParcel()
1215 .build();
1216 }
1217
1218 /**
1219 * Wraps an icon for a particular package. If the icon is a resource id, it is converted into
1220 * an android.resource:// URI.
1221 *
1222 * @param source The source of the icon
1223 * @param icon The icon retrieved from a suggestion column
1224 * @return An icon string appropriate for the package.
1225 */
1226 private String wrapIconForPackage(ComponentName source, String icon) {
1227 if (icon == null || icon.length() == 0 || "0".equals(icon)) {
1228 // SearchManager specifies that null or zero can be returned to indicate
1229 // no icon. We also allow empty string.
1230 return null;
1231 } else if (!Character.isDigit(icon.charAt(0))){
1232 return icon;
1233 } else {
1234 String packageName = source.getPackageName();
1235 return new Uri.Builder()
1236 .scheme(ContentResolver.SCHEME_ANDROID_RESOURCE)
1237 .authority(packageName)
1238 .encodedPath(icon)
1239 .toString();
1240 }
1241 }
1242
1243 /**
Karl Rosaenea52d292009-07-20 09:26:10 -07001244 * Launches an intent, including any special intent handling. Doesn't dismiss the dialog
1245 * since that will be handled in {@link SearchDialogWrapper#performActivityResuming}
Karl Rosaen875d50a2009-04-23 19:00:21 -07001246 */
1247 private void launchIntent(Intent intent) {
1248 if (intent == null) {
1249 return;
1250 }
1251 if (handleSpecialIntent(intent)){
1252 return;
1253 }
Karl Rosaenea52d292009-07-20 09:26:10 -07001254 Log.d(LOG_TAG, "launching " + intent);
Bjorn Bringert4899e382009-07-22 10:25:25 +01001255 try {
1256 // in global search mode, we send the activity straight to the original suggestion
1257 // source. this is because GlobalSearch may not have permission to launch the
1258 // intent, and to avoid the extra step of going through GlobalSearch.
1259 if (mGlobalSearchMode) {
1260 launchGlobalSearchIntent(intent);
1261 } else {
1262 getContext().startActivity(intent);
1263 // in global search mode, SearchDialogWrapper#performActivityResuming
1264 // will handle hiding the dialog when the next activity starts, but for
1265 // in-app search, we still need to dismiss the dialog.
1266 dismiss();
1267 }
1268 } catch (RuntimeException ex) {
1269 Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
Karl Rosaen876627d2009-07-20 14:30:55 -07001270 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001271 }
Bjorn Bringert4899e382009-07-22 10:25:25 +01001272
1273 private void launchGlobalSearchIntent(Intent intent) {
1274 final String packageName;
1275 // GlobalSearch puts the original source of the suggestion in the
1276 // 'component name' column. If set, we send the intent to that activity.
1277 // We trust GlobalSearch to always set this to the suggestion source.
1278 String intentComponent = intent.getStringExtra(SearchManager.COMPONENT_NAME_KEY);
1279 if (intentComponent != null) {
1280 ComponentName componentName = ComponentName.unflattenFromString(intentComponent);
1281 intent.setComponent(componentName);
1282 intent.removeExtra(SearchManager.COMPONENT_NAME_KEY);
1283 // Launch the intent as the suggestion source.
1284 // This prevents sources from using the search dialog to launch
1285 // intents that they don't have permission for themselves.
1286 packageName = componentName.getPackageName();
1287 } else {
1288 // If there is no component in the suggestion, it must be a built-in suggestion
1289 // from GlobalSearch (e.g. "Search the web for") or the intent
1290 // launched when pressing the search/go button in the search dialog.
1291 // Launch the intent with the permissions of GlobalSearch.
1292 packageName = mSearchable.getSearchActivity().getPackageName();
1293 }
1294
1295 // Launch all global search suggestions as new tasks, since they don't relate
1296 // to the current task.
1297 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
1298 setBrowserApplicationId(intent);
1299
1300 if (DBG) Log.d(LOG_TAG, "Launching intent " + intent.toURI() + " as " + packageName);
1301 startActivityInPackage(intent, packageName);
1302 }
1303
1304 /**
1305 * If the intent is to open an HTTP or HTTPS URL, we set
1306 * {@link Browser#EXTRA_APPLICATION_ID} so that any existing browser window that
1307 * has been opened by us for the same URL will be reused.
1308 */
1309 private void setBrowserApplicationId(Intent intent) {
1310 Uri data = intent.getData();
1311 if (Intent.ACTION_VIEW.equals(intent.getAction()) && data != null) {
1312 String scheme = data.getScheme();
1313 if (scheme != null && scheme.startsWith("http")) {
1314 intent.putExtra(Browser.EXTRA_APPLICATION_ID, data.toString());
1315 }
1316 }
1317 }
1318
1319 /**
1320 * Starts an activity as if it had been started by the given package.
1321 *
1322 * @param intent The description of the activity to start.
1323 * @param packageName
1324 * @throws ActivityNotFoundException If the intent could not be resolved to
1325 * and existing activity.
1326 * @throws SecurityException If the package does not have permission to start
1327 * start the activity.
1328 * @throws AndroidRuntimeException If some other error occurs.
1329 */
1330 private void startActivityInPackage(Intent intent, String packageName) {
1331 try {
1332 int uid = ActivityThread.getPackageManager().getPackageUid(packageName);
1333 if (uid < 0) {
1334 throw new AndroidRuntimeException("Package UID not found " + packageName);
1335 }
1336 String resolvedType = intent.resolveTypeIfNeeded(getContext().getContentResolver());
1337 IBinder resultTo = null;
1338 String resultWho = null;
1339 int requestCode = -1;
1340 boolean onlyIfNeeded = false;
1341 int result = ActivityManagerNative.getDefault().startActivityInPackage(
1342 uid, intent, resolvedType, resultTo, resultWho, requestCode, onlyIfNeeded);
1343 checkStartActivityResult(result, intent);
1344 } catch (RemoteException ex) {
1345 throw new AndroidRuntimeException(ex);
1346 }
1347 }
1348
1349 // Stolen from Instrumentation.checkStartActivityResult()
1350 private static void checkStartActivityResult(int res, Intent intent) {
1351 if (res >= IActivityManager.START_SUCCESS) {
1352 return;
1353 }
1354 switch (res) {
1355 case IActivityManager.START_INTENT_NOT_RESOLVED:
1356 case IActivityManager.START_CLASS_NOT_FOUND:
1357 if (intent.getComponent() != null)
1358 throw new ActivityNotFoundException(
1359 "Unable to find explicit activity class "
1360 + intent.getComponent().toShortString()
1361 + "; have you declared this activity in your AndroidManifest.xml?");
1362 throw new ActivityNotFoundException(
1363 "No Activity found to handle " + intent);
1364 case IActivityManager.START_PERMISSION_DENIED:
1365 throw new SecurityException("Not allowed to start activity "
1366 + intent);
1367 case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
1368 throw new AndroidRuntimeException(
1369 "FORWARD_RESULT_FLAG used while also requesting a result");
1370 default:
1371 throw new AndroidRuntimeException("Unknown error code "
1372 + res + " when starting " + intent);
1373 }
1374 }
1375
Karl Rosaen875d50a2009-04-23 19:00:21 -07001376 /**
1377 * Handles the special intent actions declared in {@link SearchManager}.
1378 *
1379 * @return <code>true</code> if the intent was handled.
1380 */
1381 private boolean handleSpecialIntent(Intent intent) {
1382 String action = intent.getAction();
1383 if (SearchManager.INTENT_ACTION_CHANGE_SEARCH_SOURCE.equals(action)) {
1384 handleChangeSourceIntent(intent);
1385 return true;
Karl Rosaen875d50a2009-04-23 19:00:21 -07001386 }
1387 return false;
1388 }
1389
1390 /**
Karl Rosaend4c98c42009-06-09 17:05:54 +01001391 * Handles {@link SearchManager#INTENT_ACTION_CHANGE_SEARCH_SOURCE}.
Karl Rosaen875d50a2009-04-23 19:00:21 -07001392 */
1393 private void handleChangeSourceIntent(Intent intent) {
1394 Uri dataUri = intent.getData();
1395 if (dataUri == null) {
1396 Log.w(LOG_TAG, "SearchManager.INTENT_ACTION_CHANGE_SOURCE without intent data.");
1397 return;
1398 }
1399 ComponentName componentName = ComponentName.unflattenFromString(dataUri.toString());
1400 if (componentName == null) {
1401 Log.w(LOG_TAG, "Invalid ComponentName: " + dataUri);
1402 return;
1403 }
1404 if (DBG) Log.d(LOG_TAG, "Switching to " + componentName);
1405
1406 ComponentName previous = mLaunchComponent;
1407 if (!show(componentName, mAppSearchData, false)) {
1408 Log.w(LOG_TAG, "Failed to switch to source " + componentName);
1409 return;
1410 }
1411 pushPreviousComponent(previous);
1412
1413 String query = intent.getStringExtra(SearchManager.QUERY);
1414 setUserQuery(query);
Mike LeBeau35df87c2009-06-24 13:06:39 -07001415 mSearchAutoComplete.showDropDown();
Karl Rosaen875d50a2009-04-23 19:00:21 -07001416 }
Karl Rosaena058f022009-06-01 23:11:44 +01001417
Karl Rosaen875d50a2009-04-23 19:00:21 -07001418 /**
Mike LeBeauae9760b2009-06-01 21:53:09 +01001419 * Sets the list item selection in the AutoCompleteTextView's ListView.
1420 */
1421 public void setListSelection(int index) {
1422 mSearchAutoComplete.setListSelection(index);
1423 }
Karl Rosaena058f022009-06-01 23:11:44 +01001424
Mike LeBeauae9760b2009-06-01 21:53:09 +01001425 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001426 * Saves the previous component that was searched, so that we can go
1427 * back to it.
1428 */
1429 private void pushPreviousComponent(ComponentName componentName) {
1430 if (mPreviousComponents == null) {
1431 mPreviousComponents = new ArrayList<ComponentName>();
1432 }
1433 mPreviousComponents.add(componentName);
1434 }
1435
1436 /**
1437 * Pops the previous component off the stack and returns it.
1438 *
1439 * @return The component name, or <code>null</code> if there was
1440 * no previous component.
1441 */
1442 private ComponentName popPreviousComponent() {
1443 if (mPreviousComponents == null) {
1444 return null;
1445 }
1446 int size = mPreviousComponents.size();
1447 if (size == 0) {
1448 return null;
1449 }
1450 return mPreviousComponents.remove(size - 1);
1451 }
1452
1453 /**
1454 * Goes back to the previous component that was searched, if any.
1455 *
1456 * @return <code>true</code> if there was a previous component that we could go back to.
1457 */
1458 private boolean backToPreviousComponent() {
1459 ComponentName previous = popPreviousComponent();
1460 if (previous == null) {
1461 return false;
1462 }
1463 if (!show(previous, mAppSearchData, false)) {
1464 Log.w(LOG_TAG, "Failed to switch to source " + previous);
1465 return false;
1466 }
1467
1468 // must touch text to trigger suggestions
1469 // TODO: should this be the text as it was when the user left
1470 // the source that we are now going back to?
1471 String query = mSearchAutoComplete.getText().toString();
1472 setUserQuery(query);
1473
1474 return true;
1475 }
1476
1477 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001478 * When a particular suggestion has been selected, perform the various lookups required
1479 * to use the suggestion. This includes checking the cursor for suggestion-specific data,
1480 * and/or falling back to the XML for defaults; It also creates REST style Uri data when
1481 * the suggestion includes a data id.
1482 *
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001483 * @param c The suggestions cursor, moved to the row of the user's selection
Karl Rosaen875d50a2009-04-23 19:00:21 -07001484 * @param actionKey The key code of the action key that was pressed,
1485 * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1486 * @param actionMsg The message for the action key that was pressed,
1487 * or <code>null</code> if none.
1488 * @return An intent for the suggestion at the cursor's position.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001489 */
Karl Rosaen875d50a2009-04-23 19:00:21 -07001490 private Intent createIntentFromSuggestion(Cursor c, int actionKey, String actionMsg) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001491 try {
1492 // use specific action if supplied, or default action if supplied, or fixed default
Karl Rosaen875d50a2009-04-23 19:00:21 -07001493 String action = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
Karl Rosaena058f022009-06-01 23:11:44 +01001494
1495 // some items are display only, or have effect via the cursor respond click reporting.
1496 if (SearchManager.INTENT_ACTION_NONE.equals(action)) {
1497 return null;
1498 }
1499
Karl Rosaen875d50a2009-04-23 19:00:21 -07001500 if (action == null) {
1501 action = mSearchable.getSuggestIntentAction();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001502 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001503 if (action == null) {
1504 action = Intent.ACTION_SEARCH;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001505 }
1506
1507 // use specific data if supplied, or default data if supplied
Karl Rosaen875d50a2009-04-23 19:00:21 -07001508 String data = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001509 if (data == null) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001510 data = mSearchable.getSuggestIntentData();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001511 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001512 // then, if an ID was provided, append it.
1513 if (data != null) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001514 String id = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
1515 if (id != null) {
1516 data = data + "/" + Uri.encode(id);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001517 }
1518 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001519 Uri dataUri = (data == null) ? null : Uri.parse(data);
1520
Satish Sampathbf23fe02009-06-15 23:47:56 +01001521 String componentName = getColumnString(
1522 c, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME);
Karl Rosaena058f022009-06-01 23:11:44 +01001523
Karl Rosaen875d50a2009-04-23 19:00:21 -07001524 String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY);
Satish Sampathbf23fe02009-06-15 23:47:56 +01001525 String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA);
Karl Rosaen875d50a2009-04-23 19:00:21 -07001526
Satish Sampathbf23fe02009-06-15 23:47:56 +01001527 return createIntent(action, dataUri, extraData, query, componentName, actionKey,
1528 actionMsg);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001529 } catch (RuntimeException e ) {
1530 int rowNum;
1531 try { // be really paranoid now
1532 rowNum = c.getPosition();
1533 } catch (RuntimeException e2 ) {
1534 rowNum = -1;
1535 }
1536 Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum +
1537 " returned exception" + e.toString());
Karl Rosaen875d50a2009-04-23 19:00:21 -07001538 return null;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001539 }
1540 }
1541
1542 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001543 * Constructs an intent from the given information and the search dialog state.
1544 *
1545 * @param action Intent action.
1546 * @param data Intent data, or <code>null</code>.
Karl Rosaen875d50a2009-04-23 19:00:21 -07001547 * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
Satish Sampathbf23fe02009-06-15 23:47:56 +01001548 * @param query Intent query, or <code>null</code>.
1549 * @param componentName Data for {@link SearchManager#COMPONENT_NAME_KEY} or <code>null</code>.
Karl Rosaen875d50a2009-04-23 19:00:21 -07001550 * @param actionKey The key code of the action key that was pressed,
1551 * or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
1552 * @param actionMsg The message for the action key that was pressed,
1553 * or <code>null</code> if none.
1554 * @return The intent.
1555 */
Satish Sampathbf23fe02009-06-15 23:47:56 +01001556 private Intent createIntent(String action, Uri data, String extraData, String query,
1557 String componentName, int actionKey, String actionMsg) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001558 // Now build the Intent
1559 Intent intent = new Intent(action);
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +01001560 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Karl Rosaen875d50a2009-04-23 19:00:21 -07001561 if (data != null) {
1562 intent.setData(data);
1563 }
Bjorn Bringert5f806052009-06-24 12:02:26 +01001564 intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
Karl Rosaen875d50a2009-04-23 19:00:21 -07001565 if (query != null) {
1566 intent.putExtra(SearchManager.QUERY, query);
1567 }
1568 if (extraData != null) {
1569 intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
1570 }
Satish Sampathbf23fe02009-06-15 23:47:56 +01001571 if (componentName != null) {
1572 intent.putExtra(SearchManager.COMPONENT_NAME_KEY, componentName);
1573 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001574 if (mAppSearchData != null) {
1575 intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
1576 }
1577 if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
1578 intent.putExtra(SearchManager.ACTION_KEY, actionKey);
1579 intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
1580 }
Bjorn Bringert4899e382009-07-22 10:25:25 +01001581 // Only allow 3rd-party intents from GlobalSearch
1582 if (!mGlobalSearchMode) {
1583 intent.setComponent(mSearchable.getSearchActivity());
1584 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001585 return intent;
1586 }
1587
1588 /**
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001589 * For a given suggestion and a given cursor row, get the action message. If not provided
1590 * by the specific row/column, also check for a single definition (for the action key).
1591 *
1592 * @param c The cursor providing suggestions
1593 * @param actionKey The actionkey record being examined
1594 *
1595 * @return Returns a string, or null if no action key message for this suggestion
1596 */
Karl Rosaen875d50a2009-04-23 19:00:21 -07001597 private static String getActionKeyMessage(Cursor c, SearchableInfo.ActionKeyInfo actionKey) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001598 String result = null;
1599 // check first in the cursor data, for a suggestion-specific message
Bjorn Bringerta9204132009-05-05 14:06:35 +01001600 final String column = actionKey.getSuggestActionMsgColumn();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001601 if (column != null) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001602 result = SuggestionsAdapter.getColumnString(c, column);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001603 }
1604 // If the cursor didn't give us a message, see if there's a single message defined
1605 // for the actionkey (for all suggestions)
1606 if (result == null) {
Bjorn Bringerta9204132009-05-05 14:06:35 +01001607 result = actionKey.getSuggestActionMsg();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001608 }
1609 return result;
1610 }
1611
1612 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001613 * Local subclass for AutoCompleteTextView.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001614 */
1615 public static class SearchAutoComplete extends AutoCompleteTextView {
1616
Karl Rosaen875d50a2009-04-23 19:00:21 -07001617 private int mThreshold;
1618 private SearchDialog mSearchDialog;
1619
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001620 public SearchAutoComplete(Context context) {
Marco Nelissen1746d6f2009-05-14 13:29:24 -07001621 super(context);
Karl Rosaen875d50a2009-04-23 19:00:21 -07001622 mThreshold = getThreshold();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001623 }
1624
1625 public SearchAutoComplete(Context context, AttributeSet attrs) {
1626 super(context, attrs);
Karl Rosaen875d50a2009-04-23 19:00:21 -07001627 mThreshold = getThreshold();
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001628 }
1629
1630 public SearchAutoComplete(Context context, AttributeSet attrs, int defStyle) {
1631 super(context, attrs, defStyle);
Karl Rosaen875d50a2009-04-23 19:00:21 -07001632 mThreshold = getThreshold();
1633 }
1634
1635 private void setSearchDialog(SearchDialog searchDialog) {
1636 mSearchDialog = searchDialog;
1637 }
1638
1639 @Override
1640 public void setThreshold(int threshold) {
1641 super.setThreshold(threshold);
1642 mThreshold = threshold;
1643 }
1644
1645 /**
1646 * Returns true if the text field is empty, or contains only whitespace.
1647 */
1648 private boolean isEmpty() {
1649 return TextUtils.getTrimmedLength(getText()) == 0;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001650 }
Bjorn Bringert8d17f3f2009-06-05 13:22:28 +01001651
Karl Rosaen875d50a2009-04-23 19:00:21 -07001652 /**
1653 * We override this method to avoid replacing the query box text
1654 * when a suggestion is clicked.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001655 */
1656 @Override
Karl Rosaen875d50a2009-04-23 19:00:21 -07001657 protected void replaceText(CharSequence text) {
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001658 }
1659
1660 /**
Mike LeBeau617202a2009-07-06 14:29:25 -07001661 * We override this method to avoid an extra onItemClick being called on the
1662 * drop-down's OnItemClickListener by {@link AutoCompleteTextView#onKeyUp(int, KeyEvent)}
1663 * when an item is clicked with the trackball.
1664 */
1665 @Override
1666 public void performCompletion() {
1667 }
Mike LeBeau1fffbd92009-07-14 15:57:41 -07001668
1669 /**
1670 * We override this method to be sure and show the soft keyboard if appropriate when
1671 * the TextView has focus.
1672 */
1673 @Override
1674 public void onWindowFocusChanged(boolean hasWindowFocus) {
1675 super.onWindowFocusChanged(hasWindowFocus);
1676
1677 if (hasWindowFocus) {
1678 InputMethodManager inputManager = (InputMethodManager)
1679 getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
1680 inputManager.showSoftInput(this, 0);
1681 }
1682 }
1683
Mike LeBeau617202a2009-07-06 14:29:25 -07001684 /**
Karl Rosaen875d50a2009-04-23 19:00:21 -07001685 * We override this method so that we can allow a threshold of zero, which ACTV does not.
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001686 */
1687 @Override
1688 public boolean enoughToFilter() {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001689 return mThreshold <= 0 || super.enoughToFilter();
1690 }
Karl Rosaen98e333f2009-04-28 10:39:09 -07001691
Karl Rosaen875d50a2009-04-23 19:00:21 -07001692 /**
1693 * {@link AutoCompleteTextView#onKeyPreIme(int, KeyEvent)}) dismisses the drop-down on BACK,
1694 * so we must override this method to modify the BACK behavior.
1695 */
1696 @Override
1697 public boolean onKeyPreIme(int keyCode, KeyEvent event) {
Bjorn Bringertee716fa2009-07-16 09:15:37 +01001698 if (mSearchDialog.mSearchable == null) {
1699 return false;
1700 }
Karl Rosaen98e333f2009-04-28 10:39:09 -07001701 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
1702 if (mSearchDialog.backToPreviousComponent()) {
1703 return true;
1704 }
1705 return false; // will dismiss soft keyboard if necessary
1706 }
1707 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001708 }
1709 }
1710
Karl Rosaen875d50a2009-04-23 19:00:21 -07001711 protected boolean handleBackKey(int keyCode, KeyEvent event) {
1712 if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001713 if (backToPreviousComponent()) {
1714 return true;
1715 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001716 cancel();
1717 return true;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001718 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001719 return false;
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001720 }
1721
1722 /**
1723 * Implements OnItemClickListener
1724 */
1725 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001726 if (DBG) Log.d(LOG_TAG, "onItemClick() position " + position);
1727 launchSuggestion(position);
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001728 }
Karl Rosaen875d50a2009-04-23 19:00:21 -07001729
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001730 /**
1731 * Implements OnItemSelectedListener
1732 */
1733 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001734 if (DBG) Log.d(LOG_TAG, "onItemSelected() position " + position);
1735 // A suggestion has been selected, rewrite the query if possible,
1736 // otherwise the restore the original query.
1737 if (REWRITE_QUERIES) {
1738 rewriteQueryFromSuggestion(position);
1739 }
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001740 }
1741
1742 /**
1743 * Implements OnItemSelectedListener
1744 */
1745 public void onNothingSelected(AdapterView<?> parent) {
Karl Rosaen875d50a2009-04-23 19:00:21 -07001746 if (DBG) Log.d(LOG_TAG, "onNothingSelected()");
1747 }
1748
1749 /**
1750 * Query rewriting.
1751 */
1752
1753 private void rewriteQueryFromSuggestion(int position) {
1754 Cursor c = mSuggestionsAdapter.getCursor();
1755 if (c == null) {
1756 return;
1757 }
1758 if (c.moveToPosition(position)) {
1759 // Get the new query from the suggestion.
1760 CharSequence newQuery = mSuggestionsAdapter.convertToString(c);
1761 if (newQuery != null) {
1762 // The suggestion rewrites the query.
1763 if (DBG) Log.d(LOG_TAG, "Rewriting query to '" + newQuery + "'");
1764 // Update the text field, without getting new suggestions.
1765 setQuery(newQuery);
1766 } else {
1767 // The suggestion does not rewrite the query, restore the user's query.
1768 if (DBG) Log.d(LOG_TAG, "Suggestion gives no rewrite, restoring user query.");
1769 restoreUserQuery();
1770 }
1771 } else {
1772 // We got a bad position, restore the user's query.
1773 Log.w(LOG_TAG, "Bad suggestion position: " + position);
1774 restoreUserQuery();
1775 }
1776 }
1777
1778 /**
1779 * Restores the query entered by the user if needed.
1780 */
1781 private void restoreUserQuery() {
1782 if (DBG) Log.d(LOG_TAG, "Restoring query to '" + mUserQuery + "'");
1783 setQuery(mUserQuery);
1784 }
1785
1786 /**
1787 * Sets the text in the query box, without updating the suggestions.
1788 */
1789 private void setQuery(CharSequence query) {
1790 mSearchAutoComplete.setText(query, false);
1791 if (query != null) {
1792 mSearchAutoComplete.setSelection(query.length());
1793 }
1794 }
1795
1796 /**
1797 * Sets the text in the query box, updating the suggestions.
1798 */
1799 private void setUserQuery(String query) {
1800 if (query == null) {
1801 query = "";
1802 }
1803 mUserQuery = query;
1804 mSearchAutoComplete.setText(query);
1805 mSearchAutoComplete.setSelection(query.length());
The Android Open Source Project9066cfe2009-03-03 19:31:44 -08001806 }
1807
1808 /**
1809 * Debugging Support
1810 */
1811
1812 /**
1813 * For debugging only, sample the millisecond clock and log it.
1814 * Uses AtomicLong so we can use in multiple threads
1815 */
1816 private AtomicLong mLastLogTime = new AtomicLong(SystemClock.uptimeMillis());
1817 private void dbgLogTiming(final String caller) {
1818 long millis = SystemClock.uptimeMillis();
1819 long oldTime = mLastLogTime.getAndSet(millis);
1820 long delta = millis - oldTime;
1821 final String report = millis + " (+" + delta + ") ticks for Search keystroke in " + caller;
1822 Log.d(LOG_TAG,report);
1823 }
1824}