SearchActivity.java revision c29c9f854db8fa0c85f17cc32bae33dc17c6b127
13e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert/*
23e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Copyright (C) 2009 The Android Open Source Project
33e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
43e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Licensed under the Apache License, Version 2.0 (the "License");
53e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * you may not use this file except in compliance with the License.
63e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * You may obtain a copy of the License at
73e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
83e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *      http://www.apache.org/licenses/LICENSE-2.0
93e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * Unless required by applicable law or agreed to in writing, software
113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * distributed under the License is distributed on an "AS IS" BASIS,
123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * See the License for the specific language governing permissions and
143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * limitations under the License.
153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert */
163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertpackage com.android.quicksearchbox;
183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
193f71a11f8858b9164ca83e8d2f558dd8a8a5e2deBjorn Bringertimport com.android.common.Search;
20c5a3ecb62318a6be1d1a626a36b52829e989f740Bjorn Bringertimport com.android.quicksearchbox.ui.CorpusViewFactory;
21185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionClickListener;
227b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionSelectionListener;
23185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionsAdapter;
24883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringertimport com.android.quicksearchbox.ui.SuggestionsFooter;
25185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionsView;
26185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
273e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.app.Activity;
282617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringertimport android.app.Dialog;
293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.app.SearchManager;
303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.Intent;
313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.database.DataSetObserver;
323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.graphics.drawable.Animatable;
33d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinneyimport android.graphics.drawable.Drawable;
34fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringertimport android.net.Uri;
353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.os.Bundle;
36ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringertimport android.os.SystemClock;
373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.text.Editable;
383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.text.TextUtils;
393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.text.TextWatcher;
403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.util.Log;
413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.KeyEvent;
423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.Menu;
433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.View;
44883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringertimport android.view.ViewGroup;
453e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.View.OnFocusChangeListener;
463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.inputmethod.InputMethodManager;
473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.widget.EditText;
483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.widget.ImageButton;
493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
50ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringertimport java.util.ArrayList;
51ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert
523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert/**
533e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * The main activity for Quick Search Box. Shows the search UI.
543e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert */
563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertpublic class SearchActivity extends Activity {
573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private static final boolean DBG = true;
590484fb4d652bfa9d5c7fb238a7cec1a6f2244e44Bjorn Bringert    private static final String TAG = "QSB.SearchActivity";
603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
61fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private static final String SCHEME_CORPUS = "qsb.corpus";
62fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
63fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public static final String INTENT_ACTION_QSB_AND_SELECT_CORPUS
64fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            = "com.android.quicksearchbox.action.QSB_AND_SELECT_CORPUS";
653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
66c15f386b167efb3f7559f2f7ee362497040b7fe6Bjorn Bringert    // The string used for privateImeOptions to identify to the IME that it should not show
67c15f386b167efb3f7559f2f7ee362497040b7fe6Bjorn Bringert    // a microphone button since one already exists in the search dialog.
68c15f386b167efb3f7559f2f7ee362497040b7fe6Bjorn Bringert    // TODO: This should move to android-common or something.
69c15f386b167efb3f7559f2f7ee362497040b7fe6Bjorn Bringert    private static final String IME_OPTION_NO_MICROPHONE = "nm";
70c15f386b167efb3f7559f2f7ee362497040b7fe6Bjorn Bringert
719ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    // Keys for the saved instance state.
72fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private static final String INSTANCE_KEY_CORPUS = "corpus";
739ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    private static final String INSTANCE_KEY_USER_QUERY = "query";
74713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
752617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    // Dialog IDs
76fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private static final int CORPUS_SELECTION_DIALOG = 0;
772617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
78ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    // Timestamp for last onCreate()/onNewIntent() call, as returned by SystemClock.uptimeMillis().
79ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    private long mStartTime;
80ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    // Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume().
81ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    private boolean mStarting;
82ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    // True if the user has taken some action, e.g. launching a search, voice search,
83ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    // or suggestions, since QSB was last started.
84ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    private boolean mTookAction;
85ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert
86185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    protected SuggestionsAdapter mSuggestionsAdapter;
87185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected EditText mQueryTextView;
89e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    // True if the query was empty on the previous call to updateQuery()
90e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    protected boolean mQueryWasEmpty = true;
913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected SuggestionsView mSuggestionsView;
93883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringert    protected SuggestionsFooter mSuggestionsFooter;
943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected ImageButton mSearchGoButton;
963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected ImageButton mVoiceSearchButton;
97a576b1abd602c08dcf0d470facc43fac0243b07cBjorn Bringert    protected ImageButton mCorpusIndicator;
983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private Launcher mLauncher;
1003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
101fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private Corpus mCorpus;
1022617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    private Bundle mAppSearchData;
103713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private boolean mUpdateSuggestions;
104713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private String mUserQuery;
105713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private boolean mSelectAll;
1063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /** Called when the activity is first created. */
1083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
1093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public void onCreate(Bundle savedInstanceState) {
110ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        recordStartTime();
1113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "onCreate()");
1123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onCreate(savedInstanceState);
113713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
114e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        setContentView(R.layout.search_activity);
1153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
116816804b67619af133860a1e28e92f58bc642260dBjorn Bringert        mSuggestionsAdapter = getQsbApplication().createSuggestionsAdapter();
117185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
1183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mQueryTextView = (EditText) findViewById(R.id.search_src_text);
1193e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSuggestionsView = (SuggestionsView) findViewById(R.id.suggestions);
120782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mSuggestionsView.setSuggestionClickListener(new ClickHandler());
1217b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        mSuggestionsView.setSuggestionSelectionListener(new SelectionHandler());
122782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mSuggestionsView.setInteractionListener(new InputMethodCloser());
123713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSuggestionsView.setOnKeyListener(new SuggestionsViewKeyListener());
1247b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        mSuggestionsView.setOnFocusChangeListener(new SuggestListFocusListener());
1253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
126883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringert        mSuggestionsFooter = getQsbApplication().createSuggestionsFooter();
127dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert        ViewGroup footerFrame = (ViewGroup) findViewById(R.id.footer);
128dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert        mSuggestionsFooter.addToContainer(footerFrame);
129883c1bf364e38c5b133afb55f8493a14b65f4dd4Bjorn Bringert
1303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSearchGoButton = (ImageButton) findViewById(R.id.search_go_btn);
1313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mVoiceSearchButton = (ImageButton) findViewById(R.id.search_voice_btn);
132a576b1abd602c08dcf0d470facc43fac0243b07cBjorn Bringert        mCorpusIndicator = (ImageButton) findViewById(R.id.corpus_indicator);
1333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1349ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        mLauncher = new Launcher(this);
1353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
136782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mQueryTextView.addTextChangedListener(new SearchTextWatcher());
137782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mQueryTextView.setOnKeyListener(new QueryTextViewKeyListener());
1387b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        mQueryTextView.setOnFocusChangeListener(new QueryTextViewFocusListener());
139782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
140fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        mCorpusIndicator.setOnClickListener(new CorpusIndicatorClickListener());
1412617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
142782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mSearchGoButton.setOnClickListener(new SearchGoButtonClickListener());
143782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
144782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mVoiceSearchButton.setOnClickListener(new VoiceSearchButtonClickListener());
1457be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert
1467be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        ButtonsKeyListener buttonsKeyListener = new ButtonsKeyListener();
1477be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        mSearchGoButton.setOnKeyListener(buttonsKeyListener);
1487be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        mVoiceSearchButton.setOnKeyListener(buttonsKeyListener);
149fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        mCorpusIndicator.setOnKeyListener(buttonsKeyListener);
150713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
1519ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        mUpdateSuggestions = true;
1529ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert
1539ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        // First get setup from intent
1549ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        Intent intent = getIntent();
1559ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        setupFromIntent(intent);
1569ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        // Then restore any saved instance state
1579ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        restoreInstanceState(savedInstanceState);
1589ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert
159713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        // Do this at the end, to avoid updating the list view when setSource()
160713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        // is called.
161713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSuggestionsView.setAdapter(mSuggestionsAdapter);
162dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert        mSuggestionsFooter.setAdapter(mSuggestionsAdapter);
1639ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    }
164713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
1659ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    @Override
1669ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    protected void onNewIntent(Intent intent) {
167ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        recordStartTime();
1689ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        setIntent(intent);
1699ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        setupFromIntent(intent);
1709ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    }
1719ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert
172ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    private void recordStartTime() {
173ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        mStartTime = SystemClock.uptimeMillis();
174ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        mStarting = true;
175ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        mTookAction = false;
176ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    }
177ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert
1789ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    protected void restoreInstanceState(Bundle savedInstanceState) {
1799ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        if (savedInstanceState == null) return;
180fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String corpusName = savedInstanceState.getString(INSTANCE_KEY_CORPUS);
1819ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        String query = savedInstanceState.getString(INSTANCE_KEY_USER_QUERY);
182fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        setCorpus(getCorpus(corpusName));
1839ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        setUserQuery(query);
184713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
185713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
186713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    @Override
1879ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    protected void onSaveInstanceState(Bundle outState) {
1889ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        super.onSaveInstanceState(outState);
1899ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        // We don't save appSearchData, since we always get the value
1909ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        // from the intent and the user can't change it.
191fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
192fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String corpusName = mCorpus == null ? null : mCorpus.getName();
193fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        outState.putString(INSTANCE_KEY_CORPUS, corpusName);
1949ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        outState.putString(INSTANCE_KEY_USER_QUERY, mUserQuery);
195713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
196713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
197713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private void setupFromIntent(Intent intent) {
198713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
199fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Corpus corpus = getCorpusFromUri(intent.getData());
2002617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        String query = intent.getStringExtra(SearchManager.QUERY);
2012617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
2022617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
203fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        setCorpus(corpus);
2042617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        setUserQuery(query);
205154017089860da89ec020ed37e502d194253cc67Bjorn Bringert        mSelectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false);
206c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        mAppSearchData = appSearchData;
2072617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
208fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(intent.getAction())) {
20941fe9d1fc461c42907250d44d3537909e4c46a7dBjorn Bringert            showCorpusSelectionDialog();
2102617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        }
2119ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    }
2129ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert
213fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    public static Uri getCorpusUri(Corpus corpus) {
214fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (corpus == null) return null;
215fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return new Uri.Builder()
216fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                .scheme(SCHEME_CORPUS)
217fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                .authority(corpus.getName())
218fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                .build();
219fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
220fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
221fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private Corpus getCorpusFromUri(Uri uri) {
222fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (uri == null) return null;
223fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (!SCHEME_CORPUS.equals(uri.getScheme())) return null;
224fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        String name = uri.getAuthority();
225fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return getCorpus(name);
226fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
227fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
228fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private Corpus getCorpus(String sourceName) {
2290ccab5903ba2c64ec4cdd33dbcea91b1e279d556Bjorn Bringert        if (sourceName == null) return null;
230fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        Corpus corpus = getCorpora().getCorpus(sourceName);
231fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (corpus == null) {
232fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            Log.w(TAG, "Unknown corpus " + sourceName);
2339ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert            return null;
234713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
235fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return corpus;
236713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
237713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
238fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private void setCorpus(Corpus corpus) {
239fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (DBG) Log.d(TAG, "setCorpus(" + corpus + ")");
240fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        mCorpus = corpus;
2419ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        Drawable sourceIcon;
242fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (corpus == null) {
243c5a3ecb62318a6be1d1a626a36b52829e989f740Bjorn Bringert            sourceIcon = getCorpusViewFactory().getGlobalSearchIcon();
244713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        } else {
245fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            sourceIcon = corpus.getCorpusIcon();
246713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
247fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        mSuggestionsAdapter.setCorpus(corpus);
248a576b1abd602c08dcf0d470facc43fac0243b07cBjorn Bringert        mCorpusIndicator.setImageDrawable(sourceIcon);
2493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
250e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        updateVoiceSearchButton(getQuery().length() == 0);
2510484fb4d652bfa9d5c7fb238a7cec1a6f2244e44Bjorn Bringert    }
2520484fb4d652bfa9d5c7fb238a7cec1a6f2244e44Bjorn Bringert
2533e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private QsbApplication getQsbApplication() {
2543e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return (QsbApplication) getApplication();
2553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
257fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private Corpora getCorpora() {
258fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return getQsbApplication().getCorpora();
259fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    }
260fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
261fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private CorpusRanker getCorpusRanker() {
262fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return getQsbApplication().getCorpusRanker();
263713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
264713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
2653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private ShortcutRepository getShortcutRepository() {
2663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return getQsbApplication().getShortcutRepository();
2673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
269ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    private SuggestionsProvider getSuggestionsProvider() {
270fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return getQsbApplication().getSuggestionsProvider(mCorpus);
2713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
273c5a3ecb62318a6be1d1a626a36b52829e989f740Bjorn Bringert    private CorpusViewFactory getCorpusViewFactory() {
274c5a3ecb62318a6be1d1a626a36b52829e989f740Bjorn Bringert        return getQsbApplication().getCorpusViewFactory();
275185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    }
276185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
277ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    private Logger getLogger() {
278ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        return getQsbApplication().getLogger();
279ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    }
280ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert
2813e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
2823e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onDestroy() {
2833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "onDestroy()");
2843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onDestroy();
285dbdffd8316e75bc2813dbbcbef13d357970e8c84Bjorn Bringert        mSuggestionsFooter.setAdapter(null);
286185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsView.setAdapter(null);  // closes mSuggestionsAdapter
2873e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
2903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onStop() {
2919ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        if (DBG) Log.d(TAG, "onStop()");
2929ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        // Close all open suggestion cursors. The query will be redone in onResume()
2933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // if we come back to this activity.
294185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsAdapter.setSuggestions(null);
29594e8a2be78530170f50e7895a558bf8011bbf8e8Bryan Mawhinney        getQsbApplication().getShortcutRefresher().reset();
2963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onStop();
2973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
300b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert    protected void onResume() {
3019ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        if (DBG) Log.d(TAG, "onResume()");
302b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert        super.onResume();
3039ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        setQuery(mUserQuery, mSelectAll);
3049ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        // Only select everything the first time after creating the activity.
3059ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        mSelectAll = false;
3069ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        updateSuggestions(mUserQuery);
307b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert        mQueryTextView.requestFocus();
308ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        if (mStarting) {
309ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert            mStarting = false;
310ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert            // Start up latency should not exceed 2^31 ms (~ 25 days). Note that
311ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert            // SystemClock.uptimeMillis() does not advance during deep sleep.
312ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert            int latency = (int) (SystemClock.uptimeMillis() - mStartTime);
3133f71a11f8858b9164ca83e8d2f558dd8a8a5e2deBjorn Bringert            String source = getIntent().getStringExtra(Search.SOURCE);
314fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            getLogger().logStart(latency, source, mCorpus,
315fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                    getSuggestionsProvider().getOrderedCorpora());
316ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        }
317b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert    }
318b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert
319b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert    @Override
3203e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public boolean onCreateOptionsMenu(Menu menu) {
3213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onCreateOptionsMenu(menu);
32226d85260316193a1aeff5d407d4f32f5297be012Bjorn Bringert        SearchSettings.addSearchSettingsMenuItem(this, menu);
3233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return true;
3243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3269ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    /**
3279ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert     * Sets the query as typed by the user. Does not update the suggestions
3289ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert     * or the text in the query box.
3299ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert     */
3309ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    protected void setUserQuery(String userQuery) {
3319ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        if (userQuery == null) userQuery = "";
3329ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert        mUserQuery = userQuery;
3339ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert    }
3349ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert
335713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    protected String getQuery() {
3363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        CharSequence q = mQueryTextView.getText();
3373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return q == null ? "" : q.toString();
3383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
3413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Restores the query entered by the user.
3423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
3433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void restoreUserQuery() {
3443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "Restoring query to '" + mUserQuery + "'");
3453e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        setQuery(mUserQuery, false);
3463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
3493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Sets the text in the query box. Does not update the suggestions,
3503e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * and does not change the saved user-entered query.
3513e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * {@link #restoreUserQuery()} will restore the query to the last
3523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * user-entered query.
3533e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
3543e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void setQuery(String query, boolean selectAll) {
3553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mUpdateSuggestions = false;
3563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mQueryTextView.setText(query);
3573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        setTextSelection(selectAll);
3583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mUpdateSuggestions = true;
3593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
3623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Sets the text selection in the query text view.
3633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     *
3643e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * @param selectAll If {@code true}, selects the entire query.
3653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     *        If {@false}, no characters are selected, and the cursor is placed
3663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     *        at the end of the query.
3673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
3683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void setTextSelection(boolean selectAll) {
3693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (selectAll) {
3703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mQueryTextView.setSelection(0, mQueryTextView.length());
3713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        } else {
3723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mQueryTextView.setSelection(mQueryTextView.length());
3733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
376e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    private void updateQueryTextView(boolean queryEmpty) {
377e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        if (queryEmpty) {
378e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setBackgroundResource(R.drawable.textfield_search_empty);
379e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setCompoundDrawablesWithIntrinsicBounds(
380e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert                    R.drawable.placeholder_google, 0, 0, 0);
381e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setCursorVisible(false);
382e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        } else {
383e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setBackgroundResource(R.drawable.textfield_search);
384e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
385e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setCursorVisible(true);
386e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        }
387e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    }
388e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert
389e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    private void updateSearchGoButton(boolean queryEmpty) {
390e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        if (queryEmpty) {
391e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mSearchGoButton.setVisibility(View.GONE);
392e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        } else {
393e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mSearchGoButton.setVisibility(View.VISIBLE);
394e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        }
395e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    }
396e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert
397e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    protected void updateVoiceSearchButton(boolean queryEmpty) {
398c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        if (queryEmpty && mLauncher.shouldShowVoiceSearch(mCorpus)) {
399e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mVoiceSearchButton.setVisibility(View.VISIBLE);
400e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
401e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        } else {
402e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mVoiceSearchButton.setVisibility(View.GONE);
403e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            mQueryTextView.setPrivateImeOptions(null);
404e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        }
405e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert    }
406e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert
40741fe9d1fc461c42907250d44d3537909e4c46a7dBjorn Bringert    protected void showCorpusSelectionDialog() {
408fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        showDialog(CORPUS_SELECTION_DIALOG);
4092617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    }
4102617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
41141fe9d1fc461c42907250d44d3537909e4c46a7dBjorn Bringert    protected void dismissCorpusSelectionDialog() {
41241fe9d1fc461c42907250d44d3537909e4c46a7dBjorn Bringert        dismissDialog(CORPUS_SELECTION_DIALOG);
41341fe9d1fc461c42907250d44d3537909e4c46a7dBjorn Bringert    }
4142617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
4152617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    @Override
4162617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    protected Dialog onCreateDialog(int id, Bundle args) {
4172617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        switch (id) {
418fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            case CORPUS_SELECTION_DIALOG:
419fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                return createCorpusSelectionDialog();
4202617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert            default:
4212617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert                throw new IllegalArgumentException("Unknown dialog: " + id);
4222617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        }
4232617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    }
4242617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
4252617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    @Override
4262617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    protected void onPrepareDialog(int id, Dialog dialog, Bundle args) {
427c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        super.onPrepareDialog(id, dialog, args);
4282617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        switch (id) {
429fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            case CORPUS_SELECTION_DIALOG:
430fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                prepareCorpusSelectionDialog((CorpusSelectionDialog) dialog);
4312617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert                break;
4322617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert            default:
4332617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert                throw new IllegalArgumentException("Unknown dialog: " + id);
4342617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        }
4352617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    }
4362617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
437fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected CorpusSelectionDialog createCorpusSelectionDialog() {
438c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        return new CorpusSelectionDialog(this);
4392617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    }
4402617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
441fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected void prepareCorpusSelectionDialog(CorpusSelectionDialog dialog) {
442fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        dialog.setCorpus(mCorpus);
4432617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        dialog.setQuery(getQuery());
4442617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        dialog.setAppData(mAppSearchData);
4452617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    }
4462617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
447ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert    protected void onSearchClicked(int method) {
4483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String query = getQuery();
4493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "Search clicked, query=" + query);
450c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        Corpus searchCorpus = getSearchCorpus();
451c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        if (searchCorpus == null) return;
452c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
453ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        mTookAction = true;
454c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
455c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Log search start
456fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        getLogger().logSearch(mCorpus, method, query.length());
457c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
458c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Create shortcut
459c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        SuggestionData searchShortcut = searchCorpus.createSearchShortcut(query);
460c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        if (searchShortcut != null) {
461c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            DataSuggestionCursor cursor = new DataSuggestionCursor(query);
462c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            cursor.add(searchShortcut);
463c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            getShortcutRepository().reportClick(cursor, 0);
464c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        }
465c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
466c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Start search
467c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        Intent intent = searchCorpus.createSearchIntent(query, mAppSearchData);
468c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        mLauncher.launchIntent(intent);
4693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onVoiceSearchClicked() {
4723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "Voice Search clicked");
473ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        mTookAction = true;
474c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        Corpus searchCorpus = getSearchCorpus();
475c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        if (searchCorpus == null) return;
476c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
477c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Log voice search start
478c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        getLogger().logVoiceSearch(searchCorpus);
479ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert
480c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Start voice search
481c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        Intent intent = searchCorpus.createVoiceSearchIntent(mAppSearchData);
482c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        mLauncher.launchIntent(intent);
483c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert    }
484c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
485c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert    /**
486c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert     * Gets the corpus to use for any searches. This is the web corpus in "All" mode,
487c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert     * and the selected corpus otherwise.
488c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert     */
489c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert    protected Corpus getSearchCorpus() {
490c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        if (mCorpus != null) {
491c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            return mCorpus;
492c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        } else {
493c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            Corpus corpus = getCorpora().getWebCorpus();
494c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            if (corpus == null) {
495c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert                Log.e(TAG, "No web corpus");
496c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            }
497c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert            return corpus;
498c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        }
4993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
501fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected SuggestionCursor getSuggestions() {
502fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        return mSuggestionsAdapter.getCurrentSuggestions();
503782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
504782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
505fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected boolean launchSuggestion(int position) {
506fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (DBG) Log.d(TAG, "Launching suggestion " + position);
507ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        mTookAction = true;
508fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        SuggestionCursor suggestions = getSuggestions();
509c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
510c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Log suggestion click
511ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        // TODO: This should be just the queried sources, but currently
512ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        // all sources are queried
513fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        ArrayList<Corpus> corpora = getCorpusRanker().rankCorpora(getCorpora().getEnabledCorpora());
514fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        getLogger().logSuggestionClick(position, suggestions, corpora);
515ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert
516c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Create shortcut
517fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        getShortcutRepository().reportClick(suggestions, position);
518c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
519c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        // Launch intent
520c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        Intent intent = mLauncher.getSuggestionIntent(suggestions, position, mAppSearchData);
521c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert        mLauncher.launchIntent(intent);
522c29c9f854db8fa0c85f17cc32bae33dc17c6b127Bjorn Bringert
5233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return true;
5243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
526fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected boolean onSuggestionLongClicked(int position) {
527fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        if (DBG) Log.d(TAG, "Long clicked on suggestion " + position);
5283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return false;
5293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
531fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    protected boolean onSuggestionKeyDown(int position, int keyCode, KeyEvent event) {
532782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        // Treat enter or search as a click
533782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
534fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            return launchSuggestion(position);
535782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        }
536782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
5377b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        if (keyCode == KeyEvent.KEYCODE_DPAD_UP
5387b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert                && mSuggestionsView.getSelectedItemPosition() == 0) {
5397b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            // Moved up from the top suggestion, restore the user query and focus query box
5407b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            if (DBG) Log.d(TAG, "Up and out");
5417b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            restoreUserQuery();
5427b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            return false;  // let the framework handle the move
5437b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        }
5447b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert
5457b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT
5467b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert                || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT) {
5477b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            // Moved left / right from a suggestion, keep current query, move
5487b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            // focus to query box, and move cursor to far left / right
5497b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            if (DBG) Log.d(TAG, "Left/right on a suggestion");
5507b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            int cursorPos = (keyCode == KeyEvent.KEYCODE_DPAD_LEFT) ? 0 : mQueryTextView.length();
5517b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            mQueryTextView.setSelection(cursorPos);
5527b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            mQueryTextView.requestFocus();
5537b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            // TODO: should we modify the list selection?
5547b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            return true;
5557b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        }
5567b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert
557782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return false;
558782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
559782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
5603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onSourceSelected() {
5613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "No suggestion selected");
5623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        restoreUserQuery();
5633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5643e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
565782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected int getSelectedPosition() {
566782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return mSuggestionsView.getSelectedPosition();
567782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
568782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
5693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
5703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Hides the input method.
5713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
5723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void hideInputMethod() {
5733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
5743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (imm != null) {
5753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            imm.hideSoftInputFromWindow(mQueryTextView.getWindowToken(), 0);
5763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
5773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5783e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5797b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert    protected void showInputMethodForQuery() {
5807b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
5817b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        if (imm != null) {
5827b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            imm.showSoftInput(mQueryTextView, 0);
5837b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        }
5847b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert    }
5857b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert
5863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
5873e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Hides the input method when the suggestions get focus.
5883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
5893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class SuggestListFocusListener implements OnFocusChangeListener {
5903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onFocusChange(View v, boolean focused) {
5917b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            if (DBG) Log.d(TAG, "Suggestions focus change, now: " + focused);
5927b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            if (focused) {
5937b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert                // The suggestions list got focus, hide the input method
5947b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert                hideInputMethod();
5957b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            }
5967b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        }
5977b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert    }
5987b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert
5997b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert    private class QueryTextViewFocusListener implements OnFocusChangeListener {
6007b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        public void onFocusChange(View v, boolean focused) {
6017b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            if (DBG) Log.d(TAG, "Query focus change, now: " + focused);
6027b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert            if (focused) {
6037b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert                // The query box got focus, show the input method if the
6047b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert                // query box got focus?
6057b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert                showInputMethodForQuery();
6063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
6073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
6083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
6093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void startSearchProgress() {
611e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert        // TODO: Do we need a progress indicator?
6123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
6133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void stopSearchProgress() {
615d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        Drawable animation = mSearchGoButton.getDrawable();
616d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        if (animation instanceof Animatable) {
617d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            // TODO: Is this needed, or is it done automatically when the
618d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            // animation is removed?
619d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            ((Animatable) animation).stop();
620d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        }
6213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSearchGoButton.setImageResource(R.drawable.ic_btn_search);
6223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
6233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void updateSuggestions(String query) {
6253d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney        query = ltrim(query);
6263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        LatencyTracker latency = new LatencyTracker(TAG);
627ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert        Suggestions suggestions = getSuggestionsProvider().getSuggestions(query);
6283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        latency.addEvent("getSuggestions_done");
6293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (!suggestions.isDone()) {
6303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            suggestions.registerDataSetObserver(new ProgressUpdater(suggestions));
6313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            startSearchProgress();
632d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        } else {
633d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            stopSearchProgress();
6343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
635185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsAdapter.setSuggestions(suggestions);
6363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        latency.addEvent("shortcuts_shown");
6373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        long userVisibleLatency = latency.getUserVisibleLatency();
6383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) {
6393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Log.d(TAG, "User visible latency (shortcuts): " + userVisibleLatency + " ms.");
6403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
6413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
6423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6437be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert    private boolean forwardKeyToQueryTextView(int keyCode, KeyEvent event) {
6447be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        if (!event.isSystem() && !isDpadKey(keyCode)) {
6457be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            if (DBG) Log.d(TAG, "Forwarding key to query box: " + event);
6467be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            if (mQueryTextView.requestFocus()) {
6477be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert                return mQueryTextView.dispatchKeyEvent(event);
6487be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            }
6497be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        }
6507be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        return false;
6517be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert    }
6527be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert
6537be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert    private boolean isDpadKey(int keyCode) {
6547be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        switch (keyCode) {
6557be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            case KeyEvent.KEYCODE_DPAD_UP:
6567be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            case KeyEvent.KEYCODE_DPAD_DOWN:
6577be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            case KeyEvent.KEYCODE_DPAD_LEFT:
6587be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            case KeyEvent.KEYCODE_DPAD_RIGHT:
6597be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            case KeyEvent.KEYCODE_DPAD_CENTER:
6607be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert                return true;
6617be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            default:
6627be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert                return false;
6637be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert        }
6647be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert    }
6657be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert
6663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
6673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Filters the suggestions list when the search text changes.
6683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
6693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class SearchTextWatcher implements TextWatcher {
6703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void afterTextChanged(Editable s) {
671e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            boolean empty = s.length() == 0;
672e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            if (empty != mQueryWasEmpty) {
673e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert                mQueryWasEmpty = empty;
674e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert                updateQueryTextView(empty);
675e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert                updateSearchGoButton(empty);
676e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert                updateVoiceSearchButton(empty);
677e4f5c630a1c9cdf75fe751dc728729c3ecb7ae07Bjorn Bringert            }
6783e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (mUpdateSuggestions) {
6793e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                String query = s == null ? "" : s.toString();
6809ad03a750a66b26441a19ff54b6057729c145eaeBjorn Bringert                setUserQuery(query);
6813e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                updateSuggestions(query);
6823e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
6833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
6843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6853e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
6863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
6873e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onTextChanged(CharSequence s, int start, int before, int count) {
6893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
6903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
6913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
6923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
6933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Handles non-text keys in the query text view.
6943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
6953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class QueryTextViewKeyListener implements View.OnKeyListener {
6963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public boolean onKey(View view, int keyCode, KeyEvent event) {
6973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Handle IME search action key
6983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
699ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert                onSearchClicked(Logger.SEARCH_METHOD_KEYBOARD);
7003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
7013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            return false;
7023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
7033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
7043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
705782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    /**
706782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     * Handles key events on the search and voice search buttons,
707782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     * by refocusing to EditText.
708782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     */
709782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    private class ButtonsKeyListener implements View.OnKeyListener {
710782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        public boolean onKey(View v, int keyCode, KeyEvent event) {
7117be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            return forwardKeyToQueryTextView(keyCode, event);
712782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        }
713782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
714782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
715782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    /**
716782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     * Handles key events on the suggestions list view.
717782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     */
718713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private class SuggestionsViewKeyListener implements View.OnKeyListener {
719782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        public boolean onKey(View v, int keyCode, KeyEvent event) {
720782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            if (event.getAction() == KeyEvent.ACTION_DOWN) {
721fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                int position = getSelectedPosition();
722fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                if (onSuggestionKeyDown(position, keyCode, event)) {
7237be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert                        return true;
724782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                }
725782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            }
7267be9c66fdc2e4df2e998d79a11bcf737ffddc2dcBjorn Bringert            return forwardKeyToQueryTextView(keyCode, event);
727782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        }
728782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
729782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
730185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    private class InputMethodCloser implements SuggestionsView.InteractionListener {
731185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        public void onInteraction() {
732185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert            hideInputMethod();
733185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        }
734185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    }
735185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
736185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    private class ClickHandler implements SuggestionClickListener {
737fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert       public void onSuggestionClicked(int position) {
738fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert           launchSuggestion(position);
7393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert       }
7403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
741fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert       public boolean onSuggestionLongClicked(int position) {
742fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert           return SearchActivity.this.onSuggestionLongClicked(position);
743713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert       }
7443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
7453e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
7467b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert    private class SelectionHandler implements SuggestionSelectionListener {
747fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        public void onSuggestionSelected(int position) {
748fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            SuggestionCursor suggestions = getSuggestions();
749fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            suggestions.moveTo(position);
750fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            String displayQuery = suggestions.getSuggestionDisplayQuery();
751fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            if (TextUtils.isEmpty(displayQuery)) {
752fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                restoreUserQuery();
753fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            } else {
754fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                setQuery(displayQuery, false);
755fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert            }
756fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        }
757fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert
758fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert        public void onNothingSelected() {
759fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                // This happens when a suggestion has been selected with the
760fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                // dpad / trackball and then a different UI element is touched.
761fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                // Do nothing, since we want to keep the query of the selection
762fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert                // in the search box.
7637b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert        }
7647b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert    }
7657b9d59cd76eecd7645072c6728e2faf9ff9e180aBjorn Bringert
7663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
7672617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert     * Listens for clicks on the source selector.
7683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
7693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class SearchGoButtonClickListener implements View.OnClickListener {
7703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onClick(View view) {
771ca78085bb2127559e6f55276a307bfa857018ecaBjorn Bringert            onSearchClicked(Logger.SEARCH_METHOD_BUTTON);
7723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
7733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
7743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
7753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
7762617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert     * Listens for clicks on the search button.
7772617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert     */
778fde948e69f59589cf0d217ea414af7947de600bbBjorn Bringert    private class CorpusIndicatorClickListener implements View.OnClickListener {
7792617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        public void onClick(View view) {
78041fe9d1fc461c42907250d44d3537909e4c46a7dBjorn Bringert            showCorpusSelectionDialog();
7812617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert        }
7822617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    }
7832617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert
7842617a0177a6088d5aaf381263229bf5a62d2238dBjorn Bringert    /**
7853e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Listens for clicks on the voice search button.
7863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
7873e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class VoiceSearchButtonClickListener implements View.OnClickListener {
7883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onClick(View view) {
7893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            onVoiceSearchClicked();
7903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
7913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
7923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
7933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
7943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Updates the progress bar when the suggestions adapter changes its progress.
7953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
7963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class ProgressUpdater extends DataSetObserver {
7973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        private final Suggestions mSuggestions;
7983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
7993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public ProgressUpdater(Suggestions suggestions) {
8003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mSuggestions = suggestions;
8013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
8023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
8033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        @Override
8043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onChanged() {
8053e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (mSuggestions.isDone()) {
8063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                stopSearchProgress();
8073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
8083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
8093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
810713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
8113d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney    private static String ltrim(String text) {
8123d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney        int start = 0;
8133d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney        int length = text.length();
8143d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney        while (start < length && Character.isWhitespace(text.charAt(start))) {
8153d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney            start++;
8163d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney        }
8173d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney        return start > 0 ? text.substring(start, length) : text;
8183d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney    }
8193d382f0ca49a8150d504cf2629124a7f13175e1eBryan Mawhinney
8203e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert}
821