SearchWidgetProvider.java revision 7399b784ec97c25084afa98bb0bcfcb70f7bc4ec
1/* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.quicksearchbox; 18 19import com.android.common.Search; 20import com.android.common.speech.Recognition; 21import com.android.quicksearchbox.ui.CorpusViewFactory; 22 23import android.app.Activity; 24import android.app.AlarmManager; 25import android.app.PendingIntent; 26import android.app.SearchManager; 27import android.appwidget.AppWidgetManager; 28import android.content.BroadcastReceiver; 29import android.content.ComponentName; 30import android.content.Context; 31import android.content.Intent; 32import android.content.SharedPreferences; 33import android.content.SharedPreferences.Editor; 34import android.graphics.Typeface; 35import android.net.Uri; 36import android.os.Bundle; 37import android.os.SystemClock; 38import android.speech.RecognizerIntent; 39import android.text.Annotation; 40import android.text.SpannableStringBuilder; 41import android.text.TextUtils; 42import android.text.style.StyleSpan; 43import android.util.Log; 44import android.view.View; 45import android.widget.RemoteViews; 46 47import java.util.ArrayList; 48import java.util.Random; 49 50/** 51 * Search widget provider. 52 * 53 */ 54public class SearchWidgetProvider extends BroadcastReceiver { 55 56 private static final boolean DBG = false; 57 private static final String TAG = "QSB.SearchWidgetProvider"; 58 59 /** 60 * Broadcast intent action for showing the next voice search hint 61 * (if voice search hints are enabled). 62 */ 63 private static final String ACTION_NEXT_VOICE_SEARCH_HINT = 64 "com.android.quicksearchbox.action.NEXT_VOICE_SEARCH_HINT"; 65 66 /** 67 * Broadcast intent action for hiding voice search hints. 68 */ 69 private static final String ACTION_HIDE_VOICE_SEARCH_HINT = 70 "com.android.quicksearchbox.action.HIDE_VOICE_SEARCH_HINT"; 71 72 /** 73 * Broadcast intent action for updating voice search hint display. Voice search hints will 74 * only be displayed with some probability. 75 */ 76 private static final String ACTION_CONSIDER_VOICE_SEARCH_HINT = 77 "com.android.quicksearchbox.action.CONSIDER_VOICE_SEARCH_HINT"; 78 79 /** 80 * Broadcast intent action for displaying voice search hints immediately. 81 */ 82 private static final String ACTION_SHOW_VOICE_SEARCH_HINT_NOW = 83 "com.android.quicksearchbox.action.SHOW_VOICE_SEARCH_HINT_NOW"; 84 85 /** 86 * Preference key used for storing the index of the next voice search hint to show. 87 */ 88 private static final String NEXT_VOICE_SEARCH_HINT_INDEX_PREF = "next_voice_search_hint"; 89 90 /** 91 * Preference key used to store the time at which the first voice search hint was displayed. 92 */ 93 private static final String FIRST_VOICE_HINT_DISPLAY_TIME = "first_voice_search_hint_time"; 94 95 /** 96 * Preference key for the version of voice search we last got hints from. 97 */ 98 private static final String LAST_SEEN_VOICE_SEARCH_VERSION = "voice_search_version"; 99 100 /** 101 * The {@link Search#SOURCE} value used when starting searches from the search widget. 102 */ 103 private static final String WIDGET_SEARCH_SOURCE = "launcher-widget"; 104 105 private static Random sRandom; 106 107 @Override 108 public void onReceive(Context context, Intent intent) { 109 if (DBG) Log.d(TAG, "onReceive(" + intent.toUri(0) + ")"); 110 String action = intent.getAction(); 111 if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { 112 scheduleVoiceHintUpdates(context); 113 } else if (AppWidgetManager.ACTION_APPWIDGET_UPDATE.equals(action)) { 114 updateSearchWidgets(context); 115 } else if (ACTION_CONSIDER_VOICE_SEARCH_HINT.equals(action)) { 116 considerShowingVoiceSearchHints(context); 117 } else if (ACTION_NEXT_VOICE_SEARCH_HINT.equals(action)) { 118 getHintsFromVoiceSearch(context); 119 } else if (ACTION_HIDE_VOICE_SEARCH_HINT.equals(action)) { 120 hideVoiceSearchHint(context); 121 } else if (ACTION_SHOW_VOICE_SEARCH_HINT_NOW.equals(action)) { 122 showVoiceSearchHintNow(context); 123 } 124 } 125 126 private static Random getRandom() { 127 if (sRandom == null) { 128 sRandom = new Random(); 129 } 130 return sRandom; 131 } 132 133 private static boolean haveVoiceSearchHintsExpired(Context context) { 134 SharedPreferences prefs = SearchSettings.getSearchPreferences(context); 135 QsbApplication app = QsbApplication.get(context); 136 int currentVoiceSearchVersion = app.getVoiceSearch().getVersion(); 137 138 if (currentVoiceSearchVersion != 0) { 139 long currentTime = System.currentTimeMillis(); 140 int lastVoiceSearchVersion = prefs.getInt(LAST_SEEN_VOICE_SEARCH_VERSION, 0); 141 long firstHintTime = prefs.getLong(FIRST_VOICE_HINT_DISPLAY_TIME, 0); 142 if (firstHintTime == 0 || currentVoiceSearchVersion != lastVoiceSearchVersion) { 143 Editor e = prefs.edit(); 144 e.putInt(LAST_SEEN_VOICE_SEARCH_VERSION, currentVoiceSearchVersion); 145 e.putLong(FIRST_VOICE_HINT_DISPLAY_TIME, currentTime); 146 e.commit(); 147 firstHintTime = currentTime; 148 } 149 if (currentTime - firstHintTime > getConfig(context).getVoiceSearchHintActivePeriod()) { 150 if (DBG) Log.d(TAG, "Voice seach hint period expired; not showing hints."); 151 return true; 152 } else { 153 return false; 154 } 155 } else { 156 if (DBG) Log.d(TAG, "Could not determine voice search version; not showing hints."); 157 return true; 158 } 159 } 160 161 private static boolean shouldShowVoiceSearchHints(Context context) { 162 return (getConfig(context).allowVoiceSearchHints() 163 && !haveVoiceSearchHintsExpired(context)); 164 } 165 166 private static SearchWidgetState[] getSearchWidgetStates 167 (Context context, boolean enableVoiceSearchHints) { 168 169 AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); 170 int[] appWidgetIds = appWidgetManager.getAppWidgetIds(myComponentName(context)); 171 SearchWidgetState[] states = new SearchWidgetState[appWidgetIds.length]; 172 for (int i = 0; i<appWidgetIds.length; ++i) { 173 states[i] = getSearchWidgetState(context, appWidgetIds[i], enableVoiceSearchHints); 174 } 175 return states; 176 } 177 178 private static void considerShowingVoiceSearchHints(Context context) { 179 if (DBG) Log.d(TAG, "considerShowingVoiceSearchHints"); 180 if (!shouldShowVoiceSearchHints(context)) return; 181 SearchWidgetState[] states = getSearchWidgetStates(context, true); 182 boolean needHint = false; 183 boolean changed = false; 184 for (SearchWidgetState state : states) { 185 changed |= state.considerShowingHint(context); 186 needHint |= state.isShowingHint(); 187 } 188 if (changed) { 189 getHintsFromVoiceSearch(context); 190 scheduleNextVoiceSearchHint(context, true); 191 } 192 } 193 194 private static void showVoiceSearchHintNow(Context context) { 195 if (DBG) Log.d(TAG, "showVoiceSearchHintNow"); 196 SearchWidgetState[] states = getSearchWidgetStates(context, true); 197 for (SearchWidgetState state : states) { 198 state.setShowingHint(true); 199 state.updateShowingHint(context); 200 } 201 getHintsFromVoiceSearch(context); 202 scheduleNextVoiceSearchHint(context, true); 203 } 204 205 private void hideVoiceSearchHint(Context context) { 206 if (DBG) Log.d(TAG, "hideVoiceSearchHint"); 207 SearchWidgetState[] states = getSearchWidgetStates(context, true); 208 boolean needHint = false; 209 for (SearchWidgetState state : states) { 210 if (state.isShowingHint()) { 211 state.hideVoiceSearchHint(context); 212 state.updateWidget(context, AppWidgetManager.getInstance(context)); 213 } 214 needHint |= state.isShowingHint(); 215 } 216 scheduleNextVoiceSearchHint(context, false); 217 } 218 219 private static void voiceSearchHintReceived(Context context, CharSequence hint) { 220 if (DBG) Log.d(TAG, "voiceSearchHintReceived('" + hint + "')"); 221 CharSequence formatted = formatVoiceSearchHint(context, hint); 222 SearchWidgetState[] states = getSearchWidgetStates(context, true); 223 boolean needHint = false; 224 for (SearchWidgetState state : states) { 225 if (state.isShowingHint()) { 226 state.setVoiceSearchHint(formatted); 227 state.updateWidget(context, AppWidgetManager.getInstance(context)); 228 needHint = true; 229 } 230 } 231 if (!needHint) { 232 scheduleNextVoiceSearchHint(context, false); 233 } 234 } 235 236 private static void scheduleVoiceHintUpdates(Context context) { 237 if (DBG) Log.d(TAG, "scheduleVoiceHintUpdates"); 238 if (!shouldShowVoiceSearchHints(context)) return; 239 scheduleVoiceSearchHintUpdates(context, true); 240 } 241 242 /** 243 * Updates all search widgets. 244 */ 245 public static void updateSearchWidgets(Context context) { 246 if (DBG) Log.d(TAG, "updateSearchWidgets"); 247 boolean showVoiceSearchHints = shouldShowVoiceSearchHints(context); 248 SearchWidgetState[] states = getSearchWidgetStates(context, showVoiceSearchHints); 249 250 boolean needVoiceSearchHint = false; 251 for (SearchWidgetState state : states) { 252 if (state.isShowingHint()) { 253 needVoiceSearchHint = true; 254 // widget update will occur when voice search hint received 255 } else { 256 state.updateWidget(context, AppWidgetManager.getInstance(context)); 257 } 258 } 259 if (DBG) Log.d(TAG, "Need voice search hints=" + needVoiceSearchHint); 260 if (needVoiceSearchHint) { 261 getHintsFromVoiceSearch(context); 262 } 263 if (!showVoiceSearchHints) { 264 scheduleVoiceSearchHintUpdates(context, false); 265 } 266 } 267 268 /** 269 * Gets the component name of this search widget provider. 270 */ 271 private static ComponentName myComponentName(Context context) { 272 String pkg = context.getPackageName(); 273 String cls = pkg + ".SearchWidgetProvider"; 274 return new ComponentName(pkg, cls); 275 } 276 277 private static Intent createQsbActivityIntent(Context context, String action, 278 Bundle widgetAppData, Corpus corpus) { 279 Intent qsbIntent = new Intent(action); 280 qsbIntent.setPackage(context.getPackageName()); 281 qsbIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK 282 | Intent.FLAG_ACTIVITY_CLEAR_TOP 283 | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED); 284 qsbIntent.putExtra(SearchManager.APP_DATA, widgetAppData); 285 qsbIntent.setData(SearchActivity.getCorpusUri(corpus)); 286 return qsbIntent; 287 } 288 289 private static SearchWidgetState getSearchWidgetState(Context context, 290 int appWidgetId, boolean enableVoiceSearchHints) { 291 String corpusName = 292 SearchWidgetConfigActivity.getWidgetCorpusName(context, appWidgetId); 293 Corpus corpus = corpusName == null ? null : getCorpora(context).getCorpus(corpusName); 294 if (DBG) Log.d(TAG, "Creating appwidget state " + appWidgetId + ", corpus=" + corpus); 295 SearchWidgetState state = new SearchWidgetState(appWidgetId); 296 297 Bundle widgetAppData = new Bundle(); 298 widgetAppData.putString(Search.SOURCE, WIDGET_SEARCH_SOURCE); 299 300 // Query text view hint 301 if (corpus == null || corpus.isWebCorpus()) { 302 state.setQueryTextViewBackgroundResource(R.drawable.textfield_search_empty_google); 303 } else { 304 state.setQueryTextViewHint(corpus.getHint()); 305 state.setQueryTextViewBackgroundResource(R.drawable.textfield_search_empty); 306 } 307 308 // Text field click 309 Intent qsbIntent = createQsbActivityIntent( 310 context, 311 SearchManager.INTENT_ACTION_GLOBAL_SEARCH, 312 widgetAppData, 313 corpus); 314 state.setQueryTextViewIntent(qsbIntent); 315 316 // Voice search button 317 Intent voiceSearchIntent = getVoiceSearchIntent(context, corpus, widgetAppData); 318 state.setVoiceSearchIntent(voiceSearchIntent); 319 if (enableVoiceSearchHints && voiceSearchIntent != null 320 && RecognizerIntent.ACTION_WEB_SEARCH.equals(voiceSearchIntent.getAction())) { 321 state.setVoiceSearchHintsEnabled(true); 322 323 boolean showingHint = 324 SearchWidgetConfigActivity.getWidgetShowingHint(context, appWidgetId); 325 if (DBG) Log.d(TAG, "Widget " + appWidgetId + " showing hint: " + showingHint); 326 state.setShowingHint(showingHint); 327 328 } 329 330 // Corpus indicator 331 state.setCorpusIconUri(getCorpusIconUri(context, corpus)); 332 333 Intent corpusIconIntent = createQsbActivityIntent(context, 334 SearchActivity.INTENT_ACTION_QSB_AND_SELECT_CORPUS, widgetAppData, corpus); 335 state.setCorpusIndicatorIntent(corpusIconIntent); 336 337 return state; 338 } 339 340 private static Intent getVoiceSearchIntent(Context context, Corpus corpus, 341 Bundle widgetAppData) { 342 VoiceSearch voiceSearch = QsbApplication.get(context).getVoiceSearch(); 343 344 if (corpus == null || !voiceSearch.isVoiceSearchAvailable()) { 345 return voiceSearch.createVoiceWebSearchIntent(widgetAppData); 346 } else { 347 return corpus.createVoiceSearchIntent(widgetAppData); 348 } 349 } 350 351 private static Intent getVoiceSearchHelpIntent(Context context) { 352 VoiceSearch voiceSearch = QsbApplication.get(context).getVoiceSearch(); 353 return voiceSearch.createVoiceSearchHelpIntent(); 354 } 355 356 private static PendingIntent createBroadcast(Context context, String action) { 357 Intent intent = new Intent(action); 358 intent.setComponent(myComponentName(context)); 359 return PendingIntent.getBroadcast(context, 0, intent, 0); 360 } 361 362 private static Uri getCorpusIconUri(Context context, Corpus corpus) { 363 if (corpus == null) { 364 return getCorpusViewFactory(context).getGlobalSearchIconUri(); 365 } 366 return corpus.getCorpusIconUri(); 367 } 368 369 private static CharSequence formatVoiceSearchHint(Context context, CharSequence hint) { 370 if (TextUtils.isEmpty(hint)) return null; 371 SpannableStringBuilder spannedHint = new SpannableStringBuilder( 372 context.getString(R.string.voice_search_hint_quotation_start)); 373 spannedHint.append(hint); 374 Object[] items = spannedHint.getSpans(0, spannedHint.length(), Object.class); 375 for (Object item : items) { 376 if (item instanceof Annotation) { 377 Annotation annotation = (Annotation) item; 378 if (annotation.getKey().equals("action") 379 && annotation.getValue().equals("true")) { 380 final int start = spannedHint.getSpanStart(annotation); 381 final int end = spannedHint.getSpanEnd(annotation); 382 spannedHint.removeSpan(item); 383 spannedHint.setSpan(new StyleSpan(Typeface.BOLD), start, end, 0); 384 } 385 } 386 } 387 spannedHint.append(context.getString(R.string.voice_search_hint_quotation_end)); 388 return spannedHint; 389 } 390 391 private static void rescheduleAction(Context context, boolean reschedule, String action, long period) { 392 AlarmManager alarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 393 PendingIntent intent = createBroadcast(context, action); 394 alarmManager.cancel(intent); 395 if (reschedule) { 396 if (DBG) Log.d(TAG, "Scheduling action " + action + " after period " + period); 397 alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, 398 SystemClock.elapsedRealtime() + period, period, intent); 399 } else { 400 if (DBG) Log.d(TAG, "Cancelled action " + action); 401 } 402 } 403 404 public static void scheduleVoiceSearchHintUpdates(Context context, boolean enabled) { 405 rescheduleAction(context, enabled, ACTION_CONSIDER_VOICE_SEARCH_HINT, 406 getConfig(context).getVoiceSearchHintUpdatePeriod()); 407 } 408 409 private static void scheduleNextVoiceSearchHint(Context context, boolean needUpdates) { 410 rescheduleAction(context, needUpdates, ACTION_NEXT_VOICE_SEARCH_HINT, 411 getConfig(context).getVoiceSearchHintChangePeriod()); 412 } 413 414 /** 415 * Requests an asynchronous update of the voice search hints. 416 */ 417 private static void getHintsFromVoiceSearch(Context context) { 418 Intent intent = new Intent(RecognizerIntent.ACTION_GET_LANGUAGE_DETAILS); 419 if (DBG) Log.d(TAG, "Broadcasting " + intent); 420 context.sendOrderedBroadcast(intent, null, 421 new HintReceiver(), null, Activity.RESULT_OK, null, null); 422 } 423 424 private static class HintReceiver extends BroadcastReceiver { 425 @Override 426 public void onReceive(Context context, Intent intent) { 427 if (getResultCode() != Activity.RESULT_OK) { 428 return; 429 } 430 ArrayList<CharSequence> hints = getResultExtras(true) 431 .getCharSequenceArrayList(Recognition.EXTRA_HINT_STRINGS); 432 CharSequence hint = getNextHint(context, hints); 433 voiceSearchHintReceived(context, hint); 434 } 435 } 436 437 /** 438 * Gets the next formatted hint, if there are any hints. 439 * Must be called on the application main thread. 440 * 441 * @return A hint, or {@code null} if no hints are available. 442 */ 443 private static CharSequence getNextHint(Context context, ArrayList<CharSequence> hints) { 444 if (hints == null || hints.isEmpty()) return null; 445 int i = getNextVoiceSearchHintIndex(context, hints.size()); 446 return hints.get(i); 447 } 448 449 private static int getNextVoiceSearchHintIndex(Context context, int size) { 450 int i = getAndIncrementIntPreference( 451 SearchSettings.getSearchPreferences(context), 452 NEXT_VOICE_SEARCH_HINT_INDEX_PREF); 453 return i % size; 454 } 455 456 // TODO: Could this be made atomic to avoid races? 457 private static int getAndIncrementIntPreference(SharedPreferences prefs, String name) { 458 int i = prefs.getInt(name, 0); 459 prefs.edit().putInt(name, i + 1).commit(); 460 return i; 461 } 462 463 private static Config getConfig(Context context) { 464 return QsbApplication.get(context).getConfig(); 465 } 466 467 private static Corpora getCorpora(Context context) { 468 return QsbApplication.get(context).getCorpora(); 469 } 470 471 private static CorpusViewFactory getCorpusViewFactory(Context context) { 472 return QsbApplication.get(context).getCorpusViewFactory(); 473 } 474 475 private static class SearchWidgetState { 476 private final int mAppWidgetId; 477 private Uri mCorpusIconUri; 478 private Intent mCorpusIndicatorIntent; 479 private CharSequence mQueryTextViewHint; 480 private int mQueryTextViewBackgroundResource; 481 private Intent mQueryTextViewIntent; 482 private Intent mVoiceSearchIntent; 483 private boolean mVoiceSearchHintsEnabled; 484 private CharSequence mVoiceSearchHint; 485 private boolean mShowHint; 486 487 public SearchWidgetState(int appWidgetId) { 488 mAppWidgetId = appWidgetId; 489 } 490 491 public void setVoiceSearchHintsEnabled(boolean enabled) { 492 mVoiceSearchHintsEnabled = enabled; 493 } 494 495 public void setShowingHint(boolean show) { 496 mShowHint = show; 497 } 498 499 public boolean isShowingHint() { 500 return mShowHint; 501 } 502 503 public void setCorpusIconUri(Uri corpusIconUri) { 504 mCorpusIconUri = corpusIconUri; 505 } 506 507 public void setCorpusIndicatorIntent(Intent corpusIndicatorIntent) { 508 mCorpusIndicatorIntent = corpusIndicatorIntent; 509 } 510 511 public void setQueryTextViewHint(CharSequence queryTextViewHint) { 512 mQueryTextViewHint = queryTextViewHint; 513 } 514 515 public void setQueryTextViewBackgroundResource(int queryTextViewBackgroundResource) { 516 mQueryTextViewBackgroundResource = queryTextViewBackgroundResource; 517 } 518 519 public void setQueryTextViewIntent(Intent queryTextViewIntent) { 520 mQueryTextViewIntent = queryTextViewIntent; 521 } 522 523 public void setVoiceSearchIntent(Intent voiceSearchIntent) { 524 mVoiceSearchIntent = voiceSearchIntent; 525 } 526 527 public void setVoiceSearchHint(CharSequence voiceSearchHint) { 528 mVoiceSearchHint = voiceSearchHint; 529 } 530 531 private boolean chooseToShowHint(Context context) { 532 // this is called every getConfig().getVoiceSearchHintUpdatePeriod() milliseconds 533 // we want to return true every getConfig().getVoiceSearchHintShowPeriod() milliseconds 534 // so: 535 Config cfg = getConfig(context); 536 float p = (float) cfg.getVoiceSearchHintUpdatePeriod() 537 / (float) cfg.getVoiceSearchHintShowPeriod(); 538 float f = getRandom().nextFloat(); 539 // if p > 1 we won't return true as often as we should (we can't return more times than 540 // we're called!) but we will always return true. 541 boolean r = (f < p); 542 if (DBG) Log.d(TAG, "chooseToShowHint p=" + p +"; f=" + f + "; r=" + r); 543 return r; 544 } 545 546 private void scheduleHintHiding(Context context) { 547 AlarmManager alarmManager = 548 (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); 549 PendingIntent hideHint = createBroadcast(context, ACTION_HIDE_VOICE_SEARCH_HINT); 550 551 long period = getConfig(context).getVoiceSearchHintVisibleTime(); 552 if (DBG) { 553 Log.d(TAG, "Scheduling action " + ACTION_HIDE_VOICE_SEARCH_HINT + 554 " after period " + period); 555 } 556 alarmManager.set(AlarmManager.ELAPSED_REALTIME, 557 SystemClock.elapsedRealtime() + period, hideHint); 558 559 } 560 561 public void updateShowingHint(Context context) { 562 SearchWidgetConfigActivity.setWidgetShowingHint(context, mAppWidgetId, mShowHint); 563 } 564 565 public boolean considerShowingHint(Context context) { 566 if (!mVoiceSearchHintsEnabled || mShowHint) return false; 567 if (!chooseToShowHint(context)) return false; 568 scheduleHintHiding(context); 569 mShowHint = true; 570 updateShowingHint(context); 571 return true; 572 } 573 574 public void hideVoiceSearchHint(Context context) { 575 mShowHint = false; 576 updateShowingHint(context); 577 } 578 579 public void updateWidget(Context context,AppWidgetManager appWidgetMgr) { 580 if (DBG) Log.d(TAG, "Updating appwidget " + mAppWidgetId); 581 RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.search_widget); 582 // Corpus indicator 583 // Before Froyo, android.resource URI could not be used in ImageViews. 584 if (QsbApplication.isFroyoOrLater()) { 585 views.setImageViewUri(R.id.corpus_indicator, mCorpusIconUri); 586 } 587 setOnClickActivityIntent(context, views, R.id.corpus_indicator, 588 mCorpusIndicatorIntent); 589 // Query TextView 590 views.setCharSequence(R.id.search_widget_text, "setHint", mQueryTextViewHint); 591 setBackgroundResource(views, R.id.search_widget_text, mQueryTextViewBackgroundResource); 592 593 setOnClickActivityIntent(context, views, R.id.search_widget_text, 594 mQueryTextViewIntent); 595 // Voice Search button 596 if (mVoiceSearchIntent != null) { 597 setOnClickActivityIntent(context, views, R.id.search_widget_voice_btn, 598 mVoiceSearchIntent); 599 views.setViewVisibility(R.id.search_widget_voice_btn, View.VISIBLE); 600 } else { 601 views.setViewVisibility(R.id.search_widget_voice_btn, View.GONE); 602 } 603 604 // Voice Search hints 605 if (mShowHint && !TextUtils.isEmpty(mVoiceSearchHint)) { 606 views.setTextViewText(R.id.voice_search_hint_text, mVoiceSearchHint); 607 608 Intent voiceSearchHelp = getVoiceSearchHelpIntent(context); 609 if (voiceSearchHelp == null) voiceSearchHelp = mVoiceSearchIntent; 610 setOnClickActivityIntent(context, views, R.id.voice_search_hint, 611 voiceSearchHelp); 612 613 PendingIntent hideIntent = createBroadcast(context, ACTION_HIDE_VOICE_SEARCH_HINT); 614 views.setOnClickPendingIntent(R.id.voice_search_hint_close_button, hideIntent); 615 616 views.setViewVisibility(R.id.voice_search_hint_container, View.VISIBLE); 617 views.setViewVisibility(R.id.search_widget_text, View.GONE); 618 views.setViewVisibility(R.id.corpus_indicator, View.GONE); 619 } else { 620 views.setViewVisibility(R.id.voice_search_hint_container, View.GONE); 621 views.setViewVisibility(R.id.search_widget_text, View.VISIBLE); 622 views.setViewVisibility(R.id.corpus_indicator, View.VISIBLE); 623 } 624 appWidgetMgr.updateAppWidget(mAppWidgetId, views); 625 } 626 627 private void setBackgroundResource(RemoteViews views, int viewId, int bgResource) { 628 // setBackgroundResource did not have @RemotableViewMethod before Froyo 629 if (QsbApplication.isFroyoOrLater()) { 630 views.setInt(viewId, "setBackgroundResource", bgResource); 631 } 632 } 633 634 private void setOnClickActivityIntent(Context context, RemoteViews views, int viewId, 635 Intent intent) { 636 PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0); 637 views.setOnClickPendingIntent(viewId, pendingIntent); 638 } 639 } 640 641} 642