• 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.content.ActivityNotFoundException;
20 import android.content.ComponentName;
21 import android.content.ContentResolver;
22 import android.content.Context;
23 import android.content.DialogInterface;
24 import android.content.Intent;
25 import android.content.pm.ResolveInfo;
26 import android.content.res.Configuration;
27 import android.database.Cursor;
28 import android.graphics.Rect;
29 import android.net.Uri;
30 import android.os.Bundle;
31 import android.os.Handler;
32 import android.os.RemoteException;
33 import android.os.ServiceManager;
34 import android.os.UserHandle;
35 import android.text.TextUtils;
36 import android.util.Log;
37 import android.view.KeyEvent;
38 
39 import java.util.List;
40 
41 /**
42  * This class provides access to the system search services.
43  *
44  * <p>In practice, you won't interact with this class directly, as search
45  * services are provided through methods in {@link android.app.Activity Activity}
46  * and the {@link android.content.Intent#ACTION_SEARCH ACTION_SEARCH}
47  * {@link android.content.Intent Intent}.
48  * If you do require direct access to the SearchManager, do not instantiate
49  * this class directly. Instead, retrieve it through
50  * {@link android.content.Context#getSystemService
51  * context.getSystemService(Context.SEARCH_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 public class SearchManager
61         implements DialogInterface.OnDismissListener, DialogInterface.OnCancelListener
62 {
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 static ISearchManager mService;
540 
541     private final Context mContext;
542 
543     /**
544      * The package associated with this seach manager.
545      */
546     private String mAssociatedPackage;
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     private SearchDialog mSearchDialog;
554 
SearchManager(Context context, Handler handler)555     /*package*/ SearchManager(Context context, Handler handler)  {
556         mContext = context;
557         mHandler = handler;
558         mService = ISearchManager.Stub.asInterface(
559                 ServiceManager.getService(Context.SEARCH_SERVICE));
560     }
561 
562     /**
563      * Launch search UI.
564      *
565      * <p>The search manager will open a search widget in an overlapping
566      * window, and the underlying activity may be obscured.  The search
567      * entry state will remain in effect until one of the following events:
568      * <ul>
569      * <li>The user completes the search.  In most cases this will launch
570      * a search intent.</li>
571      * <li>The user uses the back, home, or other keys to exit the search.</li>
572      * <li>The application calls the {@link #stopSearch}
573      * method, which will hide the search window and return focus to the
574      * activity from which it was launched.</li>
575      *
576      * <p>Most applications will <i>not</i> use this interface to invoke search.
577      * The primary method for invoking search is to call
578      * {@link android.app.Activity#onSearchRequested Activity.onSearchRequested()} or
579      * {@link android.app.Activity#startSearch Activity.startSearch()}.
580      *
581      * @param initialQuery A search string can be pre-entered here, but this
582      * is typically null or empty.
583      * @param selectInitialQuery If true, the intial query will be preselected, which means that
584      * any further typing will replace it.  This is useful for cases where an entire pre-formed
585      * query is being inserted.  If false, the selection point will be placed at the end of the
586      * inserted query.  This is useful when the inserted query is text that the user entered,
587      * and the user would expect to be able to keep typing.  <i>This parameter is only meaningful
588      * if initialQuery is a non-empty string.</i>
589      * @param launchActivity The ComponentName of the activity that has launched this search.
590      * @param appSearchData An application can insert application-specific
591      * context here, in order to improve quality or specificity of its own
592      * searches.  This data will be returned with SEARCH intent(s).  Null if
593      * no extra data is required.
594      * @param globalSearch If false, this will only launch the search that has been specifically
595      * defined by the application (which is usually defined as a local search).  If no default
596      * search is defined in the current application or activity, global search will be launched.
597      * If true, this will always launch a platform-global (e.g. web-based) search instead.
598      *
599      * @see android.app.Activity#onSearchRequested
600      * @see #stopSearch
601      */
startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch)602     public void startSearch(String initialQuery,
603                             boolean selectInitialQuery,
604                             ComponentName launchActivity,
605                             Bundle appSearchData,
606                             boolean globalSearch) {
607         startSearch(initialQuery, selectInitialQuery, launchActivity,
608                 appSearchData, globalSearch, null);
609     }
610 
611     /**
612      * As {@link #startSearch(String, boolean, ComponentName, Bundle, boolean)} but including
613      * source bounds for the global search intent.
614      *
615      * @hide
616      */
startSearch(String initialQuery, boolean selectInitialQuery, ComponentName launchActivity, Bundle appSearchData, boolean globalSearch, Rect sourceBounds)617     public void startSearch(String initialQuery,
618                             boolean selectInitialQuery,
619                             ComponentName launchActivity,
620                             Bundle appSearchData,
621                             boolean globalSearch,
622                             Rect sourceBounds) {
623         if (globalSearch) {
624             startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds);
625             return;
626         }
627 
628         UiModeManager uiModeManager = new UiModeManager();
629         // Don't show search dialog on televisions.
630         if (uiModeManager.getCurrentModeType() != Configuration.UI_MODE_TYPE_TELEVISION) {
631             ensureSearchDialog();
632 
633             mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData);
634         }
635     }
636 
ensureSearchDialog()637     private void ensureSearchDialog() {
638         if (mSearchDialog == null) {
639             mSearchDialog = new SearchDialog(mContext, this);
640             mSearchDialog.setOnCancelListener(this);
641             mSearchDialog.setOnDismissListener(this);
642         }
643     }
644 
645     /**
646      * Starts the global search activity.
647      */
startGlobalSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, Rect sourceBounds)648     /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery,
649             Bundle appSearchData, Rect sourceBounds) {
650         ComponentName globalSearchActivity = getGlobalSearchActivity();
651         if (globalSearchActivity == null) {
652             Log.w(TAG, "No global search activity found.");
653             return;
654         }
655         Intent intent = new Intent(INTENT_ACTION_GLOBAL_SEARCH);
656         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
657         intent.setComponent(globalSearchActivity);
658         // Make sure that we have a Bundle to put source in
659         if (appSearchData == null) {
660             appSearchData = new Bundle();
661         } else {
662             appSearchData = new Bundle(appSearchData);
663         }
664         // Set source to package name of app that starts global search, if not set already.
665         if (!appSearchData.containsKey("source")) {
666             appSearchData.putString("source", mContext.getPackageName());
667         }
668         intent.putExtra(APP_DATA, appSearchData);
669         if (!TextUtils.isEmpty(initialQuery)) {
670             intent.putExtra(QUERY, initialQuery);
671         }
672         if (selectInitialQuery) {
673             intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery);
674         }
675         intent.setSourceBounds(sourceBounds);
676         try {
677             if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0));
678             mContext.startActivity(intent);
679         } catch (ActivityNotFoundException ex) {
680             Log.e(TAG, "Global search activity not found: " + globalSearchActivity);
681         }
682     }
683 
684     /**
685      * Returns a list of installed apps that handle the global search
686      * intent.
687      *
688      * @hide
689      */
getGlobalSearchActivities()690     public List<ResolveInfo> getGlobalSearchActivities() {
691         try {
692             return mService.getGlobalSearchActivities();
693         } catch (RemoteException ex) {
694             Log.e(TAG, "getGlobalSearchActivities() failed: " + ex);
695             return null;
696         }
697     }
698 
699     /**
700      * Gets the name of the global search activity.
701      */
getGlobalSearchActivity()702     public ComponentName getGlobalSearchActivity() {
703         try {
704             return mService.getGlobalSearchActivity();
705         } catch (RemoteException ex) {
706             Log.e(TAG, "getGlobalSearchActivity() failed: " + ex);
707             return null;
708         }
709     }
710 
711     /**
712      * Gets the name of the web search activity.
713      *
714      * @return The name of the default activity for web searches. This activity
715      *         can be used to get web search suggestions. Returns {@code null} if
716      *         there is no default web search activity.
717      *
718      * @hide
719      */
getWebSearchActivity()720     public ComponentName getWebSearchActivity() {
721         try {
722             return mService.getWebSearchActivity();
723         } catch (RemoteException ex) {
724             Log.e(TAG, "getWebSearchActivity() failed: " + ex);
725             return null;
726         }
727     }
728 
729     /**
730      * Similar to {@link #startSearch} but actually fires off the search query after invoking
731      * the search dialog.  Made available for testing purposes.
732      *
733      * @param query The query to trigger.  If empty, request will be ignored.
734      * @param launchActivity The ComponentName of the activity that has launched this search.
735      * @param appSearchData An application can insert application-specific
736      * context here, in order to improve quality or specificity of its own
737      * searches.  This data will be returned with SEARCH intent(s).  Null if
738      * no extra data is required.
739      *
740      * @see #startSearch
741      */
triggerSearch(String query, ComponentName launchActivity, Bundle appSearchData)742     public void triggerSearch(String query,
743                               ComponentName launchActivity,
744                               Bundle appSearchData) {
745         if (!mAssociatedPackage.equals(launchActivity.getPackageName())) {
746             throw new IllegalArgumentException("invoking app search on a different package " +
747                     "not associated with this search manager");
748         }
749         if (query == null || TextUtils.getTrimmedLength(query) == 0) {
750             Log.w(TAG, "triggerSearch called with empty query, ignoring.");
751             return;
752         }
753         startSearch(query, false, launchActivity, appSearchData, false);
754         mSearchDialog.launchQuerySearch();
755     }
756 
757     /**
758      * Terminate search UI.
759      *
760      * <p>Typically the user will terminate the search UI by launching a
761      * search or by canceling.  This function allows the underlying application
762      * or activity to cancel the search prematurely (for any reason).
763      *
764      * <p>This function can be safely called at any time (even if no search is active.)
765      *
766      * @see #startSearch
767      */
stopSearch()768     public void stopSearch() {
769         if (mSearchDialog != null) {
770             mSearchDialog.cancel();
771         }
772     }
773 
774     /**
775      * Determine if the Search UI is currently displayed.
776      *
777      * This is provided primarily for application test purposes.
778      *
779      * @return Returns true if the search UI is currently displayed.
780      *
781      * @hide
782      */
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      * @param listener The {@link OnDismissListener} to use, or null.
817      */
setOnDismissListener(final OnDismissListener listener)818     public void setOnDismissListener(final OnDismissListener listener) {
819         mDismissListener = listener;
820     }
821 
822     /**
823      * Set or clear the callback that will be invoked whenever the search UI is canceled.
824      *
825      * @param listener The {@link OnCancelListener} to use, or null.
826      */
setOnCancelListener(OnCancelListener listener)827     public void setOnCancelListener(OnCancelListener listener) {
828         mCancelListener = listener;
829     }
830 
831     /**
832      * @deprecated This method is an obsolete internal implementation detail. Do not use.
833      */
834     @Deprecated
onCancel(DialogInterface dialog)835     public void onCancel(DialogInterface dialog) {
836         if (mCancelListener != null) {
837             mCancelListener.onCancel();
838         }
839     }
840 
841     /**
842      * @deprecated This method is an obsolete internal implementation detail. Do not use.
843      */
844     @Deprecated
onDismiss(DialogInterface dialog)845     public void onDismiss(DialogInterface dialog) {
846         if (mDismissListener != null) {
847             mDismissListener.onDismiss();
848         }
849     }
850 
851     /**
852      * Gets information about a searchable activity.
853      *
854      * @param componentName The activity to get searchable information for.
855      * @return Searchable information, or <code>null</code> if the activity does not
856      *         exist, or is not searchable.
857      */
getSearchableInfo(ComponentName componentName)858     public SearchableInfo getSearchableInfo(ComponentName componentName) {
859         try {
860             return mService.getSearchableInfo(componentName);
861         } catch (RemoteException ex) {
862             Log.e(TAG, "getSearchableInfo() failed: " + ex);
863             return null;
864         }
865     }
866 
867     /**
868      * Gets a cursor with search suggestions.
869      *
870      * @param searchable Information about how to get the suggestions.
871      * @param query The search text entered (so far).
872      * @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
873      *
874      * @hide because SearchableInfo is not part of the API.
875      */
getSuggestions(SearchableInfo searchable, String query)876     public Cursor getSuggestions(SearchableInfo searchable, String query) {
877         return getSuggestions(searchable, query, -1);
878     }
879 
880     /**
881      * Gets a cursor with search suggestions.
882      *
883      * @param searchable Information about how to get the suggestions.
884      * @param query The search text entered (so far).
885      * @param limit The query limit to pass to the suggestion provider. This is advisory,
886      *        the returned cursor may contain more rows. Pass {@code -1} for no limit.
887      * @return a cursor with suggestions, or <code>null</null> the suggestion query failed.
888      *
889      * @hide because SearchableInfo is not part of the API.
890      */
getSuggestions(SearchableInfo searchable, String query, int limit)891     public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) {
892         if (searchable == null) {
893             return null;
894         }
895 
896         String authority = searchable.getSuggestAuthority();
897         if (authority == null) {
898             return null;
899         }
900 
901         Uri.Builder uriBuilder = new Uri.Builder()
902                 .scheme(ContentResolver.SCHEME_CONTENT)
903                 .authority(authority)
904                 .query("")  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
905                 .fragment("");  // TODO: Remove, workaround for a bug in Uri.writeToParcel()
906 
907         // if content path provided, insert it now
908         final String contentPath = searchable.getSuggestPath();
909         if (contentPath != null) {
910             uriBuilder.appendEncodedPath(contentPath);
911         }
912 
913         // append standard suggestion query path
914         uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY);
915 
916         // get the query selection, may be null
917         String selection = searchable.getSuggestSelection();
918         // inject query, either as selection args or inline
919         String[] selArgs = null;
920         if (selection != null) {    // use selection if provided
921             selArgs = new String[] { query };
922         } else {                    // no selection, use REST pattern
923             uriBuilder.appendPath(query);
924         }
925 
926         if (limit > 0) {
927             uriBuilder.appendQueryParameter(SUGGEST_PARAMETER_LIMIT, String.valueOf(limit));
928         }
929 
930         Uri uri = uriBuilder.build();
931 
932         // finally, make the query
933         return mContext.getContentResolver().query(uri, null, selection, selArgs, null);
934     }
935 
936     /**
937      * Returns a list of the searchable activities that can be included in global search.
938      *
939      * @return a list containing searchable information for all searchable activities
940      *         that have the <code>android:includeInGlobalSearch</code> attribute set
941      *         in their searchable meta-data.
942      */
getSearchablesInGlobalSearch()943     public List<SearchableInfo> getSearchablesInGlobalSearch() {
944         try {
945             return mService.getSearchablesInGlobalSearch();
946         } catch (RemoteException e) {
947             Log.e(TAG, "getSearchablesInGlobalSearch() failed: " + e);
948             return null;
949         }
950     }
951 
952     /**
953      * Gets an intent for launching installed assistant activity, or null if not available.
954      * @return The assist intent.
955      *
956      * @hide
957      */
getAssistIntent(Context context, boolean inclContext)958     public Intent getAssistIntent(Context context, boolean inclContext) {
959         return getAssistIntent(context, inclContext, UserHandle.myUserId());
960     }
961 
962     /**
963      * Gets an intent for launching installed assistant activity, or null if not available.
964      * @return The assist intent.
965      *
966      * @hide
967      */
getAssistIntent(Context context, boolean inclContext, int userHandle)968     public Intent getAssistIntent(Context context, boolean inclContext, int userHandle) {
969         try {
970             if (mService == null) {
971                 return null;
972             }
973             ComponentName comp = mService.getAssistIntent(userHandle);
974             if (comp == null) {
975                 return null;
976             }
977             Intent intent = new Intent(Intent.ACTION_ASSIST);
978             intent.setComponent(comp);
979             if (inclContext) {
980                 IActivityManager am = ActivityManagerNative.getDefault();
981                 Bundle extras = am.getAssistContextExtras(0);
982                 if (extras != null) {
983                     intent.replaceExtras(extras);
984                 }
985             }
986             return intent;
987         } catch (RemoteException re) {
988             Log.e(TAG, "getAssistIntent() failed: " + re);
989             return null;
990         }
991     }
992 
993     /**
994      * Launch an assist action for the current top activity.
995      * @hide
996      */
launchAssistAction(int requestType, String hint, int userHandle)997     public boolean launchAssistAction(int requestType, String hint, int userHandle) {
998         try {
999             if (mService == null) {
1000                 return false;
1001             }
1002             return mService.launchAssistAction(requestType, hint, userHandle);
1003         } catch (RemoteException re) {
1004             Log.e(TAG, "launchAssistAction() failed: " + re);
1005             return false;
1006         }
1007     }
1008 }
1009