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