• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 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 
17 package android.app;
18 
19 import android.content.ActivityNotFoundException;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.PackageManager;
26 import android.content.pm.ResolveInfo;
27 import android.database.Cursor;
28 import android.graphics.Rect;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.UserHandle;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.util.Slog;
38 import android.view.KeyEvent;
39 
40 import java.util.List;
41 
42 /**
43  * This class provides access to the system search services.
44  *
45  * <p>In practice, you won't interact with this class directly, as search
46  * services are provided through methods in {@link android.app.Activity Activity}
47  * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
48  * {@link android.content.Intent Intent}.
49  * If you do require direct access to the SearchManager, do not instantiate
50  * this class directly. Instead, retrieve it through
51  * {@link android.content.Context#getSystemService
52  * context.getSystemService(Context.SEARCH_SERVICE)}.
53  *
54  * <div class="special reference">
55  * <h3>Developer Guides</h3>
56  * <p>For more information about using the search dialog and adding search
57  * suggestions in your application, read the
58  * <a href="{@docRoot}guide/topics/search/index.html">Search</a> developer guide.</p>
59  * </div>
60  */
61 public class SearchManager
62         implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
63 {
64 
65     private static final boolean DBG = false;
66     private static final String TAG = "SearchManager";
67 
68     /**
69      * This is a shortcut definition for the default menu key to use for invoking search.
70      *
71      * See Menu.Item.setAlphabeticShortcut() for more information.
72      */
73     public final static char MENU_KEY = 's';
74 
75     /**
76      * This is a shortcut definition for the default menu key to use for invoking search.
77      *
78      * See Menu.Item.setAlphabeticShortcut() for more information.
79      */
80     public final static int MENU_KEYCODE = KeyEvent.KEYCODE_S;
81 
82     /**
83      * Intent extra data key: Use this key with
84      * {@link android.content.Intent#getStringExtra
85      *  content.Intent.getStringExtra()}
86      * to obtain the query string from Intent.ACTION_SEARCH.
87      */
88     public final static String QUERY = "query";
89 
90     /**
91      * Intent extra data key: Use this key with
92      * {@link android.content.Intent#getStringExtra
93      *  content.Intent.getStringExtra()}
94      * to obtain the query string typed in by the user.
95      * This may be different from the value of {@link #QUERY}
96      * if the intent is the result of selecting a suggestion.
97      * In that case, {@link #QUERY} will contain the value of
98      * {@link #SUGGEST_COLUMN_QUERY} for the suggestion, and
99      * {@link #USER_QUERY} will contain the string typed by the
100      * user.
101      */
102     public final static String USER_QUERY = "user_query";
103 
104     /**
105      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
106      * {@link android.content.Intent#getBundleExtra
107      *  content.Intent.getBundleExtra()}
108      * to obtain any additional app-specific data that was inserted by the
109      * activity that launched the search.
110      */
111     public final static String APP_DATA = "app_data";
112 
113     /**
114      * Intent extra data key: Use {@link android.content.Intent#getBundleExtra
115      * content.Intent.getBundleExtra(SEARCH_MODE)} to get the search mode used
116      * to launch the intent.
117      * The only current value for this is {@link #MODE_GLOBAL_SEARCH_SUGGESTION}.
118      *
119      * @hide
120      */
121     public final static String SEARCH_MODE = "search_mode";
122 
123     /**
124      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
125      * {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()}
126      * to obtain the keycode that the user used to trigger this query.  It will be zero if the
127      * user simply pressed the "GO" button on the search UI.  This is primarily used in conjunction
128      * with the keycode attribute in the actionkey element of your searchable.xml configuration
129      * file.
130      */
131     public final static String ACTION_KEY = "action_key";
132 
133     /**
134      * Intent extra data key: This key will be used for the extra populated by the
135      * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column.
136      */
137     public final static String EXTRA_DATA_KEY = "intent_extra_data_key";
138 
139     /**
140      * Boolean extra data key for {@link #INTENT_ACTION_GLOBAL_SEARCH} intents. If {@code true},
141      * the initial query should be selected when the global search activity is started, so
142      * that the user can easily replace it with another query.
143      */
144     public final static String EXTRA_SELECT_QUERY = "select_query";
145 
146     /**
147      * Boolean extra data key for {@link Intent#ACTION_WEB_SEARCH} intents.  If {@code true},
148      * this search should open a new browser window, rather than using an existing one.
149      */
150     public final static String EXTRA_NEW_SEARCH = "new_search";
151 
152     /**
153      * Extra data key for {@link Intent#ACTION_WEB_SEARCH}. If set, the value must be a
154      * {@link PendingIntent}. The search activity handling the {@link Intent#ACTION_WEB_SEARCH}
155      * intent will fill in and launch the pending intent. The data URI will be filled in with an
156      * http or https URI, and {@link android.provider.Browser#EXTRA_HEADERS} may be filled in.
157      */
158     public static final String EXTRA_WEB_SEARCH_PENDINGINTENT = "web_search_pendingintent";
159 
160     /**
161      * Boolean extra data key for a suggestion provider to return in {@link Cursor#getExtras} to
162      * indicate that the search is not complete yet. This can be used by the search UI
163      * to indicate that a search is in progress. The suggestion provider can return partial results
164      * this way and send a change notification on the cursor when more results are available.
165      */
166     public final static String CURSOR_EXTRA_KEY_IN_PROGRESS = "in_progress";
167 
168     /**
169      * Intent extra data key: Use this key with Intent.ACTION_SEARCH and
170      * {@link android.content.Intent#getStringExtra content.Intent.getStringExtra()}
171      * to obtain the action message that was defined for a particular search action key and/or
172      * suggestion.  It will be null if the search was launched by typing "enter", touched the the
173      * "GO" button, or other means not involving any action key.
174      */
175     public final static String ACTION_MSG = "action_msg";
176 
177     /**
178      * Flag to specify that the entry can be used for query refinement, i.e., the query text
179      * in the search field can be replaced with the text in this entry, when a query refinement
180      * icon is clicked. The suggestion list should show such a clickable icon beside the entry.
181      * <p>Use this flag as a bit-field for {@link #SUGGEST_COLUMN_FLAGS}.
182      */
183     public final static int FLAG_QUERY_REFINEMENT = 1 << 0;
184 
185     /**
186      * Uri path for queried suggestions data.  This is the path that the search manager
187      * will use when querying your content provider for suggestions data based on user input
188      * (e.g. looking for partial matches).
189      * Typically you'll use this with a URI matcher.
190      */
191     public final static String SUGGEST_URI_PATH_QUERY = "search_suggest_query";
192 
193     /**
194      * MIME type for suggestions data.  You'll use this in your suggestions content provider
195      * in the getType() function.
196      */
197     public final static String SUGGEST_MIME_TYPE =
198             "vnd.android.cursor.dir/vnd.android.search.suggest";
199 
200     /**
201      * Uri path for shortcut validation.  This is the path that the search manager will use when
202      * querying your content provider to refresh a shortcutted suggestion result and to check if it
203      * is still valid.  When asked, a source may return an up to date result, or no result.  No
204      * result indicates the shortcut refers to a no longer valid sugggestion.
205      *
206      * @see #SUGGEST_COLUMN_SHORTCUT_ID
207      */
208     public final static String SUGGEST_URI_PATH_SHORTCUT = "search_suggest_shortcut";
209 
210     /**
211      * MIME type for shortcut validation.  You'll use this in your suggestions content provider
212      * in the getType() function.
213      */
214     public final static String SHORTCUT_MIME_TYPE =
215             "vnd.android.cursor.item/vnd.android.search.suggest";
216 
217     /**
218      * Column name for suggestions cursor.  <i>Unused - can be null or column can be omitted.</i>
219      */
220     public final static String SUGGEST_COLUMN_FORMAT = "suggest_format";
221     /**
222      * Column name for suggestions cursor.  <i>Required.</i>  This is the primary line of text that
223      * will be presented to the user as the suggestion.
224      */
225     public final static String SUGGEST_COLUMN_TEXT_1 = "suggest_text_1";
226     /**
227      * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
228      *  then all suggestions will be provided in a two-line format.  The second line of text is in
229      *  a much smaller appearance.
230      */
231     public final static String SUGGEST_COLUMN_TEXT_2 = "suggest_text_2";
232 
233     /**
234      * Column name for suggestions cursor.  <i>Optional.</i> This is a URL that will be shown
235      * as the second line of text instead of {@link #SUGGEST_COLUMN_TEXT_2}. This is a separate
236      * column so that the search UI knows to display the text as a URL, e.g. by using a different
237      * color. If this column is absent, or has the value {@code null},
238      * {@link #SUGGEST_COLUMN_TEXT_2} will be used instead.
239      */
240     public final static String SUGGEST_COLUMN_TEXT_2_URL = "suggest_text_2_url";
241 
242     /**
243      * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
244      *  then all suggestions will be provided in a format that includes space for two small icons,
245      *  one at the left and one at the right of each suggestion.  The data in the column must
246      *  be a resource ID of a drawable, or a URI in one of the following formats:
247      *
248      * <ul>
249      * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
250      * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
251      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
252      * </ul>
253      *
254      * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
255      * for more information on these schemes.
256      */
257     public final static String SUGGEST_COLUMN_ICON_1 = "suggest_icon_1";
258     /**
259      * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
260      *  then all suggestions will be provided in a format that includes space for two small icons,
261      *  one at the left and one at the right of each suggestion.  The data in the column must
262      *  be a resource ID of a drawable, or a URI in one of the following formats:
263      *
264      * <ul>
265      * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
266      * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
267      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
268      * </ul>
269      *
270      * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
271      * for more information on these schemes.
272      */
273     public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
274     /**
275      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
276      * this element exists at the given row, this is the action that will be used when
277      * forming the suggestion's intent.  If the element is not provided, the action will be taken
278      * from the android:searchSuggestIntentAction field in your XML metadata.  <i>At least one of
279      * these must be present for the suggestion to generate an intent.</i>  Note:  If your action is
280      * the same for all suggestions, it is more efficient to specify it using XML metadata and omit
281      * it from the cursor.
282      */
283     public final static String SUGGEST_COLUMN_INTENT_ACTION = "suggest_intent_action";
284     /**
285      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
286      * this element exists at the given row, this is the data that will be used when
287      * forming the suggestion's intent.  If the element is not provided, the data will be taken
288      * from the android:searchSuggestIntentData field in your XML metadata.  If neither source
289      * is provided, the Intent's data field will be null.  Note:  If your data is
290      * the same for all suggestions, or can be described using a constant part and a specific ID,
291      * it is more efficient to specify it using XML metadata and omit it from the cursor.
292      */
293     public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data";
294     /**
295      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
296      * this element exists at the given row, this is the data that will be used when
297      * forming the suggestion's intent. If not provided, the Intent's extra data field will be null.
298      * This column allows suggestions to provide additional arbitrary data which will be included as
299      * an extra under the key {@link #EXTRA_DATA_KEY}.
300      */
301     public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
302     /**
303      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
304      * this element exists at the given row, then "/" and this value will be appended to the data
305      * field in the Intent.  This should only be used if the data field has already been set to an
306      * appropriate base string.
307      */
308     public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id";
309     /**
310      * Column name for suggestions cursor.  <i>Required if action is
311      * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i>  If this
312      * column exists <i>and</i> this element exists at the given row, this is the data that will be
313      * used when forming the suggestion's query.
314      */
315     public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query";
316 
317     /**
318      * Column name for suggestions cursor. <i>Optional.</i>  This column is used to indicate whether
319      * a search suggestion should be stored as a shortcut, and whether it should be refreshed.  If
320      * missing, the result will be stored as a shortcut and never validated.  If set to
321      * {@link #SUGGEST_NEVER_MAKE_SHORTCUT}, the result will not be stored as a shortcut.
322      * Otherwise, the shortcut id will be used to check back for an up to date suggestion using
323      * {@link #SUGGEST_URI_PATH_SHORTCUT}.
324      */
325     public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id";
326 
327     /**
328      * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
329      * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion
330      * is being refreshed.
331      */
332     public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING =
333             "suggest_spinner_while_refreshing";
334 
335     /**
336      * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
337      * additional flags per item. Multiple flags can be specified.
338      * <p>
339      * Must be one of {@link #FLAG_QUERY_REFINEMENT} or 0 to indicate no flags.
340      * </p>
341      */
342     public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags";
343 
344     /**
345      * Column name for suggestions cursor. <i>Optional.</i> This column may be
346      * used to specify the time in {@link System#currentTimeMillis
347      * System.currentTImeMillis()} (wall time in UTC) when an item was last
348      * accessed within the results-providing application. If set, this may be
349      * used to show more-recently-used items first.
350      */
351     public final static String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint";
352 
353     /**
354      * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion
355      * should not be stored as a shortcut in global search.
356      */
357     public final static String SUGGEST_NEVER_MAKE_SHORTCUT = "_-1";
358 
359     /**
360      * Query parameter added to suggestion queries to limit the number of suggestions returned.
361      * This limit is only advisory and suggestion providers may chose to ignore it.
362      */
363     public final static String SUGGEST_PARAMETER_LIMIT = "limit";
364 
365     /**
366      * Intent action for starting the global search activity.
367      * The global search provider should handle this intent.
368      *
369      * Supported extra data keys: {@link #QUERY},
370      * {@link #EXTRA_SELECT_QUERY},
371      * {@link #APP_DATA}.
372      */
373     public final static String INTENT_ACTION_GLOBAL_SEARCH
374             = "android.search.action.GLOBAL_SEARCH";
375 
376     /**
377      * Intent action for starting the global search settings activity.
378      * The global search provider should handle this intent.
379      */
380     public final static String INTENT_ACTION_SEARCH_SETTINGS
381             = "android.search.action.SEARCH_SETTINGS";
382 
383     /**
384      * Intent action for starting a web search provider's settings activity.
385      * Web search providers should handle this intent if they have provider-specific
386      * settings to implement.
387      */
388     public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS
389             = "android.search.action.WEB_SEARCH_SETTINGS";
390 
391     /**
392      * Intent action broadcasted to inform that the searchables list or default have changed.
393      * Components should handle this intent if they cache any searchable data and wish to stay
394      * up to date on changes.
395      */
396     public final static String INTENT_ACTION_SEARCHABLES_CHANGED
397             = "android.search.action.SEARCHABLES_CHANGED";
398 
399     /**
400      * Intent action to be broadcast to inform that the global search provider
401      * has changed.
402      */
403     public final static String INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED
404             = "android.search.action.GLOBAL_SEARCH_ACTIVITY_CHANGED";
405 
406     /**
407      * Intent action broadcasted to inform that the search settings have changed in some way.
408      * Either searchables have been enabled or disabled, or a different web search provider
409      * has been chosen.
410      */
411     public final static String INTENT_ACTION_SEARCH_SETTINGS_CHANGED
412             = "android.search.action.SETTINGS_CHANGED";
413 
414     /**
415      * This means that context is voice, and therefore the SearchDialog should
416      * continue showing the microphone until the user indicates that he/she does
417      * not want to re-speak (e.g. by typing).
418      *
419      * @hide
420      */
421     public final static String CONTEXT_IS_VOICE = "android.search.CONTEXT_IS_VOICE";
422 
423     /**
424      * This means that the voice icon should not be shown at all, because the
425      * current search engine does not support voice search.
426      * @hide
427      */
428     public final static String DISABLE_VOICE_SEARCH
429             = "android.search.DISABLE_VOICE_SEARCH";
430 
431     /**
432      * Reference to the shared system search service.
433      */
434     private static ISearchManager mService;
435 
436     private final Context mContext;
437 
438     /**
439      * The package associated with this seach manager.
440      */
441     private String mAssociatedPackage;
442 
443     // package private since they are used by the inner class SearchManagerCallback
444     /* package */ final Handler mHandler;
445     /* package */ OnDismissListener mDismissListener = null;
446     /* package */ OnCancelListener mCancelListener = null;
447 
448     private SearchDialog mSearchDialog;
449 
SearchManager(Context context, Handler handler)450     /*package*/ SearchManager(Context context, Handler handler)  {
451         mContext = context;
452         mHandler = handler;
453         mService = ISearchManager.Stub.asInterface(
454                 ServiceManager.getService(Context.SEARCH_SERVICE));
455     }
456 
457     /**
458      * Launch search UI.
459      *
460      * <p>The search manager will open a search widget in an overlapping
461      * window, and the underlying activity may be obscured.  The search
462      * entry state will remain in effect until one of the following events:
463      * <ul>
464      * <li>The user completes the search.  In most cases this will launch
465      * a search intent.</li>
466      * <li>The user uses the back, home, or other keys to exit the search.</li>
467      * <li>The application calls the {@link #stopSearch}
468      * method, which will hide the search window and return focus to the
469      * activity from which it was launched.</li>
470      *
471      * <p>Most applications will <i>not</i> use this interface to invoke search.
472      * The primary method for invoking search is to call
473      * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or
474      * {@link android.app.Activity#startSearch Activity.startSearch()}.
475      *
476      * @param initialQuery A search string can be pre-entered here, but this
477      * is typically null or empty.
478      * @param selectInitialQuery If true, the intial query will be preselected, which means that
479      * any further typing will replace it.  This is useful for cases where an entire pre-formed
480      * query is being inserted.  If false, the selection point will be placed at the end of the
481      * inserted query.  This is useful when the inserted query is text that the user entered,
482      * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
483      * if initialQuery is a non-empty string.</i>
484      * @param launchActivity The ComponentName of the activity that has launched this search.
485      * @param appSearchData An application can insert application-specific
486      * context here, in order to improve quality or specificity of its own
487      * searches.  This data will be returned with SEARCH intent(s).  Null if
488      * no extra data is required.
489      * @param globalSearch If false, this will only launch the search that has been specifically
490      * defined by the application (which is usually defined as a local search).  If no default
491      * search is defined in the current application or activity, global search will be launched.
492      * If true, this will always launch a platform-global (e.g. web-based) search instead.
493      *
494      * @see android.app.Activity#onSearchRequested
495      * @see #stopSearch
496      */
startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch)497     public void startSearch(String initialQuery,
498                             boolean selectInitialQuery,
499                             ComponentName launchActivity,
500                             Bundle appSearchData,
501                             boolean globalSearch) {
502         startSearch(initialQuery, selectInitialQuery, launchActivity,
503                 appSearchData, globalSearch, null);
504     }
505 
506     /**
507      * As {@link #startSearch(String, boolean, ComponentName, Bundle, boolean)} but including
508      * source bounds for the global search intent.
509      *
510      * @hide
511      */
startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch, Rect sourceBounds)512     public void startSearch(String initialQuery,
513                             boolean selectInitialQuery,
514                             ComponentName launchActivity,
515                             Bundle appSearchData,
516                             boolean globalSearch,
517                             Rect sourceBounds) {
518         if (globalSearch) {
519             startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds);
520             return;
521         }
522 
523         ensureSearchDialog();
524 
525         mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData);
526     }
527 
ensureSearchDialog()528     private void ensureSearchDialog() {
529         if (mSearchDialog == null) {
530             mSearchDialog = new SearchDialog(mContext, this);
531             mSearchDialog.setOnCancelListener(this);
532             mSearchDialog.setOnDismissListener(this);
533         }
534     }
535 
536     /**
537      * Starts the global search activity.
538      */
startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds)539     /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
540             Bundle appSearchData, Rect sourceBounds) {
541         ComponentName globalSearchActivity = getGlobalSearchActivity();
542         if (globalSearchActivity == null) {
543             Log.w(TAG, "No global search activity found.");
544             return;
545         }
546         Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
547         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
548         intent.setComponent(globalSearchActivity);
549         // Make sure that we have a Bundle to put source in
550         if (appSearchData == null) {
551             appSearchData = new Bundle();
552         } else {
553             appSearchData = new Bundle(appSearchData);
554         }
555         // Set source to package name of app that starts global search, if not set already.
556         if (!appSearchData.containsKey("source")) {
557             appSearchData.putString("source", mContext.getPackageName());
558         }
559         intent.putExtra(APP_DATA, appSearchData);
560         if (!TextUtils.isEmpty(initialQuery)) {
561             intent.putExtra(QUERY, initialQuery);
562         }
563         if (selectInitialQuery) {
564             intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery);
565         }
566         intent.setSourceBounds(sourceBounds);
567         try {
568             if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0));
569             mContext.startActivity(intent);
570         } catch (ActivityNotFoundException ex) {
571             Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
572         }
573     }
574 
575     /**
576      * Returns a list of installed apps that handle the global search
577      * intent.
578      *
579      * @hide
580      */
getGlobalSearchActivities()581     public List<ResolveInfo> getGlobalSearchActivities() {
582         try {
583             return mService.getGlobalSearchActivities();
584         } catch (RemoteException ex) {
585             Log.e(TAG, "getGlobalSearchActivities() failed: " + ex);
586             return null;
587         }
588     }
589 
590     /**
591      * Gets the name of the global search activity.
592      */
getGlobalSearchActivity()593     public ComponentName getGlobalSearchActivity() {
594         try {
595             return mService.getGlobalSearchActivity();
596         } catch (RemoteException ex) {
597             Log.e(TAG, "getGlobalSearchActivity() failed: " + ex);
598             return null;
599         }
600     }
601 
602     /**
603      * Gets the name of the web search activity.
604      *
605      * @return The name of the default activity for web searches. This activity
606      *         can be used to get web search suggestions. Returns {@code null} if
607      *         there is no default web search activity.
608      *
609      * @hide
610      */
getWebSearchActivity()611     public ComponentName getWebSearchActivity() {
612         try {
613             return mService.getWebSearchActivity();
614         } catch (RemoteException ex) {
615             Log.e(TAG, "getWebSearchActivity() failed: " + ex);
616             return null;
617         }
618     }
619 
620     /**
621      * Similar to {@link #startSearch} but actually fires off the search query after invoking
622      * the search dialog.  Made available for testing purposes.
623      *
624      * @param query The query to trigger.  If empty, request will be ignored.
625      * @param launchActivity The ComponentName of the activity that has launched this search.
626      * @param appSearchData An application can insert application-specific
627      * context here, in order to improve quality or specificity of its own
628      * searches.  This data will be returned with SEARCH intent(s).  Null if
629      * no extra data is required.
630      *
631      * @see #startSearch
632      */
triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData)633     public void triggerSearch(String query,
634                               ComponentName launchActivity,
635                               Bundle appSearchData) {
636         if (!mAssociatedPackage.equals(launchActivity.getPackageName())) {
637             throw new IllegalArgumentException("invoking app search on a different package " +
638                     "not associated with this search manager");
639         }
640         if (query == null || TextUtils.getTrimmedLength(query) == 0) {
641             Log.w(TAG, "triggerSearch called with empty query, ignoring.");
642             return;
643         }
644         startSearch(query, false, launchActivity, appSearchData, false);
645         mSearchDialog.launchQuerySearch();
646     }
647 
648     /**
649      * Terminate search UI.
650      *
651      * <p>Typically the user will terminate the search UI by launching a
652      * search or by canceling.  This function allows the underlying application
653      * or activity to cancel the search prematurely (for any reason).
654      *
655      * <p>This function can be safely called at any time (even if no search is active.)
656      *
657      * @see #startSearch
658      */
stopSearch()659     public void stopSearch() {
660         if (mSearchDialog != null) {
661             mSearchDialog.cancel();
662         }
663     }
664 
665     /**
666      * Determine if the Search UI is currently displayed.
667      *
668      * This is provided primarily for application test purposes.
669      *
670      * @return Returns true if the search UI is currently displayed.
671      *
672      * @hide
673      */
isVisible()674     public boolean isVisible() {
675         return mSearchDialog == null? false : mSearchDialog.isShowing();
676     }
677 
678     /**
679      * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
680      * search UI state.
681      */
682     public interface OnDismissListener {
683         /**
684          * This method will be called when the search UI is dismissed. To make use of it, you must
685          * implement this method in your activity, and call
686          * {@link SearchManager#setOnDismissListener} to register it.
687          */
onDismiss()688         public void onDismiss();
689     }
690 
691     /**
692      * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
693      * search UI state.
694      */
695     public interface OnCancelListener {
696         /**
697          * This method will be called when the search UI is canceled. To make use if it, you must
698          * implement this method in your activity, and call
699          * {@link SearchManager#setOnCancelListener} to register it.
700          */
onCancel()701         public void onCancel();
702     }
703 
704     /**
705      * Set or clear the callback that will be invoked whenever the search UI is dismissed.
706      *
707      * @param listener The {@link OnDismissListener} to use, or null.
708      */
setOnDismissListener(final OnDismissListener listener)709     public void setOnDismissListener(final OnDismissListener listener) {
710         mDismissListener = listener;
711     }
712 
713     /**
714      * Set or clear the callback that will be invoked whenever the search UI is canceled.
715      *
716      * @param listener The {@link OnCancelListener} to use, or null.
717      */
setOnCancelListener(OnCancelListener listener)718     public void setOnCancelListener(OnCancelListener listener) {
719         mCancelListener = listener;
720     }
721 
722     /**
723      * @deprecated This method is an obsolete internal implementation detail. Do not use.
724      */
725     @Deprecated
onCancel(DialogInterface dialog)726     public void onCancel(DialogInterface dialog) {
727         if (mCancelListener != null) {
728             mCancelListener.onCancel();
729         }
730     }
731 
732     /**
733      * @deprecated This method is an obsolete internal implementation detail. Do not use.
734      */
735     @Deprecated
onDismiss(DialogInterface dialog)736     public void onDismiss(DialogInterface dialog) {
737         if (mDismissListener != null) {
738             mDismissListener.onDismiss();
739         }
740     }
741 
742     /**
743      * Gets information about a searchable activity.
744      *
745      * @param componentName The activity to get searchable information for.
746      * @return Searchable information, or <code>null</code> if the activity does not
747      *         exist, or is not searchable.
748      */
getSearchableInfo(ComponentName componentName)749     public SearchableInfo getSearchableInfo(ComponentName componentName) {
750         try {
751             return mService.getSearchableInfo(componentName);
752         } catch (RemoteException ex) {
753             Log.e(TAG, "getSearchableInfo() failed: " + ex);
754             return null;
755         }
756     }
757 
758     /**
759      * Gets a cursor with search suggestions.
760      *
761      * @param searchable Information about how to get the suggestions.
762      * @param query The search text entered (so far).
763      * @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
764      *
765      * @hide because SearchableInfo is not part of the API.
766      */
getSuggestions(SearchableInfo searchable, String query)767     public Cursor getSuggestions(SearchableInfo searchable, String query) {
768         return getSuggestions(searchable, query, -1);
769     }
770 
771     /**
772      * Gets a cursor with search suggestions.
773      *
774      * @param searchable Information about how to get the suggestions.
775      * @param query The search text entered (so far).
776      * @param limit The query limit to pass to the suggestion provider. This is advisory,
777      *        the returned cursor may contain more rows. Pass {@code -1} for no limit.
778      * @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
779      *
780      * @hide because SearchableInfo is not part of the API.
781      */
getSuggestions(SearchableInfo searchable, String query, int limit)782     public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) {
783         if (searchable == null) {
784             return null;
785         }
786 
787         String authority = searchable.getSuggestAuthority();
788         if (authority == null) {
789             return null;
790         }
791 
792         Uri.Builder uriBuilder = new Uri.Builder()
793                 .scheme(ContentResolver.SCHEME_CONTENT)
794                 .authority(authority)
795                 .query("")  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
796                 .fragment("");  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
797 
798         // if content path provided, insert it now
799         final String contentPath = searchable.getSuggestPath();
800         if (contentPath != null) {
801             uriBuilder.appendEncodedPath(contentPath);
802         }
803 
804         // append standard suggestion query path
805         uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
806 
807         // get the query selection, may be null
808         String selection = searchable.getSuggestSelection();
809         // inject query, either as selection args or inline
810         String[] selArgs = null;
811         if (selection != null) {    // use selection if provided
812             selArgs = new String[] { query };
813         } else {                    // no selection, use REST pattern
814             uriBuilder.appendPath(query);
815         }
816 
817         if (limit > 0) {
818             uriBuilder.appendQueryParameter(SUGGEST_PARAMETER_LIMIT, String.valueOf(limit));
819         }
820 
821         Uri uri = uriBuilder.build();
822 
823         // finally, make the query
824         return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
825     }
826 
827     /**
828      * Returns a list of the searchable activities that can be included in global search.
829      *
830      * @return a list containing searchable information for all searchable activities
831      *         that have the <code>android:includeInGlobalSearch</code> attribute set
832      *         in their searchable meta-data.
833      */
getSearchablesInGlobalSearch()834     public List<SearchableInfo> getSearchablesInGlobalSearch() {
835         try {
836             return mService.getSearchablesInGlobalSearch();
837         } catch (RemoteException e) {
838             Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e);
839             return null;
840         }
841     }
842 
843     /**
844      * Gets an intent for launching installed assistant activity, or null if not available.
845      * @return The assist intent.
846      *
847      * @hide
848      */
getAssistIntent(Context context, boolean inclContext)849     public Intent getAssistIntent(Context context, boolean inclContext) {
850         return getAssistIntent(context, inclContext, UserHandle.myUserId());
851     }
852 
853     /**
854      * Gets an intent for launching installed assistant activity, or null if not available.
855      * @return The assist intent.
856      *
857      * @hide
858      */
getAssistIntent(Context context, boolean inclContext, int userHandle)859     public Intent getAssistIntent(Context context, boolean inclContext, int userHandle) {
860         try {
861             if (mService == null) {
862                 return null;
863             }
864             ComponentName comp = mService.getAssistIntent(userHandle);
865             if (comp == null) {
866                 return null;
867             }
868             Intent intent = new Intent(Intent.ACTION_ASSIST);
869             intent.setComponent(comp);
870             if (inclContext) {
871                 IActivityManager am = ActivityManagerNative.getDefault();
872                 Bundle extras = am.getAssistContextExtras(0);
873                 if (extras != null) {
874                     intent.replaceExtras(extras);
875                 }
876             }
877             return intent;
878         } catch (RemoteException re) {
879             Log.e(TAG, "getAssistIntent() failed: " + re);
880             return null;
881         }
882     }
883 }
884