• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2008 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 
20 import android.content.BroadcastReceiver;
21 import android.content.ComponentName;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentFilter;
25 import android.content.pm.ActivityInfo;
26 import android.content.pm.PackageManager;
27 import android.content.pm.PackageManager.NameNotFoundException;
28 import android.content.res.Configuration;
29 import android.graphics.drawable.Drawable;
30 import android.net.Uri;
31 import android.os.Bundle;
32 import android.speech.RecognizerIntent;
33 import android.text.InputType;
34 import android.text.TextUtils;
35 import android.util.AttributeSet;
36 import android.util.Log;
37 import android.util.TypedValue;
38 import android.view.ActionMode;
39 import android.view.Gravity;
40 import android.view.KeyEvent;
41 import android.view.MotionEvent;
42 import android.view.View;
43 import android.view.ViewConfiguration;
44 import android.view.ViewGroup;
45 import android.view.Window;
46 import android.view.WindowManager;
47 import android.view.inputmethod.InputMethodManager;
48 import android.widget.AutoCompleteTextView;
49 import android.widget.ImageView;
50 import android.widget.LinearLayout;
51 import android.widget.SearchView;
52 import android.widget.TextView;
53 
54 /**
55  * Search dialog. This is controlled by the
56  * SearchManager and runs in the current foreground process.
57  *
58  * @hide
59  */
60 public class SearchDialog extends Dialog {
61 
62     // Debugging support
63     private static final boolean DBG = false;
64     private static final String LOG_TAG = "SearchDialog";
65 
66     private static final String INSTANCE_KEY_COMPONENT = "comp";
67     private static final String INSTANCE_KEY_APPDATA = "data";
68     private static final String INSTANCE_KEY_USER_QUERY = "uQry";
69 
70     // The string used for privateImeOptions to identify to the IME that it should not show
71     // a microphone button since one already exists in the search dialog.
72     private static final String IME_OPTION_NO_MICROPHONE = "nm";
73 
74     private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
75 
76     // views & widgets
77     private TextView mBadgeLabel;
78     private ImageView mAppIcon;
79     private AutoCompleteTextView mSearchAutoComplete;
80     private View mSearchPlate;
81     private SearchView mSearchView;
82     private Drawable mWorkingSpinner;
83     private View mCloseSearch;
84 
85     // interaction with searchable application
86     private SearchableInfo mSearchable;
87     private ComponentName mLaunchComponent;
88     private Bundle mAppSearchData;
89     private Context mActivityContext;
90 
91     // For voice searching
92     private final Intent mVoiceWebSearchIntent;
93     private final Intent mVoiceAppSearchIntent;
94 
95     // The query entered by the user. This is not changed when selecting a suggestion
96     // that modifies the contents of the text field. But if the user then edits
97     // the suggestion, the resulting string is saved.
98     private String mUserQuery;
99 
100     // Last known IME options value for the search edit text.
101     private int mSearchAutoCompleteImeOptions;
102 
103     private BroadcastReceiver mConfChangeListener = new BroadcastReceiver() {
104         @Override
105         public void onReceive(Context context, Intent intent) {
106             if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
107                 onConfigurationChanged();
108             }
109         }
110     };
111 
resolveDialogTheme(Context context)112     static int resolveDialogTheme(Context context) {
113         TypedValue outValue = new TypedValue();
114         context.getTheme().resolveAttribute(com.android.internal.R.attr.searchDialogTheme,
115                 outValue, true);
116         return outValue.resourceId;
117     }
118 
119     /**
120      * Constructor - fires it up and makes it look like the search UI.
121      *
122      * @param context Application Context we can use for system acess
123      */
SearchDialog(Context context, SearchManager searchManager)124     public SearchDialog(Context context, SearchManager searchManager) {
125         super(context, resolveDialogTheme(context));
126 
127         // Save voice intent for later queries/launching
128         mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
129         mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
130         mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
131                 RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
132 
133         mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
134         mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
135     }
136 
137     /**
138      * Create the search dialog and any resources that are used for the
139      * entire lifetime of the dialog.
140      */
141     @Override
onCreate(Bundle savedInstanceState)142     protected void onCreate(Bundle savedInstanceState) {
143         super.onCreate(savedInstanceState);
144 
145         Window theWindow = getWindow();
146         WindowManager.LayoutParams lp = theWindow.getAttributes();
147         lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
148         // taking up the whole window (even when transparent) is less than ideal,
149         // but necessary to show the popup window until the window manager supports
150         // having windows anchored by their parent but not clipped by them.
151         lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
152         lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
153         lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
154         theWindow.setAttributes(lp);
155 
156         // Touching outside of the search dialog will dismiss it
157         setCanceledOnTouchOutside(true);
158     }
159 
160     /**
161      * We recreate the dialog view each time it becomes visible so as to limit
162      * the scope of any problems with the contained resources.
163      */
createContentView()164     private void createContentView() {
165         setContentView(com.android.internal.R.layout.search_bar);
166 
167         // get the view elements for local access
168         SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar);
169         searchBar.setSearchDialog(this);
170         mSearchView = (SearchView) findViewById(com.android.internal.R.id.search_view);
171         mSearchView.setIconified(false);
172         mSearchView.setOnCloseListener(mOnCloseListener);
173         mSearchView.setOnQueryTextListener(mOnQueryChangeListener);
174         mSearchView.setOnSuggestionListener(mOnSuggestionSelectionListener);
175         mSearchView.onActionViewExpanded();
176 
177         mCloseSearch = findViewById(com.android.internal.R.id.closeButton);
178         mCloseSearch.setOnClickListener(new View.OnClickListener() {
179             @Override
180             public void onClick(View v) {
181                 dismiss();
182             }
183         });
184 
185         // TODO: Move the badge logic to SearchView or move the badge to search_bar.xml
186         mBadgeLabel = (TextView) mSearchView.findViewById(com.android.internal.R.id.search_badge);
187         mSearchAutoComplete = (AutoCompleteTextView)
188                 mSearchView.findViewById(com.android.internal.R.id.search_src_text);
189         mAppIcon = (ImageView) findViewById(com.android.internal.R.id.search_app_icon);
190         mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate);
191         mWorkingSpinner = getContext().getResources().
192                 getDrawable(com.android.internal.R.drawable.search_spinner);
193         // TODO: Restore the spinner for slow suggestion lookups
194         // mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
195         //        null, null, mWorkingSpinner, null);
196         setWorking(false);
197 
198         // pre-hide all the extraneous elements
199         mBadgeLabel.setVisibility(View.GONE);
200 
201         // Additional adjustments to make Dialog work for Search
202         mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions();
203     }
204 
205     /**
206      * Set up the search dialog
207      *
208      * @return true if search dialog launched, false if not
209      */
show(String initialQuery, boolean selectInitialQuery, ComponentName componentName, Bundle appSearchData)210     public boolean show(String initialQuery, boolean selectInitialQuery,
211             ComponentName componentName, Bundle appSearchData) {
212         boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData);
213         if (success) {
214             // Display the drop down as soon as possible instead of waiting for the rest of the
215             // pending UI stuff to get done, so that things appear faster to the user.
216             mSearchAutoComplete.showDropDownAfterLayout();
217         }
218         return success;
219     }
220 
221     /**
222      * Does the rest of the work required to show the search dialog. Called by
223      * {@link #show(String, boolean, ComponentName, Bundle)} and
224      *
225      * @return true if search dialog showed, false if not
226      */
doShow(String initialQuery, boolean selectInitialQuery, ComponentName componentName, Bundle appSearchData)227     private boolean doShow(String initialQuery, boolean selectInitialQuery,
228             ComponentName componentName, Bundle appSearchData) {
229         // set up the searchable and show the dialog
230         if (!show(componentName, appSearchData)) {
231             return false;
232         }
233 
234         // finally, load the user's initial text (which may trigger suggestions)
235         setUserQuery(initialQuery);
236         if (selectInitialQuery) {
237             mSearchAutoComplete.selectAll();
238         }
239 
240         return true;
241     }
242 
243     /**
244      * Sets up the search dialog and shows it.
245      *
246      * @return <code>true</code> if search dialog launched
247      */
show(ComponentName componentName, Bundle appSearchData)248     private boolean show(ComponentName componentName, Bundle appSearchData) {
249 
250         if (DBG) {
251             Log.d(LOG_TAG, "show(" + componentName + ", "
252                     + appSearchData + ")");
253         }
254 
255         SearchManager searchManager = (SearchManager)
256                 mContext.getSystemService(Context.SEARCH_SERVICE);
257         // Try to get the searchable info for the provided component.
258         mSearchable = searchManager.getSearchableInfo(componentName);
259 
260         if (mSearchable == null) {
261             return false;
262         }
263 
264         mLaunchComponent = componentName;
265         mAppSearchData = appSearchData;
266         mActivityContext = mSearchable.getActivityContext(getContext());
267 
268         // show the dialog. this will call onStart().
269         if (!isShowing()) {
270             // Recreate the search bar view every time the dialog is shown, to get rid
271             // of any bad state in the AutoCompleteTextView etc
272             createContentView();
273             mSearchView.setSearchableInfo(mSearchable);
274             mSearchView.setAppSearchData(mAppSearchData);
275 
276             show();
277         }
278         updateUI();
279 
280         return true;
281     }
282 
283     @Override
onStart()284     public void onStart() {
285         super.onStart();
286 
287         // Register a listener for configuration change events.
288         IntentFilter filter = new IntentFilter();
289         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
290         getContext().registerReceiver(mConfChangeListener, filter);
291     }
292 
293     /**
294      * The search dialog is being dismissed, so handle all of the local shutdown operations.
295      *
296      * This function is designed to be idempotent so that dismiss() can be safely called at any time
297      * (even if already closed) and more likely to really dump any memory.  No leaks!
298      */
299     @Override
onStop()300     public void onStop() {
301         super.onStop();
302 
303         getContext().unregisterReceiver(mConfChangeListener);
304 
305         // dump extra memory we're hanging on to
306         mLaunchComponent = null;
307         mAppSearchData = null;
308         mSearchable = null;
309         mUserQuery = null;
310     }
311 
312     /**
313      * Sets the search dialog to the 'working' state, which shows a working spinner in the
314      * right hand size of the text field.
315      *
316      * @param working true to show spinner, false to hide spinner
317      */
setWorking(boolean working)318     public void setWorking(boolean working) {
319         mWorkingSpinner.setAlpha(working ? 255 : 0);
320         mWorkingSpinner.setVisible(working, false);
321         mWorkingSpinner.invalidateSelf();
322     }
323 
324     /**
325      * Save the minimal set of data necessary to recreate the search
326      *
327      * @return A bundle with the state of the dialog, or {@code null} if the search
328      *         dialog is not showing.
329      */
330     @Override
onSaveInstanceState()331     public Bundle onSaveInstanceState() {
332         if (!isShowing()) return null;
333 
334         Bundle bundle = new Bundle();
335 
336         // setup info so I can recreate this particular search
337         bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
338         bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
339         bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
340 
341         return bundle;
342     }
343 
344     /**
345      * Restore the state of the dialog from a previously saved bundle.
346      *
347      * @param savedInstanceState The state of the dialog previously saved by
348      *     {@link #onSaveInstanceState()}.
349      */
350     @Override
onRestoreInstanceState(Bundle savedInstanceState)351     public void onRestoreInstanceState(Bundle savedInstanceState) {
352         if (savedInstanceState == null) return;
353 
354         ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
355         Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
356         String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
357 
358         // show the dialog.
359         if (!doShow(userQuery, false, launchComponent, appSearchData)) {
360             // for some reason, we couldn't re-instantiate
361             return;
362         }
363     }
364 
365     /**
366      * Called after resources have changed, e.g. after screen rotation or locale change.
367      */
onConfigurationChanged()368     public void onConfigurationChanged() {
369         if (mSearchable != null && isShowing()) {
370             // Redraw (resources may have changed)
371             updateSearchAppIcon();
372             updateSearchBadge();
373             if (isLandscapeMode(getContext())) {
374                 mSearchAutoComplete.ensureImeVisible(true);
375             }
376         }
377     }
378 
isLandscapeMode(Context context)379     static boolean isLandscapeMode(Context context) {
380         return context.getResources().getConfiguration().orientation
381                 == Configuration.ORIENTATION_LANDSCAPE;
382     }
383 
384     /**
385      * Update the UI according to the info in the current value of {@link #mSearchable}.
386      */
updateUI()387     private void updateUI() {
388         if (mSearchable != null) {
389             mDecor.setVisibility(View.VISIBLE);
390             updateSearchAutoComplete();
391             updateSearchAppIcon();
392             updateSearchBadge();
393 
394             // In order to properly configure the input method (if one is being used), we
395             // need to let it know if we'll be providing suggestions.  Although it would be
396             // difficult/expensive to know if every last detail has been configured properly, we
397             // can at least see if a suggestions provider has been configured, and use that
398             // as our trigger.
399             int inputType = mSearchable.getInputType();
400             // We only touch this if the input type is set up for text (which it almost certainly
401             // should be, in the case of search!)
402             if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
403                 // The existence of a suggestions authority is the proxy for "suggestions
404                 // are available here"
405                 inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
406                 if (mSearchable.getSuggestAuthority() != null) {
407                     inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
408                 }
409             }
410             mSearchAutoComplete.setInputType(inputType);
411             mSearchAutoCompleteImeOptions = mSearchable.getImeOptions();
412             mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions);
413 
414             // If the search dialog is going to show a voice search button, then don't let
415             // the soft keyboard display a microphone button if it would have otherwise.
416             if (mSearchable.getVoiceSearchEnabled()) {
417                 mSearchAutoComplete.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
418             } else {
419                 mSearchAutoComplete.setPrivateImeOptions(null);
420             }
421         }
422     }
423 
424     /**
425      * Updates the auto-complete text view.
426      */
updateSearchAutoComplete()427     private void updateSearchAutoComplete() {
428         // we dismiss the entire dialog instead
429         mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
430         mSearchAutoComplete.setForceIgnoreOutsideTouch(false);
431     }
432 
updateSearchAppIcon()433     private void updateSearchAppIcon() {
434         PackageManager pm = getContext().getPackageManager();
435         Drawable icon;
436         try {
437             ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
438             icon = pm.getApplicationIcon(info.applicationInfo);
439             if (DBG)
440                 Log.d(LOG_TAG, "Using app-specific icon");
441         } catch (NameNotFoundException e) {
442             icon = pm.getDefaultActivityIcon();
443             Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
444         }
445         mAppIcon.setImageDrawable(icon);
446         mAppIcon.setVisibility(View.VISIBLE);
447         mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL, mSearchPlate.getPaddingTop(), mSearchPlate.getPaddingRight(), mSearchPlate.getPaddingBottom());
448     }
449 
450     /**
451      * Setup the search "Badge" if requested by mode flags.
452      */
updateSearchBadge()453     private void updateSearchBadge() {
454         // assume both hidden
455         int visibility = View.GONE;
456         Drawable icon = null;
457         CharSequence text = null;
458 
459         // optionally show one or the other.
460         if (mSearchable.useBadgeIcon()) {
461             icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId());
462             visibility = View.VISIBLE;
463             if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId());
464         } else if (mSearchable.useBadgeLabel()) {
465             text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
466             visibility = View.VISIBLE;
467             if (DBG) Log.d(LOG_TAG, "Using badge label: " + mSearchable.getLabelId());
468         }
469 
470         mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
471         mBadgeLabel.setText(text);
472         mBadgeLabel.setVisibility(visibility);
473     }
474 
475     /*
476      * Listeners of various types
477      */
478 
479     /**
480      * {@link Dialog#onTouchEvent(MotionEvent)} will cancel the dialog only when the
481      * touch is outside the window. But the window includes space for the drop-down,
482      * so we also cancel on taps outside the search bar when the drop-down is not showing.
483      */
484     @Override
onTouchEvent(MotionEvent event)485     public boolean onTouchEvent(MotionEvent event) {
486         // cancel if the drop-down is not showing and the touch event was outside the search plate
487         if (!mSearchAutoComplete.isPopupShowing() && isOutOfBounds(mSearchPlate, event)) {
488             if (DBG) Log.d(LOG_TAG, "Pop-up not showing and outside of search plate.");
489             cancel();
490             return true;
491         }
492         // Let Dialog handle events outside the window while the pop-up is showing.
493         return super.onTouchEvent(event);
494     }
495 
isOutOfBounds(View v, MotionEvent event)496     private boolean isOutOfBounds(View v, MotionEvent event) {
497         final int x = (int) event.getX();
498         final int y = (int) event.getY();
499         final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
500         return (x < -slop) || (y < -slop)
501                 || (x > (v.getWidth()+slop))
502                 || (y > (v.getHeight()+slop));
503     }
504 
505     @Override
hide()506     public void hide() {
507         if (!isShowing()) return;
508 
509         // We made sure the IME was displayed, so also make sure it is closed
510         // when we go away.
511         InputMethodManager imm = (InputMethodManager)getContext()
512                 .getSystemService(Context.INPUT_METHOD_SERVICE);
513         if (imm != null) {
514             imm.hideSoftInputFromWindow(
515                     getWindow().getDecorView().getWindowToken(), 0);
516         }
517 
518         super.hide();
519     }
520 
521     /**
522      * Launch a search for the text in the query text field.
523      */
launchQuerySearch()524     public void launchQuerySearch() {
525         launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
526     }
527 
528     /**
529      * Launch a search for the text in the query text field.
530      *
531      * @param actionKey The key code of the action key that was pressed,
532      *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
533      * @param actionMsg The message for the action key that was pressed,
534      *        or <code>null</code> if none.
535      */
launchQuerySearch(int actionKey, String actionMsg)536     protected void launchQuerySearch(int actionKey, String actionMsg) {
537         String query = mSearchAutoComplete.getText().toString();
538         String action = Intent.ACTION_SEARCH;
539         Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
540         launchIntent(intent);
541     }
542 
543     /**
544      * Launches an intent, including any special intent handling.
545      */
launchIntent(Intent intent)546     private void launchIntent(Intent intent) {
547         if (intent == null) {
548             return;
549         }
550         Log.d(LOG_TAG, "launching " + intent);
551         try {
552             // If the intent was created from a suggestion, it will always have an explicit
553             // component here.
554             getContext().startActivity(intent);
555             // If the search switches to a different activity,
556             // SearchDialogWrapper#performActivityResuming
557             // will handle hiding the dialog when the next activity starts, but for
558             // real in-app search, we still need to dismiss the dialog.
559             dismiss();
560         } catch (RuntimeException ex) {
561             Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
562         }
563     }
564 
565     /**
566      * Sets the list item selection in the AutoCompleteTextView's ListView.
567      */
setListSelection(int index)568     public void setListSelection(int index) {
569         mSearchAutoComplete.setListSelection(index);
570     }
571 
572     /**
573      * Constructs an intent from the given information and the search dialog state.
574      *
575      * @param action Intent action.
576      * @param data Intent data, or <code>null</code>.
577      * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
578      * @param query Intent query, or <code>null</code>.
579      * @param actionKey The key code of the action key that was pressed,
580      *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
581      * @param actionMsg The message for the action key that was pressed,
582      *        or <code>null</code> if none.
583      * @param mode The search mode, one of the acceptable values for
584      *             {@link SearchManager#SEARCH_MODE}, or {@code null}.
585      * @return The intent.
586      */
createIntent(String action, Uri data, String extraData, String query, int actionKey, String actionMsg)587     private Intent createIntent(String action, Uri data, String extraData, String query,
588             int actionKey, String actionMsg) {
589         // Now build the Intent
590         Intent intent = new Intent(action);
591         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
592         // We need CLEAR_TOP to avoid reusing an old task that has other activities
593         // on top of the one we want. We don't want to do this in in-app search though,
594         // as it can be destructive to the activity stack.
595         if (data != null) {
596             intent.setData(data);
597         }
598         intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
599         if (query != null) {
600             intent.putExtra(SearchManager.QUERY, query);
601         }
602         if (extraData != null) {
603             intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
604         }
605         if (mAppSearchData != null) {
606             intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
607         }
608         if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
609             intent.putExtra(SearchManager.ACTION_KEY, actionKey);
610             intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
611         }
612         intent.setComponent(mSearchable.getSearchActivity());
613         return intent;
614     }
615 
616     /**
617      * The root element in the search bar layout. This is a custom view just to override
618      * the handling of the back button.
619      */
620     public static class SearchBar extends LinearLayout {
621 
622         private SearchDialog mSearchDialog;
623 
SearchBar(Context context, AttributeSet attrs)624         public SearchBar(Context context, AttributeSet attrs) {
625             super(context, attrs);
626         }
627 
SearchBar(Context context)628         public SearchBar(Context context) {
629             super(context);
630         }
631 
setSearchDialog(SearchDialog searchDialog)632         public void setSearchDialog(SearchDialog searchDialog) {
633             mSearchDialog = searchDialog;
634         }
635 
636         /**
637          * Don't allow action modes in a SearchBar, it looks silly.
638          */
639         @Override
startActionModeForChild(View child, ActionMode.Callback callback)640         public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
641             return null;
642         }
643     }
644 
isEmpty(AutoCompleteTextView actv)645     private boolean isEmpty(AutoCompleteTextView actv) {
646         return TextUtils.getTrimmedLength(actv.getText()) == 0;
647     }
648 
649     @Override
onBackPressed()650     public void onBackPressed() {
651         // If the input method is covering the search dialog completely,
652         // e.g. in landscape mode with no hard keyboard, dismiss just the input method
653         InputMethodManager imm = (InputMethodManager)getContext()
654                 .getSystemService(Context.INPUT_METHOD_SERVICE);
655         if (imm != null && imm.isFullscreenMode() &&
656                 imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) {
657             return;
658         }
659         // Close search dialog
660         cancel();
661     }
662 
onClosePressed()663     private boolean onClosePressed() {
664         // Dismiss the dialog if close button is pressed when there's no query text
665         if (isEmpty(mSearchAutoComplete)) {
666             dismiss();
667             return true;
668         }
669 
670         return false;
671     }
672 
673     private final SearchView.OnCloseListener mOnCloseListener = new SearchView.OnCloseListener() {
674 
675         public boolean onClose() {
676             return onClosePressed();
677         }
678     };
679 
680     private final SearchView.OnQueryTextListener mOnQueryChangeListener =
681             new SearchView.OnQueryTextListener() {
682 
683         public boolean onQueryTextSubmit(String query) {
684             dismiss();
685             return false;
686         }
687 
688         public boolean onQueryTextChange(String newText) {
689             return false;
690         }
691     };
692 
693     private final SearchView.OnSuggestionListener mOnSuggestionSelectionListener =
694             new SearchView.OnSuggestionListener() {
695 
696         public boolean onSuggestionSelect(int position) {
697             return false;
698         }
699 
700         public boolean onSuggestionClick(int position) {
701             dismiss();
702             return false;
703         }
704     };
705 
706     /**
707      * Sets the text in the query box, updating the suggestions.
708      */
setUserQuery(String query)709     private void setUserQuery(String query) {
710         if (query == null) {
711             query = "";
712         }
713         mUserQuery = query;
714         mSearchAutoComplete.setText(query);
715         mSearchAutoComplete.setSelection(query.length());
716     }
717 }
718