RemoteViews.java revision f01345e19d6ab39c368d030a7741a06d25b4d2cc
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                        // If we don't know, be pessimistic and assume 4
800                        int bpp = 4;
801                        if (c != null) {
802                            switch (c) {
803                            case ALPHA_8:
804                                bpp = 1;
805                                break;
806                            case RGB_565:
807                            case ARGB_4444:
808                                bpp = 2;
809                                break;
810                            case ARGB_8888:
811                                bpp = 4;
812                                break;
813                            }
814                        }
815                        counter.bitmapIncrement(b.getWidth() * b.getHeight() * bpp);
816                    }
817                    break;
818                default:
819                    break;
820            }
821        }
822    }
823
824    /**
825     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
826     * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
827     * when null. This allows users to build "nested" {@link RemoteViews}.
828     */
829    private class ViewGroupAction extends Action {
830        public ViewGroupAction(int viewId, RemoteViews nestedViews) {
831            this.viewId = viewId;
832            this.nestedViews = nestedViews;
833        }
834
835        public ViewGroupAction(Parcel parcel) {
836            viewId = parcel.readInt();
837            nestedViews = parcel.readParcelable(null);
838        }
839
840        public void writeToParcel(Parcel dest, int flags) {
841            dest.writeInt(TAG);
842            dest.writeInt(viewId);
843            dest.writeParcelable(nestedViews, 0 /* no flags */);
844        }
845
846        @Override
847        public void apply(View root) {
848            final Context context = root.getContext();
849            final ViewGroup target = (ViewGroup) root.findViewById(viewId);
850            if (target == null) return;
851            if (nestedViews != null) {
852                // Inflate nested views and add as children
853                target.addView(nestedViews.apply(context, target));
854            } else {
855                // Clear all children when nested views omitted
856                target.removeAllViews();
857            }
858        }
859
860        @Override
861        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
862            if (nestedViews != null) {
863                counter.bitmapIncrement(nestedViews.estimateBitmapMemoryUsage());
864            }
865        }
866
867        int viewId;
868        RemoteViews nestedViews;
869
870        public final static int TAG = 4;
871    }
872
873    /**
874     * Simple class used to keep track of memory usage in a RemoteViews.
875     *
876     */
877    private class MemoryUsageCounter {
878        public void clear() {
879            mBitmapHeapMemoryUsage = 0;
880        }
881
882        public void bitmapIncrement(int numBytes) {
883            mBitmapHeapMemoryUsage += numBytes;
884        }
885
886        public int getBitmapHeapMemoryUsage() {
887            return mBitmapHeapMemoryUsage;
888        }
889
890        int mBitmapHeapMemoryUsage;
891    }
892
893    /**
894     * Create a new RemoteViews object that will display the views contained
895     * in the specified layout file.
896     *
897     * @param packageName Name of the package that contains the layout resource
898     * @param layoutId The id of the layout resource
899     */
900    public RemoteViews(String packageName, int layoutId) {
901        mPackage = packageName;
902        mLayoutId = layoutId;
903
904        // setup the memory usage statistics
905        mMemoryUsageCounter = new MemoryUsageCounter();
906        recalculateMemoryUsage();
907    }
908
909    /**
910     * Reads a RemoteViews object from a parcel.
911     *
912     * @param parcel
913     */
914    public RemoteViews(Parcel parcel) {
915        mPackage = parcel.readString();
916        mLayoutId = parcel.readInt();
917        mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false;
918
919        int count = parcel.readInt();
920        if (count > 0) {
921            mActions = new ArrayList<Action>(count);
922            for (int i=0; i<count; i++) {
923                int tag = parcel.readInt();
924                switch (tag) {
925                case SetOnClickPendingIntent.TAG:
926                    mActions.add(new SetOnClickPendingIntent(parcel));
927                    break;
928                case SetDrawableParameters.TAG:
929                    mActions.add(new SetDrawableParameters(parcel));
930                    break;
931                case ReflectionAction.TAG:
932                    mActions.add(new ReflectionAction(parcel));
933                    break;
934                case ViewGroupAction.TAG:
935                    mActions.add(new ViewGroupAction(parcel));
936                    break;
937                case ReflectionActionWithoutParams.TAG:
938                    mActions.add(new ReflectionActionWithoutParams(parcel));
939                    break;
940                case SetEmptyView.TAG:
941                    mActions.add(new SetEmptyView(parcel));
942                    break;
943                case SetPendingIntentTemplate.TAG:
944                    mActions.add(new SetPendingIntentTemplate(parcel));
945                    break;
946                case SetOnClickFillInIntent.TAG:
947                    mActions.add(new SetOnClickFillInIntent(parcel));
948                    break;
949                default:
950                    throw new ActionException("Tag " + tag + " not found");
951                }
952            }
953        }
954
955        // setup the memory usage statistics
956        mMemoryUsageCounter = new MemoryUsageCounter();
957        recalculateMemoryUsage();
958    }
959
960    @Override
961    public RemoteViews clone() {
962        final RemoteViews that = new RemoteViews(mPackage, mLayoutId);
963        if (mActions != null) {
964            that.mActions = (ArrayList<Action>)mActions.clone();
965        }
966
967        // update the memory usage stats of the cloned RemoteViews
968        that.recalculateMemoryUsage();
969        return that;
970    }
971
972    public String getPackage() {
973        return mPackage;
974    }
975
976    public int getLayoutId() {
977        return mLayoutId;
978    }
979
980    /*
981     * This flag indicates whether this RemoteViews object is being created from a
982     * RemoteViewsService for use as a child of a widget collection. This flag is used
983     * to determine whether or not certain features are available, in particular,
984     * setting on click extras and setting on click pending intents. The former is enabled,
985     * and the latter disabled when this flag is true.
986     */
987    void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
988        mIsWidgetCollectionChild = isWidgetCollectionChild;
989    }
990
991    /**
992     * Updates the memory usage statistics.
993     */
994    private void recalculateMemoryUsage() {
995        mMemoryUsageCounter.clear();
996
997        // Accumulate the memory usage for each action
998        if (mActions != null) {
999            final int count = mActions.size();
1000            for (int i= 0; i < count; ++i) {
1001                mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
1002            }
1003        }
1004    }
1005
1006    /**
1007     * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
1008     */
1009    int estimateBitmapMemoryUsage() {
1010        return mMemoryUsageCounter.getBitmapHeapMemoryUsage();
1011    }
1012
1013    /**
1014     * Add an action to be executed on the remote side when apply is called.
1015     *
1016     * @param a The action to add
1017     */
1018    private void addAction(Action a) {
1019        if (mActions == null) {
1020            mActions = new ArrayList<Action>();
1021        }
1022        mActions.add(a);
1023
1024        // update the memory usage stats
1025        a.updateMemoryUsageEstimate(mMemoryUsageCounter);
1026    }
1027
1028    /**
1029     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
1030     * given {@link RemoteViews}. This allows users to build "nested"
1031     * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
1032     * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
1033     * children.
1034     *
1035     * @param viewId The id of the parent {@link ViewGroup} to add child into.
1036     * @param nestedView {@link RemoteViews} that describes the child.
1037     */
1038    public void addView(int viewId, RemoteViews nestedView) {
1039        addAction(new ViewGroupAction(viewId, nestedView));
1040    }
1041
1042    /**
1043     * Equivalent to calling {@link ViewGroup#removeAllViews()}.
1044     *
1045     * @param viewId The id of the parent {@link ViewGroup} to remove all
1046     *            children from.
1047     */
1048    public void removeAllViews(int viewId) {
1049        addAction(new ViewGroupAction(viewId, null));
1050    }
1051
1052    /**
1053     * Equivalent to calling {@link AdapterViewFlipper#showNext()}
1054     *
1055     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()}
1056     */
1057    public void showNext(int viewId) {
1058        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
1059    }
1060
1061    /**
1062     * Equivalent to calling {@link AdapterViewFlipper#showPrevious()}
1063     *
1064     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()}
1065     */
1066    public void showPrevious(int viewId) {
1067        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
1068    }
1069
1070    /**
1071     * Equivalent to calling View.setVisibility
1072     *
1073     * @param viewId The id of the view whose visibility should change
1074     * @param visibility The new visibility for the view
1075     */
1076    public void setViewVisibility(int viewId, int visibility) {
1077        setInt(viewId, "setVisibility", visibility);
1078    }
1079
1080    /**
1081     * Equivalent to calling TextView.setText
1082     *
1083     * @param viewId The id of the view whose text should change
1084     * @param text The new text for the view
1085     */
1086    public void setTextViewText(int viewId, CharSequence text) {
1087        setCharSequence(viewId, "setText", text);
1088    }
1089
1090    /**
1091     * Equivalent to calling ImageView.setImageResource
1092     *
1093     * @param viewId The id of the view whose drawable should change
1094     * @param srcId The new resource id for the drawable
1095     */
1096    public void setImageViewResource(int viewId, int srcId) {
1097        setInt(viewId, "setImageResource", srcId);
1098    }
1099
1100    /**
1101     * Equivalent to calling ImageView.setImageURI
1102     *
1103     * @param viewId The id of the view whose drawable should change
1104     * @param uri The Uri for the image
1105     */
1106    public void setImageViewUri(int viewId, Uri uri) {
1107        setUri(viewId, "setImageURI", uri);
1108    }
1109
1110    /**
1111     * Equivalent to calling ImageView.setImageBitmap
1112     *
1113     * @param viewId The id of the view whose drawable should change
1114     * @param bitmap The new Bitmap for the drawable
1115     */
1116    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
1117        setBitmap(viewId, "setImageBitmap", bitmap);
1118    }
1119
1120    /**
1121     * Equivalent to calling AdapterView.setEmptyView
1122     *
1123     * @param viewId The id of the view on which to set the empty view
1124     * @param emptyViewId The view id of the empty view
1125     */
1126    public void setEmptyView(int viewId, int emptyViewId) {
1127        addAction(new SetEmptyView(viewId, emptyViewId));
1128    }
1129
1130    /**
1131     * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
1132     * {@link Chronometer#setFormat Chronometer.setFormat},
1133     * and {@link Chronometer#start Chronometer.start()} or
1134     * {@link Chronometer#stop Chronometer.stop()}.
1135     *
1136     * @param viewId The id of the view whose text should change
1137     * @param base The time at which the timer would have read 0:00.  This
1138     *             time should be based off of
1139     *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
1140     * @param format The Chronometer format string, or null to
1141     *               simply display the timer value.
1142     * @param started True if you want the clock to be started, false if not.
1143     */
1144    public void setChronometer(int viewId, long base, String format, boolean started) {
1145        setLong(viewId, "setBase", base);
1146        setString(viewId, "setFormat", format);
1147        setBoolean(viewId, "setStarted", started);
1148    }
1149
1150    /**
1151     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
1152     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
1153     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
1154     *
1155     * If indeterminate is true, then the values for max and progress are ignored.
1156     *
1157     * @param viewId The id of the view whose text should change
1158     * @param max The 100% value for the progress bar
1159     * @param progress The current value of the progress bar.
1160     * @param indeterminate True if the progress bar is indeterminate,
1161     *                false if not.
1162     */
1163    public void setProgressBar(int viewId, int max, int progress,
1164            boolean indeterminate) {
1165        setBoolean(viewId, "setIndeterminate", indeterminate);
1166        if (!indeterminate) {
1167            setInt(viewId, "setMax", max);
1168            setInt(viewId, "setProgress", progress);
1169        }
1170    }
1171
1172    /**
1173     * Equivalent to calling
1174     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
1175     * to launch the provided {@link PendingIntent}.
1176     *
1177     * When setting the on-click action of items within collections (eg. {@link ListView},
1178     * {@link StackView} etc.), this method will not work. Instead, use {@link
1179     * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
1180     * RemoteViews#setOnClickFillInIntent(int, Intent).
1181     *
1182     * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
1183     * @param pendingIntent The {@link PendingIntent} to send when user clicks
1184     */
1185    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
1186        addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
1187    }
1188
1189    /**
1190     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
1191     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
1192     * this method should be used to set a single PendingIntent template on the collection, and
1193     * individual items can differentiate their on-click behavior using
1194     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
1195     *
1196     * @param viewId The id of the collection who's children will use this PendingIntent template
1197     *          when clicked
1198     * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
1199     *          by a child of viewId and executed when that child is clicked
1200     */
1201    public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
1202        addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
1203    }
1204
1205    /**
1206     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
1207     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
1208     * a single PendingIntent template can be set on the collection, see {@link
1209     * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
1210     * action of a given item can be distinguished by setting a fillInIntent on that item. The
1211     * fillInIntent is then combined with the PendingIntent template in order to determine the final
1212     * intent which will be executed when the item is clicked. This works as follows: any fields
1213     * which are left blank in the PendingIntent template, but are provided by the fillInIntent
1214     * will be overwritten, and the resulting PendingIntent will be used.
1215     *
1216     *
1217     * of the PendingIntent template will then be filled in with the associated fields that are
1218     * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
1219     *
1220     * @param viewId The id of the view on which to set the fillInIntent
1221     * @param fillInIntent The intent which will be combined with the parent's PendingIntent
1222     *        in order to determine the on-click behavior of the view specified by viewId
1223     */
1224    public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
1225        addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
1226    }
1227
1228    /**
1229     * @hide
1230     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
1231     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
1232     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
1233     * view.
1234     * <p>
1235     * You can omit specific calls by marking their values with null or -1.
1236     *
1237     * @param viewId The id of the view that contains the target
1238     *            {@link Drawable}
1239     * @param targetBackground If true, apply these parameters to the
1240     *            {@link Drawable} returned by
1241     *            {@link android.view.View#getBackground()}. Otherwise, assume
1242     *            the target view is an {@link ImageView} and apply them to
1243     *            {@link ImageView#getDrawable()}.
1244     * @param alpha Specify an alpha value for the drawable, or -1 to leave
1245     *            unchanged.
1246     * @param colorFilter Specify a color for a
1247     *            {@link android.graphics.ColorFilter} for this drawable, or -1
1248     *            to leave unchanged.
1249     * @param mode Specify a PorterDuff mode for this drawable, or null to leave
1250     *            unchanged.
1251     * @param level Specify the level for the drawable, or -1 to leave
1252     *            unchanged.
1253     */
1254    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
1255            int colorFilter, PorterDuff.Mode mode, int level) {
1256        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
1257                colorFilter, mode, level));
1258    }
1259
1260    /**
1261     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
1262     *
1263     * @param viewId The id of the view whose text should change
1264     * @param color Sets the text color for all the states (normal, selected,
1265     *            focused) to be this color.
1266     */
1267    public void setTextColor(int viewId, int color) {
1268        setInt(viewId, "setTextColor", color);
1269    }
1270
1271    /**
1272     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
1273     *
1274     * @param viewId The id of the view whose text should change
1275     * @param intent The intent of the service which will be
1276     *            providing data to the RemoteViewsAdapter
1277     */
1278    public void setRemoteAdapter(int viewId, Intent intent) {
1279        setIntent(viewId, "setRemoteViewsAdapter", intent);
1280    }
1281
1282    /**
1283     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
1284     *
1285     * @param viewId The id of the view whose text should change
1286     * @param position Scroll to this adapter position
1287     */
1288    public void setScrollPosition(int viewId, int position) {
1289        setInt(viewId, "smoothScrollToPosition", position);
1290    }
1291
1292    /**
1293     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
1294     *
1295     * @param viewId The id of the view whose text should change
1296     * @param offset Scroll by this adapter position offset
1297     */
1298    public void setRelativeScrollPosition(int viewId, int offset) {
1299        setInt(viewId, "smoothScrollByOffset", offset);
1300    }
1301
1302    /**
1303     * Call a method taking one boolean on a view in the layout for this RemoteViews.
1304     *
1305     * @param viewId The id of the view whose text should change
1306     * @param methodName The name of the method to call.
1307     * @param value The value to pass to the method.
1308     */
1309    public void setBoolean(int viewId, String methodName, boolean value) {
1310        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
1311    }
1312
1313    /**
1314     * Call a method taking one byte on a view in the layout for this RemoteViews.
1315     *
1316     * @param viewId The id of the view whose text should change
1317     * @param methodName The name of the method to call.
1318     * @param value The value to pass to the method.
1319     */
1320    public void setByte(int viewId, String methodName, byte value) {
1321        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
1322    }
1323
1324    /**
1325     * Call a method taking one short on a view in the layout for this RemoteViews.
1326     *
1327     * @param viewId The id of the view whose text should change
1328     * @param methodName The name of the method to call.
1329     * @param value The value to pass to the method.
1330     */
1331    public void setShort(int viewId, String methodName, short value) {
1332        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
1333    }
1334
1335    /**
1336     * Call a method taking one int on a view in the layout for this RemoteViews.
1337     *
1338     * @param viewId The id of the view whose text should change
1339     * @param methodName The name of the method to call.
1340     * @param value The value to pass to the method.
1341     */
1342    public void setInt(int viewId, String methodName, int value) {
1343        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
1344    }
1345
1346    /**
1347     * Call a method taking one long on a view in the layout for this RemoteViews.
1348     *
1349     * @param viewId The id of the view whose text should change
1350     * @param methodName The name of the method to call.
1351     * @param value The value to pass to the method.
1352     */
1353    public void setLong(int viewId, String methodName, long value) {
1354        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
1355    }
1356
1357    /**
1358     * Call a method taking one float on a view in the layout for this RemoteViews.
1359     *
1360     * @param viewId The id of the view whose text should change
1361     * @param methodName The name of the method to call.
1362     * @param value The value to pass to the method.
1363     */
1364    public void setFloat(int viewId, String methodName, float value) {
1365        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
1366    }
1367
1368    /**
1369     * Call a method taking one double on a view in the layout for this RemoteViews.
1370     *
1371     * @param viewId The id of the view whose text should change
1372     * @param methodName The name of the method to call.
1373     * @param value The value to pass to the method.
1374     */
1375    public void setDouble(int viewId, String methodName, double value) {
1376        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
1377    }
1378
1379    /**
1380     * Call a method taking one char on a view in the layout for this RemoteViews.
1381     *
1382     * @param viewId The id of the view whose text should change
1383     * @param methodName The name of the method to call.
1384     * @param value The value to pass to the method.
1385     */
1386    public void setChar(int viewId, String methodName, char value) {
1387        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
1388    }
1389
1390    /**
1391     * Call a method taking one String on a view in the layout for this RemoteViews.
1392     *
1393     * @param viewId The id of the view whose text should change
1394     * @param methodName The name of the method to call.
1395     * @param value The value to pass to the method.
1396     */
1397    public void setString(int viewId, String methodName, String value) {
1398        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
1399    }
1400
1401    /**
1402     * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
1403     *
1404     * @param viewId The id of the view whose text should change
1405     * @param methodName The name of the method to call.
1406     * @param value The value to pass to the method.
1407     */
1408    public void setCharSequence(int viewId, String methodName, CharSequence value) {
1409        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
1410    }
1411
1412    /**
1413     * Call a method taking one Uri on a view in the layout for this RemoteViews.
1414     *
1415     * @param viewId The id of the view whose text should change
1416     * @param methodName The name of the method to call.
1417     * @param value The value to pass to the method.
1418     */
1419    public void setUri(int viewId, String methodName, Uri value) {
1420        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
1421    }
1422
1423    /**
1424     * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
1425     * @more
1426     * <p class="note">The bitmap will be flattened into the parcel if this object is
1427     * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
1428     *
1429     * @param viewId The id of the view whose text should change
1430     * @param methodName The name of the method to call.
1431     * @param value The value to pass to the method.
1432     */
1433    public void setBitmap(int viewId, String methodName, Bitmap value) {
1434        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value));
1435    }
1436
1437    /**
1438     * Call a method taking one Bundle on a view in the layout for this RemoteViews.
1439     *
1440     * @param viewId The id of the view whose text should change
1441     * @param methodName The name of the method to call.
1442     * @param value The value to pass to the method.
1443     */
1444    public void setBundle(int viewId, String methodName, Bundle value) {
1445        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
1446    }
1447
1448    /**
1449     *
1450     * @param viewId
1451     * @param methodName
1452     * @param value
1453     */
1454    public void setIntent(int viewId, String methodName, Intent value) {
1455        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
1456    }
1457
1458    /**
1459     * Inflates the view hierarchy represented by this object and applies
1460     * all of the actions.
1461     *
1462     * <p><strong>Caller beware: this may throw</strong>
1463     *
1464     * @param context Default context to use
1465     * @param parent Parent that the resulting view hierarchy will be attached to. This method
1466     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
1467     * @return The inflated view hierarchy
1468     */
1469    public View apply(Context context, ViewGroup parent) {
1470        View result;
1471
1472        Context c = prepareContext(context);
1473
1474        LayoutInflater inflater = (LayoutInflater)
1475                c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1476
1477        inflater = inflater.cloneInContext(c);
1478        inflater.setFilter(this);
1479
1480        result = inflater.inflate(mLayoutId, parent, false);
1481
1482        performApply(result);
1483
1484        return result;
1485    }
1486
1487    /**
1488     * Applies all of the actions to the provided view.
1489     *
1490     * <p><strong>Caller beware: this may throw</strong>
1491     *
1492     * @param v The view to apply the actions to.  This should be the result of
1493     * the {@link #apply(Context,ViewGroup)} call.
1494     */
1495    public void reapply(Context context, View v) {
1496        prepareContext(context);
1497        performApply(v);
1498    }
1499
1500    private void performApply(View v) {
1501        if (mActions != null) {
1502            final int count = mActions.size();
1503            for (int i = 0; i < count; i++) {
1504                Action a = mActions.get(i);
1505                a.apply(v);
1506            }
1507        }
1508    }
1509
1510    private Context prepareContext(Context context) {
1511        Context c;
1512        String packageName = mPackage;
1513
1514        if (packageName != null) {
1515            try {
1516                c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED);
1517            } catch (NameNotFoundException e) {
1518                Log.e(LOG_TAG, "Package name " + packageName + " not found");
1519                c = context;
1520            }
1521        } else {
1522            c = context;
1523        }
1524
1525        return c;
1526    }
1527
1528    /* (non-Javadoc)
1529     * Used to restrict the views which can be inflated
1530     *
1531     * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
1532     */
1533    public boolean onLoadClass(Class clazz) {
1534        return clazz.isAnnotationPresent(RemoteView.class);
1535    }
1536
1537    public int describeContents() {
1538        return 0;
1539    }
1540
1541    public void writeToParcel(Parcel dest, int flags) {
1542        dest.writeString(mPackage);
1543        dest.writeInt(mLayoutId);
1544        dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
1545        int count;
1546        if (mActions != null) {
1547            count = mActions.size();
1548        } else {
1549            count = 0;
1550        }
1551        dest.writeInt(count);
1552        for (int i=0; i<count; i++) {
1553            Action a = mActions.get(i);
1554            a.writeToParcel(dest, 0);
1555        }
1556    }
1557
1558    /**
1559     * Parcelable.Creator that instantiates RemoteViews objects
1560     */
1561    public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
1562        public RemoteViews createFromParcel(Parcel parcel) {
1563            return new RemoteViews(parcel);
1564        }
1565
1566        public RemoteViews[] newArray(int size) {
1567            return new RemoteViews[size];
1568        }
1569    };
1570}
1571