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