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