Home | History | Annotate | Download | only in app
      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 
    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      */
    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
    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      */
    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      */
    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      */
    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      */
    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
    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
    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      */
    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
    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
    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      */
    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 
    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      */
    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      */
    427     private void updateSearchAutoComplete() {
    428         // we dismiss the entire dialog instead
    429         mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
    430         mSearchAutoComplete.setForceIgnoreOutsideTouch(false);
    431     }
    432 
    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      */
    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
    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 
    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
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    624         public SearchBar(Context context, AttributeSet attrs) {
    625             super(context, attrs);
    626         }
    627 
    628         public SearchBar(Context context) {
    629             super(context);
    630         }
    631 
    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
    640         public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
    641             return null;
    642         }
    643     }
    644 
    645     private boolean isEmpty(AutoCompleteTextView actv) {
    646         return TextUtils.getTrimmedLength(actv.getText()) == 0;
    647     }
    648 
    649     @Override
    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 
    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      */
    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