SearchActivity.java revision 713194910648268c094fa81b81f40ce2f7e39333
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
19713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringertimport com.android.quicksearchbox.ui.SourceSelector;
20185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionClickListener;
21185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionViewFactory;
22185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionsAdapter;
23185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringertimport com.android.quicksearchbox.ui.SuggestionsView;
24185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.app.Activity;
263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.app.SearchManager;
27713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringertimport android.content.ActivityNotFoundException;
28713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringertimport android.content.ComponentName;
293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.content.Intent;
303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.database.DataSetObserver;
313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.graphics.Rect;
323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.graphics.drawable.Animatable;
33d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinneyimport android.graphics.drawable.Drawable;
343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.os.Bundle;
353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.text.Editable;
363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.text.TextUtils;
373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.text.TextWatcher;
383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.util.Log;
393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.KeyEvent;
403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.Menu;
413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.View;
423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.View.OnFocusChangeListener;
433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.view.inputmethod.InputMethodManager;
443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.widget.EditText;
453e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertimport android.widget.ImageButton;
463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
47782dd228e78e9294692d639597f96c26283968bbBjorn Bringert// TODO: restore user query when using dpad to move up from top suggestion
48782dd228e78e9294692d639597f96c26283968bbBjorn Bringert// TODO: nicer progress animation. one per source?
49782dd228e78e9294692d639597f96c26283968bbBjorn Bringert// TODO: handle long clicks
50782dd228e78e9294692d639597f96c26283968bbBjorn Bringert// TODO: support action keys
51782dd228e78e9294692d639597f96c26283968bbBjorn Bringert// TODO: support IME search action
52782dd228e78e9294692d639597f96c26283968bbBjorn Bringert// TODO: allow typing everywhere in the UI
53185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
543e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: don't show new results until there is at least one, or it's done
553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: add timeout for source queries
563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: group refreshes that happen close to each other.
573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: use queryAfterZeroResults()
58782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
59782dd228e78e9294692d639597f96c26283968bbBjorn Bringert// TODO: use permission to get extended Genie suggestions
603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: support intent extras for source (e.g. launcher widget)
613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: support intent extras for initial state, e.g. query, selection
623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: make Config server-side configurable
633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: log impressions
643e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert// TODO: use source ranking
653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert/**
673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert * The main activity for Quick Search Box. Shows the search UI.
683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert *
693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert */
703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringertpublic class SearchActivity extends Activity {
713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private static final boolean DBG = true;
733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private static final String TAG = "SearchActivity";
743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    // TODO: This is hidden in SearchManager
763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public final static String INTENT_ACTION_SEARCH_SETTINGS
773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            = "android.search.action.SEARCH_SETTINGS";
783e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
79713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private static final int REQUEST_SELECT_SOURCE = 0;
80713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
81185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    protected SuggestionsAdapter mSuggestionsAdapter;
82185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected EditText mQueryTextView;
843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
853e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected SuggestionsView mSuggestionsView;
863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
873e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected ImageButton mSearchGoButton;
883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected ImageButton mVoiceSearchButton;
89713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    protected SourceSelector mSourceSelector;
903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private Launcher mLauncher;
923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
93713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private Source mSource;
94713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private boolean mUpdateSuggestions;
95713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private Bundle mAppSearchData;
96713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private String mUserQuery;
97713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private boolean mSelectAll;
983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /** Called when the activity is first created. */
1003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
1013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public void onCreate(Bundle savedInstanceState) {
1023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "onCreate()");
1033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onCreate(savedInstanceState);
104713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
105713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        // TODO: Use savedInstanceState to restore state
106713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSource = null;
107713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mUpdateSuggestions = true;
108713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mAppSearchData = null;
109713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mUserQuery = "";
110713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSelectAll = false;
111713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
1123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        setContentView(R.layout.search_bar);
1133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
114d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        Config config = getConfig();
115185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        SuggestionViewFactory viewFactory = getSuggestionViewFactory();
116185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsAdapter = new SuggestionsAdapter(viewFactory);
117185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsAdapter
118185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert                .setInitialSourceResultWaitMillis(config.getInitialSourceResultWaitMillis());
119185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsAdapter
120185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert                .setSourceResultPublishDelayMillis(config.getSourceResultPublishDelayMillis());
121185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
1223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mQueryTextView = (EditText) findViewById(R.id.search_src_text);
1233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSuggestionsView = (SuggestionsView) findViewById(R.id.suggestions);
124782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mSuggestionsView.setSuggestionClickListener(new ClickHandler());
125782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mSuggestionsView.setInteractionListener(new InputMethodCloser());
126713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSuggestionsView.setOnKeyListener(new SuggestionsViewKeyListener());
1273e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSearchGoButton = (ImageButton) findViewById(R.id.search_go_btn);
1293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mVoiceSearchButton = (ImageButton) findViewById(R.id.search_voice_btn);
130713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSourceSelector = (SourceSelector) findViewById(R.id.source_selector);
1313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        Intent intent = getIntent();
1333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // getIntent() currently always returns non-null, but the API does not guarantee
1343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // that it always will.
1353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (intent != null) {
136713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            setupFromIntent(intent);
1373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
138713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mLauncher = new Launcher(this, mAppSearchData);
1393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mVoiceSearchButton.setVisibility(
1403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                mLauncher.isVoiceSearchAvailable() ? View.VISIBLE : View.GONE);
1413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
142782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mQueryTextView.addTextChangedListener(new SearchTextWatcher());
143782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mQueryTextView.setOnKeyListener(new QueryTextViewKeyListener());
144782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mQueryTextView.setOnFocusChangeListener(new SuggestListFocusListener());
145782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
146782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mSearchGoButton.setOnClickListener(new SearchGoButtonClickListener());
147782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mSearchGoButton.setOnKeyListener(new ButtonsKeyListener());
148782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
149782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mVoiceSearchButton.setOnClickListener(new VoiceSearchButtonClickListener());
150782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mVoiceSearchButton.setOnKeyListener(new ButtonsKeyListener());
151713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
152713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        // Do this at the end, to avoid updating the list view when setSource()
153713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        // is called.
154713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSuggestionsView.setAdapter(mSuggestionsAdapter);
155713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
156713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        // Must be done after the source is set
157713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSourceSelector.setSearchInfo(new SearchInfo());
158713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
159713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
160713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    @Override
161713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
162713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        if (requestCode == REQUEST_SELECT_SOURCE) {
163713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            if (resultCode == RESULT_OK && data != null) {
164713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert                setupFromIntent(data);
165713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            }
166713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
167713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
168713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
169713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private void setupFromIntent(Intent intent) {
170713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
171713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        ComponentName sourceName = SourceSelector.getGlobalSearchComponent(intent);
172713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        setSource(sourceName);
173713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
174713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        String initialQuery = intent.getStringExtra(SearchManager.QUERY);
175713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        if (!TextUtils.isEmpty(initialQuery)) {
176713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            mUserQuery = initialQuery;
177713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
178713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        // TODO: Declare an intent extra for selectAll
179713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mAppSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
180713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
181713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
182713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private void setSource(ComponentName sourceName) {
183713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        if (sourceName == null) {
184713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            mSource = null;
185713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            mSuggestionsAdapter.setSource(null);
186713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        } else {
187713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            SourceLookup sources = getSources();
188713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            mSource = sources.getSourceByComponentName(sourceName);
189713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            if (mSource != null) {
190713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert                mSuggestionsAdapter.setSource(sourceName);
191713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            } else {
192713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert                Log.w(TAG, "Unknown source " + sourceName);
193713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert                mSuggestionsAdapter.setSource(null);
194713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            }
195713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
196713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        mSourceSelector.update();
1973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
1983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
1993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private QsbApplication getQsbApplication() {
2003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return (QsbApplication) getApplication();
2013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
203d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney    private Config getConfig() {
204d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        return getQsbApplication().getConfig();
205d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney    }
206d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney
207713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private SourceLookup getSources() {
208713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        return getQsbApplication().getSources();
209713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
210713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
2113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private ShortcutRepository getShortcutRepository() {
2123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return getQsbApplication().getShortcutRepository();
2133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private SuggestionsProvider getSuggestionsProvider() {
2163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return getQsbApplication().getSuggestionsProvider();
2173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
219185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    private SuggestionViewFactory getSuggestionViewFactory() {
220185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        return getQsbApplication().getSuggestionViewFactory();
221185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    }
222185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
2233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
2243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onDestroy() {
2253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "onDestroy()");
2263e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onDestroy();
227185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsView.setAdapter(null);  // closes mSuggestionsAdapter
2283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2303e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
2313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onStart() {
2323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "onStart()");
2333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onStart();
2343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        setQuery(mUserQuery, mSelectAll);
2353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // Only select everything the first time after creating the activity.
2363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSelectAll = false;
2373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        updateSuggestions(mUserQuery);
2383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
2413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onStop() {
2423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "onResume()");
2433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // Close all open suggestion cursors. The query will be redone in onStart()
2443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // if we come back to this activity.
245185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsAdapter.setSuggestions(null);
2463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onStop();
2473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    @Override
250b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert    protected void onResume() {
251b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert        super.onResume();
252b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert        mQueryTextView.requestFocus();
253b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert    }
254b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert
255b32ee8f1b45c65daff18b40c0614cf18843b8c17Bjorn Bringert    @Override
2563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    public boolean onCreateOptionsMenu(Menu menu) {
2573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        super.onCreateOptionsMenu(menu);
2583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        Intent settings = new Intent(INTENT_ACTION_SEARCH_SETTINGS);
2603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        settings.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
2613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // Don't show activity chooser if there are multiple search settings activities,
2623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // e.g. from different QSB implementations.
2633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        settings.setPackage(this.getPackageName());
2643e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        menu.add(Menu.NONE, Menu.NONE, 0, R.string.menu_settings)
2653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                .setIcon(android.R.drawable.ic_menu_preferences).setAlphabeticShortcut('P')
2663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                .setIntent(settings);
2673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return true;
2693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
271713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    protected String getQuery() {
2723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        CharSequence q = mQueryTextView.getText();
2733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return q == null ? "" : q.toString();
2743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
2773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Restores the query entered by the user.
2783e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
2793e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void restoreUserQuery() {
2803e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "Restoring query to '" + mUserQuery + "'");
2813e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        setQuery(mUserQuery, false);
2823e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
2853e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Sets the text in the query box. Does not update the suggestions,
2863e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * and does not change the saved user-entered query.
2873e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * {@link #restoreUserQuery()} will restore the query to the last
2883e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * user-entered query.
2893e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
2903e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void setQuery(String query, boolean selectAll) {
2913e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mUpdateSuggestions = false;
2923e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mQueryTextView.setText(query);
2933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        setTextSelection(selectAll);
2943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mUpdateSuggestions = true;
2953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
2963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
2973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
2983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Sets the text selection in the query text view.
2993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     *
3003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * @param selectAll If {@code true}, selects the entire query.
3013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     *        If {@false}, no characters are selected, and the cursor is placed
3023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     *        at the end of the query.
3033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
3043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void setTextSelection(boolean selectAll) {
3053e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (selectAll) {
3063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mQueryTextView.setSelection(0, mQueryTextView.length());
3073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        } else {
3083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mQueryTextView.setSelection(mQueryTextView.length());
3093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onSearchClicked() {
3133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String query = getQuery();
3143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "Search clicked, query=" + query);
3153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mLauncher.startWebSearch(query);
3163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
3183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onVoiceSearchClicked() {
3193e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "Voice Search clicked");
3203e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mLauncher.startVoiceSearch();
3213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
323782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected boolean launchSuggestion(SuggestionPosition suggestion) {
324782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return launchSuggestion(suggestion, KeyEvent.KEYCODE_UNKNOWN, null);
325782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
326782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
327782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected boolean launchSuggestion(SuggestionPosition suggestion,
328782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            int actionKey, String actionMsg) {
329782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        if (DBG) Log.d(TAG, "Launching suggestion " + suggestion);
330782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        mLauncher.launchSuggestion(suggestion, actionKey, actionMsg);
3313e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        getShortcutRepository().reportClick(suggestion);
3323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // Update search widgets, since the top shortcuts can have changed.
3333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        SearchWidgetProvider.updateSearchWidgets(this);
3343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return true;
3353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
337782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected boolean launchSuggestionSecondary(SuggestionPosition suggestion, Rect target) {
3383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert      if (DBG) Log.d(TAG, "Clicked on suggestion icon " + suggestion);
3393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert      mLauncher.launchSuggestionSecondary(suggestion, target);
3403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert      getShortcutRepository().reportClick(suggestion);
3413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert      return true;
3423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
344782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected boolean onSuggestionLongClicked(SuggestionPosition suggestion) {
345782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        SuggestionCursor sourceResult = suggestion.getSuggestion();
3463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "Long clicked on suggestion " + sourceResult.getSuggestionText1());
3473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        return false;
3483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
350782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected void onSuggestionSelected(SuggestionPosition suggestion) {
351782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        SuggestionCursor sourceResult = suggestion.getSuggestion();
3523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        String displayQuery = sourceResult.getSuggestionDisplayQuery();
3533e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) {
3543e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Log.d(TAG, "Selected suggestion " + sourceResult.getSuggestionText1()
3553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                    + ",displayQuery="+ displayQuery);
3563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (TextUtils.isEmpty(displayQuery)) {
3583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            restoreUserQuery();
3593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        } else {
3603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            setQuery(displayQuery, false);
3613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
3623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
364782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected boolean onSuggestionKeyDown(SuggestionPosition suggestion,
365782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            int keyCode, KeyEvent event) {
366782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        // Treat enter or search as a click
367782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
368782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            return launchSuggestion(suggestion);
369782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        }
370782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
371782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        // Handle source-specified action keys
372782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        String actionMsg = suggestion.getSuggestion().getActionKeyMsg(keyCode);
373782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        if (actionMsg != null) {
374782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            return launchSuggestion(suggestion, keyCode, actionMsg);
375782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        }
376782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
377782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return false;
378782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
379782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
3803e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void onSourceSelected() {
3813e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) Log.d(TAG, "No suggestion selected");
3823e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        restoreUserQuery();
3833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
3843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
385782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected int getSelectedPosition() {
386782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return mSuggestionsView.getSelectedPosition();
387782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
388782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
389782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    protected SuggestionPosition getSelectedSuggestion() {
390782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        return mSuggestionsView.getSelectedSuggestion();
391782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
392782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
3933e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
3943e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Hides the input method.
3953e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
3963e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    protected void hideInputMethod() {
3973e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
3983e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (imm != null) {
3993e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            imm.hideSoftInputFromWindow(mQueryTextView.getWindowToken(), 0);
4003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4013e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4023e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4033e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
4043e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Hides the input method when the suggestions get focus.
4053e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
4063e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class SuggestListFocusListener implements OnFocusChangeListener {
4073e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onFocusChange(View v, boolean focused) {
4083e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (v == mQueryTextView) {
4093e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                if (!focused) {
4103e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                    hideInputMethod();
4113e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                } else {
4123e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                    // TODO: clear list selection?
4133e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                }
4143e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
4153e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4163e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4173e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4183e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void startSearchProgress() {
4193e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        // TODO: Cache animation between calls?
4203e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSearchGoButton.setImageResource(R.drawable.searching);
4213e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        Animatable animation = (Animatable) mSearchGoButton.getDrawable();
4223e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        animation.start();
4233e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4243e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4253e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void stopSearchProgress() {
426d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        Drawable animation = mSearchGoButton.getDrawable();
427d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        if (animation instanceof Animatable) {
428d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            // TODO: Is this needed, or is it done automatically when the
429d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            // animation is removed?
430d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            ((Animatable) animation).stop();
431d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        }
4323e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        mSearchGoButton.setImageResource(R.drawable.ic_btn_search);
4333e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4343e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4353e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private void updateSuggestions(String query) {
4363e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        LatencyTracker latency = new LatencyTracker(TAG);
4373e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        Suggestions suggestions = getSuggestionsProvider().getSuggestions(query);
4383e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        latency.addEvent("getSuggestions_done");
4393e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (!suggestions.isDone()) {
4403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            suggestions.registerDataSetObserver(new ProgressUpdater(suggestions));
4413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            startSearchProgress();
442d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney        } else {
443d7a95801d20a9405b4ab9c9b4beecd106e880166Bryan Mawhinney            stopSearchProgress();
4443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
445185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        mSuggestionsAdapter.setSuggestions(suggestions);
4463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        latency.addEvent("shortcuts_shown");
4473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        long userVisibleLatency = latency.getUserVisibleLatency();
4483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        if (DBG) {
4493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            Log.d(TAG, "User visible latency (shortcuts): " + userVisibleLatency + " ms.");
4503e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4513e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4533e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
4543e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Filters the suggestions list when the search text changes.
4553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
4563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class SearchTextWatcher implements TextWatcher {
4573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void afterTextChanged(Editable s) {
4583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (mUpdateSuggestions) {
4593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                String query = s == null ? "" : s.toString();
4603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                mUserQuery = query;
4613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                updateSuggestions(query);
4623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
4633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4643e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
4663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onTextChanged(CharSequence s, int start, int before, int count) {
4693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
4723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
4733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Handles non-text keys in the query text view.
4743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
4753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class QueryTextViewKeyListener implements View.OnKeyListener {
4763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public boolean onKey(View view, int keyCode, KeyEvent event) {
4773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            // Handle IME search action key
4783e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
4793e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                onSearchClicked();
4803e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
4813e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            return false;
4823e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
4833e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
4843e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
485782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    /**
486782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     * Handles key events on the search and voice search buttons,
487782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     * by refocusing to EditText.
488782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     */
489782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    private class ButtonsKeyListener implements View.OnKeyListener {
490782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        public boolean onKey(View v, int keyCode, KeyEvent event) {
491782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            if (!event.isSystem() &&
492782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                    (keyCode != KeyEvent.KEYCODE_DPAD_UP) &&
493782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                    (keyCode != KeyEvent.KEYCODE_DPAD_LEFT) &&
494782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                    (keyCode != KeyEvent.KEYCODE_DPAD_RIGHT) &&
495782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                    (keyCode != KeyEvent.KEYCODE_DPAD_CENTER)) {
496782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                if (mQueryTextView.requestFocus()) {
497782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                    return mQueryTextView.dispatchKeyEvent(event);
498782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                }
499782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            }
500782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            return false;
501782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        }
502782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
503782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
504782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    /**
505782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     * Handles key events on the suggestions list view.
506782dd228e78e9294692d639597f96c26283968bbBjorn Bringert     */
507713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private class SuggestionsViewKeyListener implements View.OnKeyListener {
508782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        public boolean onKey(View v, int keyCode, KeyEvent event) {
509782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            if (event.getAction() == KeyEvent.ACTION_DOWN) {
510782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                SuggestionPosition suggestion = getSelectedSuggestion();
511782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                if (suggestion != null) {
512782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                    return onSuggestionKeyDown(suggestion, keyCode, event);
513782dd228e78e9294692d639597f96c26283968bbBjorn Bringert                }
514782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            }
515782dd228e78e9294692d639597f96c26283968bbBjorn Bringert            return false;
516782dd228e78e9294692d639597f96c26283968bbBjorn Bringert        }
517782dd228e78e9294692d639597f96c26283968bbBjorn Bringert    }
518782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
519185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    private class InputMethodCloser implements SuggestionsView.InteractionListener {
520185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        public void onInteraction() {
521185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert            hideInputMethod();
522185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert        }
523185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    }
524185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert
525185bb2e3881452c084fde44d9bee657f65881b0eBjorn Bringert    private class ClickHandler implements SuggestionClickListener {
526713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert       public void onSuggestionClicked(SuggestionPosition suggestion) {
527713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert           launchSuggestion(suggestion);
5283e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert       }
5293e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
530713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert       public boolean onSuggestionLongClicked(SuggestionPosition suggestion) {
531713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert           return SearchActivity.this.onSuggestionLongClicked(suggestion);
532713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert       }
533713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
534713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert       public void onSuggestionSelected(SuggestionPosition suggestion) {
535713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert           SearchActivity.this.onSuggestionSelected(suggestion);
536782dd228e78e9294692d639597f96c26283968bbBjorn Bringert       }
537782dd228e78e9294692d639597f96c26283968bbBjorn Bringert
538713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert       public void onSuggestionIconClicked(SuggestionPosition suggestion, Rect rect) {
539713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert           launchSuggestionSecondary(suggestion, rect);
5403e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert       }
5413e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5423e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5433e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
5443e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Listens for clicks on the search button.
5453e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
5463e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class SearchGoButtonClickListener implements View.OnClickListener {
5473e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onClick(View view) {
5483e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            onSearchClicked();
5493e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
5503e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5513e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5523e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
5533e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Listens for clicks on the voice search button.
5543e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
5553e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class VoiceSearchButtonClickListener implements View.OnClickListener {
5563e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onClick(View view) {
5573e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            onVoiceSearchClicked();
5583e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
5593e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
5603e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5613e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    /**
5623e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     * Updates the progress bar when the suggestions adapter changes its progress.
5633e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert     */
5643e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    private class ProgressUpdater extends DataSetObserver {
5653e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        private final Suggestions mSuggestions;
5663e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5673e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public ProgressUpdater(Suggestions suggestions) {
5683e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            mSuggestions = suggestions;
5693e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
5703e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert
5713e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        @Override
5723e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        public void onChanged() {
5733e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            if (mSuggestions.isDone()) {
5743e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert                stopSearchProgress();
5753e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert            }
5763e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert        }
5773e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert    }
578713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert
579713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    private class SearchInfo implements SourceSelector.SearchInfo {
580713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        public Bundle getAppSearchData() {
581713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            return mAppSearchData;
582713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
583713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        public Drawable getSourceIcon() {
584713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            if (mSource == null) {
585713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert                return getSuggestionViewFactory().getGlobalSearchIcon();
586713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            } else {
587713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert                return mSource.getSourceIcon();
588713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            }
589713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
590713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        public String getQuery() {
591713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            return SearchActivity.this.getQuery();
592713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
593713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        public ComponentName getSourceName() {
594713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            return mSource == null ? null : mSource.getComponentName();
595713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
596713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        public void startActivity(Intent intent) throws ActivityNotFoundException {
597713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert            startActivityForResult(intent, REQUEST_SELECT_SOURCE);
598713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert        }
599713194910648268c094fa81b81f40ce2f7e39333Bjorn Bringert    }
6003e44ff1f2a204db3f479698cf0b3eab3d451dec2Bjorn Bringert}
601