blob: 78f7944f8e840f4d48dbbbe5ca8ae6e47272b42d [file] [log] [blame]
/*
* Copyright (C) 2009 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.quicksearchbox;
import com.android.common.Search;
import com.android.quicksearchbox.ui.CorpusViewFactory;
import com.android.quicksearchbox.ui.SuggestionClickListener;
import com.android.quicksearchbox.ui.SuggestionsAdapter;
import com.android.quicksearchbox.ui.SuggestionsView;
import android.app.Activity;
import android.app.SearchManager;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.DataSetObserver;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Debug;
import android.os.Handler;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.View;
import android.view.View.OnFocusChangeListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AbsListView;
import android.widget.EditText;
import android.widget.ImageButton;
import java.io.File;
import java.util.Collection;
/**
* The main activity for Quick Search Box. Shows the search UI.
*
*/
public class SearchActivity extends Activity {
private static final boolean DBG = true;
private static final String TAG = "QSB.SearchActivity";
private static final boolean TRACE = false;
private static final String SCHEME_CORPUS = "qsb.corpus";
public static final String INTENT_ACTION_QSB_AND_SELECT_CORPUS
= "com.android.quicksearchbox.action.QSB_AND_SELECT_CORPUS";
// The string used for privateImeOptions to identify to the IME that it should not show
// a microphone button since one already exists in the search dialog.
// TODO: This should move to android-common or something.
private static final String IME_OPTION_NO_MICROPHONE = "nm";
// Keys for the saved instance state.
private static final String INSTANCE_KEY_CORPUS = "corpus";
private static final String INSTANCE_KEY_QUERY = "query";
// Measures time from for last onCreate()/onNewIntent() call.
private LatencyTracker mStartLatencyTracker;
// Whether QSB is starting. True between the calls to onCreate()/onNewIntent() and onResume().
private boolean mStarting;
// True if the user has taken some action, e.g. launching a search, voice search,
// or suggestions, since QSB was last started.
private boolean mTookAction;
private CorpusSelectionDialog mCorpusSelectionDialog;
protected SuggestionsAdapter mSuggestionsAdapter;
private CorporaObserver mCorporaObserver;
protected EditText mQueryTextView;
// True if the query was empty on the previous call to updateQuery()
protected boolean mQueryWasEmpty = true;
protected SuggestionsView mSuggestionsView;
protected ImageButton mSearchGoButton;
protected ImageButton mVoiceSearchButton;
protected ImageButton mCorpusIndicator;
private VoiceSearch mVoiceSearch;
private Corpus mCorpus;
private Bundle mAppSearchData;
private boolean mUpdateSuggestions;
private final Handler mHandler = new Handler();
private final Runnable mUpdateSuggestionsTask = new Runnable() {
public void run() {
updateSuggestions(getQuery());
}
};
private final Runnable mShowInputMethodTask = new Runnable() {
public void run() {
showInputMethodForQuery();
}
};
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
if (TRACE) startMethodTracing();
recordStartTime();
if (DBG) Log.d(TAG, "onCreate()");
super.onCreate(savedInstanceState);
setContentView();
mSuggestionsAdapter = getQsbApplication().createSuggestionsAdapter();
mQueryTextView = (EditText) findViewById(R.id.search_src_text);
mSuggestionsView = (SuggestionsView) findViewById(R.id.suggestions);
mSuggestionsView.setSuggestionClickListener(new ClickHandler());
mSuggestionsView.setOnScrollListener(new InputMethodCloser());
mSuggestionsView.setOnKeyListener(new SuggestionsViewKeyListener());
mSuggestionsView.setOnFocusChangeListener(new SuggestListFocusListener());
mSearchGoButton = (ImageButton) findViewById(R.id.search_go_btn);
mVoiceSearchButton = (ImageButton) findViewById(R.id.search_voice_btn);
mCorpusIndicator = (ImageButton) findViewById(R.id.corpus_indicator);
mVoiceSearch = new VoiceSearch(this);
mQueryTextView.addTextChangedListener(new SearchTextWatcher());
mQueryTextView.setOnKeyListener(new QueryTextViewKeyListener());
mQueryTextView.setOnFocusChangeListener(new QueryTextViewFocusListener());
mCorpusIndicator.setOnClickListener(new CorpusIndicatorClickListener());
mSearchGoButton.setOnClickListener(new SearchGoButtonClickListener());
mVoiceSearchButton.setOnClickListener(new VoiceSearchButtonClickListener());
ButtonsKeyListener buttonsKeyListener = new ButtonsKeyListener();
mSearchGoButton.setOnKeyListener(buttonsKeyListener);
mVoiceSearchButton.setOnKeyListener(buttonsKeyListener);
mCorpusIndicator.setOnKeyListener(buttonsKeyListener);
mUpdateSuggestions = true;
// First get setup from intent
Intent intent = getIntent();
setupFromIntent(intent);
// Then restore any saved instance state
restoreInstanceState(savedInstanceState);
// Do this at the end, to avoid updating the list view when setSource()
// is called.
mSuggestionsView.setAdapter(mSuggestionsAdapter);
mCorporaObserver = new CorporaObserver();
getCorpora().registerDataSetObserver(mCorporaObserver);
}
protected void setContentView() {
setContentView(R.layout.search_activity);
}
private void startMethodTracing() {
File traceDir = getDir("traces", 0);
String traceFile = new File(traceDir, "qsb.trace").getAbsolutePath();
Debug.startMethodTracing(traceFile);
}
@Override
protected void onNewIntent(Intent intent) {
if (DBG) Log.d(TAG, "onNewIntent()");
recordStartTime();
setIntent(intent);
setupFromIntent(intent);
}
private void recordStartTime() {
mStartLatencyTracker = new LatencyTracker();
mStarting = true;
mTookAction = false;
}
protected void restoreInstanceState(Bundle savedInstanceState) {
if (savedInstanceState == null) return;
String corpusName = savedInstanceState.getString(INSTANCE_KEY_CORPUS);
String query = savedInstanceState.getString(INSTANCE_KEY_QUERY);
setCorpus(corpusName);
setQuery(query, false);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// We don't save appSearchData, since we always get the value
// from the intent and the user can't change it.
outState.putString(INSTANCE_KEY_CORPUS, getCorpusName());
outState.putString(INSTANCE_KEY_QUERY, getQuery());
}
private void setupFromIntent(Intent intent) {
if (DBG) Log.d(TAG, "setupFromIntent(" + intent.toUri(0) + ")");
String corpusName = getCorpusNameFromUri(intent.getData());
String query = intent.getStringExtra(SearchManager.QUERY);
Bundle appSearchData = intent.getBundleExtra(SearchManager.APP_DATA);
boolean selectAll = intent.getBooleanExtra(SearchManager.EXTRA_SELECT_QUERY, false);
setCorpus(corpusName);
setQuery(query, selectAll);
mAppSearchData = appSearchData;
if (startedIntoCorpusSelectionDialog()) {
showCorpusSelectionDialog();
}
}
public boolean startedIntoCorpusSelectionDialog() {
return INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(getIntent().getAction());
}
/**
* Removes corpus selector intent action, so that BACK works normally after
* dismissing and reopening the corpus selector.
*/
private void clearStartedIntoCorpusSelectionDialog() {
Intent oldIntent = getIntent();
if (SearchActivity.INTENT_ACTION_QSB_AND_SELECT_CORPUS.equals(oldIntent.getAction())) {
Intent newIntent = new Intent(oldIntent);
newIntent.setAction(SearchManager.INTENT_ACTION_GLOBAL_SEARCH);
setIntent(newIntent);
}
}
public static Uri getCorpusUri(Corpus corpus) {
if (corpus == null) return null;
return new Uri.Builder()
.scheme(SCHEME_CORPUS)
.authority(corpus.getName())
.build();
}
private String getCorpusNameFromUri(Uri uri) {
if (uri == null) return null;
if (!SCHEME_CORPUS.equals(uri.getScheme())) return null;
return uri.getAuthority();
}
private Corpus getCorpus(String sourceName) {
if (sourceName == null) return null;
Corpus corpus = getCorpora().getCorpus(sourceName);
if (corpus == null) {
Log.w(TAG, "Unknown corpus " + sourceName);
return null;
}
return corpus;
}
private void setCorpus(String corpusName) {
if (DBG) Log.d(TAG, "setCorpus(" + corpusName + ")");
mCorpus = getCorpus(corpusName);
Drawable sourceIcon;
if (mCorpus == null) {
sourceIcon = getCorpusViewFactory().getGlobalSearchIcon();
} else {
sourceIcon = mCorpus.getCorpusIcon();
}
mSuggestionsAdapter.setCorpus(mCorpus);
mCorpusIndicator.setImageDrawable(sourceIcon);
updateUi(getQuery().length() == 0);
}
private String getCorpusName() {
return mCorpus == null ? null : mCorpus.getName();
}
private QsbApplication getQsbApplication() {
return (QsbApplication) getApplication();
}
private Config getConfig() {
return getQsbApplication().getConfig();
}
private Corpora getCorpora() {
return getQsbApplication().getCorpora();
}
private ShortcutRepository getShortcutRepository() {
return getQsbApplication().getShortcutRepository();
}
private SuggestionsProvider getSuggestionsProvider() {
return getQsbApplication().getSuggestionsProvider();
}
private CorpusViewFactory getCorpusViewFactory() {
return getQsbApplication().getCorpusViewFactory();
}
private Logger getLogger() {
return getQsbApplication().getLogger();
}
@Override
protected void onDestroy() {
if (DBG) Log.d(TAG, "onDestroy()");
super.onDestroy();
getCorpora().unregisterDataSetObserver(mCorporaObserver);
mSuggestionsView.setAdapter(null); // closes mSuggestionsAdapter
}
@Override
protected void onStop() {
if (DBG) Log.d(TAG, "onStop()");
if (!mTookAction) {
// TODO: This gets logged when starting other activities, e.g. by opening he search
// settings, or clicking a notification in the status bar.
getLogger().logExit(getCurrentSuggestions(), getQuery().length());
}
// Close all open suggestion cursors. The query will be redone in onResume()
// if we come back to this activity.
mSuggestionsAdapter.setSuggestions(null);
getQsbApplication().getShortcutRefresher().reset();
dismissCorpusSelectionDialog();
super.onStop();
}
@Override
protected void onRestart() {
if (DBG) Log.d(TAG, "onRestart()");
super.onRestart();
}
@Override
protected void onResume() {
if (DBG) Log.d(TAG, "onResume()");
super.onResume();
updateSuggestionsBuffered();
if (!isCorpusSelectionDialogShowing()) {
mQueryTextView.requestFocus();
}
if (TRACE) Debug.stopMethodTracing();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
SearchSettings.addSearchSettingsMenuItem(this, menu);
return true;
}
@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus) {
// Launch the IME after a bit
mHandler.postDelayed(mShowInputMethodTask, 0);
}
}
protected String getQuery() {
CharSequence q = mQueryTextView.getText();
return q == null ? "" : q.toString();
}
/**
* Sets the text in the query box. Does not update the suggestions,
* and does not change the saved user-entered query.
* {@link #restoreUserQuery()} will restore the query to the last
* user-entered query.
*/
private void setQuery(String query, boolean selectAll) {
mUpdateSuggestions = false;
mQueryTextView.setText(query);
setTextSelection(selectAll);
mUpdateSuggestions = true;
}
/**
* Sets the text selection in the query text view.
*
* @param selectAll If {@code true}, selects the entire query.
* If {@false}, no characters are selected, and the cursor is placed
* at the end of the query.
*/
private void setTextSelection(boolean selectAll) {
if (selectAll) {
mQueryTextView.selectAll();
} else {
mQueryTextView.setSelection(mQueryTextView.length());
}
}
protected void updateUi(boolean queryEmpty) {
updateQueryTextView(queryEmpty);
updateSearchGoButton(queryEmpty);
updateVoiceSearchButton(queryEmpty);
}
private void updateQueryTextView(boolean queryEmpty) {
if (queryEmpty) {
if (mCorpus == null || mCorpus.isWebCorpus()) {
mQueryTextView.setBackgroundResource(R.drawable.textfield_search_empty_google);
mQueryTextView.setHint(null);
} else {
mQueryTextView.setBackgroundResource(R.drawable.textfield_search_empty);
mQueryTextView.setHint(mCorpus.getHint());
}
} else {
mQueryTextView.setBackgroundResource(R.drawable.textfield_search);
}
}
private void updateSearchGoButton(boolean queryEmpty) {
if (queryEmpty) {
mSearchGoButton.setVisibility(View.GONE);
} else {
mSearchGoButton.setVisibility(View.VISIBLE);
}
}
protected void updateVoiceSearchButton(boolean queryEmpty) {
if (queryEmpty && mVoiceSearch.shouldShowVoiceSearch(mCorpus)) {
mVoiceSearchButton.setVisibility(View.VISIBLE);
mQueryTextView.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE);
} else {
mVoiceSearchButton.setVisibility(View.GONE);
mQueryTextView.setPrivateImeOptions(null);
}
}
protected void showCorpusSelectionDialog() {
if (mCorpusSelectionDialog == null) {
mCorpusSelectionDialog = new CorpusSelectionDialog(this);
mCorpusSelectionDialog.setOwnerActivity(this);
mCorpusSelectionDialog.setOnDismissListener(new CorpusSelectorDismissListener());
mCorpusSelectionDialog.setOnCorpusSelectedListener(new CorpusSelectionListener());
}
mCorpusSelectionDialog.show(mCorpus);
}
protected boolean isCorpusSelectionDialogShowing() {
return mCorpusSelectionDialog != null && mCorpusSelectionDialog.isShowing();
}
protected void dismissCorpusSelectionDialog() {
if (mCorpusSelectionDialog != null) {
mCorpusSelectionDialog.dismiss();
}
}
/**
* @return true if a search was performed as a result of this click, false otherwise.
*/
protected boolean onSearchClicked(int method) {
String query = ltrim(getQuery());
if (DBG) Log.d(TAG, "Search clicked, query=" + query);
// Don't do empty queries
if (TextUtils.getTrimmedLength(query) == 0) return false;
Corpus searchCorpus = getSearchCorpus();
if (searchCorpus == null) return false;
mTookAction = true;
// Log search start
getLogger().logSearch(mCorpus, method, query.length());
// Create shortcut
SuggestionData searchShortcut = searchCorpus.createSearchShortcut(query);
if (searchShortcut != null) {
DataSuggestionCursor cursor = new DataSuggestionCursor(query);
cursor.add(searchShortcut);
getShortcutRepository().reportClick(cursor, 0);
}
// Start search
Intent intent = searchCorpus.createSearchIntent(query, mAppSearchData);
launchIntent(intent);
return true;
}
protected void onVoiceSearchClicked() {
if (DBG) Log.d(TAG, "Voice Search clicked");
Corpus searchCorpus = getSearchCorpus();
if (searchCorpus == null) return;
mTookAction = true;
// Log voice search start
getLogger().logVoiceSearch(searchCorpus);
// Start voice search
Intent intent = searchCorpus.createVoiceSearchIntent(mAppSearchData);
launchIntent(intent);
}
/**
* Gets the corpus to use for any searches. This is the web corpus in "All" mode,
* and the selected corpus otherwise.
*/
protected Corpus getSearchCorpus() {
if (mCorpus != null) {
return mCorpus;
} else {
Corpus webCorpus = getCorpora().getWebCorpus();
if (webCorpus == null) {
Log.e(TAG, "No web corpus");
}
return webCorpus;
}
}
protected SuggestionCursor getCurrentSuggestions() {
return mSuggestionsAdapter.getCurrentSuggestions();
}
protected void launchIntent(Intent intent) {
if (intent == null) {
return;
}
try {
startActivity(intent);
} catch (RuntimeException ex) {
// Since the intents for suggestions specified by suggestion providers,
// guard against them not being handled, not allowed, etc.
Log.e(TAG, "Failed to start " + intent.toUri(0), ex);
}
}
protected boolean launchSuggestion(int position) {
SuggestionCursor suggestions = getCurrentSuggestions();
if (position < 0 || position >= suggestions.getCount()) {
Log.w(TAG, "Tried to launch invalid suggestion " + position);
return false;
}
if (DBG) Log.d(TAG, "Launching suggestion " + position);
mTookAction = true;
// Log suggestion click
Collection<Corpus> corpora = mSuggestionsAdapter.getSuggestions().getIncludedCorpora();
getLogger().logSuggestionClick(position, suggestions, corpora);
// Create shortcut
getShortcutRepository().reportClick(suggestions, position);
// Launch intent
suggestions.moveTo(position);
Intent intent = suggestions.getSuggestionIntent(mAppSearchData);
launchIntent(intent);
return true;
}
protected boolean onSuggestionLongClicked(int position) {
if (DBG) Log.d(TAG, "Long clicked on suggestion " + position);
return false;
}
protected boolean onSuggestionKeyDown(int position, int keyCode, KeyEvent event) {
// Treat enter or search as a click
if (keyCode == KeyEvent.KEYCODE_ENTER || keyCode == KeyEvent.KEYCODE_SEARCH) {
return launchSuggestion(position);
}
return false;
}
protected int getSelectedPosition() {
return mSuggestionsView.getSelectedPosition();
}
/**
* Hides the input method.
*/
protected void hideInputMethod() {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.hideSoftInputFromWindow(mQueryTextView.getWindowToken(), 0);
}
}
protected void showInputMethodForQuery() {
InputMethodManager imm = (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
if (imm != null) {
imm.showSoftInput(mQueryTextView, 0);
}
}
protected void onSuggestionListFocusChange(boolean focused) {
}
protected void onQueryTextViewFocusChange(boolean focused) {
}
/**
* Hides the input method when the suggestions get focus.
*/
private class SuggestListFocusListener implements OnFocusChangeListener {
public void onFocusChange(View v, boolean focused) {
if (DBG) Log.d(TAG, "Suggestions focus change, now: " + focused);
if (focused) {
// The suggestions list got focus, hide the input method
hideInputMethod();
}
onSuggestionListFocusChange(focused);
}
}
private class QueryTextViewFocusListener implements OnFocusChangeListener {
public void onFocusChange(View v, boolean focused) {
if (DBG) Log.d(TAG, "Query focus change, now: " + focused);
if (focused) {
// The query box got focus, show the input method
showInputMethodForQuery();
}
onQueryTextViewFocusChange(focused);
}
}
private int getMaxSuggestions() {
Config config = getConfig();
return mCorpus == null
? config.getMaxPromotedSuggestions()
: config.getMaxResultsPerSource();
}
private void updateSuggestionsBuffered() {
mHandler.removeCallbacks(mUpdateSuggestionsTask);
long delay = getConfig().getTypingUpdateSuggestionsDelayMillis();
mHandler.postDelayed(mUpdateSuggestionsTask, delay);
}
protected void updateSuggestions(String query) {
query = ltrim(query);
Suggestions suggestions = getSuggestionsProvider().getSuggestions(
query, mCorpus, getMaxSuggestions());
// Log start latency if this is the first suggestions update
if (mStarting) {
mStarting = false;
String source = getIntent().getStringExtra(Search.SOURCE);
int latency = mStartLatencyTracker.getLatency();
getLogger().logStart(latency, source, mCorpus, suggestions.getExpectedCorpora());
}
mSuggestionsAdapter.setSuggestions(suggestions);
}
private boolean forwardKeyToQueryTextView(int keyCode, KeyEvent event) {
if (!event.isSystem() && !isDpadKey(keyCode)) {
if (DBG) Log.d(TAG, "Forwarding key to query box: " + event);
if (mQueryTextView.requestFocus()) {
return mQueryTextView.dispatchKeyEvent(event);
}
}
return false;
}
private boolean isDpadKey(int keyCode) {
switch (keyCode) {
case KeyEvent.KEYCODE_DPAD_UP:
case KeyEvent.KEYCODE_DPAD_DOWN:
case KeyEvent.KEYCODE_DPAD_LEFT:
case KeyEvent.KEYCODE_DPAD_RIGHT:
case KeyEvent.KEYCODE_DPAD_CENTER:
return true;
default:
return false;
}
}
/**
* Filters the suggestions list when the search text changes.
*/
private class SearchTextWatcher implements TextWatcher {
public void afterTextChanged(Editable s) {
boolean empty = s.length() == 0;
if (empty != mQueryWasEmpty) {
mQueryWasEmpty = empty;
updateUi(empty);
}
if (mUpdateSuggestions) {
updateSuggestionsBuffered();
}
}
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}
/**
* Handles non-text keys in the query text view.
*/
private class QueryTextViewKeyListener implements View.OnKeyListener {
public boolean onKey(View view, int keyCode, KeyEvent event) {
// Handle IME search action key
if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() == KeyEvent.ACTION_UP) {
// if no action was taken, consume the key event so that the keyboard
// remains on screen.
return !onSearchClicked(Logger.SEARCH_METHOD_KEYBOARD);
}
return false;
}
}
/**
* Handles key events on the search and voice search buttons,
* by refocusing to EditText.
*/
private class ButtonsKeyListener implements View.OnKeyListener {
public boolean onKey(View v, int keyCode, KeyEvent event) {
return forwardKeyToQueryTextView(keyCode, event);
}
}
/**
* Handles key events on the suggestions list view.
*/
private class SuggestionsViewKeyListener implements View.OnKeyListener {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() == KeyEvent.ACTION_DOWN) {
int position = getSelectedPosition();
if (onSuggestionKeyDown(position, keyCode, event)) {
return true;
}
}
return forwardKeyToQueryTextView(keyCode, event);
}
}
private class InputMethodCloser implements SuggestionsView.OnScrollListener {
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
}
public void onScrollStateChanged(AbsListView view, int scrollState) {
hideInputMethod();
}
}
private class ClickHandler implements SuggestionClickListener {
public void onSuggestionClicked(int position) {
launchSuggestion(position);
}
public boolean onSuggestionLongClicked(int position) {
return SearchActivity.this.onSuggestionLongClicked(position);
}
public void onSuggestionQueryRefineClicked(int position) {
if (DBG) Log.d(TAG, "query refine clicked, pos " + position);
SuggestionCursor suggestions = getCurrentSuggestions();
if (suggestions != null) {
suggestions.moveTo(position);
String query = suggestions.getSuggestionQuery();
if (!TextUtils.isEmpty(query)) {
query += " ";
setQuery(query, false);
updateSuggestions(query);
}
}
}
}
/**
* Listens for clicks on the source selector.
*/
private class SearchGoButtonClickListener implements View.OnClickListener {
public void onClick(View view) {
onSearchClicked(Logger.SEARCH_METHOD_BUTTON);
}
}
/**
* Listens for clicks on the search button.
*/
private class CorpusIndicatorClickListener implements View.OnClickListener {
public void onClick(View view) {
showCorpusSelectionDialog();
}
}
private class CorpusSelectorDismissListener implements DialogInterface.OnDismissListener {
public void onDismiss(DialogInterface dialog) {
if (DBG) Log.d(TAG, "Corpus selector dismissed");
clearStartedIntoCorpusSelectionDialog();
}
}
private class CorpusSelectionListener
implements CorpusSelectionDialog.OnCorpusSelectedListener {
public void onCorpusSelected(String corpusName) {
setCorpus(corpusName);
updateSuggestions(getQuery());
mQueryTextView.requestFocus();
showInputMethodForQuery();
}
}
/**
* Listens for clicks on the voice search button.
*/
private class VoiceSearchButtonClickListener implements View.OnClickListener {
public void onClick(View view) {
onVoiceSearchClicked();
}
}
private class CorporaObserver extends DataSetObserver {
@Override
public void onChanged() {
setCorpus(getCorpusName());
updateSuggestions(getQuery());
}
}
private static String ltrim(String text) {
int start = 0;
int length = text.length();
while (start < length && Character.isWhitespace(text.charAt(start))) {
start++;
}
return start > 0 ? text.substring(start, length) : text;
}
}