content: /*
   * Copyright (C) 2008 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License.
   */
  
  package android.app;
  
  
  import android.annotation.UnsupportedAppUsage;
  import android.content.BroadcastReceiver;
  import android.content.ComponentName;
  import android.content.Context;
  import android.content.Intent;
  import android.content.IntentFilter;
  import android.content.pm.ActivityInfo;
  import android.content.pm.PackageManager;
  import android.content.pm.PackageManager.NameNotFoundException;
  import android.content.res.Configuration;
  import android.graphics.drawable.Drawable;
  import android.net.Uri;
  import android.os.Bundle;
  import android.speech.RecognizerIntent;
  import android.text.InputType;
  import android.text.TextUtils;
  import android.util.AttributeSet;
  import android.util.Log;
  import android.util.TypedValue;
  import android.view.ActionMode;
  import android.view.Gravity;
  import android.view.KeyEvent;
  import android.view.MotionEvent;
  import android.view.View;
  import android.view.ViewConfiguration;
  import android.view.ViewGroup;
  import android.view.Window;
  import android.view.WindowManager;
  import android.view.inputmethod.InputMethodManager;
  import android.widget.AutoCompleteTextView;
  import android.widget.Filterable;
  import android.widget.ImageView;
  import android.widget.LinearLayout;
  import android.widget.ListPopupWindow;
  import android.widget.SearchView;
  import android.widget.TextView;
  
  /**
   * Search dialog. This is controlled by the 
   * SearchManager and runs in the current foreground process.
   * 
   * @hide
   */
  public class SearchDialog extends Dialog {
  
      // Debugging support
      private static final boolean DBG = false;
      private static final String LOG_TAG = "SearchDialog";
  
      private static final String INSTANCE_KEY_COMPONENT = "comp";
      private static final String INSTANCE_KEY_APPDATA = "data";
      private static final String INSTANCE_KEY_USER_QUERY = "uQry";
  
      // The string used for privateImeOptions to identify to the IME that it should not show
      // a microphone button since one already exists in the search dialog.
      private static final String IME_OPTION_NO_MICROPHONE = "nm";
  
      private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7;
  
      // views & widgets
      private TextView mBadgeLabel;
      private ImageView mAppIcon;
      private AutoCompleteTextView mSearchAutoComplete;
      private View mSearchPlate;
      private SearchView mSearchView;
      private Drawable mWorkingSpinner;
      private View mCloseSearch;
  
      // interaction with searchable application
      private SearchableInfo mSearchable;
      private ComponentName mLaunchComponent;
      private Bundle mAppSearchData;
      private Context mActivityContext;
  
      // For voice searching
      private final Intent mVoiceWebSearchIntent;
      private final Intent mVoiceAppSearchIntent;
  
      // The query entered by the user. This is not changed when selecting a suggestion
      // that modifies the contents of the text field. But if the user then edits
      // the suggestion, the resulting string is saved.
      private String mUserQuery;
  
      // Last known IME options value for the search edit text.
      private int mSearchAutoCompleteImeOptions;
  
      private BroadcastReceiver mConfChangeListener = new BroadcastReceiver() {
          @Override
          public void onReceive(Context context, Intent intent) {
              if (intent.getAction().equals(Intent.ACTION_CONFIGURATION_CHANGED)) {
                  onConfigurationChanged();
              }
          }
      };
  
      static int resolveDialogTheme(Context context) {
          TypedValue outValue = new TypedValue();
          context.getTheme().resolveAttribute(com.android.internal.R.attr.searchDialogTheme,
                  outValue, true);
          return outValue.resourceId;
      }
  
      /**
       * Constructor - fires it up and makes it look like the search UI.
       * 
       * @param context Application Context we can use for system acess
       */
      public SearchDialog(Context context, SearchManager searchManager) {
          super(context, resolveDialogTheme(context));
  
          // Save voice intent for later queries/launching
          mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
          mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
          mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                  RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
  
          mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
          mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
      }
  
      /**
       * Create the search dialog and any resources that are used for the
       * entire lifetime of the dialog.
       */
      @Override
      protected void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
  
          Window theWindow = getWindow();
          WindowManager.LayoutParams lp = theWindow.getAttributes();
          lp.width = ViewGroup.LayoutParams.MATCH_PARENT;
          // taking up the whole window (even when transparent) is less than ideal,
          // but necessary to show the popup window until the window manager supports
          // having windows anchored by their parent but not clipped by them.
          lp.height = ViewGroup.LayoutParams.MATCH_PARENT;
          lp.gravity = Gravity.TOP | Gravity.FILL_HORIZONTAL;
          lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
          theWindow.setAttributes(lp);
  
          // Touching outside of the search dialog will dismiss it
          setCanceledOnTouchOutside(true);        
      }
  
      /**
       * We recreate the dialog view each time it becomes visible so as to limit
       * the scope of any problems with the contained resources.
       */
      private void createContentView() {
          setContentView(com.android.internal.R.layout.search_bar);
  
          // get the view elements for local access
          mSearchView = findViewById(com.android.internal.R.id.search_view);
          mSearchView.setIconified(false);
          mSearchView.setOnCloseListener(mOnCloseListener);
          mSearchView.setOnQueryTextListener(mOnQueryChangeListener);
          mSearchView.setOnSuggestionListener(mOnSuggestionSelectionListener);
          mSearchView.onActionViewExpanded();
  
          mCloseSearch = findViewById(com.android.internal.R.id.closeButton);
          mCloseSearch.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                  dismiss();
              }
          });
  
          // TODO: Move the badge logic to SearchView or move the badge to search_bar.xml
          mBadgeLabel = (TextView) mSearchView.findViewById(com.android.internal.R.id.search_badge);
          mSearchAutoComplete = (AutoCompleteTextView)
                  mSearchView.findViewById(com.android.internal.R.id.search_src_text);
          mAppIcon = findViewById(com.android.internal.R.id.search_app_icon);
          mSearchPlate = mSearchView.findViewById(com.android.internal.R.id.search_plate);
          mWorkingSpinner = getContext().getDrawable(com.android.internal.R.drawable.search_spinner);
          // TODO: Restore the spinner for slow suggestion lookups
          // mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds(
          //        null, null, mWorkingSpinner, null);
          setWorking(false);
  
          // pre-hide all the extraneous elements
          mBadgeLabel.setVisibility(View.GONE);
  
          // Additional adjustments to make Dialog work for Search
          mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions();
      }
  
      /**
       * Set up the search dialog
       * 
       * @return true if search dialog launched, false if not
       */
      public boolean show(String initialQuery, boolean selectInitialQuery,
              ComponentName componentName, Bundle appSearchData) {
          boolean success = doShow(initialQuery, selectInitialQuery, componentName, appSearchData);
          if (success) {
              // Display the drop down as soon as possible instead of waiting for the rest of the
              // pending UI stuff to get done, so that things appear faster to the user.
              mSearchAutoComplete.showDropDownAfterLayout();
          }
          return success;
      }
  
      /**
       * Does the rest of the work required to show the search dialog. Called by
       * {@link #show(String, boolean, ComponentName, Bundle)} and
       *
       * @return true if search dialog showed, false if not
       */
      private boolean doShow(String initialQuery, boolean selectInitialQuery,
              ComponentName componentName, Bundle appSearchData) {
          // set up the searchable and show the dialog
          if (!show(componentName, appSearchData)) {
              return false;
          }
  
          // finally, load the user's initial text (which may trigger suggestions)
          setUserQuery(initialQuery);
          if (selectInitialQuery) {
              mSearchAutoComplete.selectAll();
          }
  
          return true;
      }
  
      /**
       * Sets up the search dialog and shows it.
       * 
       * @return <code>true</code> if search dialog launched
       */
      private boolean show(ComponentName componentName, Bundle appSearchData) {
          
          if (DBG) { 
              Log.d(LOG_TAG, "show(" + componentName + ", " 
                      + appSearchData + ")");
          }
          
          SearchManager searchManager = (SearchManager)
                  mContext.getSystemService(Context.SEARCH_SERVICE);
          // Try to get the searchable info for the provided component.
          mSearchable = searchManager.getSearchableInfo(componentName);
  
          if (mSearchable == null) {
              return false;
          }
  
          mLaunchComponent = componentName;
          mAppSearchData = appSearchData;
          mActivityContext = mSearchable.getActivityContext(getContext());
  
          // show the dialog. this will call onStart().
          if (!isShowing()) {
              // Recreate the search bar view every time the dialog is shown, to get rid
              // of any bad state in the AutoCompleteTextView etc
              createContentView();
              mSearchView.setSearchableInfo(mSearchable);
              mSearchView.setAppSearchData(mAppSearchData);
  
              show();
          }
          updateUI();
  
          return true;
      }
  
      @Override
      public void onStart() {
          super.onStart();
  
          // Register a listener for configuration change events.
          IntentFilter filter = new IntentFilter();
          filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED);
          getContext().registerReceiver(mConfChangeListener, filter);
      }
  
      /**
       * The search dialog is being dismissed, so handle all of the local shutdown operations.
       * 
       * This function is designed to be idempotent so that dismiss() can be safely called at any time
       * (even if already closed) and more likely to really dump any memory.  No leaks!
       */
      @Override
      public void onStop() {
          super.onStop();
  
          getContext().unregisterReceiver(mConfChangeListener);
  
          // dump extra memory we're hanging on to
          mLaunchComponent = null;
          mAppSearchData = null;
          mSearchable = null;
          mUserQuery = null;
      }
  
      /**
       * Sets the search dialog to the 'working' state, which shows a working spinner in the
       * right hand size of the text field.
       * 
       * @param working true to show spinner, false to hide spinner
       */
      @UnsupportedAppUsage
      public void setWorking(boolean working) {
          mWorkingSpinner.setAlpha(working ? 255 : 0);
          mWorkingSpinner.setVisible(working, false);
          mWorkingSpinner.invalidateSelf();
      }
  
      /**
       * Save the minimal set of data necessary to recreate the search
       * 
       * @return A bundle with the state of the dialog, or {@code null} if the search
       *         dialog is not showing.
       */
      @Override
      public Bundle onSaveInstanceState() {
          if (!isShowing()) return null;
  
          Bundle bundle = new Bundle();
  
          // setup info so I can recreate this particular search       
          bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent);
          bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData);
          bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
  
          return bundle;
      }
  
      /**
       * Restore the state of the dialog from a previously saved bundle.
       * 
       * @param savedInstanceState The state of the dialog previously saved by
       *     {@link #onSaveInstanceState()}.
       */
      @Override
      public void onRestoreInstanceState(Bundle savedInstanceState) {
          if (savedInstanceState == null) return;
  
          ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
          Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
          String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
  
          // show the dialog.
          if (!doShow(userQuery, false, launchComponent, appSearchData)) {
              // for some reason, we couldn't re-instantiate
              return;
          }
      }
  
      /**
       * Called after resources have changed, e.g. after screen rotation or locale change.
       */
      public void onConfigurationChanged() {
          if (mSearchable != null && isShowing()) {
              // Redraw (resources may have changed)
              updateSearchAppIcon();
              updateSearchBadge();
              if (isLandscapeMode(getContext())) {
                  mSearchAutoComplete.setInputMethodMode(ListPopupWindow.INPUT_METHOD_NEEDED);
                  if (mSearchAutoComplete.isDropDownAlwaysVisible() || enoughToFilter()) {
                      mSearchAutoComplete.showDropDown();
                  }
              }
          }
      }
  
      @UnsupportedAppUsage
      static boolean isLandscapeMode(Context context) {
          return context.getResources().getConfiguration().orientation
                  == Configuration.ORIENTATION_LANDSCAPE;
      }
  
      private boolean enoughToFilter() {
          Filterable filterableAdapter = (Filterable) mSearchAutoComplete.getAdapter();
          if (filterableAdapter == null || filterableAdapter.getFilter() == null) {
              return false;
          }
  
          return mSearchAutoComplete.enoughToFilter();
      }
  
      /**
       * Update the UI according to the info in the current value of {@link #mSearchable}.
       */
      private void updateUI() {
          if (mSearchable != null) {
              mDecor.setVisibility(View.VISIBLE);
              updateSearchAutoComplete();
              updateSearchAppIcon();
              updateSearchBadge();
              
              // In order to properly configure the input method (if one is being used), we
              // need to let it know if we'll be providing suggestions.  Although it would be
              // difficult/expensive to know if every last detail has been configured properly, we 
              // can at least see if a suggestions provider has been configured, and use that
              // as our trigger.
              int inputType = mSearchable.getInputType();
              // We only touch this if the input type is set up for text (which it almost certainly
              // should be, in the case of search!)
              if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) {
                  // The existence of a suggestions authority is the proxy for "suggestions 
                  // are available here"
                  inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
                  if (mSearchable.getSuggestAuthority() != null) {
                      inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE;
                  }
              }
              mSearchAutoComplete.setInputType(inputType);
              mSearchAutoCompleteImeOptions = mSearchable.getImeOptions();
              mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions);
              
              // If the search dialog is going to show a voice search button, then don't let
              // the soft keyboard display a microphone button if it would have otherwise.
              if (mSearchable.getVoiceSearchEnabled()) {
                  mSearchAutoComplete.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
              } else {
                  mSearchAutoComplete.setPrivateImeOptions(null);
              }
          }
      }
      
      /**
       * Updates the auto-complete text view.
       */
      private void updateSearchAutoComplete() {
          // we dismiss the entire dialog instead
          mSearchAutoComplete.setDropDownDismissedOnCompletion(false);
          mSearchAutoComplete.setForceIgnoreOutsideTouch(false);
      }
      
      private void updateSearchAppIcon() {
          PackageManager pm = getContext().getPackageManager();
          Drawable icon;
          try {
              ActivityInfo info = pm.getActivityInfo(mLaunchComponent, 0);
              icon = pm.getApplicationIcon(info.applicationInfo);
              if (DBG)
                  Log.d(LOG_TAG, "Using app-specific icon");
          } catch (NameNotFoundException e) {
              icon = pm.getDefaultActivityIcon();
              Log.w(LOG_TAG, mLaunchComponent + " not found, using generic app icon");
          }
          mAppIcon.setImageDrawable(icon);
          mAppIcon.setVisibility(View.VISIBLE);
          mSearchPlate.setPadding(SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL, mSearchPlate.getPaddingTop(), mSearchPlate.getPaddingRight(), mSearchPlate.getPaddingBottom());
      }
  
      /**
       * Setup the search "Badge" if requested by mode flags.
       */
      private void updateSearchBadge() {
          // assume both hidden
          int visibility = View.GONE;
          Drawable icon = null;
          CharSequence text = null;
  
          // optionally show one or the other.
          if (mSearchable.useBadgeIcon()) {
              icon = mActivityContext.getDrawable(mSearchable.getIconId());
              visibility = View.VISIBLE;
              if (DBG) Log.d(LOG_TAG, "Using badge icon: " + mSearchable.getIconId());
          } else if (mSearchable.useBadgeLabel()) {
              text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
              visibility = View.VISIBLE;
              if (DBG) Log.d(LOG_TAG, "Using badge label: " + mSearchable.getLabelId());
          }
  
          mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
          mBadgeLabel.setText(text);
          mBadgeLabel.setVisibility(visibility);
      }
  
      /*
       * Listeners of various types
       */
  
      /**
       * {@link Dialog#onTouchEvent(MotionEvent)} will cancel the dialog only when the
       * touch is outside the window. But the window includes space for the drop-down,
       * so we also cancel on taps outside the search bar when the drop-down is not showing.
       */
      @Override
      public boolean onTouchEvent(MotionEvent event) {
          // cancel if the drop-down is not showing and the touch event was outside the search plate
          if (!mSearchAutoComplete.isPopupShowing() && isOutOfBounds(mSearchPlate, event)) {
              if (DBG) Log.d(LOG_TAG, "Pop-up not showing and outside of search plate.");
              cancel();
              return true;
          }
          // Let Dialog handle events outside the window while the pop-up is showing.
          return super.onTouchEvent(event);
      }
  
      private boolean isOutOfBounds(View v, MotionEvent event) {
          final int x = (int) event.getX();
          final int y = (int) event.getY();
          final int slop = ViewConfiguration.get(mContext).getScaledWindowTouchSlop();
          return (x < -slop) || (y < -slop)
                  || (x > (v.getWidth()+slop))
                  || (y > (v.getHeight()+slop));
      }
  
      @Override
      public void hide() {
          if (!isShowing()) return;
  
          // We made sure the IME was displayed, so also make sure it is closed
          // when we go away.
          InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
          if (imm != null) {
              imm.hideSoftInputFromWindow(
                      getWindow().getDecorView().getWindowToken(), 0);
          }
  
          super.hide();
      }
      
      /**
       * Launch a search for the text in the query text field.
       */
      @UnsupportedAppUsage
      public void launchQuerySearch() {
          launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
      }
  
      /**
       * Launch a search for the text in the query text field.
       *
       * @param actionKey The key code of the action key that was pressed,
       *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
       * @param actionMsg The message for the action key that was pressed,
       *        or <code>null</code> if none.
       */
      @UnsupportedAppUsage
      protected void launchQuerySearch(int actionKey, String actionMsg) {
          String query = mSearchAutoComplete.getText().toString();
          String action = Intent.ACTION_SEARCH;
          Intent intent = createIntent(action, null, null, query, actionKey, actionMsg);
          launchIntent(intent);
      }
  
      /**
       * Launches an intent, including any special intent handling.
       */
      private void launchIntent(Intent intent) {
          if (intent == null) {
              return;
          }
          Log.d(LOG_TAG, "launching " + intent);
          try {
              // If the intent was created from a suggestion, it will always have an explicit
              // component here.
              getContext().startActivity(intent);
              // If the search switches to a different activity,
              // SearchDialogWrapper#performActivityResuming
              // will handle hiding the dialog when the next activity starts, but for
              // real in-app search, we still need to dismiss the dialog.
              dismiss();
          } catch (RuntimeException ex) {
              Log.e(LOG_TAG, "Failed launch activity: " + intent, ex);
          }
      }
  
      /**
       * Sets the list item selection in the AutoCompleteTextView's ListView.
       */
      public void setListSelection(int index) {
          mSearchAutoComplete.setListSelection(index);
      }
  
      /**
       * Constructs an intent from the given information and the search dialog state.
       * 
       * @param action Intent action.
       * @param data Intent data, or <code>null</code>.
       * @param extraData Data for {@link SearchManager#EXTRA_DATA_KEY} or <code>null</code>.
       * @param query Intent query, or <code>null</code>.
       * @param actionKey The key code of the action key that was pressed,
       *        or {@link KeyEvent#KEYCODE_UNKNOWN} if none.
       * @param actionMsg The message for the action key that was pressed,
       *        or <code>null</code> if none.
       * @param mode The search mode, one of the acceptable values for
       *             {@link SearchManager#SEARCH_MODE}, or {@code null}.
       * @return The intent.
       */
      private Intent createIntent(String action, Uri data, String extraData, String query,
              int actionKey, String actionMsg) {
          // Now build the Intent
          Intent intent = new Intent(action);
          intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
          // We need CLEAR_TOP to avoid reusing an old task that has other activities
          // on top of the one we want. We don't want to do this in in-app search though,
          // as it can be destructive to the activity stack.
          if (data != null) {
              intent.setData(data);
          }
          intent.putExtra(SearchManager.USER_QUERY, mUserQuery);
          if (query != null) {
              intent.putExtra(SearchManager.QUERY, query);
          }
          if (extraData != null) {
              intent.putExtra(SearchManager.EXTRA_DATA_KEY, extraData);
          }
          if (mAppSearchData != null) {
              intent.putExtra(SearchManager.APP_DATA, mAppSearchData);
          }
          if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
              intent.putExtra(SearchManager.ACTION_KEY, actionKey);
              intent.putExtra(SearchManager.ACTION_MSG, actionMsg);
          }
          intent.setComponent(mSearchable.getSearchActivity());
          return intent;
      }
  
      /**
       * The root element in the search bar layout. This is a custom view just to override
       * the handling of the back button.
       */
      public static class SearchBar extends LinearLayout {
  
          public SearchBar(Context context, AttributeSet attrs) {
              super(context, attrs);
          }
  
          public SearchBar(Context context) {
              super(context);
          }
  
          @Override
          public ActionMode startActionModeForChild(
                  View child, ActionMode.Callback callback, int type) {
              // Disable Primary Action Modes in the SearchBar, as they overlap.
              if (type != ActionMode.TYPE_PRIMARY) {
                  return super.startActionModeForChild(child, callback, type);
              }
              return null;
          }
      }
  
      private boolean isEmpty(AutoCompleteTextView actv) {
          return TextUtils.getTrimmedLength(actv.getText()) == 0;
      }
  
      @Override
      public void onBackPressed() {
          // If the input method is covering the search dialog completely,
          // e.g. in landscape mode with no hard keyboard, dismiss just the input method
          InputMethodManager imm = getContext().getSystemService(InputMethodManager.class);
          if (imm != null && imm.isFullscreenMode() &&
                  imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) {
              return;
          }
          // Close search dialog
          cancel();
      }
  
      private boolean onClosePressed() {
          // Dismiss the dialog if close button is pressed when there's no query text
          if (isEmpty(mSearchAutoComplete)) {
              dismiss();
              return true;
          }
  
          return false;
      }
  
      private final SearchView.OnCloseListener mOnCloseListener = new SearchView.OnCloseListener() {
  
          public boolean onClose() {
              return onClosePressed();
          }
      };
  
      private final SearchView.OnQueryTextListener mOnQueryChangeListener =
              new SearchView.OnQueryTextListener() {
  
          public boolean onQueryTextSubmit(String query) {
              dismiss();
              return false;
          }
  
          public boolean onQueryTextChange(String newText) {
              return false;
          }
      };
  
      private final SearchView.OnSuggestionListener mOnSuggestionSelectionListener =
              new SearchView.OnSuggestionListener() {
  
          public boolean onSuggestionSelect(int position) {
              return false;
          }
  
          public boolean onSuggestionClick(int position) {
              dismiss();
              return false;
          }
      };
  
      /**
       * Sets the text in the query box, updating the suggestions.
       */
      private void setUserQuery(String query) {
          if (query == null) {
              query = "";
          }
          mUserQuery = query;
          mSearchAutoComplete.setText(query);
          mSearchAutoComplete.setSelection(query.length());
      }
  }
  