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.compat.annotation.UnsupportedAppUsage;
21 import android.content.ActivityNotFoundException;
22 import android.content.ComponentName;
23 import android.content.ContentResolver;
24 import android.content.Context;
25 import android.content.DialogInterface;
26 import android.content.Intent;
27 import android.content.pm.ResolveInfo;
28 import android.content.res.Configuration;
29 import android.database.Cursor;
30 import android.graphics.Rect;
31 import android.net.Uri;
32 import android.os.Bundle;
33 import android.os.Handler;
34 import android.os.RemoteException;
35 import android.os.ServiceManager;
36 import android.os.ServiceManager.ServiceNotFoundException;
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  * <p>
52  * {@link Configuration#UI_MODE_TYPE_WATCH} does not support this system 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 @SystemService(Context.SEARCH_SERVICE)
62 public class SearchManager
63         implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener {
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", touching 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     /**
260      * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
261      *  then all suggestions will be provided in a format that includes space for two small icons,
262      *  one at the left and one at the right of each suggestion.  The data in the column must
263      *  be a resource ID of a drawable, or a URI in one of the following formats:
264      *
265      * <ul>
266      * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
267      * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
268      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
269      * </ul>
270      *
271      * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
272      * for more information on these schemes.
273      */
274     public final static String SUGGEST_COLUMN_ICON_2 = "suggest_icon_2";
275 
276     /**
277      * Column name for suggestions cursor.  <i>Optional.</i>  If your cursor includes this column,
278      * then the image will be displayed when forming the suggestion. The suggested dimension for
279      * the image is 270x400 px for portrait mode and 400x225 px for landscape mode. The data in the
280      * column must be a resource ID of a drawable, or a URI in one of the following formats:
281      *
282      * <ul>
283      * <li>content ({@link android.content.ContentResolver#SCHEME_CONTENT})</li>
284      * <li>android.resource ({@link android.content.ContentResolver#SCHEME_ANDROID_RESOURCE})</li>
285      * <li>file ({@link android.content.ContentResolver#SCHEME_FILE})</li>
286      * </ul>
287      *
288      * See {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String)}
289      * for more information on these schemes.
290      */
291     public final static String SUGGEST_COLUMN_RESULT_CARD_IMAGE = "suggest_result_card_image";
292 
293     /**
294      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
295      * this element exists at the given row, this is the action that will be used when
296      * forming the suggestion's intent.  If the element is not provided, the action will be taken
297      * from the android:searchSuggestIntentAction field in your XML metadata.  <i>At least one of
298      * these must be present for the suggestion to generate an intent.</i>  Note:  If your action is
299      * the same for all suggestions, it is more efficient to specify it using XML metadata and omit
300      * it from the cursor.
301      */
302     public final static String SUGGEST_COLUMN_INTENT_ACTION = "suggest_intent_action";
303 
304     /**
305      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
306      * this element exists at the given row, this is the data that will be used when
307      * forming the suggestion's intent.  If the element is not provided, the data will be taken
308      * from the android:searchSuggestIntentData field in your XML metadata.  If neither source
309      * is provided, the Intent's data field will be null.  Note:  If your data is
310      * the same for all suggestions, or can be described using a constant part and a specific ID,
311      * it is more efficient to specify it using XML metadata and omit it from the cursor.
312      */
313     public final static String SUGGEST_COLUMN_INTENT_DATA = "suggest_intent_data";
314 
315     /**
316      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
317      * this element exists at the given row, this is the data that will be used when
318      * forming the suggestion's intent. If not provided, the Intent's extra data field will be null.
319      * This column allows suggestions to provide additional arbitrary data which will be included as
320      * an extra under the key {@link #EXTRA_DATA_KEY}.
321      */
322     public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data";
323 
324     /**
325      * Column name for suggestions cursor.  <i>Optional.</i>  If this column exists <i>and</i>
326      * this element exists at the given row, then "/" and this value will be appended to the data
327      * field in the Intent.  This should only be used if the data field has already been set to an
328      * appropriate base string.
329      */
330     public final static String SUGGEST_COLUMN_INTENT_DATA_ID = "suggest_intent_data_id";
331 
332     /**
333      * Column name for suggestions cursor.  <i>Required if action is
334      * {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}, optional otherwise.</i>  If this
335      * column exists <i>and</i> this element exists at the given row, this is the data that will be
336      * used when forming the suggestion's query.
337      */
338     public final static String SUGGEST_COLUMN_QUERY = "suggest_intent_query";
339 
340     /**
341      * Column name for suggestions cursor. <i>Optional.</i>  This column is used to indicate whether
342      * a search suggestion should be stored as a shortcut, and whether it should be refreshed.  If
343      * missing, the result will be stored as a shortcut and never validated.  If set to
344      * {@link #SUGGEST_NEVER_MAKE_SHORTCUT}, the result will not be stored as a shortcut.
345      * Otherwise, the shortcut id will be used to check back for an up to date suggestion using
346      * {@link #SUGGEST_URI_PATH_SHORTCUT}.
347      */
348     public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id";
349 
350     /**
351      * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
352      * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion
353      * is being refreshed.
354      */
355     public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING =
356             "suggest_spinner_while_refreshing";
357 
358     /**
359      * Column name for suggestions cursor. <i>Optional.</i>  If your content is media type, you
360      * should provide this column so search app could understand more about your content. The data
361      * in the column must specify the MIME type of the content.
362      */
363     public final static String SUGGEST_COLUMN_CONTENT_TYPE = "suggest_content_type";
364 
365     /**
366      * Column name for suggestions cursor. <i>Optional.</i>  If your content is media type, you
367      * should provide this column to specify whether your content is live media such as live video
368      * or live audio. The value in the column is of integer type with value of either 0 indicating
369      * non-live content or 1 indicating live content.
370      */
371     public final static String SUGGEST_COLUMN_IS_LIVE = "suggest_is_live";
372 
373     /**
374      * Column name for suggestions cursor. <i>Optional.</i>  If your content is video, you should
375      * provide this column to specify the number of vertical lines. The data in the column is of
376      * integer type.
377      */
378     public final static String SUGGEST_COLUMN_VIDEO_WIDTH = "suggest_video_width";
379 
380     /**
381      * Column name for suggestions cursor. <i>Optional.</i>  If your content is video, you should
382      * provide this column to specify the number of horizontal lines. The data in the column is of
383      * integer type.
384      */
385     public final static String SUGGEST_COLUMN_VIDEO_HEIGHT = "suggest_video_height";
386 
387     /**
388      * Column name for suggestions cursor. <i>Optional.</i>  If your content contains audio, you
389      * should provide this column to specify the audio channel configuration. The data in the
390      * column is string with format like "channels.subchannels" such as "1.0" or "5.1".
391      */
392     public final static String SUGGEST_COLUMN_AUDIO_CHANNEL_CONFIG = "suggest_audio_channel_config";
393 
394     /**
395      * Column name for suggestions cursor. <i>Optional.</i>  If your content is purchasable, you
396      * should provide this column to specify the displayable string representation of the purchase
397      * price of your content including the currency and the amount. If it's free, you should
398      * provide localized string to specify that it's free. This column can be omitted if the content
399      * is not applicable to purchase.
400      */
401     public final static String SUGGEST_COLUMN_PURCHASE_PRICE = "suggest_purchase_price";
402 
403     /**
404      * Column name for suggestions cursor. <i>Optional.</i>  If your content is rentable, you
405      * should provide this column to specify the displayable string representation of the rental
406      * price of your content including the currency and the amount. If it's free, you should
407      * provide localized string to specify that it's free. This column can be omitted if the
408      * content is not applicable to rent.
409      */
410     public final static String SUGGEST_COLUMN_RENTAL_PRICE = "suggest_rental_price";
411 
412     /**
413      * Column name for suggestions cursor. <i>Optional.</i>  If your content has a rating, you
414      * should provide this column to specify the rating style of your content. The data in the
415      * column must be one of the constant values specified in {@link android.media.Rating}
416      */
417     public final static String SUGGEST_COLUMN_RATING_STYLE = "suggest_rating_style";
418 
419     /**
420      * Column name for suggestions cursor. <i>Optional.</i>  If your content has a rating, you
421      * should provide this column to specify the rating score of your content. The data in the
422      * column is of float type. See {@link android.media.Rating} about valid rating scores for each
423      * rating style.
424      */
425     public final static String SUGGEST_COLUMN_RATING_SCORE = "suggest_rating_score";
426 
427     /**
428      * Column name for suggestions cursor. <i>Optional.</i>  If your content is video or audio and
429      * has a known production year, you should provide this column to specify the production year
430      * of your content. The data in the column is of integer type.
431      */
432     public final static String SUGGEST_COLUMN_PRODUCTION_YEAR = "suggest_production_year";
433 
434     /**
435      * Column name for suggestions cursor. <i>Optional.</i>  If your content is video or audio, you
436      * should provide this column to specify the duration of your content in milliseconds. The data
437      * in the column is of long type.
438      */
439     public final static String SUGGEST_COLUMN_DURATION = "suggest_duration";
440 
441     /**
442      * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify
443      * additional flags per item. Multiple flags can be specified.
444      * <p>
445      * Must be one of {@link #FLAG_QUERY_REFINEMENT} or 0 to indicate no flags.
446      * </p>
447      */
448     public final static String SUGGEST_COLUMN_FLAGS = "suggest_flags";
449 
450     /**
451      * Column name for suggestions cursor. <i>Optional.</i> This column may be
452      * used to specify the time in {@link System#currentTimeMillis
453      * System.currentTImeMillis()} (wall time in UTC) when an item was last
454      * accessed within the results-providing application. If set, this may be
455      * used to show more-recently-used items first.
456      */
457     public final static String SUGGEST_COLUMN_LAST_ACCESS_HINT = "suggest_last_access_hint";
458 
459     /**
460      * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion
461      * should not be stored as a shortcut in global search.
462      */
463     public final static String SUGGEST_NEVER_MAKE_SHORTCUT = "_-1";
464 
465     /**
466      * Query parameter added to suggestion queries to limit the number of suggestions returned.
467      * This limit is only advisory and suggestion providers may chose to ignore it.
468      */
469     public final static String SUGGEST_PARAMETER_LIMIT = "limit";
470 
471     /**
472      * Intent action for starting the global search activity.
473      * The global search provider should handle this intent.
474      *
475      * Supported extra data keys: {@link #QUERY},
476      * {@link #EXTRA_SELECT_QUERY},
477      * {@link #APP_DATA}.
478      */
479     public final static String INTENT_ACTION_GLOBAL_SEARCH
480             = "android.search.action.GLOBAL_SEARCH";
481 
482     /**
483      * Intent action for starting the global search settings activity.
484      * The global search provider should handle this intent.
485      */
486     public final static String INTENT_ACTION_SEARCH_SETTINGS
487             = "android.search.action.SEARCH_SETTINGS";
488 
489     /**
490      * Intent action for starting a web search provider's settings activity.
491      * Web search providers should handle this intent if they have provider-specific
492      * settings to implement.
493      */
494     public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS
495             = "android.search.action.WEB_SEARCH_SETTINGS";
496 
497     /**
498      * Intent action broadcasted to inform that the searchables list or default have changed.
499      * Components should handle this intent if they cache any searchable data and wish to stay
500      * up to date on changes.
501      */
502     public final static String INTENT_ACTION_SEARCHABLES_CHANGED
503             = "android.search.action.SEARCHABLES_CHANGED";
504 
505     /**
506      * Intent action to be broadcast to inform that the global search provider
507      * has changed.
508      */
509     public final static String INTENT_GLOBAL_SEARCH_ACTIVITY_CHANGED
510             = "android.search.action.GLOBAL_SEARCH_ACTIVITY_CHANGED";
511 
512     /**
513      * Intent action broadcasted to inform that the search settings have changed in some way.
514      * Either searchables have been enabled or disabled, or a different web search provider
515      * has been chosen.
516      */
517     public final static String INTENT_ACTION_SEARCH_SETTINGS_CHANGED
518             = "android.search.action.SETTINGS_CHANGED";
519 
520     /**
521      * This means that context is voice, and therefore the SearchDialog should
522      * continue showing the microphone until the user indicates that he/she does
523      * not want to re-speak (e.g. by typing).
524      *
525      * @hide
526      */
527     public final static String CONTEXT_IS_VOICE = "android.search.CONTEXT_IS_VOICE";
528 
529     /**
530      * This means that the voice icon should not be shown at all, because the
531      * current search engine does not support voice search.
532      * @hide
533      */
534     @UnsupportedAppUsage
535     public final static String DISABLE_VOICE_SEARCH
536             = "android.search.DISABLE_VOICE_SEARCH";
537 
538     /**
539      * Reference to the shared system search service.
540      */
541     private final ISearchManager mService;
542 
543     private final Context mContext;
544 
545     // package private since they are used by the inner class SearchManagerCallback
546     /* package */ final Handler mHandler;
547     /* package */ OnDismissListener mDismissListener = null;
548     /* package */ OnCancelListener mCancelListener = null;
549 
550     @UnsupportedAppUsage
551     private SearchDialog mSearchDialog;
552 
553     @UnsupportedAppUsage
SearchManager(Context context, Handler handler)554     /*package*/ SearchManager(Context context, Handler handler) throws ServiceNotFoundException {
555         mContext = context;
556         mHandler = handler;
557         mService = ISearchManager.Stub.asInterface(
558                 ServiceManager.getServiceOrThrow(Context.SEARCH_SERVICE));
559     }
560 
561     /**
562      * Launch search UI.
563      *
564      * <p>The search manager will open a search widget in an overlapping
565      * window, and the underlying activity may be obscured.  The search
566      * entry state will remain in effect until one of the following events:
567      * <ul>
568      * <li>The user completes the search.  In most cases this will launch
569      * a search intent.</li>
570      * <li>The user uses the back, home, or other keys to exit the search.</li>
571      * <li>The application calls the {@link #stopSearch}
572      * method, which will hide the search window and return focus to the
573      * activity from which it was launched.</li>
574      *
575      * <p>Most applications will <i>not</i> use this interface to invoke search.
576      * The primary method for invoking search is to call
577      * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or
578      * {@link android.app.Activity#startSearch Activity.startSearch()}.
579      *
580      * @param initialQuery A search string can be pre-entered here, but this
581      * is typically null or empty.
582      * @param selectInitialQuery If true, the initial query will be preselected, which means that
583      * any further typing will replace it.  This is useful for cases where an entire pre-formed
584      * query is being inserted.  If false, the selection point will be placed at the end of the
585      * inserted query.  This is useful when the inserted query is text that the user entered,
586      * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
587      * if initialQuery is a non-empty string.</i>
588      * @param launchActivity The ComponentName of the activity that has launched this search.
589      * @param appSearchData An application can insert application-specific
590      * context here, in order to improve quality or specificity of its own
591      * searches.  This data will be returned with SEARCH intent(s).  Null if
592      * no extra data is required.
593      * @param globalSearch If false, this will only launch the search that has been specifically
594      * defined by the application (which is usually defined as a local search).  If no default
595      * search is defined in the current application or activity, global search will be launched.
596      * If true, this will always launch a platform-global (e.g. web-based) search instead.
597      *
598      * @see android.app.Activity#onSearchRequested
599      * @see #stopSearch
600      */
startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch)601     public void startSearch(String initialQuery,
602                             boolean selectInitialQuery,
603                             ComponentName launchActivity,
604                             Bundle appSearchData,
605                             boolean globalSearch) {
606         startSearch(initialQuery, selectInitialQuery, launchActivity,
607                 appSearchData, globalSearch, null);
608     }
609 
610     /**
611      * As {@link #startSearch(String, boolean, ComponentName, Bundle, boolean)} but including
612      * source bounds for the global search intent.
613      *
614      * @hide
615      */
616     @UnsupportedAppUsage
startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch, Rect sourceBounds)617     public void startSearch(String initialQuery,
618                             boolean selectInitialQuery,
619                             ComponentName launchActivity,
620                             Bundle appSearchData,
621                             boolean globalSearch,
622                             Rect sourceBounds) {
623         if (globalSearch) {
624             startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds);
625             return;
626         }
627 
628         final UiModeManager uiModeManager = mContext.getSystemService(UiModeManager.class);
629         // Don't show search dialog on televisions.
630         if (uiModeManager.getCurrentModeType() != Configuration.UI_MODE_TYPE_TELEVISION) {
631             ensureSearchDialog();
632 
633             mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData);
634         }
635     }
636 
ensureSearchDialog()637     private void ensureSearchDialog() {
638         if (mSearchDialog == null) {
639             mSearchDialog = new SearchDialog(mContext, this);
640             mSearchDialog.setOnCancelListener(this);
641             mSearchDialog.setOnDismissListener(this);
642         }
643     }
644 
645     /**
646      * Starts the global search activity.
647      */
startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds)648     /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
649             Bundle appSearchData, Rect sourceBounds) {
650         ComponentName globalSearchActivity = getGlobalSearchActivity();
651         if (globalSearchActivity == null) {
652             Log.w(TAG, "No global search activity found.");
653             return;
654         }
655         Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
656         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
657         intent.setComponent(globalSearchActivity);
658         // Make sure that we have a Bundle to put source in
659         if (appSearchData == null) {
660             appSearchData = new Bundle();
661         } else {
662             appSearchData = new Bundle(appSearchData);
663         }
664         // Set source to package name of app that starts global search, if not set already.
665         if (!appSearchData.containsKey("source")) {
666             appSearchData.putString("source", mContext.getPackageName());
667         }
668         intent.putExtra(APP_DATA, appSearchData);
669         if (!TextUtils.isEmpty(initialQuery)) {
670             intent.putExtra(QUERY, initialQuery);
671         }
672         if (selectInitialQuery) {
673             intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery);
674         }
675         intent.setSourceBounds(sourceBounds);
676         try {
677             if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0));
678             mContext.startActivity(intent);
679         } catch (ActivityNotFoundException ex) {
680             Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
681         }
682     }
683 
684     /**
685      * Returns a list of installed apps that handle the global search
686      * intent.
687      *
688      * @hide
689      */
getGlobalSearchActivities()690     public List<ResolveInfo> getGlobalSearchActivities() {
691         try {
692             return mService.getGlobalSearchActivities();
693         } catch (RemoteException ex) {
694             throw ex.rethrowFromSystemServer();
695         }
696     }
697 
698     /**
699      * Gets the name of the global search activity.
700      */
getGlobalSearchActivity()701     public ComponentName getGlobalSearchActivity() {
702         try {
703             return mService.getGlobalSearchActivity();
704         } catch (RemoteException ex) {
705             throw ex.rethrowFromSystemServer();
706         }
707     }
708 
709     /**
710      * Gets the name of the web search activity.
711      *
712      * @return The name of the default activity for web searches. This activity
713      *         can be used to get web search suggestions. Returns {@code null} if
714      *         there is no default web search activity.
715      *
716      * @hide
717      */
718     @UnsupportedAppUsage
getWebSearchActivity()719     public ComponentName getWebSearchActivity() {
720         try {
721             return mService.getWebSearchActivity();
722         } catch (RemoteException ex) {
723             throw ex.rethrowFromSystemServer();
724         }
725     }
726 
727     /**
728      * Similar to {@link #startSearch} but actually fires off the search query after invoking
729      * the search dialog.  Made available for testing purposes.
730      *
731      * @param query The query to trigger.  If empty, request will be ignored.
732      * @param launchActivity The ComponentName of the activity that has launched this search.
733      * @param appSearchData An application can insert application-specific
734      * context here, in order to improve quality or specificity of its own
735      * searches.  This data will be returned with SEARCH intent(s).  Null if
736      * no extra data is required.
737      *
738      * @see #startSearch
739      */
triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData)740     public void triggerSearch(String query,
741                               ComponentName launchActivity,
742                               Bundle appSearchData) {
743         if (query == null || TextUtils.getTrimmedLength(query) == 0) {
744             Log.w(TAG, "triggerSearch called with empty query, ignoring.");
745             return;
746         }
747         startSearch(query, false, launchActivity, appSearchData, false);
748         mSearchDialog.launchQuerySearch();
749     }
750 
751     /**
752      * Terminate search UI.
753      *
754      * <p>Typically the user will terminate the search UI by launching a
755      * search or by canceling.  This function allows the underlying application
756      * or activity to cancel the search prematurely (for any reason).
757      *
758      * <p>This function can be safely called at any time (even if no search is active.)
759      *
760      * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method.
761      *
762      * @see #startSearch
763      */
stopSearch()764     public void stopSearch() {
765         if (mSearchDialog != null) {
766             mSearchDialog.cancel();
767         }
768     }
769 
770     /**
771      * Determine if the Search UI is currently displayed.
772      *
773      * This is provided primarily for application test purposes.
774      *
775      * @return Returns true if the search UI is currently displayed.
776      *
777      * @hide
778      */
779     @UnsupportedAppUsage
isVisible()780     public boolean isVisible() {
781         return mSearchDialog == null? false : mSearchDialog.isShowing();
782     }
783 
784     /**
785      * See {@link SearchManager#setOnDismissListener} for configuring your activity to monitor
786      * search UI state.
787      */
788     public interface OnDismissListener {
789         /**
790          * This method will be called when the search UI is dismissed. To make use of it, you must
791          * implement this method in your activity, and call
792          * {@link SearchManager#setOnDismissListener} to register it.
793          */
onDismiss()794         public void onDismiss();
795     }
796 
797     /**
798      * See {@link SearchManager#setOnCancelListener} for configuring your activity to monitor
799      * search UI state.
800      */
801     public interface OnCancelListener {
802         /**
803          * This method will be called when the search UI is canceled. To make use if it, you must
804          * implement this method in your activity, and call
805          * {@link SearchManager#setOnCancelListener} to register it.
806          */
onCancel()807         public void onCancel();
808     }
809 
810     /**
811      * Set or clear the callback that will be invoked whenever the search UI is dismissed.
812      *
813      * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method.
814      *
815      * @param listener The {@link OnDismissListener} to use, or null.
816      */
setOnDismissListener(final OnDismissListener listener)817     public void setOnDismissListener(final OnDismissListener listener) {
818         mDismissListener = listener;
819     }
820 
821     /**
822      * Set or clear the callback that will be invoked whenever the search UI is canceled.
823      *
824      * <p>{@link Configuration#UI_MODE_TYPE_TELEVISION} does not support this method.
825      *
826      * @param listener The {@link OnCancelListener} to use, or null.
827      */
setOnCancelListener(OnCancelListener listener)828     public void setOnCancelListener(OnCancelListener listener) {
829         mCancelListener = listener;
830     }
831 
832     /**
833      * @deprecated This method is an obsolete internal implementation detail. Do not use.
834      */
835     @Deprecated
onCancel(DialogInterface dialog)836     public void onCancel(DialogInterface dialog) {
837         if (mCancelListener != null) {
838             mCancelListener.onCancel();
839         }
840     }
841 
842     /**
843      * @deprecated This method is an obsolete internal implementation detail. Do not use.
844      */
845     @Deprecated
onDismiss(DialogInterface dialog)846     public void onDismiss(DialogInterface dialog) {
847         if (mDismissListener != null) {
848             mDismissListener.onDismiss();
849         }
850     }
851 
852     /**
853      * Gets information about a searchable activity.
854      *
855      * @param componentName The activity to get searchable information for.
856      * @return Searchable information, or <code>null</code> if the activity does not
857      *         exist, or is not searchable.
858      */
getSearchableInfo(ComponentName componentName)859     public SearchableInfo getSearchableInfo(ComponentName componentName) {
860         try {
861             return mService.getSearchableInfo(componentName);
862         } catch (RemoteException ex) {
863             throw ex.rethrowFromSystemServer();
864         }
865     }
866 
867     /**
868      * Gets a cursor with search suggestions.
869      *
870      * @param searchable Information about how to get the suggestions.
871      * @param query The search text entered (so far).
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      */
876     @UnsupportedAppUsage
getSuggestions(SearchableInfo searchable, String query)877     public Cursor getSuggestions(SearchableInfo searchable, String query) {
878         return getSuggestions(searchable, query, -1);
879     }
880 
881     /**
882      * Gets a cursor with search suggestions.
883      *
884      * @param searchable Information about how to get the suggestions.
885      * @param query The search text entered (so far).
886      * @param limit The query limit to pass to the suggestion provider. This is advisory,
887      *        the returned cursor may contain more rows. Pass {@code -1} for no limit.
888      * @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
889      *
890      * @hide because SearchableInfo is not part of the API.
891      */
892     @UnsupportedAppUsage
getSuggestions(SearchableInfo searchable, String query, int limit)893     public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) {
894         if (searchable == null) {
895             return null;
896         }
897 
898         String authority = searchable.getSuggestAuthority();
899         if (authority == null) {
900             return null;
901         }
902 
903         Uri.Builder uriBuilder = new Uri.Builder()
904                 .scheme(ContentResolver.SCHEME_CONTENT)
905                 .authority(authority)
906                 .query("")  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
907                 .fragment("");  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
908 
909         // if content path provided, insert it now
910         final String contentPath = searchable.getSuggestPath();
911         if (contentPath != null) {
912             uriBuilder.appendEncodedPath(contentPath);
913         }
914 
915         // append standard suggestion query path
916         uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
917 
918         // get the query selection, may be null
919         String selection = searchable.getSuggestSelection();
920         // inject query, either as selection args or inline
921         String[] selArgs = null;
922         if (selection != null) {    // use selection if provided
923             selArgs = new String[] { query };
924         } else {                    // no selection, use REST pattern
925             uriBuilder.appendPath(query);
926         }
927 
928         if (limit > 0) {
929             uriBuilder.appendQueryParameter(SUGGEST_PARAMETER_LIMIT, String.valueOf(limit));
930         }
931 
932         Uri uri = uriBuilder.build();
933 
934         // finally, make the query
935         return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
936     }
937 
938     /**
939      * Returns a list of the searchable activities that can be included in global search.
940      *
941      * @return a list containing searchable information for all searchable activities
942      *         that have the <code>android:includeInGlobalSearch</code> attribute set
943      *         in their searchable meta-data.
944      */
getSearchablesInGlobalSearch()945     public List<SearchableInfo> getSearchablesInGlobalSearch() {
946         try {
947             return mService.getSearchablesInGlobalSearch();
948         } catch (RemoteException e) {
949             throw e.rethrowFromSystemServer();
950         }
951     }
952 
953     /**
954      * Gets an intent for launching installed assistant activity, or null if not available.
955      * @return The assist intent.
956      *
957      * @hide
958      */
getAssistIntent(boolean inclContext)959     public Intent getAssistIntent(boolean inclContext) {
960         try {
961             Intent intent = new Intent(Intent.ACTION_ASSIST);
962             if (inclContext) {
963                 IActivityTaskManager am = ActivityTaskManager.getService();
964                 Bundle extras = am.getAssistContextExtras(ActivityManager.ASSIST_CONTEXT_BASIC);
965                 if (extras != null) {
966                     intent.replaceExtras(extras);
967                 }
968             }
969             return intent;
970         } catch (RemoteException re) {
971             throw re.rethrowFromSystemServer();
972         }
973     }
974 
975     /**
976      * Starts the assistant.
977      *
978      * @param args the args to pass to the assistant
979      *
980      * @hide
981      */
982     @UnsupportedAppUsage
launchAssist(Bundle args)983     public void launchAssist(Bundle args) {
984         try {
985             if (mService == null) {
986                 return;
987             }
988             mService.launchAssist(args);
989         } catch (RemoteException re) {
990             throw re.rethrowFromSystemServer();
991         }
992     }
993 
994     /**
995      * Starts the legacy assistant (i.e. the {@link Intent#ACTION_ASSIST}).
996      *
997      * @param args the args to pass to the assistant
998      *
999      * @hide
1000      */
launchLegacyAssist(String hint, int userHandle, Bundle args)1001     public boolean launchLegacyAssist(String hint, int userHandle, Bundle args) {
1002         try {
1003             if (mService == null) {
1004                 return false;
1005             }
1006             return mService.launchLegacyAssist(hint, userHandle, args);
1007         } catch (RemoteException re) {
1008             throw re.rethrowFromSystemServer();
1009         }
1010     }
1011 }
1012