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