FileDocCategorySizeDatePackage
SearchDialog.javaAPI DocAndroid 1.5 API68515Wed May 06 22:41:54 BST 2009android.app

SearchDialog

public class SearchDialog extends Dialog implements android.widget.AdapterView.OnItemClickListener, android.widget.AdapterView.OnItemSelectedListener
In-application-process implementation of Search Bar. This is still controlled by the SearchManager, but it runs in the current activity's process to keep things lighter weight.
hide

Fields Summary
static final String
LOG_TAG
private static final int
DBG_LOG_TIMING
static final int
DBG_JAM_THREADING
android.content.IntentFilter
mCloseDialogsFilter
android.content.IntentFilter
mPackageFilter
private static final String
INSTANCE_KEY_COMPONENT
private static final String
INSTANCE_KEY_APPDATA
private static final String
INSTANCE_KEY_GLOBALSEARCH
private static final String
INSTANCE_KEY_DISPLAY_QUERY
private static final String
INSTANCE_KEY_DISPLAY_SEL_START
private static final String
INSTANCE_KEY_DISPLAY_SEL_END
private static final String
INSTANCE_KEY_USER_QUERY
private static final String
INSTANCE_KEY_SUGGESTION_QUERY
private static final String
INSTANCE_KEY_SELECTED_ELEMENT
private static final int
INSTANCE_SELECTED_BUTTON
private static final int
INSTANCE_SELECTED_QUERY
private android.widget.TextView
mBadgeLabel
private android.widget.AutoCompleteTextView
mSearchTextField
private android.widget.Button
mGoButton
private android.widget.ImageButton
mVoiceButton
private android.content.ComponentName
mLaunchComponent
private android.os.Bundle
mAppSearchData
private boolean
mGlobalSearchMode
private android.content.Context
mActivityContext
private android.server.search.SearchableInfo
mSearchable
private String
mUserQuery
private int
mUserQuerySelStart
private int
mUserQuerySelEnd
private boolean
mLeaveJammedQueryOnRefocus
private String
mPreviousSuggestionQuery
private int
mPresetSelection
private String
mSuggestionAction
private android.net.Uri
mSuggestionData
private String
mSuggestionQuery
private android.content.Intent
mVoiceWebSearchIntent
private android.content.Intent
mVoiceAppSearchIntent
private SuggestionsAdapter
mSuggestionsAdapter
private android.text.TextWatcher
mTextWatcher
Callback to watch the textedit field for empty/non-empty
private static final String[]
ONE_LINE_FROM
private static final String[]
ONE_LINE_ICONS_FROM
private static final String[]
TWO_LINE_FROM
private static final String[]
TWO_LINE_ICONS_FROM
private static final int[]
ONE_LINE_TO
private static final int[]
ONE_LINE_ICONS_TO
private static final int[]
TWO_LINE_TO
private static final int[]
TWO_LINE_ICONS_TO
View.OnKeyListener
mButtonsKeyListener
React to typing in the GO search button by refocusing to EditText. Continue typing the query.
View.OnClickListener
mGoButtonClickListener
React to a click in the GO button by launching a search.
View.OnClickListener
mVoiceButtonClickListener
React to a click in the voice search button.
View.OnKeyListener
mTextKeyListener
React to the user typing "enter" or other hardwired keys while typing in the search box. This handles these special keys while the edit box has focus.
private android.content.BroadcastReceiver
mBroadcastReceiver
This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that we should close ourselves immediately, in order to allow a higher-priority UI to take over (e.g. phone call received).
private AtomicLong
mLastLogTime
For debugging only, sample the millisecond clock and log it. Uses AtomicLong so we can use in multiple threads
Constructors Summary
public SearchDialog(android.content.Context context)
Constructor - fires it up and makes it look like the search UI.

param
context Application Context we can use for system acess


                                 
       
        super(context, com.android.internal.R.style.Theme_SearchBar);
    
Methods Summary
public voidcancel()


    
       
        // We made sure the IME was displayed, so also make sure it is closed
        // when we go away.
        InputMethodManager imm = (InputMethodManager)getContext()
                .getSystemService(Context.INPUT_METHOD_SERVICE);
        if (imm != null) {
            imm.hideSoftInputFromWindow(
                    getWindow().getDecorView().getWindowToken(), 0);
        }
        
        super.cancel();
    
private android.content.IntentcreateVoiceAppSearchIntent(android.content.Intent baseIntent)
Create and return an Intent that can launch the voice search activity, perform a specific voice transcription, and forward the results to the searchable activity.

param
baseIntent The voice app search intent to start from
return
A completely-configured intent ready to send to the voice search activity

    
                                                         
        
        // create the necessary intent to set up a search-and-forward operation
        // in the voice search system.   We have to keep the bundle separate,
        // because it becomes immutable once it enters the PendingIntent
        Intent queryIntent = new Intent(Intent.ACTION_SEARCH);
        queryIntent.setComponent(mSearchable.mSearchActivity);
        PendingIntent pending = PendingIntent.getActivity(
                getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT);
        
        // Now set up the bundle that will be inserted into the pending intent
        // when it's time to do the search.  We always build it here (even if empty)
        // because the voice search activity will always need to insert "QUERY" into
        // it anyway.
        Bundle queryExtras = new Bundle();
        if (mAppSearchData != null) {
            queryExtras.putBundle(SearchManager.APP_DATA, mAppSearchData);
        }
        
        // Now build the intent to launch the voice search.  Add all necessary
        // extras to launch the voice recognizer, and then all the necessary extras
        // to forward the results to the searchable activity
        Intent voiceIntent = new Intent(baseIntent);
        
        // Add all of the configuration options supplied by the searchable's metadata
        String languageModel = RecognizerIntent.LANGUAGE_MODEL_FREE_FORM;
        String prompt = null;
        String language = null;
        int maxResults = 1;
        Resources resources = mActivityContext.getResources();
        if (mSearchable.getVoiceLanguageModeId() != 0) {
            languageModel = resources.getString(mSearchable.getVoiceLanguageModeId());
        }
        if (mSearchable.getVoicePromptTextId() != 0) {
            prompt = resources.getString(mSearchable.getVoicePromptTextId());
        }
        if (mSearchable.getVoiceLanguageId() != 0) {
            language = resources.getString(mSearchable.getVoiceLanguageId());
        }
        if (mSearchable.getVoiceMaxResults() != 0) {
            maxResults = mSearchable.getVoiceMaxResults();
        }
        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, languageModel);
        voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt);
        voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language);
        voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults);
        
        // Add the values that configure forwarding the results
        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending);
        voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT_BUNDLE, queryExtras);
        
        return voiceIntent;
    
private voiddbgLogTiming(java.lang.String caller)

         
        long millis = SystemClock.uptimeMillis();
        long oldTime = mLastLogTime.getAndSet(millis);
        long delta = millis - oldTime;
        final String report = millis + " (+" + delta + ") ticks for Search keystroke in " + caller;
        Log.d(LOG_TAG,report);
    
private booleandoSuggestionsKey(android.view.View v, int keyCode, android.view.KeyEvent event)
React to the user typing an action key while in the suggestions list

        // Exit early in case of race condition
        if (mSuggestionsAdapter == null) {
            return false;
        }
        if (event.getAction() == KeyEvent.ACTION_DOWN) {
            if (DBG_LOG_TIMING == 1) {
                dbgLogTiming("doSuggestionsKey()");
            }
            
            // First, check for enter or search (both of which we'll treat as a "click")
            if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
                int position = mSearchTextField.getListSelection();
                return launchSuggestion(mSuggestionsAdapter, position);
            }
            
            // Next, check for left/right moves, which we use to "return" the user to the edit view
            if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
                // give "focus" to text editor, but don't restore the user's original query
                int selPoint = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 
                        0 : mSearchTextField.length();
                mSearchTextField.setSelection(selPoint);
                mSearchTextField.setListSelection(0);
                mSearchTextField.clearListSelection();
                return true;
            }
            
            // Next, check for an "up and out" move
            if (keyCode == KeyEvent.KEYCODE_DPAD_UP && 0 == mSearchTextField.getListSelection()) {
                jamSuggestionQuery(false, null, -1);
                // let ACTV complete the move
                return false;
            }
            
            // Next, check for an "action key"
            SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
            if ((actionKey != null) && 
                    ((actionKey.mSuggestActionMsg != null) || 
                     (actionKey.mSuggestActionMsgColumn != null))) {
                //   launch suggestion using action key column
                int position = mSearchTextField.getListSelection();
                if (position >= 0) {
                    Cursor c = mSuggestionsAdapter.getCursor();
                    if (c.moveToPosition(position)) {
                        final String actionMsg = getActionKeyMessage(c, actionKey);
                        if (actionMsg != null && (actionMsg.length() > 0)) {
                            // shut down search bar and launch the activity
                            // cache everything we need because dismiss releases mems
                            setupSuggestionIntent(c, mSearchable);
                            final String query = mSearchTextField.getText().toString();
                            final Bundle appData =  mAppSearchData;
                            SearchableInfo si = mSearchable;
                            String suggestionAction = mSuggestionAction;
                            Uri suggestionData = mSuggestionData;
                            String suggestionQuery = mSuggestionQuery;
                            dismiss();
                            sendLaunchIntent(suggestionAction, suggestionData,
                                    suggestionQuery, appData,
                                             keyCode, actionMsg, si);
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    
private java.lang.StringgetActionKeyMessage(android.database.Cursor c, SearchableInfo.ActionKeyInfo actionKey)
For a given suggestion and a given cursor row, get the action message. If not provided by the specific row/column, also check for a single definition (for the action key).

param
c The cursor providing suggestions
param
actionKey The actionkey record being examined
return
Returns a string, or null if no action key message for this suggestion

        String result = null;
        // check first in the cursor data, for a suggestion-specific message
        final String column = actionKey.mSuggestActionMsgColumn;
        if (column != null) {
            try {
                int colId = c.getColumnIndexOrThrow(column);
                result = c.getString(colId);
            } catch (RuntimeException e) {
                // OK - result is already null
            }
        }
        // If the cursor didn't give us a message, see if there's a single message defined
        // for the actionkey (for all suggestions)
        if (result == null) {
            result = actionKey.mSuggestActionMsg;
        }
        return result;
    
private static android.widget.CursorAdaptergetSuggestionsAdapter(android.widget.AdapterView adapterView)
Safely retrieve the suggestions cursor adapter from the ListView

param
adapterView The ListView containing our adapter
result
The CursorAdapter that we installed, or null if not set

    
                                     
         
        CursorAdapter result = null;
        if (adapterView != null) {
            Object ad = adapterView.getAdapter();
            if (ad instanceof CursorAdapter) {
                result = (CursorAdapter) ad;
            } else if (ad instanceof WrapperListAdapter) {
                result = (CursorAdapter) ((WrapperListAdapter)ad).getWrappedAdapter();
            }
        }
        return result;
    
private voidjamSuggestionQuery(boolean jamQuery, android.widget.AdapterView parent, int position)
Set or reset the user query to follow the selections in the suggestions

param
jamQuery True means to set the query, false means to reset it to the user's choice

        // quick check against race conditions
        if (mSearchable == null) {
            return;
        }
        
        mSuggestionsAdapter.setNonUserQuery(true);       // disables any suggestions processing
        if (jamQuery) {
            CursorAdapter ca = getSuggestionsAdapter(parent);
            Cursor c = ca.getCursor();
            if (c.moveToPosition(position)) {
                setupSuggestionIntent(c, mSearchable);
                String jamText = null;

                // Simple heuristic for selecting text with which to rewrite the query.
                if (mSuggestionQuery != null) {
                    jamText = mSuggestionQuery;
                } else if (mSearchable.mQueryRewriteFromData && (mSuggestionData != null)) {
                    jamText = mSuggestionData.toString();
                } else if (mSearchable.mQueryRewriteFromText) {
                    try {
                        int column = c.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1);
                        jamText = c.getString(column);
                    } catch (RuntimeException e) {
                        // no work here, jamText is null
                    }
                }
                if (jamText != null) {
                    mSearchTextField.setText(jamText);
                    /* mSearchTextField.selectAll(); */ // this didn't work anyway in the old UI
                    // TODO this is only needed in the model where we have a selection in the ACTV
                    // and in the dropdown at the same time.
                    mSearchTextField.setSelection(jamText.length());
                }
            }
        } else {
            // reset user query
            mSearchTextField.setText(mUserQuery);
            try {
                mSearchTextField.setSelection(mUserQuerySelStart, mUserQuerySelEnd);
            } catch (IndexOutOfBoundsException e) {
                // In case of error, just select all
                Log.e(LOG_TAG, "Caught IndexOutOfBoundsException while setting selection.  " +
                        "start=" + mUserQuerySelStart + " end=" + mUserQuerySelEnd +
                        " text=\"" + mUserQuery + "\"");
                mSearchTextField.selectAll();
            }
        }
        // TODO because the new query is (not) processed in another thread, we can't just
        // take away this flag (yet).  The better solution here is going to require a new API
        // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions.
//      mSuggestionsAdapter.setNonUserQuery(false);
    
private voidlaunchQuerySearch(int actionKey, java.lang.String actionMsg)
React to the user clicking the "GO" button. Hide the UI and launch a search.

param
actionKey Pass a keycode if the launch was triggered by an action key. Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
param
actionMsg Pass the suggestion-provided message if the launch was triggered by an action key. Pass null for no actionKey message.

        final String query = mSearchTextField.getText().toString();
        final Bundle appData = mAppSearchData;
        final SearchableInfo si = mSearchable;      // cache briefly (dismiss() nulls it)
        dismiss();
        sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, actionKey, actionMsg, si);
    
private booleanlaunchSuggestion(android.widget.CursorAdapter ca, int position)
Shared code for launching a query from a suggestion.

param
ca The cursor adapter containing the suggestions
param
position The suggestion we'll be launching from
return
true if a successful launch, false if could not (e.g. bad position)

        Cursor c = ca.getCursor();
        if ((c != null) && c.moveToPosition(position)) {
            setupSuggestionIntent(c, mSearchable);
            
            final Bundle appData =  mAppSearchData;
            SearchableInfo si = mSearchable;
            String suggestionAction = mSuggestionAction;
            Uri suggestionData = mSuggestionData;
            String suggestionQuery = mSuggestionQuery;
            dismiss();
            sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, appData,
                                KeyEvent.KEYCODE_UNKNOWN, null, si);
            return true;
        }
        return false;
    
public voidonConfigurationChanged(android.content.res.Configuration newConfig)
Hook for updating layout on a rotation

        if (isShowing()) {
            // Redraw (resources may have changed)
            updateSearchButton();
            updateSearchBadge();
            updateQueryHint();
        } 
    
protected voidonCreate(android.os.Bundle savedInstanceState)
We create the search dialog just once, and it stays around (hidden) until activated by the user.

        super.onCreate(savedInstanceState);

        Window theWindow = getWindow();
        theWindow.setGravity(Gravity.TOP|Gravity.FILL_HORIZONTAL);

        setContentView(com.android.internal.R.layout.search_bar);

        theWindow.setLayout(ViewGroup.LayoutParams.FILL_PARENT,
                ViewGroup.LayoutParams.WRAP_CONTENT);
        WindowManager.LayoutParams lp = theWindow.getAttributes();
        lp.setTitle("Search Dialog");
        lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
        theWindow.setAttributes(lp);

        // get the view elements for local access
        mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge);
        mSearchTextField = (AutoCompleteTextView) 
                findViewById(com.android.internal.R.id.search_src_text);
        mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn);
        mVoiceButton = (ImageButton) findViewById(com.android.internal.R.id.search_voice_btn);
        
        // attach listeners
        mSearchTextField.addTextChangedListener(mTextWatcher);
        mSearchTextField.setOnKeyListener(mTextKeyListener);
        mGoButton.setOnClickListener(mGoButtonClickListener);
        mGoButton.setOnKeyListener(mButtonsKeyListener);
        mVoiceButton.setOnClickListener(mVoiceButtonClickListener);
        mVoiceButton.setOnKeyListener(mButtonsKeyListener);

        // pre-hide all the extraneous elements
        mBadgeLabel.setVisibility(View.GONE);

        // Additional adjustments to make Dialog work for Search

        // Touching outside of the search dialog will dismiss it 
        setCanceledOnTouchOutside(true);
        
        // Set up broadcast filters
        mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
        mPackageFilter = new IntentFilter();
        mPackageFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
        mPackageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
        mPackageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        mPackageFilter.addDataScheme("package");
        
        // Save voice intent for later queries/launching
        mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH);
        mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
        
        mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH);
    
public voidonItemClick(android.widget.AdapterView parent, android.view.View view, int position, long id)
Implements OnItemClickListener

        // Log.d(LOG_TAG, "onItemClick() position " + position);
        launchSuggestion(mSuggestionsAdapter, position);
    
public voidonItemSelected(android.widget.AdapterView parent, android.view.View view, int position, long id)
Implements OnItemSelectedListener

         // Log.d(LOG_TAG, "onItemSelected() position " + position);
         jamSuggestionQuery(true, parent, position);
     
public booleanonKeyDown(int keyCode, android.view.KeyEvent event)
Dialog's OnKeyListener implements various search-specific functionality

param
keyCode This is the keycode of the typed key, and is the same value as found in the KeyEvent parameter.
param
event The complete event record for the typed key
return
Return true if the event was handled here, or false if not.

        switch (keyCode) {
        case KeyEvent.KEYCODE_BACK:
            cancel();
            return true;
        case KeyEvent.KEYCODE_SEARCH:
            if (TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0) {
                launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null);
            } else {
                cancel();
            }
            return true;
        default:
            SearchableInfo.ActionKeyInfo actionKey = mSearchable.findActionKey(keyCode);
            if ((actionKey != null) && (actionKey.mQueryActionMsg != null)) {
                launchQuerySearch(keyCode, actionKey.mQueryActionMsg);
                return true;
            }
            break;
        }
        return false;
    
public voidonNothingSelected(android.widget.AdapterView parent)
Implements OnItemSelectedListener

         // Log.d(LOG_TAG, "onNothingSelected()");
     
public voidonPackageListChange()
The list of installed packages has just changed. This means that our current context may no longer be valid. This would only happen if a package is installed/removed exactly when the search bar is open. So for now we're just going to close the search bar. Anything fancier would require some checks to see if the user's context was still valid. Which would be messier.

        cancel();
    
public voidonRestoreInstanceState(android.os.Bundle savedInstanceState)
Restore the state of the dialog from a previously saved bundle.

param
savedInstanceState The state of the dialog previously saved by {@link #onSaveInstanceState()}.

        // Get the launch info
        ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT);
        Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA);
        boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH);
        
        // get the UI state
        String displayQuery = savedInstanceState.getString(INSTANCE_KEY_DISPLAY_QUERY);
        int querySelStart = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_START, -1);
        int querySelEnd = savedInstanceState.getInt(INSTANCE_KEY_DISPLAY_SEL_END, -1);
        String userQuery = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
        int selectedElement = savedInstanceState.getInt(INSTANCE_KEY_SELECTED_ELEMENT);
        String suggestionQuery = savedInstanceState.getString(INSTANCE_KEY_SUGGESTION_QUERY);
        
        // show the dialog.  skip any show/hide animation, we want to go fast.
        // send the text that actually generates the suggestions here;  we'll replace the display
        // text as necessary in a moment.
        if (!show(suggestionQuery, false, launchComponent, appSearchData, globalSearch)) {
            // for some reason, we couldn't re-instantiate
            return;
        }
        
        if (mSuggestionsAdapter != null) {
            mSuggestionsAdapter.setNonUserQuery(true);
        }
        mSearchTextField.setText(displayQuery);
        // TODO because the new query is (not) processed in another thread, we can't just
        // take away this flag (yet).  The better solution here is going to require a new API
        // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions.
//      mSuggestionsAdapter.setNonUserQuery(false);
        
        // clean up the selection state
        switch (selectedElement) {
        case INSTANCE_SELECTED_BUTTON:
            mGoButton.setEnabled(true);
            mGoButton.setFocusable(true);
            mGoButton.requestFocus();
            break;
        case INSTANCE_SELECTED_QUERY:
            if (querySelStart >= 0 && querySelEnd >= 0) {
                mSearchTextField.requestFocus();
                mSearchTextField.setSelection(querySelStart, querySelEnd);
            }
            break;
        default:
            // defer selecting a list element until suggestion list appears
            mPresetSelection = selectedElement;
            // TODO mSearchTextField.setListSelection(selectedElement)
            break;
        }
    
public android.os.BundleonSaveInstanceState()
Save the minimal set of data necessary to recreate the search

return
A bundle with the state of the dialog.

        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.putBoolean(INSTANCE_KEY_GLOBALSEARCH, mGlobalSearchMode);
        
        // UI state
        bundle.putString(INSTANCE_KEY_DISPLAY_QUERY, mSearchTextField.getText().toString());
        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_START, mSearchTextField.getSelectionStart());
        bundle.putInt(INSTANCE_KEY_DISPLAY_SEL_END, mSearchTextField.getSelectionEnd());
        bundle.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
        bundle.putString(INSTANCE_KEY_SUGGESTION_QUERY, mPreviousSuggestionQuery);
        
        int selectedElement = INSTANCE_SELECTED_QUERY;
        if (mGoButton.isFocused()) {
            selectedElement = INSTANCE_SELECTED_BUTTON;
        } else if (mSearchTextField.isPopupShowing()) {
            selectedElement = 0; // TODO mSearchTextField.getListSelection()    // 0..n
        }
        bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement);
        
        return bundle;
    
public voidonStop()
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!

        super.onStop();
        
        setOnCancelListener(null);
        setOnDismissListener(null);
        
        // stop receiving broadcasts (throws exception if none registered)
        try {
            getContext().unregisterReceiver(mBroadcastReceiver);
        } catch (RuntimeException e) {
            // This is OK - it just means we didn't have any registered
        }
        
        // close any leftover cursor
        if (mSuggestionsAdapter != null) {
            mSuggestionsAdapter.changeCursor(null);
        }
        
        // dump extra memory we're hanging on to
        mLaunchComponent = null;
        mAppSearchData = null;
        mSearchable = null;
        mSuggestionAction = null;
        mSuggestionData = null;
        mSuggestionQuery = null;
        mActivityContext = null;
        mPreviousSuggestionQuery = null;
        mUserQuery = null;
    
private booleanonSuggestionsKey(android.view.View v, int keyCode, android.view.KeyEvent event)
React to the user typing while the suggestions are focused. First, check for action keys. If not handled, try refocusing regular characters into the EditText. In this case, replace the query text (start typing fresh text).


                                                
            
        boolean handled = false;
        // also guard against possible race conditions (late arrival after dismiss)
        if (mSearchable != null) {
            handled = doSuggestionsKey(v, keyCode, event);
        }
        return handled;
    
private booleanrefocusingKeyListener(android.view.View v, int keyCode, android.view.KeyEvent event)
Per UI design, we're going to "steer" any typed keystrokes back into the EditText box, even if the user has navigated the focus to the dropdown or to the GO button.

param
v The view into which the keystroke was typed
param
keyCode keyCode of entered key
param
event Full KeyEvent record of entered key

        boolean handled = false;

        if (!event.isSystem() && 
                (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
                (keyCode != KeyEvent.KEYCODE_DPAD_DOWN) &&
                (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
                (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
                (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
            // restore focus and give key to EditText ...
            // but don't replace the user's query
            mLeaveJammedQueryOnRefocus = true;
            if (mSearchTextField.requestFocus()) {
                handled = mSearchTextField.dispatchKeyEvent(event);
            }
            mLeaveJammedQueryOnRefocus = false;
        }
        return handled;
    
private voidsendLaunchIntent(java.lang.String action, android.net.Uri data, java.lang.String query, android.os.Bundle appData, int actionKey, java.lang.String actionMsg, android.server.search.SearchableInfo si)
Assemble a search intent and send it.

param
action The intent to send, typically Intent.ACTION_SEARCH
param
data The data for the intent
param
query The user text entered (so far)
param
appData The app data bundle (if supplied)
param
actionKey If the intent was triggered by an action key, e.g. KEYCODE_CALL, it will be sent here. Pass KeyEvent.KEYCODE_UNKNOWN for no actionKey code.
param
actionMsg If the intent was triggered by an action key, e.g. KEYCODE_CALL, the corresponding tag message will be sent here. Pass null for no actionKey message.
param
si Reference to the current SearchableInfo. Passed here so it can be used even after we've called dismiss(), which attempts to null mSearchable.

        Intent launcher = new Intent(action);

        if (query != null) {
            launcher.putExtra(SearchManager.QUERY, query);
        }

        if (data != null) {
            launcher.setData(data);
        }

        if (appData != null) {
            launcher.putExtra(SearchManager.APP_DATA, appData);
        }

        // add launch info (action key, etc.)
        if (actionKey != KeyEvent.KEYCODE_UNKNOWN) {
            launcher.putExtra(SearchManager.ACTION_KEY, actionKey);
            launcher.putExtra(SearchManager.ACTION_MSG, actionMsg);
        }

        // attempt to enforce security requirement (no 3rd-party intents)
        launcher.setComponent(si.mSearchActivity);

        getContext().startActivity(launcher);
    
private voidsetupSearchableInfo()
Use SearchableInfo record (from search manager service) to preconfigure the UI in various ways.

        if (mSearchable != null) {
            mActivityContext = mSearchable.getActivityContext(getContext());
            
            updateSearchButton();
            updateSearchBadge();
            updateQueryHint();
            updateVoiceButton();
            
            // 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;
                }
            }
            mSearchTextField.setInputType(inputType);
            mSearchTextField.setImeOptions(mSearchable.getImeOptions());
        }
    
voidsetupSuggestionIntent(android.database.Cursor c, android.server.search.SearchableInfo si)
When a particular suggestion has been selected, perform the various lookups required to use the suggestion. This includes checking the cursor for suggestion-specific data, and/or falling back to the XML for defaults; It also creates REST style Uri data when the suggestion includes a data id. NOTE: Return values are in member variables mSuggestionAction & mSuggestionData.

param
c The suggestions cursor, moved to the row of the user's selection
param
si The searchable activity's info record

        try {
            // use specific action if supplied, or default action if supplied, or fixed default
            mSuggestionAction = null;
            int mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
            if (mColumn >= 0) {
                final String action = c.getString(mColumn);
                if (action != null) {
                    mSuggestionAction = action;
                }
            }
            if (mSuggestionAction == null) {
                mSuggestionAction = si.getSuggestIntentAction();
            }
            if (mSuggestionAction == null) {
                mSuggestionAction = Intent.ACTION_SEARCH;
            }
            
            // use specific data if supplied, or default data if supplied
            String data = null;
            mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
            if (mColumn >= 0) {
                final String rowData = c.getString(mColumn);
                if (rowData != null) {
                    data = rowData;
                }
            }
            if (data == null) {
                data = si.getSuggestIntentData();
            }
            
            // then, if an ID was provided, append it.
            if (data != null) {
                mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
                if (mColumn >= 0) {
                    final String id = c.getString(mColumn);
                    if (id != null) {
                        data = data + "/" + Uri.encode(id);
                    }
                }
            }
            mSuggestionData = (data == null) ? null : Uri.parse(data);
            
            mSuggestionQuery = null;
            mColumn = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
            if (mColumn >= 0) {
                final String query = c.getString(mColumn);
                if (query != null) {
                    mSuggestionQuery = query;
                }
            }
        } catch (RuntimeException e ) {
            int rowNum;
            try {                       // be really paranoid now
                rowNum = c.getPosition();
            } catch (RuntimeException e2 ) {
                rowNum = -1;
            }
            Log.w(LOG_TAG, "Search Suggestions cursor at row " + rowNum + 
                            " returned exception" + e.toString());
        }
    
public booleanshow(java.lang.String initialQuery, boolean selectInitialQuery, android.content.ComponentName componentName, android.os.Bundle appSearchData, boolean globalSearch)
Set up the search dialog

param
Returns true if search dialog launched, false if not

        if (isShowing()) {
            // race condition - already showing but not handling events yet.
            // in this case, just discard the "show" request
            return true;
        }

        // Get searchable info from search manager and use to set up other elements of UI
        // Do this first so we can get out quickly if there's nothing to search
        ISearchManager sms;
        sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
        try {
            mSearchable = sms.getSearchableInfo(componentName, globalSearch);
        } catch (RemoteException e) {
            mSearchable = null;
        }
        if (mSearchable == null) {
            // unfortunately, we can't log here.  it would be logspam every time the user
            // clicks the "search" key on a non-search app
            return false;
        }
        
        // OK, we're going to show ourselves
        super.show();

        setupSearchableInfo();
        
        mLaunchComponent = componentName;
        mAppSearchData = appSearchData;
        mGlobalSearchMode = globalSearch;

        // receive broadcasts
        getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter);
        getContext().registerReceiver(mBroadcastReceiver, mPackageFilter);
        
        // configure the autocomplete aspects of the input box
        mSearchTextField.setOnItemClickListener(this);
        mSearchTextField.setOnItemSelectedListener(this);

        // This conversion is necessary to force a preload of the EditText and thus force
        // suggestions to be presented (even for an empty query)
        if (initialQuery == null) {
            initialQuery = "";     // This forces the preload to happen, triggering suggestions
        }

        // attach the suggestions adapter, if suggestions are available
        // The existence of a suggestions authority is the proxy for "suggestions available here"
        if (mSearchable.getSuggestAuthority() == null) {
            mSuggestionsAdapter = null;
            mSearchTextField.setAdapter(mSuggestionsAdapter);
            mSearchTextField.setText(initialQuery);
        } else {
            mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable, 
                    mSearchTextField);
            mSearchTextField.setAdapter(mSuggestionsAdapter);

            // finally, load the user's initial text (which may trigger suggestions)
            mSuggestionsAdapter.setNonUserQuery(false);
            mSearchTextField.setText(initialQuery);
        }
        
        if (selectInitialQuery) {
            mSearchTextField.selectAll();
        } else {
            mSearchTextField.setSelection(initialQuery.length());
        }
        return true;
    
public voidshow()
The default show() for this Dialog is not supported.

        return;
    
private voidupdateQueryHint()
Update the hint in the query text field.

        if (isShowing()) {
            String hint = null;
            if (mSearchable != null) {
                int hintId = mSearchable.getHintId();
                if (hintId != 0) {
                    hint = mActivityContext.getString(hintId);
                }
            }
            mSearchTextField.setHint(hint);
        }
    
private voidupdateSearchBadge()
Setup the search "Badge" if request by mode flags.

        // assume both hidden
        int visibility = View.GONE;
        Drawable icon = null;
        String text = null;
        
        // optionally show one or the other.
        if (mSearchable.mBadgeIcon) {
            icon = mActivityContext.getResources().getDrawable(mSearchable.getIconId());
            visibility = View.VISIBLE;
        } else if (mSearchable.mBadgeLabel) {
            text = mActivityContext.getResources().getText(mSearchable.getLabelId()).toString();
            visibility = View.VISIBLE;
        }
        
        mBadgeLabel.setCompoundDrawablesWithIntrinsicBounds(icon, null, null, null);
        mBadgeLabel.setText(text);
        mBadgeLabel.setVisibility(visibility);
    
private voidupdateSearchButton()
Update the text in the search button. Note: This is deprecated functionality, for 1.0 compatibility only.

 
        String textLabel = null;
        Drawable iconLabel = null;
        int textId = mSearchable.getSearchButtonText(); 
        if (textId != 0) {
            textLabel = mActivityContext.getResources().getString(textId);  
        } else {
            iconLabel = getContext().getResources().
                    getDrawable(com.android.internal.R.drawable.ic_btn_search);
        }
        mGoButton.setText(textLabel);  
        mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null);
    
private voidupdateVoiceButton()
Update the visibility of the voice button. There are actually two voice search modes, either of which will activate the button.

        int visibility = View.GONE;
        if (mSearchable.getVoiceSearchEnabled()) {
            Intent testIntent = null;
            if (mSearchable.getVoiceSearchLaunchWebSearch()) {
                testIntent = mVoiceWebSearchIntent;
            } else if (mSearchable.getVoiceSearchLaunchRecognizer()) {
                testIntent = mVoiceAppSearchIntent;
            }      
            if (testIntent != null) {
                ResolveInfo ri = getContext().getPackageManager().
                        resolveActivity(testIntent, PackageManager.MATCH_DEFAULT_ONLY);
                if (ri != null) {
                    visibility = View.VISIBLE;
                }
            }
        }
        mVoiceButton.setVisibility(visibility);
    
private voidupdateWidgetState()
Enable/Disable the cancel button based on edit text state (any text?)


                    
       
        // enable the button if we have one or more non-space characters
        boolean enabled =
            TextUtils.getTrimmedLength(mSearchTextField.getText()) != 0;

        mGoButton.setEnabled(enabled);
        mGoButton.setFocusable(enabled);