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