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