blob: 449caf9976eda009b0cba2b41f74501f9f2ac992 [file] [log] [blame]
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -07001/*
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 com.android.launcher;
18
19import android.app.ISearchManager;
20import android.app.SearchManager;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080021import android.content.ActivityNotFoundException;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070022import android.content.Context;
23import android.content.Intent;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080024import android.content.pm.PackageManager;
25import android.content.pm.ResolveInfo;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070026import android.content.res.Resources;
27import android.content.res.Resources.NotFoundException;
28import android.database.Cursor;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080029import android.graphics.Rect;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070030import android.graphics.drawable.Drawable;
31import android.net.Uri;
32import android.os.Bundle;
33import android.os.RemoteException;
34import android.os.ServiceManager;
35import android.server.search.SearchableInfo;
36import android.text.Editable;
37import android.text.TextUtils;
38import android.text.TextWatcher;
39import android.util.AttributeSet;
40import android.util.Log;
41import android.view.KeyEvent;
42import android.view.MotionEvent;
43import android.view.View;
44import android.view.ViewGroup;
45import android.view.View.OnClickListener;
46import android.view.View.OnKeyListener;
47import android.view.View.OnLongClickListener;
48import android.widget.AdapterView;
49import android.widget.AutoCompleteTextView;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070050import android.widget.CursorAdapter;
51import android.widget.Filter;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080052import android.widget.ImageButton;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070053import android.widget.ImageView;
54import android.widget.LinearLayout;
55import android.widget.SimpleCursorAdapter;
56import android.widget.TextView;
57import android.widget.AdapterView.OnItemClickListener;
58import android.widget.AdapterView.OnItemSelectedListener;
59
The Android Open Source Project15a88802009-02-10 15:44:05 -080060import java.util.List;
61
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070062public class Search extends LinearLayout implements OnClickListener, OnKeyListener,
63 OnLongClickListener, TextWatcher, OnItemClickListener, OnItemSelectedListener {
64
65 private final String TAG = "SearchGadget";
66
67 private AutoCompleteTextView mSearchText;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080068 private ImageButton mGoButton;
The Android Open Source Project15a88802009-02-10 15:44:05 -080069 private ImageButton mVoiceButton;
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070070 private OnLongClickListener mLongClickListener;
71
72 // Support for suggestions
73 private SuggestionsAdapter mSuggestionsAdapter;
74 private SearchableInfo mSearchable;
75 private String mSuggestionAction = null;
76 private Uri mSuggestionData = null;
77 private String mSuggestionQuery = null;
78 private int mItemSelected = -1;
The Android Open Source Projectd097a182008-12-17 18:05:58 -080079
The Android Open Source Project15a88802009-02-10 15:44:05 -080080 // For voice searching
81 private Intent mVoiceSearchIntent;
82
The Android Open Source Projectd097a182008-12-17 18:05:58 -080083 private Rect mTempRect = new Rect();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070084
85 /**
86 * Used to inflate the Workspace from XML.
87 *
88 * @param context The application's context.
The Android Open Source Projectd097a182008-12-17 18:05:58 -080089 * @param attrs The attributes set containing the Workspace's customization values.
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070090 */
91 public Search(Context context, AttributeSet attrs) {
92 super(context, attrs);
The Android Open Source Project15a88802009-02-10 15:44:05 -080093
94 mVoiceSearchIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
95 mVoiceSearchIntent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL,
96 android.speech.RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -070097 }
98
99 /**
100 * Implements OnClickListener (for button)
101 */
102 public void onClick(View v) {
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800103 if (v == mGoButton) {
104 query();
The Android Open Source Project15a88802009-02-10 15:44:05 -0800105 } else if (v == mVoiceButton) {
106 try {
107 getContext().startActivity(mVoiceSearchIntent);
108 } catch (ActivityNotFoundException ex) {
109 // Should not happen, since we check the availability of
110 // voice search before showing the button. But just in case...
111 Log.w(TAG, "Could not find voice search activity");
112 }
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800113 }
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700114 }
115
116 private void query() {
117 String query = mSearchText.getText().toString();
118 if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
119 return;
120 }
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800121 Bundle appData = new Bundle();
122 appData.putString(SearchManager.SOURCE, "launcher-widget");
123 sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, 0, null, mSearchable);
The Android Open Source Project946cd912009-01-15 16:12:13 -0800124 clearQuery();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700125 }
126
127 /**
128 * Assemble a search intent and send it.
129 *
130 * This is copied from SearchDialog.
131 *
132 * @param action The intent to send, typically Intent.ACTION_SEARCH
133 * @param data The data for the intent
134 * @param query The user text entered (so far)
135 * @param appData The app data bundle (if supplied)
136 * @param actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will
137 * be sent here. Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
138 * @param actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the
139 * corresponding tag message will be sent here. Pass null for no actionKey message.
140 * @param si Reference to the current SearchableInfo. Passed here so it can be used even after
141 * we've called dismiss(), which attempts to null mSearchable.
142 */
143 private void sendLaunchIntent(final String action, final Uri data, final String query,
144 final Bundle appData, int actionKey, final String actionMsg, final SearchableInfo si) {
145 Intent launcher = new Intent(action);
146 launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
147
148 if (query != null) {
149 launcher.putExtra(SearchManager.QUERY, query);
150 }
151
152 if (data != null) {
153 launcher.setData(data);
154 }
155
156 if (appData != null) {
157 launcher.putExtra(SearchManager.APP_DATA, appData);
158 }
159
160 // add launch info (action key, etc.)
161 if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
162 launcher.putExtra(SearchManager.ACTION_KEY, actionKey);
163 launcher.putExtra(SearchManager.ACTION_MSG, actionMsg);
164 }
165
166 // attempt to enforce security requirement (no 3rd-party intents)
167 if (si != null) {
168 launcher.setComponent(si.mSearchActivity);
169 }
170
171 getContext().startActivity(launcher);
172 }
173
174 /**
175 * Implements TextWatcher (for EditText)
176 */
177 public void beforeTextChanged(CharSequence s, int start, int before, int after) {
178 }
179
180 /**
181 * Implements TextWatcher (for EditText)
182 */
183 public void onTextChanged(CharSequence s, int start, int before, int after) {
184 // enable the button if we have one or more non-space characters
185 boolean enabled = TextUtils.getTrimmedLength(mSearchText.getText()) != 0;
186 mGoButton.setEnabled(enabled);
187 mGoButton.setFocusable(enabled);
188 }
189
190 /**
191 * Implements TextWatcher (for EditText)
192 */
193 public void afterTextChanged(Editable s) {
194 }
195
196 /**
197 * Implements OnKeyListener (for EditText and for button)
198 *
199 * This plays some games with state in order to "soften" the strength of suggestions
200 * presented. Suggestions should not be used unless the user specifically navigates to them
201 * (or clicks them, in which case it's obvious). This is not the way that AutoCompleteTextBox
202 * normally works.
203 */
204 public final boolean onKey(View v, int keyCode, KeyEvent event) {
205 if (v == mSearchText) {
206 boolean searchTrigger = (keyCode == KeyEvent.KEYCODE_ENTER ||
207 keyCode == KeyEvent.KEYCODE_SEARCH ||
208 keyCode == KeyEvent.KEYCODE_DPAD_CENTER);
209 if (event.getAction() == KeyEvent.ACTION_UP) {
210// Log.d(TAG, "onKey() ACTION_UP isPopupShowing:" + mSearchText.isPopupShowing());
211 if (!mSearchText.isPopupShowing()) {
212 if (searchTrigger) {
213 query();
214 return true;
215 }
216 }
217 } else {
218// Log.d(TAG, "onKey() ACTION_DOWN isPopupShowing:" + mSearchText.isPopupShowing() +
219// " mItemSelected="+ mItemSelected);
220 if (searchTrigger && mItemSelected < 0) {
221 query();
222 return true;
223 }
224 }
The Android Open Source Project15a88802009-02-10 15:44:05 -0800225 } else if (v == mGoButton || v == mVoiceButton) {
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700226 boolean handled = false;
227 if (!event.isSystem() &&
228 (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
229 (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
230 (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
231 (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
232 (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
233 if (mSearchText.requestFocus()) {
234 handled = mSearchText.dispatchKeyEvent(event);
235 }
236 }
237 return handled;
238 }
239
240 return false;
241 }
242
243 @Override
244 public void setOnLongClickListener(OnLongClickListener l) {
245 super.setOnLongClickListener(l);
246 mLongClickListener = l;
247 }
248
249 /**
250 * Implements OnLongClickListener (for button)
251 */
252 public boolean onLongClick(View v) {
253 // Pretend that a long press on a child view is a long press on the search widget
254 if (mLongClickListener != null) {
255 return mLongClickListener.onLongClick(this);
256 }
257 return false;
258 }
259
260 @Override
261 public boolean onInterceptTouchEvent(MotionEvent ev) {
The Android Open Source Project15a88802009-02-10 15:44:05 -0800262 // Request focus unless the user tapped on the voice search button
263 final int x = (int) ev.getX();
264 final int y = (int) ev.getY();
265 final Rect frame = mTempRect;
266 mVoiceButton.getHitRect(frame);
267 if (!frame.contains(x, y)) {
268 requestFocusFromTouch();
269 }
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700270 return super.onInterceptTouchEvent(ev);
271 }
272
273 /**
274 * In order to keep things simple, the external trigger will clear the query just before
275 * focusing, so as to give you a fresh query. This way we eliminate any sources of
276 * accidental query launching.
277 */
278 public void clearQuery() {
279 mSearchText.setText(null);
280 }
281
282 @Override
283 protected void onFinishInflate() {
284 super.onFinishInflate();
285
286 mSearchText = (AutoCompleteTextView) findViewById(R.id.input);
287 // TODO: This can be confusing when the user taps the text field to give the focus
288 // (it is not necessary but I ran into this issue several times myself)
289 // mTitleInput.setOnClickListener(this);
290 mSearchText.setOnKeyListener(this);
291 mSearchText.addTextChangedListener(this);
292
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800293 mGoButton = (ImageButton) findViewById(R.id.search_go_btn);
The Android Open Source Project15a88802009-02-10 15:44:05 -0800294 mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700295 mGoButton.setOnClickListener(this);
The Android Open Source Project15a88802009-02-10 15:44:05 -0800296 mVoiceButton.setOnClickListener(this);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700297 mGoButton.setOnKeyListener(this);
The Android Open Source Project15a88802009-02-10 15:44:05 -0800298 mVoiceButton.setOnKeyListener(this);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700299
300 mSearchText.setOnLongClickListener(this);
301 mGoButton.setOnLongClickListener(this);
The Android Open Source Project15a88802009-02-10 15:44:05 -0800302 mVoiceButton.setOnLongClickListener(this);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700303
304 // disable the button since we start out w/empty input
305 mGoButton.setEnabled(false);
306 mGoButton.setFocusable(false);
307
The Android Open Source Project15a88802009-02-10 15:44:05 -0800308 configureSearchableInfo();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700309 configureSuggestions();
The Android Open Source Project15a88802009-02-10 15:44:05 -0800310 configureVoiceSearchButton();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700311 }
312
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700313 /**
The Android Open Source Project15a88802009-02-10 15:44:05 -0800314 * Read the searchable info from the search manager
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700315 */
The Android Open Source Project15a88802009-02-10 15:44:05 -0800316 private void configureSearchableInfo() {
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700317 ISearchManager sms;
318 SearchableInfo searchable;
319 sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
320 try {
321 // TODO null isn't the published use of this API, but it works when global=true
322 // TODO better implementation: defer all of this, let Home set it up
323 searchable = sms.getSearchableInfo(null, true);
324 } catch (RemoteException e) {
325 searchable = null;
326 }
327 if (searchable == null) {
328 // no suggestions so just get out (no need to continue)
329 return;
330 }
331 mSearchable = searchable;
The Android Open Source Project15a88802009-02-10 15:44:05 -0800332 }
333
334 /**
335 * If appropriate & available, configure voice search
336 *
337 * Note: Because the home screen search widget is always web search, we only check for
338 * getVoiceSearchLaunchWebSearch() modes. We don't support the alternate form of app-specific
339 * voice search.
340 */
341 private void configureVoiceSearchButton() {
342 boolean voiceSearchVisible = false;
343 if (mSearchable.getVoiceSearchEnabled() && mSearchable.getVoiceSearchLaunchWebSearch()) {
344 // Enable the voice search button if there is an activity that can handle it
345 PackageManager pm = getContext().getPackageManager();
346 List<ResolveInfo> list = pm.queryIntentActivities(mVoiceSearchIntent,
347 PackageManager.MATCH_DEFAULT_ONLY);
348 voiceSearchVisible = list.size() > 0;
349 }
350
351 // finally, set visible state of voice search button, as appropriate
352 mVoiceButton.setVisibility(voiceSearchVisible ? View.VISIBLE : View.GONE);
353 }
354
355 /** The rest of the class deals with providing search suggestions */
356
357 /**
358 * Set up the suggestions provider mechanism
359 */
360 private void configureSuggestions() {
361 // get SearchableInfo
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700362
363 mSearchText.setOnItemClickListener(this);
364 mSearchText.setOnItemSelectedListener(this);
365
366 // attach the suggestions adapter
367 mSuggestionsAdapter = new SuggestionsAdapter(mContext,
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800368 com.android.internal.R.layout.search_dropdown_item_2line, null,
369 SuggestionsAdapter.TWO_LINE_FROM, SuggestionsAdapter.TWO_LINE_TO, mSearchable);
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700370 mSearchText.setAdapter(mSuggestionsAdapter);
371 }
372
373 /**
374 * Implements OnItemClickListener
375 */
376 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
377// Log.d(TAG, "onItemClick() position " + position);
378 launchSuggestion(mSuggestionsAdapter, position);
379 }
380
381 /**
382 * Implements OnItemSelectedListener
383 */
384 public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
385// Log.d(TAG, "onItemSelected() position " + position);
386 mItemSelected = position;
387 }
388
389 /**
390 * Implements OnItemSelectedListener
391 */
392 public void onNothingSelected(AdapterView<?> parent) {
393// Log.d(TAG, "onNothingSelected()");
394 mItemSelected = -1;
395 }
396
397 /**
398 * Code to launch a suggestion query.
399 *
400 * This is copied from SearchDialog.
401 *
402 * @param ca The CursorAdapter containing the suggestions
403 * @param position The suggestion we'll be launching from
404 *
405 * @return Returns true if a successful launch, false if could not (e.g. bad position)
406 */
407 private boolean launchSuggestion(CursorAdapter ca, int position) {
408 if (ca != null) {
409 Cursor c = ca.getCursor();
410 if ((c != null) && c.moveToPosition(position)) {
411 setupSuggestionIntent(c, mSearchable);
412
413 SearchableInfo si = mSearchable;
414 String suggestionAction = mSuggestionAction;
415 Uri suggestionData = mSuggestionData;
416 String suggestionQuery = mSuggestionQuery;
417 sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, null,
418 KeyEvent.KEYCODE_UNKNOWN, null, si);
The Android Open Source Project946cd912009-01-15 16:12:13 -0800419 clearQuery();
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700420 return true;
421 }
422 }
423 return false;
424 }
425
426 /**
427 * When a particular suggestion has been selected, perform the various lookups required
428 * to use the suggestion. This includes checking the cursor for suggestion-specific data,
429 * and/or falling back to the XML for defaults; It also creates REST style Uri data when
430 * the suggestion includes a data id.
431 *
432 * NOTE: Return values are in member variables mSuggestionAction, mSuggestionData and
433 * mSuggestionQuery.
434 *
435 * This is copied from SearchDialog.
436 *
437 * @param c The suggestions cursor, moved to the row of the user's selection
438 * @param si The searchable activity's info record
439 */
440 void setupSuggestionIntent(Cursor c, SearchableInfo si) {
441 try {
442 // use specific action if supplied, or default action if supplied, or fixed default
443 mSuggestionAction = null;
444 int column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
445 if (column >= 0) {
446 final String action = c.getString(column);
447 if (action != null) {
448 mSuggestionAction = action;
449 }
450 }
451 if (mSuggestionAction == null) {
452 mSuggestionAction = si.getSuggestIntentAction();
453 }
454 if (mSuggestionAction == null) {
455 mSuggestionAction = Intent.ACTION_SEARCH;
456 }
457
458 // use specific data if supplied, or default data if supplied
459 String data = null;
460 column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
461 if (column >= 0) {
462 final String rowData = c.getString(column);
463 if (rowData != null) {
464 data = rowData;
465 }
466 }
467 if (data == null) {
468 data = si.getSuggestIntentData();
469 }
470
471 // then, if an ID was provided, append it.
472 if (data != null) {
473 column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
474 if (column >= 0) {
475 final String id = c.getString(column);
476 if (id != null) {
477 data = data + "/" + Uri.encode(id);
478 }
479 }
480 }
481 mSuggestionData = (data == null) ? null : Uri.parse(data);
482
483 mSuggestionQuery = null;
484 column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
485 if (column >= 0) {
486 final String query = c.getString(column);
487 if (query != null) {
488 mSuggestionQuery = query;
489 }
490 }
491 } catch (RuntimeException e ) {
492 int rowNum;
493 try { // be really paranoid now
494 rowNum = c.getPosition();
495 } catch (RuntimeException e2 ) {
496 rowNum = -1;
497 }
498 Log.w(TAG, "Search Suggestions cursor at row " + rowNum +
499 " returned exception" + e.toString());
500 }
501 }
502
503 /**
504 * This class provides the filtering-based interface to suggestions providers.
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800505 * It is hardwired in a couple of places to support GoogleSearch - for example, it supports
506 * two-line suggestions, but it does not support icons.
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700507 */
508 private static class SuggestionsAdapter extends SimpleCursorAdapter {
The Android Open Source Projectd097a182008-12-17 18:05:58 -0800509 public final static String[] TWO_LINE_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1,
510 SearchManager.SUGGEST_COLUMN_TEXT_2 };
511 public final static int[] TWO_LINE_TO = {com.android.internal.R.id.text1,
512 com.android.internal.R.id.text2};
The Android Open Source Projectc8f00b62008-10-21 07:00:00 -0700513
514 private final String TAG = "SuggestionsAdapter";
515
516 Filter mFilter;
517 SearchableInfo mSearchable;
518 private Resources mProviderResources;
519 String[] mFromStrings;
520
521 public SuggestionsAdapter(Context context, int layout, Cursor c,
522 String[] from, int[] to, SearchableInfo searchable) {
523 super(context, layout, c, from, to);
524 mFromStrings = from;
525 mSearchable = searchable;
526
527 // set up provider resources (gives us icons, etc.)
528 Context activityContext = mSearchable.getActivityContext(mContext);
529 Context providerContext = mSearchable.getProviderContext(mContext, activityContext);
530 mProviderResources = providerContext.getResources();
531 }
532
533 /**
534 * Use the search suggestions provider to obtain a live cursor. This will be called
535 * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions).
536 * The results will be processed in the UI thread and changeCursor() will be called.
537 */
538 @Override
539 public Cursor runQueryOnBackgroundThread(CharSequence constraint) {
540 String query = (constraint == null) ? "" : constraint.toString();
541 return getSuggestions(mSearchable, query);
542 }
543
544 /**
545 * Overriding this allows us to write the selected query back into the box.
546 * NOTE: This is a vastly simplified version of SearchDialog.jamQuery() and does
547 * not universally support the search API. But it is sufficient for Google Search.
548 */
549 @Override
550 public CharSequence convertToString(Cursor cursor) {
551 CharSequence result = null;
552 if (cursor != null) {
553 int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
554 if (column >= 0) {
555 final String query = cursor.getString(column);
556 if (query != null) {
557 result = query;
558 }
559 }
560 }
561 return result;
562 }
563
564 /**
565 * Get the query cursor for the search suggestions.
566 *
567 * TODO this is functionally identical to the version in SearchDialog.java. Perhaps it
568 * could be hoisted into SearchableInfo or some other shared spot.
569 *
570 * @param query The search text entered (so far)
571 * @return Returns a cursor with suggestions, or null if no suggestions
572 */
573 private Cursor getSuggestions(final SearchableInfo searchable, final String query) {
574 Cursor cursor = null;
575 if (searchable.getSuggestAuthority() != null) {
576 try {
577 StringBuilder uriStr = new StringBuilder("content://");
578 uriStr.append(searchable.getSuggestAuthority());
579
580 // if content path provided, insert it now
581 final String contentPath = searchable.getSuggestPath();
582 if (contentPath != null) {
583 uriStr.append('/');
584 uriStr.append(contentPath);
585 }
586
587 // append standard suggestion query path
588 uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY);
589
590 // inject query, either as selection args or inline
591 String[] selArgs = null;
592 if (searchable.getSuggestSelection() != null) { // use selection if provided
593 selArgs = new String[] {query};
594 } else {
595 uriStr.append('/'); // no sel, use REST pattern
596 uriStr.append(Uri.encode(query));
597 }
598
599 // finally, make the query
600 cursor = mContext.getContentResolver().query(
601 Uri.parse(uriStr.toString()), null,
602 searchable.getSuggestSelection(), selArgs,
603 null);
604 } catch (RuntimeException e) {
605 Log.w(TAG, "Search Suggestions query returned exception " + e.toString());
606 cursor = null;
607 }
608 }
609
610 return cursor;
611 }
612
613 /**
614 * Overriding this allows us to affect the way that an icon is loaded. Specifically,
615 * we can be more controlling about the resource path (and allow icons to come from other
616 * packages).
617 *
618 * TODO: This is 100% identical to the version in SearchDialog.java
619 *
620 * @param v ImageView to receive an image
621 * @param value the value retrieved from the cursor
622 */
623 @Override
624 public void setViewImage(ImageView v, String value) {
625 int resID;
626 Drawable img = null;
627
628 try {
629 resID = Integer.parseInt(value);
630 if (resID != 0) {
631 img = mProviderResources.getDrawable(resID);
632 }
633 } catch (NumberFormatException nfe) {
634 // img = null;
635 } catch (NotFoundException e2) {
636 // img = null;
637 }
638
639 // finally, set the image to whatever we've gotten
640 v.setImageDrawable(img);
641 }
642
643 /**
644 * This method is overridden purely to provide a bit of protection against
645 * flaky content providers.
646 *
647 * TODO: This is 100% identical to the version in SearchDialog.java
648 *
649 * @see android.widget.ListAdapter#getView(int, View, ViewGroup)
650 */
651 @Override
652 public View getView(int position, View convertView, ViewGroup parent) {
653 try {
654 return super.getView(position, convertView, parent);
655 } catch (RuntimeException e) {
656 Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString());
657 // what can I return here?
658 View v = newView(mContext, mCursor, parent);
659 if (v != null) {
660 TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1);
661 tv.setText(e.toString());
662 }
663 return v;
664 }
665 }
666
667 }
668}