RemoteViews.java revision 35ae9ca5bf4b99bc341afe43d501a2d166f5df43
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 java.lang.annotation.ElementType;
20import java.lang.annotation.Retention;
21import java.lang.annotation.RetentionPolicy;
22import java.lang.annotation.Target;
23import java.lang.reflect.Method;
24import java.util.ArrayList;
25
26import android.app.PendingIntent;
27import android.appwidget.AppWidgetHostView;
28import android.content.Context;
29import android.content.Intent;
30import android.content.IntentSender;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.graphics.Bitmap;
33import android.graphics.PorterDuff;
34import android.graphics.Rect;
35import android.graphics.drawable.Drawable;
36import android.net.Uri;
37import android.os.Bundle;
38import android.os.Parcel;
39import android.os.Parcelable;
40import android.text.TextUtils;
41import android.util.Log;
42import android.view.LayoutInflater;
43import android.view.RemotableViewMethod;
44import android.view.View;
45import android.view.ViewGroup;
46import android.view.LayoutInflater.Filter;
47import android.view.View.OnClickListener;
48
49
50/**
51 * A class that describes a view hierarchy that can be displayed in
52 * another process. The hierarchy is inflated from a layout resource
53 * file, and this class provides some basic operations for modifying
54 * the content of the inflated hierarchy.
55 */
56public class RemoteViews implements Parcelable, Filter {
57
58    private static final String LOG_TAG = "RemoteViews";
59
60    /**
61     * The package name of the package containing the layout
62     * resource. (Added to the parcel)
63     */
64    private final String mPackage;
65
66    /**
67     * The resource ID of the layout file. (Added to the parcel)
68     */
69    private final int mLayoutId;
70
71    /**
72     * An array of actions to perform on the view tree once it has been
73     * inflated
74     */
75    private ArrayList<Action> mActions;
76
77
78    /**
79     * This flag indicates whether this RemoteViews object is being created from a
80     * RemoteViewsService for use as a child of a widget collection. This flag is used
81     * to determine whether or not certain features are available, in particular,
82     * setting on click extras and setting on click pending intents. The former is enabled,
83     * and the latter disabled when this flag is true.
84     */
85     private boolean mIsWidgetCollectionChild = false;
86
87    /**
88     * This annotation indicates that a subclass of View is alllowed to be used
89     * with the {@link RemoteViews} mechanism.
90     */
91    @Target({ ElementType.TYPE })
92    @Retention(RetentionPolicy.RUNTIME)
93    public @interface RemoteView {
94    }
95
96    /**
97     * Exception to send when something goes wrong executing an action
98     *
99     */
100    public static class ActionException extends RuntimeException {
101        public ActionException(Exception ex) {
102            super(ex);
103        }
104        public ActionException(String message) {
105            super(message);
106        }
107    }
108
109    /**
110     * Base class for all actions that can be performed on an
111     * inflated view.
112     *
113     *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
114     */
115    private abstract static class Action implements Parcelable {
116        public abstract void apply(View root) throws ActionException;
117
118        public int describeContents() {
119            return 0;
120        }
121    }
122
123    private class SetEmptyView extends Action {
124        int viewId;
125        int emptyViewId;
126
127        public final static int TAG = 6;
128
129        SetEmptyView(int viewId, int emptyViewId) {
130            this.viewId = viewId;
131            this.emptyViewId = emptyViewId;
132        }
133
134        SetEmptyView(Parcel in) {
135            this.viewId = in.readInt();
136            this.emptyViewId = in.readInt();
137        }
138
139        public void writeToParcel(Parcel out, int flags) {
140            out.writeInt(TAG);
141            out.writeInt(this.viewId);
142            out.writeInt(this.emptyViewId);
143        }
144
145        @Override
146        public void apply(View root) {
147            final View view = root.findViewById(viewId);
148            if (!(view instanceof AdapterView<?>)) return;
149
150            AdapterView<?> adapterView = (AdapterView<?>) view;
151
152            final View emptyView = root.findViewById(emptyViewId);
153            if (emptyView == null) return;
154
155            adapterView.setEmptyView(emptyView);
156        }
157    }
158
159    private class SetOnClickFillInIntent extends Action {
160        public SetOnClickFillInIntent(int id, Intent fillInIntent) {
161            this.viewId = id;
162            this.fillInIntent = fillInIntent;
163        }
164
165        public SetOnClickFillInIntent(Parcel parcel) {
166            viewId = parcel.readInt();
167            fillInIntent = Intent.CREATOR.createFromParcel(parcel);
168        }
169
170        public void writeToParcel(Parcel dest, int flags) {
171            dest.writeInt(TAG);
172            dest.writeInt(viewId);
173            fillInIntent.writeToParcel(dest, 0 /* no flags */);
174        }
175
176        @Override
177        public void apply(View root) {
178            final View target = root.findViewById(viewId);
179
180            if (!mIsWidgetCollectionChild) {
181                Log.e("RemoteViews", "The method setOnClickFillInIntent is available " +
182                        "only from RemoteViewsFactory (ie. on collection items).");
183                return;
184            }
185
186            if (target != null && fillInIntent != null) {
187                OnClickListener listener = new OnClickListener() {
188                    public void onClick(View v) {
189                        // Insure that this view is a child of an AdapterView
190                        View parent = (View) v.getParent();
191                        while (!(parent instanceof AdapterView<?>)
192                                && !(parent instanceof AppWidgetHostView)) {
193                            parent = (View) parent.getParent();
194                        }
195
196                        if (parent instanceof AppWidgetHostView) {
197                            // Somehow they've managed to get this far without having
198                            // and AdapterView as a parent.
199                            Log.e("RemoteViews", "Collection item doesn't have AdapterView parent");
200                            return;
201                        }
202
203                        // Insure that a template pending intent has been set on an ancestor
204                        if (!(parent.getTag() instanceof PendingIntent)) {
205                            Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" +
206					" calling setPendingIntentTemplate on parent.");
207                            return;
208                        }
209
210                        PendingIntent pendingIntent = (PendingIntent) parent.getTag();
211
212                        final float appScale = v.getContext().getResources()
213                        .getCompatibilityInfo().applicationScale;
214                        final int[] pos = new int[2];
215                        v.getLocationOnScreen(pos);
216
217                        final Rect rect = new Rect();
218                        rect.left = (int) (pos[0] * appScale + 0.5f);
219                        rect.top = (int) (pos[1] * appScale + 0.5f);
220                        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
221                        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
222
223                        fillInIntent.setSourceBounds(rect);
224                        try {
225                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
226                            v.getContext().startIntentSender(
227                                    pendingIntent.getIntentSender(), fillInIntent,
228                                    Intent.FLAG_ACTIVITY_NEW_TASK,
229                                    Intent.FLAG_ACTIVITY_NEW_TASK, 0);
230                        } catch (IntentSender.SendIntentException e) {
231                            android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
232                        }
233                    }
234
235                };
236                target.setOnClickListener(listener);
237            }
238        }
239
240        int viewId;
241        Intent fillInIntent;
242
243        public final static int TAG = 9;
244    }
245
246    private class SetOnClickExtras extends Action {
247        public SetOnClickExtras(int id, Bundle extras) {
248            this.viewId = id;
249            this.extras = extras;
250        }
251
252        public SetOnClickExtras(Parcel parcel) {
253            viewId = parcel.readInt();
254            extras = Bundle.CREATOR.createFromParcel(parcel);
255        }
256
257        public void writeToParcel(Parcel dest, int flags) {
258            dest.writeInt(TAG);
259            dest.writeInt(viewId);
260            extras.writeToParcel(dest, 0 /* no flags */);
261        }
262
263        @Override
264        public void apply(View root) {
265            final View target = root.findViewById(viewId);
266
267            if (!mIsWidgetCollectionChild) {
268                Log.e("RemoteViews", "The method setOnClickExtras is available " +
269                        "only from RemoteViewsFactory (ie. on collection items).");
270                return;
271            }
272
273            if (target != null && extras != null) {
274                OnClickListener listener = new OnClickListener() {
275                    public void onClick(View v) {
276                        // Insure that this view is a child of an AdapterView
277                        View parent = (View) v.getParent();
278                        while (!(parent instanceof AdapterView<?>)
279                                && !(parent instanceof AppWidgetHostView)) {
280                            parent = (View) parent.getParent();
281                        }
282
283                        if (parent instanceof AppWidgetHostView) {
284                            // Somehow they've managed to get this far without having
285                            // and AdapterView as a parent.
286                            Log.e("RemoteViews", "Collection item doesn't have AdapterView parent");
287                            return;
288                        }
289
290                        // Insure that a template pending intent has been set on an ancestor
291                        if (!(parent.getTag() instanceof PendingIntent)) {
292                            Log.e("RemoteViews", "Attempting setOnClickExtras without calling " +
293                                "setPendingIntentTemplate on parent.");
294                            return;
295                        }
296
297                        PendingIntent pendingIntent = (PendingIntent) parent.getTag();
298
299                        final float appScale = v.getContext().getResources()
300                        .getCompatibilityInfo().applicationScale;
301                        final int[] pos = new int[2];
302                        v.getLocationOnScreen(pos);
303
304                        final Rect rect = new Rect();
305                        rect.left = (int) (pos[0] * appScale + 0.5f);
306                        rect.top = (int) (pos[1] * appScale + 0.5f);
307                        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
308                        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
309
310                        final Intent intent = new Intent();
311                        intent.setSourceBounds(rect);
312                        intent.putExtras(extras);
313
314                        try {
315                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
316                            v.getContext().startIntentSender(
317                                    pendingIntent.getIntentSender(), intent,
318                                    Intent.FLAG_ACTIVITY_NEW_TASK,
319                                    Intent.FLAG_ACTIVITY_NEW_TASK, 0);
320                        } catch (IntentSender.SendIntentException e) {
321                            android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
322                        }
323                    }
324
325                };
326                target.setOnClickListener(listener);
327            }
328        }
329
330        int viewId;
331        Bundle extras;
332
333        public final static int TAG = 7;
334    }
335
336    private class SetPendingIntentTemplate extends Action {
337        public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
338            this.viewId = id;
339            this.pendingIntentTemplate = pendingIntentTemplate;
340        }
341
342        public SetPendingIntentTemplate(Parcel parcel) {
343            viewId = parcel.readInt();
344            pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
345        }
346
347        public void writeToParcel(Parcel dest, int flags) {
348            dest.writeInt(TAG);
349            dest.writeInt(viewId);
350            pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
351        }
352
353        @Override
354        public void apply(View root) {
355            final View target = root.findViewById(viewId);
356
357            // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense
358            if (target instanceof AdapterView<?>) {
359                // The PendingIntent template is stored in the view's tag.
360                target.setTag(pendingIntentTemplate);
361            } else {
362                Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" +
363                        "an AdapterView (id: " + viewId + ")");
364                return;
365            }
366        }
367
368        int viewId;
369        PendingIntent pendingIntentTemplate;
370
371        public final static int TAG = 8;
372    }
373
374    /**
375     * Equivalent to calling
376     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
377     * to launch the provided {@link PendingIntent}.
378     */
379    private class SetOnClickPendingIntent extends Action {
380        public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
381            this.viewId = id;
382            this.pendingIntent = pendingIntent;
383        }
384
385        public SetOnClickPendingIntent(Parcel parcel) {
386            viewId = parcel.readInt();
387            pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
388        }
389
390        public void writeToParcel(Parcel dest, int flags) {
391            dest.writeInt(TAG);
392            dest.writeInt(viewId);
393            pendingIntent.writeToParcel(dest, 0 /* no flags */);
394        }
395
396        @Override
397        public void apply(View root) {
398            final View target = root.findViewById(viewId);
399
400            // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
401            // sense, do they mean to set a PendingIntent template for the AdapterView's children?
402            if (mIsWidgetCollectionChild) {
403                Log.e("RemoteViews", "Cannot setOnClickPendingIntent for collection item " +
404				"(id: " + viewId + ")");
405                // TODO: return; We'll let this slide until apps are up to date.
406            }
407
408            if (target != null && pendingIntent != null) {
409                OnClickListener listener = new OnClickListener() {
410                    public void onClick(View v) {
411                        // Find target view location in screen coordinates and
412                        // fill into PendingIntent before sending.
413                        final float appScale = v.getContext().getResources()
414                                .getCompatibilityInfo().applicationScale;
415                        final int[] pos = new int[2];
416                        v.getLocationOnScreen(pos);
417
418                        final Rect rect = new Rect();
419                        rect.left = (int) (pos[0] * appScale + 0.5f);
420                        rect.top = (int) (pos[1] * appScale + 0.5f);
421                        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
422                        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
423
424                        final Intent intent = new Intent();
425                        intent.setSourceBounds(rect);
426                        try {
427                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
428                            v.getContext().startIntentSender(
429                                    pendingIntent.getIntentSender(), intent,
430                                    Intent.FLAG_ACTIVITY_NEW_TASK,
431                                    Intent.FLAG_ACTIVITY_NEW_TASK, 0);
432                        } catch (IntentSender.SendIntentException e) {
433                            android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
434                        }
435                    }
436                };
437                target.setOnClickListener(listener);
438            }
439        }
440
441        int viewId;
442        PendingIntent pendingIntent;
443
444        public final static int TAG = 1;
445    }
446
447    /**
448     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
449     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
450     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
451     * <p>
452     * These operations will be performed on the {@link Drawable} returned by the
453     * target {@link View#getBackground()} by default.  If targetBackground is false,
454     * we assume the target is an {@link ImageView} and try applying the operations
455     * to {@link ImageView#getDrawable()}.
456     * <p>
457     * You can omit specific calls by marking their values with null or -1.
458     */
459    private class SetDrawableParameters extends Action {
460        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
461                int colorFilter, PorterDuff.Mode mode, int level) {
462            this.viewId = id;
463            this.targetBackground = targetBackground;
464            this.alpha = alpha;
465            this.colorFilter = colorFilter;
466            this.filterMode = mode;
467            this.level = level;
468        }
469
470        public SetDrawableParameters(Parcel parcel) {
471            viewId = parcel.readInt();
472            targetBackground = parcel.readInt() != 0;
473            alpha = parcel.readInt();
474            colorFilter = parcel.readInt();
475            boolean hasMode = parcel.readInt() != 0;
476            if (hasMode) {
477                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
478            } else {
479                filterMode = null;
480            }
481            level = parcel.readInt();
482        }
483
484        public void writeToParcel(Parcel dest, int flags) {
485            dest.writeInt(TAG);
486            dest.writeInt(viewId);
487            dest.writeInt(targetBackground ? 1 : 0);
488            dest.writeInt(alpha);
489            dest.writeInt(colorFilter);
490            if (filterMode != null) {
491                dest.writeInt(1);
492                dest.writeString(filterMode.toString());
493            } else {
494                dest.writeInt(0);
495            }
496            dest.writeInt(level);
497        }
498
499        @Override
500        public void apply(View root) {
501            final View target = root.findViewById(viewId);
502            if (target == null) {
503                return;
504            }
505
506            // Pick the correct drawable to modify for this view
507            Drawable targetDrawable = null;
508            if (targetBackground) {
509                targetDrawable = target.getBackground();
510            } else if (target instanceof ImageView) {
511                ImageView imageView = (ImageView) target;
512                targetDrawable = imageView.getDrawable();
513            }
514
515            if (targetDrawable != null) {
516                // Perform modifications only if values are set correctly
517                if (alpha != -1) {
518                    targetDrawable.setAlpha(alpha);
519                }
520                if (colorFilter != -1 && filterMode != null) {
521                    targetDrawable.setColorFilter(colorFilter, filterMode);
522                }
523                if (level != -1) {
524                    targetDrawable.setLevel(level);
525                }
526            }
527        }
528
529        int viewId;
530        boolean targetBackground;
531        int alpha;
532        int colorFilter;
533        PorterDuff.Mode filterMode;
534        int level;
535
536        public final static int TAG = 3;
537    }
538
539    private class ReflectionActionWithoutParams extends Action {
540        int viewId;
541        String methodName;
542
543        public final static int TAG = 5;
544
545        ReflectionActionWithoutParams(int viewId, String methodName) {
546            this.viewId = viewId;
547            this.methodName = methodName;
548        }
549
550        ReflectionActionWithoutParams(Parcel in) {
551            this.viewId = in.readInt();
552            this.methodName = in.readString();
553        }
554
555        public void writeToParcel(Parcel out, int flags) {
556            out.writeInt(TAG);
557            out.writeInt(this.viewId);
558            out.writeString(this.methodName);
559        }
560
561        @Override
562        public void apply(View root) {
563            final View view = root.findViewById(viewId);
564            if (view == null) {
565                throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
566            }
567
568            Class klass = view.getClass();
569            Method method;
570            try {
571                method = klass.getMethod(this.methodName);
572            } catch (NoSuchMethodException ex) {
573                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
574                        + this.methodName + "()");
575            }
576
577            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
578                throw new ActionException("view: " + klass.getName()
579                        + " can't use method with RemoteViews: "
580                        + this.methodName + "()");
581            }
582
583            try {
584                //noinspection ConstantIfStatement
585                if (false) {
586                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
587                        + this.methodName + "()");
588                }
589                method.invoke(view);
590            } catch (Exception ex) {
591                throw new ActionException(ex);
592            }
593        }
594    }
595
596    /**
597     * Base class for the reflection actions.
598     */
599    private class ReflectionAction extends Action {
600        static final int TAG = 2;
601
602        static final int BOOLEAN = 1;
603        static final int BYTE = 2;
604        static final int SHORT = 3;
605        static final int INT = 4;
606        static final int LONG = 5;
607        static final int FLOAT = 6;
608        static final int DOUBLE = 7;
609        static final int CHAR = 8;
610        static final int STRING = 9;
611        static final int CHAR_SEQUENCE = 10;
612        static final int URI = 11;
613        static final int BITMAP = 12;
614        static final int BUNDLE = 13;
615        static final int INTENT = 14;
616
617        int viewId;
618        String methodName;
619        int type;
620        Object value;
621
622        ReflectionAction(int viewId, String methodName, int type, Object value) {
623            this.viewId = viewId;
624            this.methodName = methodName;
625            this.type = type;
626            this.value = value;
627        }
628
629        ReflectionAction(Parcel in) {
630            this.viewId = in.readInt();
631            this.methodName = in.readString();
632            this.type = in.readInt();
633            //noinspection ConstantIfStatement
634            if (false) {
635                Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
636                        + " methodName=" + this.methodName + " type=" + this.type);
637            }
638            switch (this.type) {
639                case BOOLEAN:
640                    this.value = in.readInt() != 0;
641                    break;
642                case BYTE:
643                    this.value = in.readByte();
644                    break;
645                case SHORT:
646                    this.value = (short)in.readInt();
647                    break;
648                case INT:
649                    this.value = in.readInt();
650                    break;
651                case LONG:
652                    this.value = in.readLong();
653                    break;
654                case FLOAT:
655                    this.value = in.readFloat();
656                    break;
657                case DOUBLE:
658                    this.value = in.readDouble();
659                    break;
660                case CHAR:
661                    this.value = (char)in.readInt();
662                    break;
663                case STRING:
664                    this.value = in.readString();
665                    break;
666                case CHAR_SEQUENCE:
667                    this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
668                    break;
669                case URI:
670                    this.value = Uri.CREATOR.createFromParcel(in);
671                    break;
672                case BITMAP:
673                    this.value = Bitmap.CREATOR.createFromParcel(in);
674                    break;
675                case BUNDLE:
676                    this.value = in.readBundle();
677                    break;
678                case INTENT:
679                    this.value = Intent.CREATOR.createFromParcel(in);
680                    break;
681                default:
682                    break;
683            }
684        }
685
686        public void writeToParcel(Parcel out, int flags) {
687            out.writeInt(TAG);
688            out.writeInt(this.viewId);
689            out.writeString(this.methodName);
690            out.writeInt(this.type);
691            //noinspection ConstantIfStatement
692            if (false) {
693                Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
694                        + " methodName=" + this.methodName + " type=" + this.type);
695            }
696            switch (this.type) {
697                case BOOLEAN:
698                    out.writeInt((Boolean) this.value ? 1 : 0);
699                    break;
700                case BYTE:
701                    out.writeByte((Byte) this.value);
702                    break;
703                case SHORT:
704                    out.writeInt((Short) this.value);
705                    break;
706                case INT:
707                    out.writeInt((Integer) this.value);
708                    break;
709                case LONG:
710                    out.writeLong((Long) this.value);
711                    break;
712                case FLOAT:
713                    out.writeFloat((Float) this.value);
714                    break;
715                case DOUBLE:
716                    out.writeDouble((Double) this.value);
717                    break;
718                case CHAR:
719                    out.writeInt((int)((Character)this.value).charValue());
720                    break;
721                case STRING:
722                    out.writeString((String)this.value);
723                    break;
724                case CHAR_SEQUENCE:
725                    TextUtils.writeToParcel((CharSequence)this.value, out, flags);
726                    break;
727                case URI:
728                    ((Uri)this.value).writeToParcel(out, flags);
729                    break;
730                case BITMAP:
731                    ((Bitmap)this.value).writeToParcel(out, flags);
732                    break;
733                case BUNDLE:
734                    out.writeBundle((Bundle) this.value);
735                    break;
736                case INTENT:
737                    ((Intent)this.value).writeToParcel(out, flags);
738                    break;
739                default:
740                    break;
741            }
742        }
743
744        private Class getParameterType() {
745            switch (this.type) {
746                case BOOLEAN:
747                    return boolean.class;
748                case BYTE:
749                    return byte.class;
750                case SHORT:
751                    return short.class;
752                case INT:
753                    return int.class;
754                case LONG:
755                    return long.class;
756                case FLOAT:
757                    return float.class;
758                case DOUBLE:
759                    return double.class;
760                case CHAR:
761                    return char.class;
762                case STRING:
763                    return String.class;
764                case CHAR_SEQUENCE:
765                    return CharSequence.class;
766                case URI:
767                    return Uri.class;
768                case BITMAP:
769                    return Bitmap.class;
770                case BUNDLE:
771                    return Bundle.class;
772                case INTENT:
773                    return Intent.class;
774                default:
775                    return null;
776            }
777        }
778
779        @Override
780        public void apply(View root) {
781            final View view = root.findViewById(viewId);
782            if (view == null) {
783                throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
784            }
785
786            Class param = getParameterType();
787            if (param == null) {
788                throw new ActionException("bad type: " + this.type);
789            }
790
791            Class klass = view.getClass();
792            Method method;
793            try {
794                method = klass.getMethod(this.methodName, getParameterType());
795            }
796            catch (NoSuchMethodException ex) {
797                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
798                        + this.methodName + "(" + param.getName() + ")");
799            }
800
801            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
802                throw new ActionException("view: " + klass.getName()
803                        + " can't use method with RemoteViews: "
804                        + this.methodName + "(" + param.getName() + ")");
805            }
806
807            try {
808                //noinspection ConstantIfStatement
809                if (false) {
810                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
811                        + this.methodName + "(" + param.getName() + ") with "
812                        + (this.value == null ? "null" : this.value.getClass().getName()));
813                }
814                method.invoke(view, this.value);
815            }
816            catch (Exception ex) {
817                throw new ActionException(ex);
818            }
819        }
820    }
821
822    /**
823     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
824     * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
825     * when null. This allows users to build "nested" {@link RemoteViews}.
826     */
827    private class ViewGroupAction extends Action {
828        public ViewGroupAction(int viewId, RemoteViews nestedViews) {
829            this.viewId = viewId;
830            this.nestedViews = nestedViews;
831        }
832
833        public ViewGroupAction(Parcel parcel) {
834            viewId = parcel.readInt();
835            nestedViews = parcel.readParcelable(null);
836        }
837
838        public void writeToParcel(Parcel dest, int flags) {
839            dest.writeInt(TAG);
840            dest.writeInt(viewId);
841            dest.writeParcelable(nestedViews, 0 /* no flags */);
842        }
843
844        @Override
845        public void apply(View root) {
846            final Context context = root.getContext();
847            final ViewGroup target = (ViewGroup) root.findViewById(viewId);
848            if (nestedViews != null) {
849                // Inflate nested views and add as children
850                target.addView(nestedViews.apply(context, target));
851            } else if (target != null) {
852                // Clear all children when nested views omitted
853                target.removeAllViews();
854            }
855        }
856
857        int viewId;
858        RemoteViews nestedViews;
859
860        public final static int TAG = 4;
861    }
862
863    /**
864     * Create a new RemoteViews object that will display the views contained
865     * in the specified layout file.
866     *
867     * @param packageName Name of the package that contains the layout resource
868     * @param layoutId The id of the layout resource
869     */
870    public RemoteViews(String packageName, int layoutId) {
871        mPackage = packageName;
872        mLayoutId = layoutId;
873    }
874
875    /**
876     * Reads a RemoteViews object from a parcel.
877     *
878     * @param parcel
879     */
880    public RemoteViews(Parcel parcel) {
881        mPackage = parcel.readString();
882        mLayoutId = parcel.readInt();
883        mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false;
884
885        int count = parcel.readInt();
886        if (count > 0) {
887            mActions = new ArrayList<Action>(count);
888            for (int i=0; i<count; i++) {
889                int tag = parcel.readInt();
890                switch (tag) {
891                case SetOnClickPendingIntent.TAG:
892                    mActions.add(new SetOnClickPendingIntent(parcel));
893                    break;
894                case SetDrawableParameters.TAG:
895                    mActions.add(new SetDrawableParameters(parcel));
896                    break;
897                case ReflectionAction.TAG:
898                    mActions.add(new ReflectionAction(parcel));
899                    break;
900                case ViewGroupAction.TAG:
901                    mActions.add(new ViewGroupAction(parcel));
902                    break;
903                case ReflectionActionWithoutParams.TAG:
904                    mActions.add(new ReflectionActionWithoutParams(parcel));
905                    break;
906                case SetEmptyView.TAG:
907                    mActions.add(new SetEmptyView(parcel));
908                    break;
909                case SetOnClickExtras.TAG:
910                    mActions.add(new SetOnClickExtras(parcel));
911                    break;
912                case SetPendingIntentTemplate.TAG:
913                    mActions.add(new SetPendingIntentTemplate(parcel));
914                    break;
915                case SetOnClickFillInIntent.TAG:
916                    mActions.add(new SetOnClickFillInIntent(parcel));
917                    break;
918                default:
919                    throw new ActionException("Tag " + tag + " not found");
920                }
921            }
922        }
923    }
924
925    @Override
926    public RemoteViews clone() {
927        final RemoteViews that = new RemoteViews(mPackage, mLayoutId);
928        if (mActions != null) {
929            that.mActions = (ArrayList<Action>)mActions.clone();
930        }
931        return that;
932    }
933
934    public String getPackage() {
935        return mPackage;
936    }
937
938    public int getLayoutId() {
939        return mLayoutId;
940    }
941
942    /**
943     * This flag indicates whether this RemoteViews object is being created from a
944     * RemoteViewsService for use as a child of a widget collection. This flag is used
945     * to determine whether or not certain features are available, in particular,
946     * setting on click extras and setting on click pending intents. The former is enabled,
947     * and the latter disabled when this flag is true.
948     */
949    void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
950        mIsWidgetCollectionChild = isWidgetCollectionChild;
951    }
952
953    /**
954     * Add an action to be executed on the remote side when apply is called.
955     *
956     * @param a The action to add
957     */
958    private void addAction(Action a) {
959        if (mActions == null) {
960            mActions = new ArrayList<Action>();
961        }
962        mActions.add(a);
963    }
964
965    /**
966     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
967     * given {@link RemoteViews}. This allows users to build "nested"
968     * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
969     * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
970     * children.
971     *
972     * @param viewId The id of the parent {@link ViewGroup} to add child into.
973     * @param nestedView {@link RemoteViews} that describes the child.
974     */
975    public void addView(int viewId, RemoteViews nestedView) {
976        addAction(new ViewGroupAction(viewId, nestedView));
977    }
978
979    /**
980     * Equivalent to calling {@link ViewGroup#removeAllViews()}.
981     *
982     * @param viewId The id of the parent {@link ViewGroup} to remove all
983     *            children from.
984     */
985    public void removeAllViews(int viewId) {
986        addAction(new ViewGroupAction(viewId, null));
987    }
988
989    /**
990     * Equivalent to calling {@link AdapterViewFlipper#showNext()}
991     *
992     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()}
993     */
994    public void showNext(int viewId) {
995        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
996    }
997
998    /**
999     * Equivalent to calling {@link AdapterViewFlipper#showPrevious()}
1000     *
1001     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()}
1002     */
1003    public void showPrevious(int viewId) {
1004        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
1005    }
1006
1007    /**
1008     * Equivalent to calling View.setVisibility
1009     *
1010     * @param viewId The id of the view whose visibility should change
1011     * @param visibility The new visibility for the view
1012     */
1013    public void setViewVisibility(int viewId, int visibility) {
1014        setInt(viewId, "setVisibility", visibility);
1015    }
1016
1017    /**
1018     * Equivalent to calling TextView.setText
1019     *
1020     * @param viewId The id of the view whose text should change
1021     * @param text The new text for the view
1022     */
1023    public void setTextViewText(int viewId, CharSequence text) {
1024        setCharSequence(viewId, "setText", text);
1025    }
1026
1027    /**
1028     * Equivalent to calling ImageView.setImageResource
1029     *
1030     * @param viewId The id of the view whose drawable should change
1031     * @param srcId The new resource id for the drawable
1032     */
1033    public void setImageViewResource(int viewId, int srcId) {
1034        setInt(viewId, "setImageResource", srcId);
1035    }
1036
1037    /**
1038     * Equivalent to calling ImageView.setImageURI
1039     *
1040     * @param viewId The id of the view whose drawable should change
1041     * @param uri The Uri for the image
1042     */
1043    public void setImageViewUri(int viewId, Uri uri) {
1044        setUri(viewId, "setImageURI", uri);
1045    }
1046
1047    /**
1048     * Equivalent to calling ImageView.setImageBitmap
1049     *
1050     * @param viewId The id of the view whose drawable should change
1051     * @param bitmap The new Bitmap for the drawable
1052     */
1053    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
1054        setBitmap(viewId, "setImageBitmap", bitmap);
1055    }
1056
1057    /**
1058     * Equivalent to calling AdapterView.setEmptyView
1059     *
1060     * @param viewId The id of the view on which to set the empty view
1061     * @param emptyViewId The view id of the empty view
1062     */
1063    public void setEmptyView(int viewId, int emptyViewId) {
1064        addAction(new SetEmptyView(viewId, emptyViewId));
1065    }
1066
1067    /**
1068     * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
1069     * {@link Chronometer#setFormat Chronometer.setFormat},
1070     * and {@link Chronometer#start Chronometer.start()} or
1071     * {@link Chronometer#stop Chronometer.stop()}.
1072     *
1073     * @param viewId The id of the view whose text should change
1074     * @param base The time at which the timer would have read 0:00.  This
1075     *             time should be based off of
1076     *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
1077     * @param format The Chronometer format string, or null to
1078     *               simply display the timer value.
1079     * @param started True if you want the clock to be started, false if not.
1080     */
1081    public void setChronometer(int viewId, long base, String format, boolean started) {
1082        setLong(viewId, "setBase", base);
1083        setString(viewId, "setFormat", format);
1084        setBoolean(viewId, "setStarted", started);
1085    }
1086
1087    /**
1088     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
1089     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
1090     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
1091     *
1092     * If indeterminate is true, then the values for max and progress are ignored.
1093     *
1094     * @param viewId The id of the view whose text should change
1095     * @param max The 100% value for the progress bar
1096     * @param progress The current value of the progress bar.
1097     * @param indeterminate True if the progress bar is indeterminate,
1098     *                false if not.
1099     */
1100    public void setProgressBar(int viewId, int max, int progress,
1101            boolean indeterminate) {
1102        setBoolean(viewId, "setIndeterminate", indeterminate);
1103        if (!indeterminate) {
1104            setInt(viewId, "setMax", max);
1105            setInt(viewId, "setProgress", progress);
1106        }
1107    }
1108
1109    /**
1110     * Equivalent to calling
1111     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
1112     * to launch the provided {@link PendingIntent}.
1113     *
1114     * When setting the on-click action of items within collections (eg. {@link ListView},
1115     * {@link StackView} etc.), this method will not work. Instead, use {@link
1116     * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
1117     * RemoteViews#setOnClickFillInIntent(int, Intent).
1118     *
1119     * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
1120     * @param pendingIntent The {@link PendingIntent} to send when user clicks
1121     */
1122    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
1123        addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
1124    }
1125
1126    /**
1127     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
1128     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
1129     * this method should be used to set a single PendingIntent template on the collection, and
1130     * individual items can differentiate their on-click behavior using
1131     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
1132     *
1133     * @param viewId The id of the collection who's children will use this PendingIntent template
1134     *          when clicked
1135     * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
1136     *          by a child of viewId and executed when that child is clicked
1137     */
1138    public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
1139        addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
1140    }
1141
1142    /**
1143     * Being deprecated. See {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
1144     *
1145     * @param viewId
1146     * @param extras
1147     */
1148    public void setOnClickExtras(int viewId, Bundle extras) {
1149        addAction(new SetOnClickExtras(viewId, extras));
1150    }
1151
1152    /**
1153     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
1154     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
1155     * a single PendingIntent template can be set on the collection, see {@link
1156     * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
1157     * action of a given item can be distinguished by setting a fillInIntent on that item. The
1158     * fillInIntent is then combined with the PendingIntent template in order to determine the final
1159     * intent which will be executed when the item is clicked. This works as follows: any fields
1160     * which are left blank in the PendingIntent template, but are provided by the fillInIntent
1161     * will be overwritten, and the resulting PendingIntent will be used.
1162     *
1163     *
1164     * of the PendingIntent template will then be filled in with the associated fields that are
1165     * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
1166     *
1167     * @param viewId The id of the view on which to set the fillInIntent
1168     * @param fillInIntent The intent which will be combined with the parent's PendingIntent
1169     *        in order to determine the on-click behavior of the view specified by viewId
1170     */
1171    public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
1172        addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
1173    }
1174
1175    /**
1176     * @hide
1177     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
1178     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
1179     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
1180     * view.
1181     * <p>
1182     * You can omit specific calls by marking their values with null or -1.
1183     *
1184     * @param viewId The id of the view that contains the target
1185     *            {@link Drawable}
1186     * @param targetBackground If true, apply these parameters to the
1187     *            {@link Drawable} returned by
1188     *            {@link android.view.View#getBackground()}. Otherwise, assume
1189     *            the target view is an {@link ImageView} and apply them to
1190     *            {@link ImageView#getDrawable()}.
1191     * @param alpha Specify an alpha value for the drawable, or -1 to leave
1192     *            unchanged.
1193     * @param colorFilter Specify a color for a
1194     *            {@link android.graphics.ColorFilter} for this drawable, or -1
1195     *            to leave unchanged.
1196     * @param mode Specify a PorterDuff mode for this drawable, or null to leave
1197     *            unchanged.
1198     * @param level Specify the level for the drawable, or -1 to leave
1199     *            unchanged.
1200     */
1201    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
1202            int colorFilter, PorterDuff.Mode mode, int level) {
1203        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
1204                colorFilter, mode, level));
1205    }
1206
1207    /**
1208     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
1209     *
1210     * @param viewId The id of the view whose text should change
1211     * @param color Sets the text color for all the states (normal, selected,
1212     *            focused) to be this color.
1213     */
1214    public void setTextColor(int viewId, int color) {
1215        setInt(viewId, "setTextColor", color);
1216    }
1217
1218    /**
1219     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
1220     *
1221     * @param viewId The id of the view whose text should change
1222     * @param intent The intent of the service which will be
1223     *            providing data to the RemoteViewsAdapter
1224     */
1225    public void setRemoteAdapter(int viewId, Intent intent) {
1226        setIntent(viewId, "setRemoteViewsAdapter", intent);
1227    }
1228
1229    /**
1230     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
1231     *
1232     * @param viewId The id of the view whose text should change
1233     * @param position Scroll to this adapter position
1234     */
1235    public void setScrollPosition(int viewId, int position) {
1236        setInt(viewId, "smoothScrollToPosition", position);
1237    }
1238
1239    /**
1240     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
1241     *
1242     * @param viewId The id of the view whose text should change
1243     * @param offset Scroll by this adapter position offset
1244     */
1245    public void setRelativeScrollPosition(int viewId, int offset) {
1246        setInt(viewId, "smoothScrollByOffset", offset);
1247    }
1248
1249    /**
1250     * Call a method taking one boolean on a view in the layout for this RemoteViews.
1251     *
1252     * @param viewId The id of the view whose text should change
1253     * @param methodName The name of the method to call.
1254     * @param value The value to pass to the method.
1255     */
1256    public void setBoolean(int viewId, String methodName, boolean value) {
1257        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
1258    }
1259
1260    /**
1261     * Call a method taking one byte on a view in the layout for this RemoteViews.
1262     *
1263     * @param viewId The id of the view whose text should change
1264     * @param methodName The name of the method to call.
1265     * @param value The value to pass to the method.
1266     */
1267    public void setByte(int viewId, String methodName, byte value) {
1268        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
1269    }
1270
1271    /**
1272     * Call a method taking one short on a view in the layout for this RemoteViews.
1273     *
1274     * @param viewId The id of the view whose text should change
1275     * @param methodName The name of the method to call.
1276     * @param value The value to pass to the method.
1277     */
1278    public void setShort(int viewId, String methodName, short value) {
1279        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
1280    }
1281
1282    /**
1283     * Call a method taking one int on a view in the layout for this RemoteViews.
1284     *
1285     * @param viewId The id of the view whose text should change
1286     * @param methodName The name of the method to call.
1287     * @param value The value to pass to the method.
1288     */
1289    public void setInt(int viewId, String methodName, int value) {
1290        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
1291    }
1292
1293    /**
1294     * Call a method taking one long on a view in the layout for this RemoteViews.
1295     *
1296     * @param viewId The id of the view whose text should change
1297     * @param methodName The name of the method to call.
1298     * @param value The value to pass to the method.
1299     */
1300    public void setLong(int viewId, String methodName, long value) {
1301        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
1302    }
1303
1304    /**
1305     * Call a method taking one float on a view in the layout for this RemoteViews.
1306     *
1307     * @param viewId The id of the view whose text should change
1308     * @param methodName The name of the method to call.
1309     * @param value The value to pass to the method.
1310     */
1311    public void setFloat(int viewId, String methodName, float value) {
1312        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
1313    }
1314
1315    /**
1316     * Call a method taking one double on a view in the layout for this RemoteViews.
1317     *
1318     * @param viewId The id of the view whose text should change
1319     * @param methodName The name of the method to call.
1320     * @param value The value to pass to the method.
1321     */
1322    public void setDouble(int viewId, String methodName, double value) {
1323        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
1324    }
1325
1326    /**
1327     * Call a method taking one char on a view in the layout for this RemoteViews.
1328     *
1329     * @param viewId The id of the view whose text should change
1330     * @param methodName The name of the method to call.
1331     * @param value The value to pass to the method.
1332     */
1333    public void setChar(int viewId, String methodName, char value) {
1334        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
1335    }
1336
1337    /**
1338     * Call a method taking one String on a view in the layout for this RemoteViews.
1339     *
1340     * @param viewId The id of the view whose text should change
1341     * @param methodName The name of the method to call.
1342     * @param value The value to pass to the method.
1343     */
1344    public void setString(int viewId, String methodName, String value) {
1345        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
1346    }
1347
1348    /**
1349     * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
1350     *
1351     * @param viewId The id of the view whose text should change
1352     * @param methodName The name of the method to call.
1353     * @param value The value to pass to the method.
1354     */
1355    public void setCharSequence(int viewId, String methodName, CharSequence value) {
1356        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
1357    }
1358
1359    /**
1360     * Call a method taking one Uri on a view in the layout for this RemoteViews.
1361     *
1362     * @param viewId The id of the view whose text should change
1363     * @param methodName The name of the method to call.
1364     * @param value The value to pass to the method.
1365     */
1366    public void setUri(int viewId, String methodName, Uri value) {
1367        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
1368    }
1369
1370    /**
1371     * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
1372     * @more
1373     * <p class="note">The bitmap will be flattened into the parcel if this object is
1374     * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
1375     *
1376     * @param viewId The id of the view whose text should change
1377     * @param methodName The name of the method to call.
1378     * @param value The value to pass to the method.
1379     */
1380    public void setBitmap(int viewId, String methodName, Bitmap value) {
1381        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value));
1382    }
1383
1384    /**
1385     * Call a method taking one Bundle on a view in the layout for this RemoteViews.
1386     *
1387     * @param viewId The id of the view whose text should change
1388     * @param methodName The name of the method to call.
1389     * @param value The value to pass to the method.
1390     */
1391    public void setBundle(int viewId, String methodName, Bundle value) {
1392        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
1393    }
1394
1395    /**
1396     *
1397     * @param viewId
1398     * @param methodName
1399     * @param value
1400     */
1401    public void setIntent(int viewId, String methodName, Intent value) {
1402        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
1403    }
1404
1405    /**
1406     * Inflates the view hierarchy represented by this object and applies
1407     * all of the actions.
1408     *
1409     * <p><strong>Caller beware: this may throw</strong>
1410     *
1411     * @param context Default context to use
1412     * @param parent Parent that the resulting view hierarchy will be attached to. This method
1413     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
1414     * @return The inflated view hierarchy
1415     */
1416    public View apply(Context context, ViewGroup parent) {
1417        View result;
1418
1419        Context c = prepareContext(context);
1420
1421        LayoutInflater inflater = (LayoutInflater)
1422                c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1423
1424        inflater = inflater.cloneInContext(c);
1425        inflater.setFilter(this);
1426
1427        result = inflater.inflate(mLayoutId, parent, false);
1428
1429        performApply(result);
1430
1431        return result;
1432    }
1433
1434    /**
1435     * Applies all of the actions to the provided view.
1436     *
1437     * <p><strong>Caller beware: this may throw</strong>
1438     *
1439     * @param v The view to apply the actions to.  This should be the result of
1440     * the {@link #apply(Context,ViewGroup)} call.
1441     */
1442    public void reapply(Context context, View v) {
1443        prepareContext(context);
1444        performApply(v);
1445    }
1446
1447    private void performApply(View v) {
1448        if (mActions != null) {
1449            final int count = mActions.size();
1450            for (int i = 0; i < count; i++) {
1451                Action a = mActions.get(i);
1452                a.apply(v);
1453            }
1454        }
1455    }
1456
1457    private Context prepareContext(Context context) {
1458        Context c;
1459        String packageName = mPackage;
1460
1461        if (packageName != null) {
1462            try {
1463                c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED);
1464            } catch (NameNotFoundException e) {
1465                Log.e(LOG_TAG, "Package name " + packageName + " not found");
1466                c = context;
1467            }
1468        } else {
1469            c = context;
1470        }
1471
1472        return c;
1473    }
1474
1475    /* (non-Javadoc)
1476     * Used to restrict the views which can be inflated
1477     *
1478     * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
1479     */
1480    public boolean onLoadClass(Class clazz) {
1481        return clazz.isAnnotationPresent(RemoteView.class);
1482    }
1483
1484    public int describeContents() {
1485        return 0;
1486    }
1487
1488    public void writeToParcel(Parcel dest, int flags) {
1489        dest.writeString(mPackage);
1490        dest.writeInt(mLayoutId);
1491        dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
1492        int count;
1493        if (mActions != null) {
1494            count = mActions.size();
1495        } else {
1496            count = 0;
1497        }
1498        dest.writeInt(count);
1499        for (int i=0; i<count; i++) {
1500            Action a = mActions.get(i);
1501            a.writeToParcel(dest, 0);
1502        }
1503    }
1504
1505    /**
1506     * Parcelable.Creator that instantiates RemoteViews objects
1507     */
1508    public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
1509        public RemoteViews createFromParcel(Parcel parcel) {
1510            return new RemoteViews(parcel);
1511        }
1512
1513        public RemoteViews[] newArray(int size) {
1514            return new RemoteViews[size];
1515        }
1516    };
1517}
1518