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