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().getDrawable(com.android.internal.R.drawable.search_spinner);
    192         // TODO: Restore the spinner for slow suggestion lookups
    193         // mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
    194         //        null, null, mWorkingSpinner, null);
    195         setWorking(false);
    196 
    197         // pre-hide all the extraneous elements
    198         mBadgeLabel.setVisibility(View.GONE);
    199 
    200         // Additional adjustments to make Dialog work for Search
    201         mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions();
    202     }
    203 
    204     /**
    205      * Set up the search dialog
    206      *
    207      * @return true if search dialog launched, false if not
    208      */
    209     public boolean show(String initialQuery, boolean selectInitialQuery,
    210             ComponentName componentName, Bundle appSearchData) {
    211         boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData);
    212         if (success) {
    213             // Display the drop down as soon as possible instead of waiting for the rest of the
    214             // pending UI stuff to get done, so that things appear faster to the user.
    215             mSearchAutoComplete.showDropDownAfterLayout();
    216         }
    217         return success;
    218     }
    219 
    220     /**
    221      * Does the rest of the work required to show the search dialog. Called by
    222      * {@link #show(String, boolean, ComponentName, Bundle)} and
    223      *
    224      * @return true if search dialog showed, false if not
    225      */
    226     private boolean doShow(String initialQuery, boolean selectInitialQuery,
    227             ComponentName componentName, Bundle appSearchData) {
    228         // set up the searchable and show the dialog
    229         if (!show(componentName, appSearchData)) {
    230             return false;
    231         }
    232 
    233         // finally, load the user's initial text (which may trigger suggestions)
    234         setUserQuery(initialQuery);
    235         if (selectInitialQuery) {
    236             mSearchAutoComplete.selectAll();
    237         }
    238 
    239         return true;
    240     }
    241 
    242     /**
    243      * Sets up the search dialog and shows it.
    244      *
    245      * @return <code>true</code> if search dialog launched
    246      */
    247     private boolean show(ComponentName componentName, Bundle appSearchData) {
    248 
    249         if (DBG) {
    250             Log.d(LOG_TAG, "show(" + componentName + ", "
    251                     + appSearchData + ")");
    252         }
    253 
    254         SearchManager searchManager = (SearchManager)
    255                 mContext.getSystemService(Context.SEARCH_SERVICE);
    256         // Try to get the searchable info for the provided component.
    257         mSearchable = searchManager.getSearchableInfo(componentName);
    258 
    259         if (mSearchable == null) {
    260             return false;
    261         }
    262 
    263         mLaunchComponent = componentName;
    264         mAppSearchData = appSearchData;
    265         mActivityContext = mSearchable.getActivityContext(getContext());
    266 
    267         // show the dialog. this will call onStart().
    268         if (!isShowing()) {
    269             // Recreate the search bar view every time the dialog is shown, to get rid
    270             // of any bad state in the AutoCompleteTextView etc
    271             createContentView();
    272             mSearchView.setSearchableInfo(mSearchable);
    273             mSearchView.setAppSearchData(mAppSearchData);
    274 
    275             show();
    276         }
    277         updateUI();
    278 
    279         return true;
    280     }
    281 
    282     @Override
    283     public void onStart() {
    284         super.onStart();
    285 
    286         // Register a listener for configuration change events.
    287         IntentFilter filter = new IntentFilter();
    288         filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
    289         getContext().registerReceiver(mConfChangeListener, filter);
    290     }
    291 
    292     /**
    293      * The search dialog is being dismissed, so handle all of the local shutdown operations.
    294      *
    295      * This function is designed to be idempotent so that dismiss() can be safely called at any time
    296      * (even if already closed) and more likely to really dump any memory.  No leaks!
    297      */
    298     @Override
    299     public void onStop() {
    300         super.onStop();
    301 
    302         getContext().unregisterReceiver(mConfChangeListener);
    303 
    304         // dump extra memory we're hanging on to
    305         mLaunchComponent = null;
    306         mAppSearchData = null;
    307         mSearchable = null;
    308         mUserQuery = null;
    309     }
    310 
    311     /**
    312      * Sets the search dialog to the 'working' state, which shows a working spinner in the
    313      * right hand size of the text field.
    314      *
    315      * @param working true to show spinner, false to hide spinner
    316      */
    317     public void setWorking(boolean working) {
    318         mWorkingSpinner.setAlpha(working ? 255 : 0);
    319         mWorkingSpinner.setVisible(working, false);
    320         mWorkingSpinner.invalidateSelf();
    321     }
    322 
    323     /**
    324      * Save the minimal set of data necessary to recreate the search
    325      *
    326      * @return A bundle with the state of the dialog, or {@code null} if the search
    327      *         dialog is not showing.
    328      */
    329     @Override
    330     public Bundle onSaveInstanceState() {
    331         if (!isShowing()) return null;
    332 
    333         Bundle bundle = new Bundle();
    334 
    335         // setup info so I can recreate this particular search
    336         bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
    337         bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
    338         bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
    339 
    340         return bundle;
    341     }
    342 
    343     /**
    344      * Restore the state of the dialog from a previously saved bundle.
    345      *
    346      * @param savedInstanceState The state of the dialog previously saved by
    347      *     {@link #onSaveInstanceState()}.
    348      */
    349     @Override
    350     public void onRestoreInstanceState(Bundle savedInstanceState) {
    351         if (savedInstanceState == null) return;
    352 
    353         ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
    354         Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
    355         String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
    356 
    357         // show the dialog.
    358         if (!doShow(userQuery, false, launchComponent, appSearchData)) {
    359             // for some reason, we couldn't re-instantiate
    360             return;
    361         }
    362     }
    363 
    364     /**
    365      * Called after resources have changed, e.g. after screen rotation or locale change.
    366      */
    367     public void onConfigurationChanged() {
    368         if (mSearchable != null && isShowing()) {
    369             // Redraw (resources may have changed)
    370             updateSearchAppIcon();
    371             updateSearchBadge();
    372             if (isLandscapeMode(getContext())) {
    373                 mSearchAutoComplete.ensureImeVisible(true);
    374             }
    375         }
    376     }
    377 
    378     static boolean isLandscapeMode(Context context) {
    379         return context.getResources().getConfiguration().orientation
    380                 == Configuration.ORIENTATION_LANDSCAPE;
    381     }
    382 
    383     /**
    384      * Update the UI according to the info in the current value of {@link #mSearchable}.
    385      */
    386     private void updateUI() {
    387         if (mSearchable != null) {
    388             mDecor.setVisibility(View.VISIBLE);
    389             updateSearchAutoComplete();
    390             updateSearchAppIcon();
    391             updateSearchBadge();
    392 
    393             // In order to properly configure the input method (if one is being used), we
    394             // need to let it know if we'll be providing suggestions.  Although it would be
    395             // difficult/expensive to know if every last detail has been configured properly, we
    396             // can at least see if a suggestions provider has been configured, and use that
    397             // as our trigger.
    398             int inputType = mSearchable.getInputType();
    399             // We only touch this if the input type is set up for text (which it almost certainly
    400             // should be, in the case of search!)
    401             if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
    402                 // The existence of a suggestions authority is the proxy for "suggestions
    403                 // are available here"
    404                 inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
    405                 if (mSearchable.getSuggestAuthority() != null) {
    406                     inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
    407                 }
    408             }
    409             mSearchAutoComplete.setInputType(inputType);
    410             mSearchAutoCompleteImeOptions = mSearchable.getImeOptions();
    411             mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions);
    412 
    413             // If the search dialog is going to show a voice search button, then don't let
    414             // the soft keyboard display a microphone button if it would have otherwise.
    415             if (mSearchable.getVoiceSearchEnabled()) {
    416                 mSearchAutoComplete.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
    417             } else {
    418                 mSearchAutoComplete.setPrivateImeOptions(null);
    419             }
    420         }
    421     }
    422 
    423     /**
    424      * Updates the auto-complete text view.
    425      */
    426     private void updateSearchAutoComplete() {
    427         // we dismiss the entire dialog instead
    428         mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
    429         mSearchAutoComplete.setForceIgnoreOutsideTouch(false);
    430     }
    431 
    432     private void updateSearchAppIcon() {
    433         PackageManager pm = getContext().getPackageManager();
    434         Drawable icon;
    435         try {
    436             ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
    437             icon = pm.getApplicationIcon(info.applicationInfo);
    438             if (DBG)
    439                 Log.d(LOG_TAG, "Using app-specific icon");
    440         } catch (NameNotFoundException e) {
    441             icon = pm.getDefaultActivityIcon();
    442             Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
    443         }
    444         mAppIcon.setImageDrawable(icon);
    445         mAppIcon.setVisibility(View.VISIBLE);
    446         mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL, mSearchPlate.getPaddingTop(), mSearchPlate.getPaddingRight(), mSearchPlate.getPaddingBottom());
    447     }
    448 
    449     /**
    450      * Setup the search "Badge" if requested by mode flags.
    451      */
    452     private void updateSearchBadge() {
    453         // assume both hidden
    454         int visibility = View.GONE;
    455         Drawable icon = null;
    456         CharSequence text = null;
    457 
    458         // optionally show one or the other.
    459         if (mSearchable.useBadgeIcon()) {
    460             icon = mActivityContext.getDrawable(mSearchable.getIconId());
    461             visibility = View.VISIBLE;
    462             if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId());
    463         } else if (mSearchable.useBadgeLabel()) {
    464             text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
    465             visibility = View.VISIBLE;
    466             if (DBG) Log.d(LOG_TAG, "Using badge label: " + mSearchable.getLabelId());
    467         }
    468 
    469         mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
    470         mBadgeLabel.setText(text);
    471         mBadgeLabel.setVisibility(visibility);
    472     }
    473 
    474     /*
    475      * Listeners of various types
    476      */
    477 
    478     /**
    479      * {@link Dialog#onTouchEvent(MotionEvent)} will cancel the dialog only when the
    480      * touch is outside the window. But the window includes space for the drop-down,
    481      * so we also cancel on taps outside the search bar when the drop-down is not showing.
    482      */
    483     @Override
    484     public boolean onTouchEvent(MotionEvent event) {
    485         // cancel if the drop-down is not showing and the touch event was outside the search plate
    486         if (!mSearchAutoComplete.isPopupShowing() && isOutOfBounds(mSearchPlate, event)) {
    487             if (DBG) Log.d(LOG_TAG, "Pop-up not showing and outside of search plate.");
    488             cancel();
    489             return true;
    490         }
    491         // Let Dialog handle events outside the window while the pop-up is showing.
    492         return super.onTouchEvent(event);
    493     }
    494 
    495     private boolean isOutOfBounds(View v, MotionEvent event) {
    496         final int x = (int) event.getX();
    497         final int y = (int) event.getY();
    498         final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
    499         return (x < -slop) || (y < -slop)
    500                 || (x > (v.getWidth()+slop))
    501                 || (y > (v.getHeight()+slop));
    502     }
    503 
    504     @Override
    505     public void hide() {
    506         if (!isShowing()) return;
    507 
    508         // We made sure the IME was displayed, so also make sure it is closed
    509         // when we go away.
    510         InputMethodManager imm = (InputMethodManager)getContext()
    511                 .getSystemService(Context.INPUT_METHOD_SERVICE);
    512         if (imm != null) {
    513             imm.hideSoftInputFromWindow(
    514                     getWindow().getDecorView().getWindowToken(), 0);
    515         }
    516 
    517         super.hide();
    518     }
    519 
    520     /**
    521      * Launch a search for the text in the query text field.
    522      */
    523     public void launchQuerySearch() {
    524         launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
    525     }
    526 
    527     /**
    528      * Launch a search for the text in the query text field.
    529      *
    530      * @param actionKey The key code of the action key that was pressed,
    531      *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
    532      * @param actionMsg The message for the action key that was pressed,
    533      *        or <code>null</code> if none.
    534      */
    535     protected void launchQuerySearch(int actionKey, String actionMsg) {
    536         String query = mSearchAutoComplete.getText().toString();
    537         String action = Intent.ACTION_SEARCH;
    538         Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
    539         launchIntent(intent);
    540     }
    541 
    542     /**
    543      * Launches an intent, including any special intent handling.
    544      */
    545     private void launchIntent(Intent intent) {
    546         if (intent == null) {
    547             return;
    548         }
    549         Log.d(LOG_TAG, "launching " + intent);
    550         try {
    551             // If the intent was created from a suggestion, it will always have an explicit
    552             // component here.
    553             getContext().startActivity(intent);
    554             // If the search switches to a different activity,
    555             // SearchDialogWrapper#performActivityResuming
    556             // will handle hiding the dialog when the next activity starts, but for
    557             // real in-app search, we still need to dismiss the dialog.
    558             dismiss();
    559         } catch (RuntimeException ex) {
    560             Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
    561         }
    562     }
    563 
    564     /**
    565      * Sets the list item selection in the AutoCompleteTextView's ListView.
    566      */
    567     public void setListSelection(int index) {
    568         mSearchAutoComplete.setListSelection(index);
    569     }
    570 
    571     /**
    572      * Constructs an intent from the given information and the search dialog state.
    573      *
    574      * @param action Intent action.
    575      * @param data Intent data, or <code>null</code>.
    576      * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
    577      * @param query Intent query, or <code>null</code>.
    578      * @param actionKey The key code of the action key that was pressed,
    579      *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
    580      * @param actionMsg The message for the action key that was pressed,
    581      *        or <code>null</code> if none.
    582      * @param mode The search mode, one of the acceptable values for
    583      *             {@link SearchManager#SEARCH_MODE}, or {@code null}.
    584      * @return The intent.
    585      */
    586     private Intent createIntent(String action, Uri data, String extraData, String query,
    587             int actionKey, String actionMsg) {
    588         // Now build the Intent
    589         Intent intent = new Intent(action);
    590         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    591         // We need CLEAR_TOP to avoid reusing an old task that has other activities
    592         // on top of the one we want. We don't want to do this in in-app search though,
    593         // as it can be destructive to the activity stack.
    594         if (data != null) {
    595             intent.setData(data);
    596         }
    597         intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
    598         if (query != null) {
    599             intent.putExtra(SearchManager.QUERY, query);
    600         }
    601         if (extraData != null) {
    602             intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
    603         }
    604         if (mAppSearchData != null) {
    605             intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
    606         }
    607         if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
    608             intent.putExtra(SearchManager.ACTION_KEY, actionKey);
    609             intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
    610         }
    611         intent.setComponent(mSearchable.getSearchActivity());
    612         return intent;
    613     }
    614 
    615     /**
    616      * The root element in the search bar layout. This is a custom view just to override
    617      * the handling of the back button.
    618      */
    619     public static class SearchBar extends LinearLayout {
    620 
    621         private SearchDialog mSearchDialog;
    622 
    623         public SearchBar(Context context, AttributeSet attrs) {
    624             super(context, attrs);
    625         }
    626 
    627         public SearchBar(Context context) {
    628             super(context);
    629         }
    630 
    631         public void setSearchDialog(SearchDialog searchDialog) {
    632             mSearchDialog = searchDialog;
    633         }
    634 
    635         /**
    636          * Don't allow action modes in a SearchBar, it looks silly.
    637          */
    638         @Override
    639         public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
    640             return null;
    641         }
    642     }
    643 
    644     private boolean isEmpty(AutoCompleteTextView actv) {
    645         return TextUtils.getTrimmedLength(actv.getText()) == 0;
    646     }
    647 
    648     @Override
    649     public void onBackPressed() {
    650         // If the input method is covering the search dialog completely,
    651         // e.g. in landscape mode with no hard keyboard, dismiss just the input method
    652         InputMethodManager imm = (InputMethodManager)getContext()
    653                 .getSystemService(Context.INPUT_METHOD_SERVICE);
    654         if (imm != null && imm.isFullscreenMode() &&
    655                 imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) {
    656             return;
    657         }
    658         // Close search dialog
    659         cancel();
    660     }
    661 
    662     private boolean onClosePressed() {
    663         // Dismiss the dialog if close button is pressed when there's no query text
    664         if (isEmpty(mSearchAutoComplete)) {
    665             dismiss();
    666             return true;
    667         }
    668 
    669         return false;
    670     }
    671 
    672     private final SearchView.OnCloseListener mOnCloseListener = new SearchView.OnCloseListener() {
    673 
    674         public boolean onClose() {
    675             return onClosePressed();
    676         }
    677     };
    678 
    679     private final SearchView.OnQueryTextListener mOnQueryChangeListener =
    680             new SearchView.OnQueryTextListener() {
    681 
    682         public boolean onQueryTextSubmit(String query) {
    683             dismiss();
    684             return false;
    685         }
    686 
    687         public boolean onQueryTextChange(String newText) {
    688             return false;
    689         }
    690     };
    691 
    692     private final SearchView.OnSuggestionListener mOnSuggestionSelectionListener =
    693             new SearchView.OnSuggestionListener() {
    694 
    695         public boolean onSuggestionSelect(int position) {
    696             return false;
    697         }
    698 
    699         public boolean onSuggestionClick(int position) {
    700             dismiss();
    701             return false;
    702         }
    703     };
    704 
    705     /**
    706      * Sets the text in the query box, updating the suggestions.
    707      */
    708     private void setUserQuery(String query) {
    709         if (query == null) {
    710             query = "";
    711         }
    712         mUserQuery = query;
    713         mSearchAutoComplete.setText(query);
    714         mSearchAutoComplete.setSelection(query.length());
    715     }
    716 }
    717