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