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