RemoteViews.java revision 2dd2197805edb4d9547b143deef2226413218f4c
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.app.PendingIntent;
20import android.content.Context;
21import android.content.Intent;
22import android.content.IntentSender;
23import android.content.pm.PackageManager.NameNotFoundException;
24import android.graphics.Bitmap;
25import android.graphics.PorterDuff;
26import android.graphics.Rect;
27import android.graphics.drawable.Drawable;
28import android.net.Uri;
29import android.os.Bundle;
30import android.os.Parcel;
31import android.os.Parcelable;
32import android.text.TextUtils;
33import android.util.Log;
34import android.view.LayoutInflater;
35import android.view.RemotableViewMethod;
36import android.view.View;
37import android.view.ViewGroup;
38import android.view.LayoutInflater.Filter;
39import android.view.View.OnClickListener;
40
41import java.lang.annotation.ElementType;
42import java.lang.annotation.Retention;
43import java.lang.annotation.RetentionPolicy;
44import java.lang.annotation.Target;
45import java.lang.reflect.Method;
46import java.util.ArrayList;
47
48
49/**
50 * A class that describes a view hierarchy that can be displayed in
51 * another process. The hierarchy is inflated from a layout resource
52 * file, and this class provides some basic operations for modifying
53 * the content of the inflated hierarchy.
54 */
55public class RemoteViews implements Parcelable, Filter {
56
57    private static final String LOG_TAG = "RemoteViews";
58
59    /**
60     * The package name of the package containing the layout
61     * resource. (Added to the parcel)
62     */
63    private final String mPackage;
64
65    /**
66     * The resource ID of the layout file. (Added to the parcel)
67     */
68    private final int mLayoutId;
69
70    /**
71     * An array of actions to perform on the view tree once it has been
72     * inflated
73     */
74    private ArrayList<Action> mActions;
75
76
77    /**
78     * This annotation indicates that a subclass of View is alllowed to be used
79     * with the {@link RemoteViews} mechanism.
80     */
81    @Target({ ElementType.TYPE })
82    @Retention(RetentionPolicy.RUNTIME)
83    public @interface RemoteView {
84    }
85
86    /**
87     * Exception to send when something goes wrong executing an action
88     *
89     */
90    public static class ActionException extends RuntimeException {
91        public ActionException(Exception ex) {
92            super(ex);
93        }
94        public ActionException(String message) {
95            super(message);
96        }
97    }
98
99    /**
100     * Base class for all actions that can be performed on an
101     * inflated view.
102     *
103     *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
104     */
105    private abstract static class Action implements Parcelable {
106        public abstract void apply(View root) throws ActionException;
107
108        public int describeContents() {
109            return 0;
110        }
111    }
112
113    /**
114     * Equivalent to calling
115     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
116     * to launch the provided {@link PendingIntent}.
117     */
118    private class SetOnClickPendingIntent extends Action {
119        public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
120            this.viewId = id;
121            this.pendingIntent = pendingIntent;
122        }
123
124        public SetOnClickPendingIntent(Parcel parcel) {
125            viewId = parcel.readInt();
126            pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
127        }
128
129        public void writeToParcel(Parcel dest, int flags) {
130            dest.writeInt(TAG);
131            dest.writeInt(viewId);
132            pendingIntent.writeToParcel(dest, 0 /* no flags */);
133        }
134
135        @Override
136        public void apply(View root) {
137            final View target = root.findViewById(viewId);
138            if (target != null && pendingIntent != null) {
139                OnClickListener listener = new OnClickListener() {
140                    public void onClick(View v) {
141                        // Find target view location in screen coordinates and
142                        // fill into PendingIntent before sending.
143                        final float appScale = v.getContext().getResources()
144                                .getCompatibilityInfo().applicationScale;
145                        final int[] pos = new int[2];
146                        v.getLocationOnScreen(pos);
147
148                        final Rect rect = new Rect();
149                        rect.left = (int) (pos[0] * appScale + 0.5f);
150                        rect.top = (int) (pos[1] * appScale + 0.5f);
151                        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
152                        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
153
154                        final Intent intent = new Intent();
155                        intent.setSourceBounds(rect);
156                        try {
157                            // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
158                            v.getContext().startIntentSender(
159                                    pendingIntent.getIntentSender(), intent,
160                                    Intent.FLAG_ACTIVITY_NEW_TASK,
161                                    Intent.FLAG_ACTIVITY_NEW_TASK, 0);
162                        } catch (IntentSender.SendIntentException e) {
163                            android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
164                        }
165                    }
166                };
167                target.setOnClickListener(listener);
168            }
169        }
170
171        int viewId;
172        PendingIntent pendingIntent;
173
174        public final static int TAG = 1;
175    }
176
177    /**
178     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
179     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
180     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
181     * <p>
182     * These operations will be performed on the {@link Drawable} returned by the
183     * target {@link View#getBackground()} by default.  If targetBackground is false,
184     * we assume the target is an {@link ImageView} and try applying the operations
185     * to {@link ImageView#getDrawable()}.
186     * <p>
187     * You can omit specific calls by marking their values with null or -1.
188     */
189    private class SetDrawableParameters extends Action {
190        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
191                int colorFilter, PorterDuff.Mode mode, int level) {
192            this.viewId = id;
193            this.targetBackground = targetBackground;
194            this.alpha = alpha;
195            this.colorFilter = colorFilter;
196            this.filterMode = mode;
197            this.level = level;
198        }
199
200        public SetDrawableParameters(Parcel parcel) {
201            viewId = parcel.readInt();
202            targetBackground = parcel.readInt() != 0;
203            alpha = parcel.readInt();
204            colorFilter = parcel.readInt();
205            boolean hasMode = parcel.readInt() != 0;
206            if (hasMode) {
207                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
208            } else {
209                filterMode = null;
210            }
211            level = parcel.readInt();
212        }
213
214        public void writeToParcel(Parcel dest, int flags) {
215            dest.writeInt(TAG);
216            dest.writeInt(viewId);
217            dest.writeInt(targetBackground ? 1 : 0);
218            dest.writeInt(alpha);
219            dest.writeInt(colorFilter);
220            if (filterMode != null) {
221                dest.writeInt(1);
222                dest.writeString(filterMode.toString());
223            } else {
224                dest.writeInt(0);
225            }
226            dest.writeInt(level);
227        }
228
229        @Override
230        public void apply(View root) {
231            final View target = root.findViewById(viewId);
232            if (target == null) {
233                return;
234            }
235
236            // Pick the correct drawable to modify for this view
237            Drawable targetDrawable = null;
238            if (targetBackground) {
239                targetDrawable = target.getBackground();
240            } else if (target instanceof ImageView) {
241                ImageView imageView = (ImageView) target;
242                targetDrawable = imageView.getDrawable();
243            }
244
245            if (targetDrawable != null) {
246                // Perform modifications only if values are set correctly
247                if (alpha != -1) {
248                    targetDrawable.setAlpha(alpha);
249                }
250                if (colorFilter != -1 && filterMode != null) {
251                    targetDrawable.setColorFilter(colorFilter, filterMode);
252                }
253                if (level != -1) {
254                    targetDrawable.setLevel(level);
255                }
256            }
257        }
258
259        int viewId;
260        boolean targetBackground;
261        int alpha;
262        int colorFilter;
263        PorterDuff.Mode filterMode;
264        int level;
265
266        public final static int TAG = 3;
267    }
268
269    private class ReflectionActionWithoutParams extends Action {
270        int viewId;
271        String methodName;
272
273        public final static int TAG = 5;
274
275        ReflectionActionWithoutParams(int viewId, String methodName) {
276            this.viewId = viewId;
277            this.methodName = methodName;
278        }
279
280        ReflectionActionWithoutParams(Parcel in) {
281            this.viewId = in.readInt();
282            this.methodName = in.readString();
283        }
284
285        public void writeToParcel(Parcel out, int flags) {
286            out.writeInt(TAG);
287            out.writeInt(this.viewId);
288            out.writeString(this.methodName);
289        }
290
291        @Override
292        public void apply(View root) {
293            final View view = root.findViewById(viewId);
294            if (view == null) {
295                throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
296            }
297
298            Class klass = view.getClass();
299            Method method;
300            try {
301                method = klass.getMethod(this.methodName);
302            } catch (NoSuchMethodException ex) {
303                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
304                        + this.methodName + "()");
305            }
306
307            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
308                throw new ActionException("view: " + klass.getName()
309                        + " can't use method with RemoteViews: "
310                        + this.methodName + "()");
311            }
312
313            try {
314                //noinspection ConstantIfStatement
315                if (false) {
316                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
317                        + this.methodName + "()");
318                }
319                method.invoke(view);
320            } catch (Exception ex) {
321                throw new ActionException(ex);
322            }
323        }
324    }
325
326    /**
327     * Base class for the reflection actions.
328     */
329    private class ReflectionAction extends Action {
330        static final int TAG = 2;
331
332        static final int BOOLEAN = 1;
333        static final int BYTE = 2;
334        static final int SHORT = 3;
335        static final int INT = 4;
336        static final int LONG = 5;
337        static final int FLOAT = 6;
338        static final int DOUBLE = 7;
339        static final int CHAR = 8;
340        static final int STRING = 9;
341        static final int CHAR_SEQUENCE = 10;
342        static final int URI = 11;
343        static final int BITMAP = 12;
344        static final int BUNDLE = 13;
345        static final int INTENT = 14;
346
347        int viewId;
348        String methodName;
349        int type;
350        Object value;
351
352        ReflectionAction(int viewId, String methodName, int type, Object value) {
353            this.viewId = viewId;
354            this.methodName = methodName;
355            this.type = type;
356            this.value = value;
357        }
358
359        ReflectionAction(Parcel in) {
360            this.viewId = in.readInt();
361            this.methodName = in.readString();
362            this.type = in.readInt();
363            //noinspection ConstantIfStatement
364            if (false) {
365                Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
366                        + " methodName=" + this.methodName + " type=" + this.type);
367            }
368            switch (this.type) {
369                case BOOLEAN:
370                    this.value = in.readInt() != 0;
371                    break;
372                case BYTE:
373                    this.value = in.readByte();
374                    break;
375                case SHORT:
376                    this.value = (short)in.readInt();
377                    break;
378                case INT:
379                    this.value = in.readInt();
380                    break;
381                case LONG:
382                    this.value = in.readLong();
383                    break;
384                case FLOAT:
385                    this.value = in.readFloat();
386                    break;
387                case DOUBLE:
388                    this.value = in.readDouble();
389                    break;
390                case CHAR:
391                    this.value = (char)in.readInt();
392                    break;
393                case STRING:
394                    this.value = in.readString();
395                    break;
396                case CHAR_SEQUENCE:
397                    this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
398                    break;
399                case URI:
400                    this.value = Uri.CREATOR.createFromParcel(in);
401                    break;
402                case BITMAP:
403                    this.value = Bitmap.CREATOR.createFromParcel(in);
404                    break;
405                case BUNDLE:
406                    this.value = in.readBundle();
407                    break;
408                case INTENT:
409                    this.value = Intent.CREATOR.createFromParcel(in);
410                    break;
411                default:
412                    break;
413            }
414        }
415
416        public void writeToParcel(Parcel out, int flags) {
417            out.writeInt(TAG);
418            out.writeInt(this.viewId);
419            out.writeString(this.methodName);
420            out.writeInt(this.type);
421            //noinspection ConstantIfStatement
422            if (false) {
423                Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
424                        + " methodName=" + this.methodName + " type=" + this.type);
425            }
426            switch (this.type) {
427                case BOOLEAN:
428                    out.writeInt((Boolean) this.value ? 1 : 0);
429                    break;
430                case BYTE:
431                    out.writeByte((Byte) this.value);
432                    break;
433                case SHORT:
434                    out.writeInt((Short) this.value);
435                    break;
436                case INT:
437                    out.writeInt((Integer) this.value);
438                    break;
439                case LONG:
440                    out.writeLong((Long) this.value);
441                    break;
442                case FLOAT:
443                    out.writeFloat((Float) this.value);
444                    break;
445                case DOUBLE:
446                    out.writeDouble((Double) this.value);
447                    break;
448                case CHAR:
449                    out.writeInt((int)((Character)this.value).charValue());
450                    break;
451                case STRING:
452                    out.writeString((String)this.value);
453                    break;
454                case CHAR_SEQUENCE:
455                    TextUtils.writeToParcel((CharSequence)this.value, out, flags);
456                    break;
457                case URI:
458                    ((Uri)this.value).writeToParcel(out, flags);
459                    break;
460                case BITMAP:
461                    ((Bitmap)this.value).writeToParcel(out, flags);
462                    break;
463                case BUNDLE:
464                    out.writeBundle((Bundle) this.value);
465                    break;
466                case INTENT:
467                    ((Intent)this.value).writeToParcel(out, flags);
468                    break;
469                default:
470                    break;
471            }
472        }
473
474        private Class getParameterType() {
475            switch (this.type) {
476                case BOOLEAN:
477                    return boolean.class;
478                case BYTE:
479                    return byte.class;
480                case SHORT:
481                    return short.class;
482                case INT:
483                    return int.class;
484                case LONG:
485                    return long.class;
486                case FLOAT:
487                    return float.class;
488                case DOUBLE:
489                    return double.class;
490                case CHAR:
491                    return char.class;
492                case STRING:
493                    return String.class;
494                case CHAR_SEQUENCE:
495                    return CharSequence.class;
496                case URI:
497                    return Uri.class;
498                case BITMAP:
499                    return Bitmap.class;
500                case BUNDLE:
501                    return Bundle.class;
502                case INTENT:
503                    return Intent.class;
504                default:
505                    return null;
506            }
507        }
508
509        @Override
510        public void apply(View root) {
511            final View view = root.findViewById(viewId);
512            if (view == null) {
513                throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
514            }
515
516            Class param = getParameterType();
517            if (param == null) {
518                throw new ActionException("bad type: " + this.type);
519            }
520
521            Class klass = view.getClass();
522            Method method;
523            try {
524                method = klass.getMethod(this.methodName, getParameterType());
525            }
526            catch (NoSuchMethodException ex) {
527                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
528                        + this.methodName + "(" + param.getName() + ")");
529            }
530
531            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
532                throw new ActionException("view: " + klass.getName()
533                        + " can't use method with RemoteViews: "
534                        + this.methodName + "(" + param.getName() + ")");
535            }
536
537            try {
538                //noinspection ConstantIfStatement
539                if (false) {
540                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
541                        + this.methodName + "(" + param.getName() + ") with "
542                        + (this.value == null ? "null" : this.value.getClass().getName()));
543                }
544                method.invoke(view, this.value);
545            }
546            catch (Exception ex) {
547                throw new ActionException(ex);
548            }
549        }
550    }
551
552    /**
553     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
554     * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
555     * when null. This allows users to build "nested" {@link RemoteViews}.
556     */
557    private class ViewGroupAction extends Action {
558        public ViewGroupAction(int viewId, RemoteViews nestedViews) {
559            this.viewId = viewId;
560            this.nestedViews = nestedViews;
561        }
562
563        public ViewGroupAction(Parcel parcel) {
564            viewId = parcel.readInt();
565            nestedViews = parcel.readParcelable(null);
566        }
567
568        public void writeToParcel(Parcel dest, int flags) {
569            dest.writeInt(TAG);
570            dest.writeInt(viewId);
571            dest.writeParcelable(nestedViews, 0 /* no flags */);
572        }
573
574        @Override
575        public void apply(View root) {
576            final Context context = root.getContext();
577            final ViewGroup target = (ViewGroup) root.findViewById(viewId);
578            if (nestedViews != null) {
579                // Inflate nested views and add as children
580                target.addView(nestedViews.apply(context, target));
581            } else if (target != null) {
582                // Clear all children when nested views omitted
583                target.removeAllViews();
584            }
585        }
586
587        int viewId;
588        RemoteViews nestedViews;
589
590        public final static int TAG = 4;
591    }
592
593    /**
594     * Create a new RemoteViews object that will display the views contained
595     * in the specified layout file.
596     *
597     * @param packageName Name of the package that contains the layout resource
598     * @param layoutId The id of the layout resource
599     */
600    public RemoteViews(String packageName, int layoutId) {
601        mPackage = packageName;
602        mLayoutId = layoutId;
603    }
604
605    /**
606     * Reads a RemoteViews object from a parcel.
607     *
608     * @param parcel
609     */
610    public RemoteViews(Parcel parcel) {
611        mPackage = parcel.readString();
612        mLayoutId = parcel.readInt();
613        int count = parcel.readInt();
614        if (count > 0) {
615            mActions = new ArrayList<Action>(count);
616            for (int i=0; i<count; i++) {
617                int tag = parcel.readInt();
618                switch (tag) {
619                case SetOnClickPendingIntent.TAG:
620                    mActions.add(new SetOnClickPendingIntent(parcel));
621                    break;
622                case SetDrawableParameters.TAG:
623                    mActions.add(new SetDrawableParameters(parcel));
624                    break;
625                case ReflectionAction.TAG:
626                    mActions.add(new ReflectionAction(parcel));
627                    break;
628                case ViewGroupAction.TAG:
629                    mActions.add(new ViewGroupAction(parcel));
630                    break;
631                case ReflectionActionWithoutParams.TAG:
632                    mActions.add(new ReflectionActionWithoutParams(parcel));
633                    break;
634                default:
635                    throw new ActionException("Tag " + tag + " not found");
636                }
637            }
638        }
639    }
640
641    @Override
642    public RemoteViews clone() {
643        final RemoteViews that = new RemoteViews(mPackage, mLayoutId);
644        if (mActions != null) {
645            that.mActions = (ArrayList<Action>)mActions.clone();
646        }
647        return that;
648    }
649
650    public String getPackage() {
651        return mPackage;
652    }
653
654    public int getLayoutId() {
655        return mLayoutId;
656    }
657
658    /**
659     * Add an action to be executed on the remote side when apply is called.
660     *
661     * @param a The action to add
662     */
663    private void addAction(Action a) {
664        if (mActions == null) {
665            mActions = new ArrayList<Action>();
666        }
667        mActions.add(a);
668    }
669
670    /**
671     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
672     * given {@link RemoteViews}. This allows users to build "nested"
673     * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
674     * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
675     * children.
676     *
677     * @param viewId The id of the parent {@link ViewGroup} to add child into.
678     * @param nestedView {@link RemoteViews} that describes the child.
679     */
680    public void addView(int viewId, RemoteViews nestedView) {
681        addAction(new ViewGroupAction(viewId, nestedView));
682    }
683
684    /**
685     * Equivalent to calling {@link ViewGroup#removeAllViews()}.
686     *
687     * @param viewId The id of the parent {@link ViewGroup} to remove all
688     *            children from.
689     */
690    public void removeAllViews(int viewId) {
691        addAction(new ViewGroupAction(viewId, null));
692    }
693
694    /**
695     * Equivalent to calling {@link AdapterViewFlipper#showNext()}
696     *
697     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()}
698     */
699    public void showNext(int viewId) {
700        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
701    }
702
703    /**
704     * Equivalent to calling {@link AdapterViewFlipper#showPrevious()}
705     *
706     * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()}
707     */
708    public void showPrevious(int viewId) {
709        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
710    }
711
712    /**
713     * Equivalent to calling View.setVisibility
714     *
715     * @param viewId The id of the view whose visibility should change
716     * @param visibility The new visibility for the view
717     */
718    public void setViewVisibility(int viewId, int visibility) {
719        setInt(viewId, "setVisibility", visibility);
720    }
721
722    /**
723     * Equivalent to calling TextView.setText
724     *
725     * @param viewId The id of the view whose text should change
726     * @param text The new text for the view
727     */
728    public void setTextViewText(int viewId, CharSequence text) {
729        setCharSequence(viewId, "setText", text);
730    }
731
732    /**
733     * Equivalent to calling ImageView.setImageResource
734     *
735     * @param viewId The id of the view whose drawable should change
736     * @param srcId The new resource id for the drawable
737     */
738    public void setImageViewResource(int viewId, int srcId) {
739        setInt(viewId, "setImageResource", srcId);
740    }
741
742    /**
743     * Equivalent to calling ImageView.setImageURI
744     *
745     * @param viewId The id of the view whose drawable should change
746     * @param uri The Uri for the image
747     */
748    public void setImageViewUri(int viewId, Uri uri) {
749        setUri(viewId, "setImageURI", uri);
750    }
751
752    /**
753     * Equivalent to calling ImageView.setImageBitmap
754     *
755     * @param viewId The id of the view whose drawable should change
756     * @param bitmap The new Bitmap for the drawable
757     */
758    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
759        setBitmap(viewId, "setImageBitmap", bitmap);
760    }
761
762    /**
763     * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
764     * {@link Chronometer#setFormat Chronometer.setFormat},
765     * and {@link Chronometer#start Chronometer.start()} or
766     * {@link Chronometer#stop Chronometer.stop()}.
767     *
768     * @param viewId The id of the view whose text should change
769     * @param base The time at which the timer would have read 0:00.  This
770     *             time should be based off of
771     *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
772     * @param format The Chronometer format string, or null to
773     *               simply display the timer value.
774     * @param started True if you want the clock to be started, false if not.
775     */
776    public void setChronometer(int viewId, long base, String format, boolean started) {
777        setLong(viewId, "setBase", base);
778        setString(viewId, "setFormat", format);
779        setBoolean(viewId, "setStarted", started);
780    }
781
782    /**
783     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
784     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
785     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
786     *
787     * If indeterminate is true, then the values for max and progress are ignored.
788     *
789     * @param viewId The id of the view whose text should change
790     * @param max The 100% value for the progress bar
791     * @param progress The current value of the progress bar.
792     * @param indeterminate True if the progress bar is indeterminate,
793     *                false if not.
794     */
795    public void setProgressBar(int viewId, int max, int progress,
796            boolean indeterminate) {
797        setBoolean(viewId, "setIndeterminate", indeterminate);
798        if (!indeterminate) {
799            setInt(viewId, "setMax", max);
800            setInt(viewId, "setProgress", progress);
801        }
802    }
803
804    /**
805     * Equivalent to calling
806     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
807     * to launch the provided {@link PendingIntent}.
808     *
809     * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
810     * @param pendingIntent The {@link PendingIntent} to send when user clicks
811     */
812    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
813        addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
814    }
815
816    /**
817     * @hide
818     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
819     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
820     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
821     * view.
822     * <p>
823     * You can omit specific calls by marking their values with null or -1.
824     *
825     * @param viewId The id of the view that contains the target
826     *            {@link Drawable}
827     * @param targetBackground If true, apply these parameters to the
828     *            {@link Drawable} returned by
829     *            {@link android.view.View#getBackground()}. Otherwise, assume
830     *            the target view is an {@link ImageView} and apply them to
831     *            {@link ImageView#getDrawable()}.
832     * @param alpha Specify an alpha value for the drawable, or -1 to leave
833     *            unchanged.
834     * @param colorFilter Specify a color for a
835     *            {@link android.graphics.ColorFilter} for this drawable, or -1
836     *            to leave unchanged.
837     * @param mode Specify a PorterDuff mode for this drawable, or null to leave
838     *            unchanged.
839     * @param level Specify the level for the drawable, or -1 to leave
840     *            unchanged.
841     */
842    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
843            int colorFilter, PorterDuff.Mode mode, int level) {
844        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
845                colorFilter, mode, level));
846    }
847
848    /**
849     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
850     *
851     * @param viewId The id of the view whose text should change
852     * @param color Sets the text color for all the states (normal, selected,
853     *            focused) to be this color.
854     */
855    public void setTextColor(int viewId, int color) {
856        setInt(viewId, "setTextColor", color);
857    }
858
859    /**
860     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
861     *
862     * @param viewId The id of the view whose text should change
863     * @param intent The intent of the service which will be
864     *            providing data to the RemoteViewsAdapter
865     */
866    public void setRemoteAdapter(int viewId, Intent intent) {
867        setIntent(viewId, "setRemoteViewsAdapter", intent);
868    }
869
870    /**
871     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
872     *
873     * @param viewId The id of the view whose text should change
874     * @param position Scroll to this adapter position
875     */
876    public void setScrollPosition(int viewId, int position) {
877        setInt(viewId, "smoothScrollToPosition", position);
878    }
879
880    /**
881     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
882     *
883     * @param viewId The id of the view whose text should change
884     * @param offset Scroll by this adapter position offset
885     */
886    public void setRelativeScrollPosition(int viewId, int offset) {
887        setInt(viewId, "smoothScrollByOffset", offset);
888    }
889
890    /**
891     * Call a method taking one boolean on a view in the layout for this RemoteViews.
892     *
893     * @param viewId The id of the view whose text should change
894     * @param methodName The name of the method to call.
895     * @param value The value to pass to the method.
896     */
897    public void setBoolean(int viewId, String methodName, boolean value) {
898        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
899    }
900
901    /**
902     * Call a method taking one byte on a view in the layout for this RemoteViews.
903     *
904     * @param viewId The id of the view whose text should change
905     * @param methodName The name of the method to call.
906     * @param value The value to pass to the method.
907     */
908    public void setByte(int viewId, String methodName, byte value) {
909        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
910    }
911
912    /**
913     * Call a method taking one short on a view in the layout for this RemoteViews.
914     *
915     * @param viewId The id of the view whose text should change
916     * @param methodName The name of the method to call.
917     * @param value The value to pass to the method.
918     */
919    public void setShort(int viewId, String methodName, short value) {
920        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
921    }
922
923    /**
924     * Call a method taking one int on a view in the layout for this RemoteViews.
925     *
926     * @param viewId The id of the view whose text should change
927     * @param methodName The name of the method to call.
928     * @param value The value to pass to the method.
929     */
930    public void setInt(int viewId, String methodName, int value) {
931        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
932    }
933
934    /**
935     * Call a method taking one long on a view in the layout for this RemoteViews.
936     *
937     * @param viewId The id of the view whose text should change
938     * @param methodName The name of the method to call.
939     * @param value The value to pass to the method.
940     */
941    public void setLong(int viewId, String methodName, long value) {
942        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
943    }
944
945    /**
946     * Call a method taking one float on a view in the layout for this RemoteViews.
947     *
948     * @param viewId The id of the view whose text should change
949     * @param methodName The name of the method to call.
950     * @param value The value to pass to the method.
951     */
952    public void setFloat(int viewId, String methodName, float value) {
953        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
954    }
955
956    /**
957     * Call a method taking one double on a view in the layout for this RemoteViews.
958     *
959     * @param viewId The id of the view whose text should change
960     * @param methodName The name of the method to call.
961     * @param value The value to pass to the method.
962     */
963    public void setDouble(int viewId, String methodName, double value) {
964        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
965    }
966
967    /**
968     * Call a method taking one char on a view in the layout for this RemoteViews.
969     *
970     * @param viewId The id of the view whose text should change
971     * @param methodName The name of the method to call.
972     * @param value The value to pass to the method.
973     */
974    public void setChar(int viewId, String methodName, char value) {
975        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
976    }
977
978    /**
979     * Call a method taking one String on a view in the layout for this RemoteViews.
980     *
981     * @param viewId The id of the view whose text should change
982     * @param methodName The name of the method to call.
983     * @param value The value to pass to the method.
984     */
985    public void setString(int viewId, String methodName, String value) {
986        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
987    }
988
989    /**
990     * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
991     *
992     * @param viewId The id of the view whose text should change
993     * @param methodName The name of the method to call.
994     * @param value The value to pass to the method.
995     */
996    public void setCharSequence(int viewId, String methodName, CharSequence value) {
997        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
998    }
999
1000    /**
1001     * Call a method taking one Uri on a view in the layout for this RemoteViews.
1002     *
1003     * @param viewId The id of the view whose text should change
1004     * @param methodName The name of the method to call.
1005     * @param value The value to pass to the method.
1006     */
1007    public void setUri(int viewId, String methodName, Uri value) {
1008        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
1009    }
1010
1011    /**
1012     * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
1013     * @more
1014     * <p class="note">The bitmap will be flattened into the parcel if this object is
1015     * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
1016     *
1017     * @param viewId The id of the view whose text should change
1018     * @param methodName The name of the method to call.
1019     * @param value The value to pass to the method.
1020     */
1021    public void setBitmap(int viewId, String methodName, Bitmap value) {
1022        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value));
1023    }
1024
1025    /**
1026     * Call a method taking one Bundle on a view in the layout for this RemoteViews.
1027     *
1028     * @param viewId The id of the view whose text should change
1029     * @param methodName The name of the method to call.
1030     * @param value The value to pass to the method.
1031     */
1032    public void setBundle(int viewId, String methodName, Bundle value) {
1033        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
1034    }
1035
1036    /**
1037     *
1038     * @param viewId
1039     * @param methodName
1040     * @param value
1041     */
1042    public void setIntent(int viewId, String methodName, Intent value) {
1043        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
1044    }
1045
1046    /**
1047     * Inflates the view hierarchy represented by this object and applies
1048     * all of the actions.
1049     *
1050     * <p><strong>Caller beware: this may throw</strong>
1051     *
1052     * @param context Default context to use
1053     * @param parent Parent that the resulting view hierarchy will be attached to. This method
1054     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
1055     * @return The inflated view hierarchy
1056     */
1057    public View apply(Context context, ViewGroup parent) {
1058        View result;
1059
1060        Context c = prepareContext(context);
1061
1062        LayoutInflater inflater = (LayoutInflater)
1063                c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
1064
1065        inflater = inflater.cloneInContext(c);
1066        inflater.setFilter(this);
1067
1068        result = inflater.inflate(mLayoutId, parent, false);
1069
1070        performApply(result);
1071
1072        return result;
1073    }
1074
1075    /**
1076     * Applies all of the actions to the provided view.
1077     *
1078     * <p><strong>Caller beware: this may throw</strong>
1079     *
1080     * @param v The view to apply the actions to.  This should be the result of
1081     * the {@link #apply(Context,ViewGroup)} call.
1082     */
1083    public void reapply(Context context, View v) {
1084        prepareContext(context);
1085        performApply(v);
1086    }
1087
1088    private void performApply(View v) {
1089        if (mActions != null) {
1090            final int count = mActions.size();
1091            for (int i = 0; i < count; i++) {
1092                Action a = mActions.get(i);
1093                a.apply(v);
1094            }
1095        }
1096    }
1097
1098    private Context prepareContext(Context context) {
1099        Context c;
1100        String packageName = mPackage;
1101
1102        if (packageName != null) {
1103            try {
1104                c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED);
1105            } catch (NameNotFoundException e) {
1106                Log.e(LOG_TAG, "Package name " + packageName + " not found");
1107                c = context;
1108            }
1109        } else {
1110            c = context;
1111        }
1112
1113        return c;
1114    }
1115
1116    /* (non-Javadoc)
1117     * Used to restrict the views which can be inflated
1118     *
1119     * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
1120     */
1121    public boolean onLoadClass(Class clazz) {
1122        return clazz.isAnnotationPresent(RemoteView.class);
1123    }
1124
1125    public int describeContents() {
1126        return 0;
1127    }
1128
1129    public void writeToParcel(Parcel dest, int flags) {
1130        dest.writeString(mPackage);
1131        dest.writeInt(mLayoutId);
1132        int count;
1133        if (mActions != null) {
1134            count = mActions.size();
1135        } else {
1136            count = 0;
1137        }
1138        dest.writeInt(count);
1139        for (int i=0; i<count; i++) {
1140            Action a = mActions.get(i);
1141            a.writeToParcel(dest, 0);
1142        }
1143    }
1144
1145    /**
1146     * Parcelable.Creator that instantiates RemoteViews objects
1147     */
1148    public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
1149        public RemoteViews createFromParcel(Parcel parcel) {
1150            return new RemoteViews(parcel);
1151        }
1152
1153        public RemoteViews[] newArray(int size) {
1154            return new RemoteViews[size];
1155        }
1156    };
1157}
1158