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