RemoteViews.java revision da996f390e17e16f2dfa60e972e7ebc4f868f37e
1/*
2 * Copyright (C) 2007 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 android.widget;
18
19import android.app.PendingIntent;
20import android.app.PendingIntent.CanceledException;
21import android.content.Context;
22import android.content.pm.PackageManager.NameNotFoundException;
23import android.content.res.Resources;
24import android.graphics.Bitmap;
25import android.graphics.PorterDuff;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.Drawable;
28import android.net.Uri;
29import android.os.Parcel;
30import android.os.Parcelable;
31import android.text.TextUtils;
32import android.util.Log;
33import android.view.LayoutInflater;
34import android.view.View;
35import android.view.ViewGroup;
36import android.view.LayoutInflater.Filter;
37import android.view.View.OnClickListener;
38import android.view.animation.Animation;
39import android.view.animation.AnimationUtils;
40
41import java.lang.annotation.ElementType;
42import java.lang.annotation.Retention;
43import java.lang.annotation.RetentionPolicy;
44import java.lang.annotation.Target;
45import java.util.ArrayList;
46
47
48/**
49 * A class that describes a view hierarchy that can be displayed in
50 * another process. The hierarchy is inflated from a layout resource
51 * file, and this class provides some basic operations for modifying
52 * the content of the inflated hierarchy.
53 */
54public class RemoteViews implements Parcelable, Filter {
55
56    private static final String LOG_TAG = "RemoteViews";
57
58    /**
59     * The package name of the package containing the layout
60     * resource. (Added to the parcel)
61     */
62    private String mPackage;
63
64    /**
65     * The resource ID of the layout file. (Added to the parcel)
66     */
67    private int mLayoutId;
68
69    /**
70     * The Context object used to inflate the layout file. Also may
71     * be used by actions if they need access to the senders resources.
72     */
73    private Context mContext;
74
75    /**
76     * An array of actions to perform on the view tree once it has been
77     * inflated
78     */
79    private ArrayList<Action> mActions;
80
81
82    /**
83     * This annotation indicates that a subclass of View is alllowed to be used with the
84     * {@link android.widget.RemoteViews} mechanism.
85     */
86    @Target({ ElementType.TYPE })
87    @Retention(RetentionPolicy.RUNTIME)
88    public @interface RemoteView {
89    }
90
91    /**
92     * Exception to send when something goes wrong executing an action
93     *
94     */
95    public static class ActionException extends RuntimeException {
96        public ActionException(String message) {
97            super(message);
98        }
99    }
100
101    /**
102     * Base class for all actions that can be performed on an
103     * inflated view.
104     *
105     */
106    private abstract static class Action implements Parcelable {
107        public abstract void apply(View root) throws ActionException;
108
109        public int describeContents() {
110            return 0;
111        }
112    };
113
114    /**
115     * Equivalent to calling View.setVisibility
116     */
117    private class SetViewVisibility extends Action {
118        public SetViewVisibility(int id, int vis) {
119            viewId = id;
120            visibility = vis;
121        }
122
123        public SetViewVisibility(Parcel parcel) {
124            viewId = parcel.readInt();
125            visibility = parcel.readInt();
126        }
127
128        public void writeToParcel(Parcel dest, int flags) {
129            dest.writeInt(TAG);
130            dest.writeInt(viewId);
131            dest.writeInt(visibility);
132        }
133
134        @Override
135        public void apply(View root) {
136            View target = root.findViewById(viewId);
137            if (target != null) {
138                target.setVisibility(visibility);
139            }
140        }
141
142        private int viewId;
143        private int visibility;
144        public final static int TAG = 0;
145    }
146
147    /**
148     * Equivalent to calling TextView.setText
149     */
150    private class SetTextViewText extends Action {
151        public SetTextViewText(int id, CharSequence t) {
152            viewId = id;
153            text = t;
154        }
155
156        public SetTextViewText(Parcel parcel) {
157            viewId = parcel.readInt();
158            text = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel);
159        }
160
161        public void writeToParcel(Parcel dest, int flags) {
162            dest.writeInt(TAG);
163            dest.writeInt(viewId);
164            TextUtils.writeToParcel(text, dest, flags);
165        }
166
167        @Override
168        public void apply(View root) {
169            TextView target = (TextView) root.findViewById(viewId);
170            if (target != null) {
171                target.setText(text);
172            }
173        }
174
175        int viewId;
176        CharSequence text;
177        public final static int TAG = 1;
178    }
179
180    /**
181     * Equivalent to calling ImageView.setResource
182     */
183    private class SetImageViewResource extends Action {
184        public SetImageViewResource(int id, int src) {
185            viewId = id;
186            srcId = src;
187        }
188
189        public SetImageViewResource(Parcel parcel) {
190            viewId = parcel.readInt();
191            srcId = parcel.readInt();
192        }
193
194        public void writeToParcel(Parcel dest, int flags) {
195            dest.writeInt(TAG);
196            dest.writeInt(viewId);
197            dest.writeInt(srcId);
198        }
199
200        @Override
201        public void apply(View root) {
202            ImageView target = (ImageView) root.findViewById(viewId);
203            Drawable d = mContext.getResources().getDrawable(srcId);
204            if (target != null) {
205                target.setImageDrawable(d);
206            }
207        }
208
209        int viewId;
210        int srcId;
211        public final static int TAG = 2;
212    }
213
214    /**
215     * Equivalent to calling ImageView.setImageURI
216     */
217    private class SetImageViewUri extends Action {
218        public SetImageViewUri(int id, Uri u) {
219            viewId = id;
220            uri = u;
221        }
222
223        public SetImageViewUri(Parcel parcel) {
224            viewId = parcel.readInt();
225            uri = Uri.CREATOR.createFromParcel(parcel);
226        }
227
228        public void writeToParcel(Parcel dest, int flags) {
229            dest.writeInt(TAG);
230            dest.writeInt(viewId);
231            Uri.writeToParcel(dest, uri);
232        }
233
234        @Override
235        public void apply(View root) {
236            ImageView target = (ImageView) root.findViewById(viewId);
237            if (target != null) {
238                target.setImageURI(uri);
239            }
240        }
241
242        int viewId;
243        Uri uri;
244        public final static int TAG = 3;
245    }
246
247    /**
248     * Equivalent to calling ImageView.setImageBitmap
249     */
250    private class SetImageViewBitmap extends Action {
251        public SetImageViewBitmap(int id, Bitmap src) {
252            viewId = id;
253            bitmap = src;
254        }
255
256        public SetImageViewBitmap(Parcel parcel) {
257            viewId = parcel.readInt();
258            bitmap = Bitmap.CREATOR.createFromParcel(parcel);
259        }
260
261        public void writeToParcel(Parcel dest, int flags) {
262            dest.writeInt(TAG);
263            dest.writeInt(viewId);
264            if (bitmap != null) {
265                bitmap.writeToParcel(dest, flags);
266            }
267        }
268
269        @Override
270        public void apply(View root) {
271            if (bitmap != null) {
272                ImageView target = (ImageView) root.findViewById(viewId);
273                Drawable d = new BitmapDrawable(bitmap);
274                if (target != null) {
275                    target.setImageDrawable(d);
276                }
277            }
278        }
279
280        int viewId;
281        Bitmap bitmap;
282        public final static int TAG = 4;
283    }
284
285    /**
286     * Equivalent to calling Chronometer.setBase, Chronometer.setFormat,
287     * and Chronometer.start/stop.
288     */
289    private class SetChronometer extends Action {
290        public SetChronometer(int id, long base, String format, boolean running) {
291            this.viewId = id;
292            this.base = base;
293            this.format = format;
294            this.running = running;
295        }
296
297        public SetChronometer(Parcel parcel) {
298            viewId = parcel.readInt();
299            base = parcel.readLong();
300            format = parcel.readString();
301            running = parcel.readInt() != 0;
302        }
303
304        public void writeToParcel(Parcel dest, int flags) {
305            dest.writeInt(TAG);
306            dest.writeInt(viewId);
307            dest.writeLong(base);
308            dest.writeString(format);
309            dest.writeInt(running ? 1 : 0);
310        }
311
312        @Override
313        public void apply(View root) {
314            Chronometer target = (Chronometer) root.findViewById(viewId);
315            if (target != null) {
316                target.setBase(base);
317                target.setFormat(format);
318                if (running) {
319                    target.start();
320                } else {
321                    target.stop();
322                }
323            }
324        }
325
326        int viewId;
327        boolean running;
328        long base;
329        String format;
330
331        public final static int TAG = 5;
332    }
333
334    /**
335     * Equivalent to calling ProgressBar.setMax, ProgressBar.setProgress and
336     * ProgressBar.setIndeterminate
337     */
338    private class SetProgressBar extends Action {
339        public SetProgressBar(int id, int max, int progress, boolean indeterminate) {
340            this.viewId = id;
341            this.progress = progress;
342            this.max = max;
343            this.indeterminate = indeterminate;
344        }
345
346        public SetProgressBar(Parcel parcel) {
347            viewId = parcel.readInt();
348            progress = parcel.readInt();
349            max = parcel.readInt();
350            indeterminate = parcel.readInt() != 0;
351        }
352
353        public void writeToParcel(Parcel dest, int flags) {
354            dest.writeInt(TAG);
355            dest.writeInt(viewId);
356            dest.writeInt(progress);
357            dest.writeInt(max);
358            dest.writeInt(indeterminate ? 1 : 0);
359        }
360
361        @Override
362        public void apply(View root) {
363            ProgressBar target = (ProgressBar) root.findViewById(viewId);
364            if (target != null) {
365                target.setIndeterminate(indeterminate);
366                if (!indeterminate) {
367                    target.setMax(max);
368                    target.setProgress(progress);
369                }
370            }
371        }
372
373        int viewId;
374        boolean indeterminate;
375        int progress;
376        int max;
377
378        public final static int TAG = 6;
379    }
380
381    /**
382     * Equivalent to calling
383     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
384     * to launch the provided {@link PendingIntent}.
385     */
386    private class SetOnClickPendingIntent extends Action {
387        public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
388            this.viewId = id;
389            this.pendingIntent = pendingIntent;
390        }
391
392        public SetOnClickPendingIntent(Parcel parcel) {
393            viewId = parcel.readInt();
394            pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
395        }
396
397        public void writeToParcel(Parcel dest, int flags) {
398            dest.writeInt(TAG);
399            dest.writeInt(viewId);
400            pendingIntent.writeToParcel(dest, 0 /* no flags */);
401        }
402
403        @Override
404        public void apply(View root) {
405            final View target = root.findViewById(viewId);
406            if (target != null && pendingIntent != null) {
407                OnClickListener listener = new OnClickListener() {
408                    public void onClick(View v) {
409                        try {
410                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
411                            pendingIntent.send();
412                        } catch (CanceledException e) {
413                            throw new ActionException(e.toString());
414                        }
415                    }
416                };
417                target.setOnClickListener(listener);
418            }
419        }
420
421        int viewId;
422        PendingIntent pendingIntent;
423
424        public final static int TAG = 7;
425    }
426
427    /**
428     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
429     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
430     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
431     * <p>
432     * These operations will be performed on the {@link Drawable} returned by the
433     * target {@link View#getBackground()} by default.  If targetBackground is false,
434     * we assume the target is an {@link ImageView} and try applying the operations
435     * to {@link ImageView#getDrawable()}.
436     * <p>
437     * You can omit specific calls by marking their values with null or -1.
438     */
439    private class SetDrawableParameters extends Action {
440        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
441                int colorFilter, PorterDuff.Mode mode, int level) {
442            this.viewId = id;
443            this.targetBackground = targetBackground;
444            this.alpha = alpha;
445            this.colorFilter = colorFilter;
446            this.filterMode = mode;
447            this.level = level;
448        }
449
450        public SetDrawableParameters(Parcel parcel) {
451            viewId = parcel.readInt();
452            targetBackground = parcel.readInt() != 0;
453            alpha = parcel.readInt();
454            colorFilter = parcel.readInt();
455            boolean hasMode = parcel.readInt() != 0;
456            if (hasMode) {
457                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
458            } else {
459                filterMode = null;
460            }
461            level = parcel.readInt();
462        }
463
464        public void writeToParcel(Parcel dest, int flags) {
465            dest.writeInt(TAG);
466            dest.writeInt(viewId);
467            dest.writeInt(targetBackground ? 1 : 0);
468            dest.writeInt(alpha);
469            dest.writeInt(colorFilter);
470            if (filterMode != null) {
471                dest.writeInt(1);
472                dest.writeString(filterMode.toString());
473            } else {
474                dest.writeInt(0);
475            }
476            dest.writeInt(level);
477        }
478
479        @Override
480        public void apply(View root) {
481            final View target = root.findViewById(viewId);
482            if (target == null) {
483                return;
484            }
485
486            // Pick the correct drawable to modify for this view
487            Drawable targetDrawable = null;
488            if (targetBackground) {
489                targetDrawable = target.getBackground();
490            } else if (target instanceof ImageView) {
491                ImageView imageView = (ImageView) target;
492                targetDrawable = imageView.getDrawable();
493            }
494
495            // Perform modifications only if values are set correctly
496            if (alpha != -1) {
497                targetDrawable.setAlpha(alpha);
498            }
499            if (colorFilter != -1 && filterMode != null) {
500                targetDrawable.setColorFilter(colorFilter, filterMode);
501            }
502            if (level != -1) {
503                targetDrawable.setLevel(level);
504            }
505        }
506
507        int viewId;
508        boolean targetBackground;
509        int alpha;
510        int colorFilter;
511        PorterDuff.Mode filterMode;
512        int level;
513
514        public final static int TAG = 8;
515    }
516
517    /**
518     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
519     */
520    private class SetTextColor extends Action {
521        public SetTextColor(int id, int color) {
522            this.viewId = id;
523            this.color = color;
524        }
525
526        public SetTextColor(Parcel parcel) {
527            viewId = parcel.readInt();
528            color = parcel.readInt();
529        }
530
531        public void writeToParcel(Parcel dest, int flags) {
532            dest.writeInt(TAG);
533            dest.writeInt(viewId);
534            dest.writeInt(color);
535        }
536
537        @Override
538        public void apply(View root) {
539            final View target = root.findViewById(viewId);
540            if (target instanceof TextView) {
541                final TextView textView = (TextView) target;
542                textView.setTextColor(color);
543            }
544        }
545
546        int viewId;
547        int color;
548
549        public final static int TAG = 9;
550    }
551
552    /**
553     * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()}
554     * or {@link android.widget.ViewFlipper#stopFlipping()} along with
555     * {@link android.widget.ViewFlipper#setFlipInterval(int)}.
556     */
557    private class SetFlipping extends Action {
558        public SetFlipping(int id, boolean flipping, int milliseconds) {
559            this.viewId = id;
560            this.flipping = flipping;
561            this.milliseconds = milliseconds;
562        }
563
564        public SetFlipping(Parcel parcel) {
565            viewId = parcel.readInt();
566            flipping = parcel.readInt() != 0;
567            milliseconds = parcel.readInt();
568        }
569
570        public void writeToParcel(Parcel dest, int flags) {
571            dest.writeInt(TAG);
572            dest.writeInt(viewId);
573            dest.writeInt(flipping ? 1 : 0);
574            dest.writeInt(milliseconds);
575        }
576
577        @Override
578        public void apply(View root) {
579            final View target = root.findViewById(viewId);
580            if (target instanceof ViewFlipper) {
581                final ViewFlipper flipper = (ViewFlipper) target;
582                if (milliseconds != -1) {
583                    flipper.setFlipInterval(milliseconds);
584                }
585                if (flipping) {
586                    flipper.startFlipping();
587                } else {
588                    flipper.stopFlipping();
589                }
590            }
591        }
592
593        int viewId;
594        boolean flipping;
595        int milliseconds;
596
597        public final static int TAG = 10;
598    }
599
600    /**
601     * Create a new RemoteViews object that will display the views contained
602     * in the specified layout file.
603     *
604     * @param packageName Name of the package that contains the layout resource
605     * @param layoutId The id of the layout resource
606     */
607    public RemoteViews(String packageName, int layoutId) {
608        mPackage = packageName;
609        mLayoutId = layoutId;
610    }
611
612    /**
613     * Reads a RemoteViews object from a parcel.
614     *
615     * @param parcel
616     */
617    public RemoteViews(Parcel parcel) {
618        mPackage = parcel.readString();
619        mLayoutId = parcel.readInt();
620        int count = parcel.readInt();
621        if (count > 0) {
622            mActions = new ArrayList<Action>(count);
623            for (int i=0; i<count; i++) {
624                int tag = parcel.readInt();
625                switch (tag) {
626                case SetViewVisibility.TAG:
627                    mActions.add(new SetViewVisibility(parcel));
628                    break;
629                case SetTextViewText.TAG:
630                    mActions.add(new SetTextViewText(parcel));
631                    break;
632                case SetImageViewResource.TAG:
633                    mActions.add(new SetImageViewResource(parcel));
634                    break;
635                case SetImageViewUri.TAG:
636                    mActions.add(new SetImageViewUri(parcel));
637                    break;
638                case SetImageViewBitmap.TAG:
639                    mActions.add(new SetImageViewBitmap(parcel));
640                    break;
641                case SetChronometer.TAG:
642                    mActions.add(new SetChronometer(parcel));
643                    break;
644                case SetProgressBar.TAG:
645                    mActions.add(new SetProgressBar(parcel));
646                    break;
647                case SetOnClickPendingIntent.TAG:
648                    mActions.add(new SetOnClickPendingIntent(parcel));
649                    break;
650                case SetDrawableParameters.TAG:
651                    mActions.add(new SetDrawableParameters(parcel));
652                    break;
653                case SetTextColor.TAG:
654                    mActions.add(new SetTextColor(parcel));
655                    break;
656                case SetFlipping.TAG:
657                    mActions.add(new SetFlipping(parcel));
658                    break;
659                default:
660                    throw new ActionException("Tag " + tag + "not found");
661                }
662            }
663        }
664    }
665
666    public String getPackage() {
667        return mPackage;
668    }
669
670    public int getLayoutId() {
671        return mLayoutId;
672    }
673
674    /**
675     * Add an action to be executed on the remote side when apply is called.
676     *
677     * @param a The action to add
678     */
679    private void addAction(Action a) {
680        if (mActions == null) {
681            mActions = new ArrayList<Action>();
682        }
683        mActions.add(a);
684    }
685
686    /**
687     * Equivalent to calling View.setVisibility
688     *
689     * @param viewId The id of the view whose visibility should change
690     * @param visibility The new visibility for the view
691     */
692    public void setViewVisibility(int viewId, int visibility) {
693        addAction(new SetViewVisibility(viewId, visibility));
694    }
695
696    /**
697     * Equivalent to calling TextView.setText
698     *
699     * @param viewId The id of the view whose text should change
700     * @param text The new text for the view
701     */
702    public void setTextViewText(int viewId, CharSequence text) {
703        addAction(new SetTextViewText(viewId, text));
704    }
705
706    /**
707     * Equivalent to calling ImageView.setImageResource
708     *
709     * @param viewId The id of the view whose drawable should change
710     * @param srcId The new resource id for the drawable
711     */
712    public void setImageViewResource(int viewId, int srcId) {
713        addAction(new SetImageViewResource(viewId, srcId));
714    }
715
716    /**
717     * Equivalent to calling ImageView.setImageURI
718     *
719     * @param viewId The id of the view whose drawable should change
720     * @param uri The Uri for the image
721     */
722    public void setImageViewUri(int viewId, Uri uri) {
723        addAction(new SetImageViewUri(viewId, uri));
724    }
725
726    /**
727     * Equivalent to calling ImageView.setImageBitmap
728     *
729     * @param viewId The id of the view whose drawable should change
730     * @param bitmap The new Bitmap for the drawable
731     */
732    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
733        addAction(new SetImageViewBitmap(viewId, bitmap));
734    }
735
736    /**
737     * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
738     * {@link Chronometer#setFormat Chronometer.setFormat},
739     * and {@link Chronometer#start Chronometer.start()} or
740     * {@link Chronometer#stop Chronometer.stop()}.
741     *
742     * @param viewId The id of the view whose text should change
743     * @param base The time at which the timer would have read 0:00.  This
744     *             time should be based off of
745     *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
746     * @param format The Chronometer format string, or null to
747     *               simply display the timer value.
748     * @param running True if you want the clock to be running, false if not.
749     */
750    public void setChronometer(int viewId, long base, String format, boolean running) {
751        addAction(new SetChronometer(viewId, base, format, running));
752    }
753
754    /**
755     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
756     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
757     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
758     *
759     * @param viewId The id of the view whose text should change
760     * @param max The 100% value for the progress bar
761     * @param progress The current value of the progress bar.
762     * @param indeterminate True if the progress bar is indeterminate,
763     *                false if not.
764     */
765    public void setProgressBar(int viewId, int max, int progress,
766            boolean indeterminate) {
767        addAction(new SetProgressBar(viewId, max, progress, indeterminate));
768    }
769
770    /**
771     * Equivalent to calling
772     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
773     * to launch the provided {@link PendingIntent}.
774     *
775     * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
776     * @param pendingIntent The {@link PendingIntent} to send when user clicks
777     */
778    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
779        addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
780    }
781
782    /**
783     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
784     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
785     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
786     * view.
787     * <p>
788     * You can omit specific calls by marking their values with null or -1.
789     *
790     * @param viewId The id of the view that contains the target
791     *            {@link Drawable}
792     * @param targetBackground If true, apply these parameters to the
793     *            {@link Drawable} returned by
794     *            {@link android.view.View#getBackground()}. Otherwise, assume
795     *            the target view is an {@link ImageView} and apply them to
796     *            {@link ImageView#getDrawable()}.
797     * @param alpha Specify an alpha value for the drawable, or -1 to leave
798     *            unchanged.
799     * @param colorFilter Specify a color for a
800     *            {@link android.graphics.ColorFilter} for this drawable, or -1
801     *            to leave unchanged.
802     * @param mode Specify a PorterDuff mode for this drawable, or null to leave
803     *            unchanged.
804     * @param level Specify the level for the drawable, or -1 to leave
805     *            unchanged.
806     */
807    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
808            int colorFilter, PorterDuff.Mode mode, int level) {
809        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
810                colorFilter, mode, level));
811    }
812
813    /**
814     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
815     *
816     * @param viewId The id of the view whose text should change
817     * @param color Sets the text color for all the states (normal, selected,
818     *            focused) to be this color.
819     */
820    public void setTextColor(int viewId, int color) {
821        addAction(new SetTextColor(viewId, color));
822    }
823
824    /**
825     * Equivalent to calling {@link android.widget.ViewFlipper#startFlipping()}
826     * or {@link android.widget.ViewFlipper#stopFlipping()} along with
827     * {@link android.widget.ViewFlipper#setFlipInterval(int)}.
828     *
829     * @param viewId The id of the view to apply changes to
830     * @param flipping True means we should
831     *            {@link android.widget.ViewFlipper#startFlipping()}, otherwise
832     *            {@link android.widget.ViewFlipper#stopFlipping()}.
833     * @param milliseconds How long to wait before flipping to the next view, or
834     *            -1 to leave unchanged.
835     */
836    public void setFlipping(int viewId, boolean flipping, int milliseconds) {
837        addAction(new SetFlipping(viewId, flipping, milliseconds));
838    }
839
840    /**
841     * Inflates the view hierarchy represented by this object and applies
842     * all of the actions.
843     *
844     * <p><strong>Caller beware: this may throw</strong>
845     *
846     * @param context Default context to use
847     * @param parent Parent that the resulting view hierarchy will be attached to. This method
848     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
849     * @return The inflated view hierarchy
850     */
851    public View apply(Context context, ViewGroup parent) {
852        View result = null;
853
854        Context c = prepareContext(context);
855
856        Resources r = c.getResources();
857        LayoutInflater inflater = (LayoutInflater) c
858                .getSystemService(Context.LAYOUT_INFLATER_SERVICE);
859
860        inflater = inflater.cloneInContext(c);
861        inflater.setFilter(this);
862
863        result = inflater.inflate(mLayoutId, parent, false);
864
865        performApply(result);
866
867        return result;
868    }
869
870    /**
871     * Applies all of the actions to the provided view.
872     *
873     * <p><strong>Caller beware: this may throw</strong>
874     *
875     * @param v The view to apply the actions to.  This should be the result of
876     * the {@link #apply(Context,ViewGroup)} call.
877     */
878    public void reapply(Context context, View v) {
879        prepareContext(context);
880        performApply(v);
881    }
882
883    private void performApply(View v) {
884        if (mActions != null) {
885            final int count = mActions.size();
886            for (int i = 0; i < count; i++) {
887                Action a = mActions.get(i);
888                a.apply(v);
889            }
890        }
891    }
892
893    private Context prepareContext(Context context) {
894        Context c = null;
895        String packageName = mPackage;
896
897        if (packageName != null) {
898            try {
899                c = context.createPackageContext(packageName, 0);
900            } catch (NameNotFoundException e) {
901                Log.e(LOG_TAG, "Package name " + packageName + " not found");
902                c = context;
903            }
904        } else {
905            c = context;
906        }
907
908        mContext = c;
909
910        return c;
911    }
912
913    /* (non-Javadoc)
914     * Used to restrict the views which can be inflated
915     *
916     * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
917     */
918    public boolean onLoadClass(Class clazz) {
919        return clazz.isAnnotationPresent(RemoteView.class);
920    }
921
922    public int describeContents() {
923        return 0;
924    }
925
926    public void writeToParcel(Parcel dest, int flags) {
927        dest.writeString(mPackage);
928        dest.writeInt(mLayoutId);
929        int count;
930        if (mActions != null) {
931            count = mActions.size();
932        } else {
933            count = 0;
934        }
935        dest.writeInt(count);
936        for (int i=0; i<count; i++) {
937            Action a = mActions.get(i);
938            a.writeToParcel(dest, 0);
939        }
940    }
941
942    /**
943     * Parcelable.Creator that instantiates RemoteViews objects
944     */
945    public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
946        public RemoteViews createFromParcel(Parcel parcel) {
947            return new RemoteViews(parcel);
948        }
949
950        public RemoteViews[] newArray(int size) {
951            return new RemoteViews[size];
952        }
953    };
954}
955