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