FileDocCategorySizeDatePackage
Search.javaAPI DocAndroid 1.5 API28416Wed May 06 22:42:46 BST 2009com.android.launcher

Search

public class Search extends android.widget.LinearLayout implements android.widget.AdapterView.OnItemClickListener, android.view.View.OnLongClickListener, android.view.View.OnClickListener, android.view.View.OnKeyListener, android.text.TextWatcher, android.widget.AdapterView.OnItemSelectedListener

Fields Summary
private final String
TAG
private android.widget.AutoCompleteTextView
mSearchText
private android.widget.ImageButton
mGoButton
private android.widget.ImageButton
mVoiceButton
private android.view.View.OnLongClickListener
mLongClickListener
private SuggestionsAdapter
mSuggestionsAdapter
private android.server.search.SearchableInfo
mSearchable
private String
mSuggestionAction
private android.net.Uri
mSuggestionData
private String
mSuggestionQuery
private int
mItemSelected
private android.content.Intent
mVoiceSearchIntent
private android.graphics.Rect
mTempRect
private boolean
mRestoreFocus
private static float
mPaddingInset
Cache of popup padding value after read from {@link Resources}.
Constructors Summary
public Search(android.content.Context context, android.util.AttributeSet attrs)
Used to inflate the Workspace from XML.

param
context The application's context.
param
attrs The attributes set containing the Workspace's customization values.


                               
         
        super(context, attrs);
        
        mVoiceSearchIntent = new Intent(android.speech.RecognizerIntent.ACTION_WEB_SEARCH);
        mVoiceSearchIntent.putExtra(android.speech.RecognizerIntent.EXTRA_LANGUAGE_MODEL,
                android.speech.RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH);
    
Methods Summary
public voidafterTextChanged(android.text.Editable s)
Implements TextWatcher (for EditText)

    
public voidbeforeTextChanged(java.lang.CharSequence s, int start, int before, int after)
Implements TextWatcher (for EditText)

 
    
public voidclearQuery()
In order to keep things simple, the external trigger will clear the query just before focusing, so as to give you a fresh query. This way we eliminate any sources of accidental query launching.

        mSearchText.setText(null);
    
private voidconfigureSearchableInfo()
Read the searchable info from the search manager

        ISearchManager sms;
        SearchableInfo searchable;
        sms = ISearchManager.Stub.asInterface(ServiceManager.getService(Context.SEARCH_SERVICE));
        try {
            // TODO null isn't the published use of this API, but it works when global=true
            // TODO better implementation:  defer all of this, let Home set it up 
            searchable = sms.getSearchableInfo(null, true);
        } catch (RemoteException e) {
            searchable = null;
        }
        if (searchable == null) {
            // no suggestions so just get out (no need to continue)
            return;
        }
        mSearchable = searchable;
    
private voidconfigureSuggestions()
Set up the suggestions provider mechanism

        // get SearchableInfo
        
        mSearchText.setOnItemClickListener(this);
        mSearchText.setOnItemSelectedListener(this);
        
        // attach the suggestions adapter
        mSuggestionsAdapter = new SuggestionsAdapter(mContext, 
                com.android.internal.R.layout.search_dropdown_item_2line, null,
                SuggestionsAdapter.TWO_LINE_FROM, SuggestionsAdapter.TWO_LINE_TO, mSearchable);
        mSearchText.setAdapter(mSuggestionsAdapter);
    
private voidconfigureVoiceSearchButton()
If appropriate & available, configure voice search Note: Because the home screen search widget is always web search, we only check for getVoiceSearchLaunchWebSearch() modes. We don't support the alternate form of app-specific voice search.

        boolean voiceSearchVisible = false;
        if (mSearchable.getVoiceSearchEnabled() && mSearchable.getVoiceSearchLaunchWebSearch()) {
            // Enable the voice search button if there is an activity that can handle it
            PackageManager pm = getContext().getPackageManager();
            ResolveInfo ri = pm.resolveActivity(mVoiceSearchIntent,
                    PackageManager.MATCH_DEFAULT_ONLY);
            voiceSearchVisible = ri != null;
        }
        
        // finally, set visible state of voice search button, as appropriate
        mVoiceButton.setVisibility(voiceSearchVisible ? View.VISIBLE : View.GONE);
    
SearchAutoCompleteTextViewgetSearchInputField()

        return (SearchAutoCompleteTextView) mSearchText;
    
private booleanlaunchSuggestion(android.widget.CursorAdapter ca, int position)
Code to launch a suggestion query. This is copied from SearchDialog.

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

        if (ca != null) {
            Cursor c = ca.getCursor();
            if ((c != null) && c.moveToPosition(position)) {
                setupSuggestionIntent(c, mSearchable);
                
                SearchableInfo si = mSearchable;
                String suggestionAction = mSuggestionAction;
                Uri suggestionData = mSuggestionData;
                String suggestionQuery = mSuggestionQuery;
                sendLaunchIntent(suggestionAction, suggestionData, suggestionQuery, null,
                                    KeyEvent.KEYCODE_UNKNOWN, null, si);
                clearQuery();
                return true;
            }
        }
        return false;
    
public voidonClick(android.view.View v)
Implements OnClickListener (for button)

        if (v == mGoButton) {
            query();
        } else if (v == mVoiceButton) {
            try {
                getContext().startActivity(mVoiceSearchIntent);
            } catch (ActivityNotFoundException ex) {
                // Should not happen, since we check the availability of
                // voice search before showing the button. But just in case...
                Log.w(TAG, "Could not find voice search activity");
            }
        }
    
public voidonDetachedFromWindow()
Remove internal cursor references when detaching from window which prevents {@link Context} leaks.

        if (mSuggestionsAdapter != null) {
            mSuggestionsAdapter.changeCursor(null);
            mSuggestionsAdapter = null;
        }
    
protected voidonFinishInflate()

        super.onFinishInflate();

        mSearchText = (AutoCompleteTextView) findViewById(R.id.input);
        // TODO: This can be confusing when the user taps the text field to give the focus
        // (it is not necessary but I ran into this issue several times myself)
        // mTitleInput.setOnClickListener(this);
        mSearchText.setOnKeyListener(this);
        mSearchText.addTextChangedListener(this);

        mGoButton = (ImageButton) findViewById(R.id.search_go_btn);
        mVoiceButton = (ImageButton) findViewById(R.id.search_voice_btn);
        mGoButton.setOnClickListener(this);
        mVoiceButton.setOnClickListener(this);
        mGoButton.setOnKeyListener(this);
        mVoiceButton.setOnKeyListener(this);
        
        mSearchText.setOnLongClickListener(this);
        mGoButton.setOnLongClickListener(this);
        mVoiceButton.setOnLongClickListener(this);
        
        // disable the button since we start out w/empty input
        mGoButton.setEnabled(false);
        mGoButton.setFocusable(false);
        
        configureSearchableInfo();
        configureSuggestions();
        configureVoiceSearchButton();
    
public booleanonInterceptTouchEvent(android.view.MotionEvent ev)

        // Request focus unless the user tapped on the voice search button
        final int x = (int) ev.getX();
        final int y = (int) ev.getY();
        final Rect frame = mTempRect;
        mVoiceButton.getHitRect(frame);
        if (!frame.contains(x, y)) {
            requestFocusFromTouch();
        }
        return super.onInterceptTouchEvent(ev);
    
public voidonItemClick(android.widget.AdapterView parent, android.view.View view, int position, long id)
Implements OnItemClickListener

//      Log.d(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(TAG, "onItemSelected() position " + position);
         mItemSelected = position;
     
public final booleanonKey(android.view.View v, int keyCode, android.view.KeyEvent event)
Implements OnKeyListener (for EditText and for button) This plays some games with state in order to "soften" the strength of suggestions presented. Suggestions should not be used unless the user specifically navigates to them (or clicks them, in which case it's obvious). This is not the way that AutoCompleteTextBox normally works.

        if (v == mSearchText) {
            boolean searchTrigger = (keyCode == KeyEvent.KEYCODE_ENTER || 
                    keyCode == KeyEvent.KEYCODE_SEARCH ||
                    keyCode == KeyEvent.KEYCODE_DPAD_CENTER);
            if (event.getAction() == KeyEvent.ACTION_UP) {
//              Log.d(TAG, "onKey() ACTION_UP isPopupShowing:" + mSearchText.isPopupShowing());
                if (!mSearchText.isPopupShowing()) {
                    if (searchTrigger) {
                        query();
                        return true;
                    }
                }
            } else {
//              Log.d(TAG, "onKey() ACTION_DOWN isPopupShowing:" + mSearchText.isPopupShowing() +
//                      " mItemSelected="+ mItemSelected);
                if (searchTrigger && mItemSelected < 0) {
                    query();
                    return true;
                }
            }
        } else if (v == mGoButton || v == mVoiceButton) {
            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)) {
                if (mSearchText.requestFocus()) {
                    handled = mSearchText.dispatchKeyEvent(event);
                }
            }
            return handled;
        }

        return false;
    
protected voidonLayout(boolean changed, int left, int top, int right, int bottom)
When our size is changed, pass down adjusted width and offset values to correctly center the {@link AutoCompleteTextView} popup and include our padding.

    
                                
    
                
        super.onLayout(changed, left, top, right, bottom);
        if (changed) {
            if (mPaddingInset == -1) {
                mPaddingInset = getResources().getDimension(R.dimen.search_widget_inset);
            }
            
            // Fill entire width of widget, minus padding inset
            float paddedWidth = getWidth() - (mPaddingInset * 2);
            float paddedOffset = -(mSearchText.getLeft() - mPaddingInset);
                
            mSearchText.setDropDownWidth((int) paddedWidth);
            mSearchText.setDropDownHorizontalOffset((int) paddedOffset);
        }
    
public booleanonLongClick(android.view.View v)
Implements OnLongClickListener (for button)

        // Pretend that a long press on a child view is a long press on the search widget
        if (mLongClickListener != null) {
            return mLongClickListener.onLongClick(this);
        }
        return false;
    
public voidonNothingSelected(android.widget.AdapterView parent)
Implements OnItemSelectedListener

//       Log.d(TAG, "onNothingSelected()");
         mItemSelected = -1;
     
public voidonTextChanged(java.lang.CharSequence s, int start, int before, int after)
Implements TextWatcher (for EditText)

        // enable the button if we have one or more non-space characters
        boolean enabled = TextUtils.getTrimmedLength(mSearchText.getText()) != 0;
        mGoButton.setEnabled(enabled);
        mGoButton.setFocusable(enabled);
    
public voidonWindowFocusChanged(boolean hasWindowFocus)

        if (!hasWindowFocus && hasFocus()) {
            mRestoreFocus = true;
        }

        super.onWindowFocusChanged(hasWindowFocus);

        if (hasWindowFocus && mRestoreFocus) {
            if (isInTouchMode()) {
                final AutoCompleteTextView searchText = mSearchText;
                searchText.setSelectAllOnFocus(false);
                searchText.requestFocusFromTouch();
                searchText.setSelectAllOnFocus(true);
            }
            mRestoreFocus = false;
        }
    
private voidquery()

        String query = mSearchText.getText().toString();
        if (TextUtils.getTrimmedLength(mSearchText.getText()) == 0) {
            return;
        }
        Bundle appData = new Bundle();
        appData.putString(SearchManager.SOURCE, "launcher-widget");
        sendLaunchIntent(Intent.ACTION_SEARCH, null, query, appData, 0, null, mSearchable);
        clearQuery();
    
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. This is copied from SearchDialog.

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);
        launcher.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

        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)
        if (si != null) {
            launcher.setComponent(si.mSearchActivity);
        }

        getContext().startActivity(launcher);
    
public voidsetOnLongClickListener(android.view.View.OnLongClickListener l)

        super.setOnLongClickListener(l);
        mLongClickListener = l;
    
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 and mSuggestionQuery. This is copied from SearchDialog.

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 column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_ACTION);
            if (column >= 0) {
                final String action = c.getString(column);
                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;
            column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA);
            if (column >= 0) {
                final String rowData = c.getString(column);
                if (rowData != null) {
                    data = rowData;
                }
            }
            if (data == null) {
                data = si.getSuggestIntentData();
            }
            
            // then, if an ID was provided, append it.
            if (data != null) {
                column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_INTENT_DATA_ID);
                if (column >= 0) {
                    final String id = c.getString(column);
                    if (id != null) {
                        data = data + "/" + Uri.encode(id);
                    }
                }
            }
            mSuggestionData = (data == null) ? null : Uri.parse(data);
            
            mSuggestionQuery = null;
            column = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY);
            if (column >= 0) {
                final String query = c.getString(column);
                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(TAG, "Search Suggestions cursor at row " + rowNum + 
                            " returned exception" + e.toString());
        }