RemoteViews.java revision 311c79c3e93589c6fc720fe6c58ed522af591376
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.ActivityOptions;
20import android.app.PendingIntent;
21import android.appwidget.AppWidgetHostView;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentSender;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.PackageManager;
27import android.content.pm.PackageManager.NameNotFoundException;
28import android.content.res.Configuration;
29import android.graphics.Bitmap;
30import android.graphics.PorterDuff;
31import android.graphics.Rect;
32import android.graphics.drawable.Drawable;
33import android.net.Uri;
34import android.os.Build;
35import android.os.Bundle;
36import android.os.Parcel;
37import android.os.Parcelable;
38import android.text.TextUtils;
39import android.util.Log;
40import android.view.LayoutInflater;
41import android.view.LayoutInflater.Filter;
42import android.view.RemotableViewMethod;
43import android.view.View;
44import android.view.View.OnClickListener;
45import android.view.ViewGroup;
46import android.widget.AdapterView.OnItemClickListener;
47
48import java.lang.annotation.ElementType;
49import java.lang.annotation.Retention;
50import java.lang.annotation.RetentionPolicy;
51import java.lang.annotation.Target;
52import java.lang.reflect.Method;
53import java.util.ArrayList;
54
55
56/**
57 * A class that describes a view hierarchy that can be displayed in
58 * another process. The hierarchy is inflated from a layout resource
59 * file, and this class provides some basic operations for modifying
60 * the content of the inflated hierarchy.
61 */
62public class RemoteViews implements Parcelable, Filter {
63
64    private static final String LOG_TAG = "RemoteViews";
65
66    /**
67     * The intent extra that contains the appWidgetId.
68     * @hide
69     */
70    static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
71
72    /**
73     * The package name of the package containing the layout
74     * resource. (Added to the parcel)
75     */
76    private final String mPackage;
77
78    /**
79     * The resource ID of the layout file. (Added to the parcel)
80     */
81    private final int mLayoutId;
82
83    /**
84     * An array of actions to perform on the view tree once it has been
85     * inflated
86     */
87    private ArrayList<Action> mActions;
88
89    /**
90     * A class to keep track of memory usage by this RemoteViews
91     */
92    private MemoryUsageCounter mMemoryUsageCounter;
93
94    /**
95     * Maps bitmaps to unique indicies to avoid Bitmap duplication.
96     */
97    private BitmapCache mBitmapCache;
98
99    /**
100     * Indicates whether or not this RemoteViews object is contained as a child of any other
101     * RemoteViews.
102     */
103    private boolean mIsRoot = true;
104
105    /**
106     * Constants to whether or not this RemoteViews is composed of a landscape and portrait
107     * RemoteViews.
108     */
109    private static final int MODE_NORMAL = 0;
110    private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
111
112    /**
113     * Used in conjunction with the special constructor
114     * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait
115     * RemoteViews.
116     */
117    private RemoteViews mLandscape = null;
118    private RemoteViews mPortrait = null;
119
120    /**
121     * This flag indicates whether this RemoteViews object is being created from a
122     * RemoteViewsService for use as a child of a widget collection. This flag is used
123     * to determine whether or not certain features are available, in particular,
124     * setting on click extras and setting on click pending intents. The former is enabled,
125     * and the latter disabled when this flag is true.
126     */
127     private boolean mIsWidgetCollectionChild = false;
128
129    /**
130     * This annotation indicates that a subclass of View is alllowed to be used
131     * with the {@link RemoteViews} mechanism.
132     */
133    @Target({ ElementType.TYPE })
134    @Retention(RetentionPolicy.RUNTIME)
135    public @interface RemoteView {
136    }
137
138    /**
139     * Exception to send when something goes wrong executing an action
140     *
141     */
142    public static class ActionException extends RuntimeException {
143        public ActionException(Exception ex) {
144            super(ex);
145        }
146        public ActionException(String message) {
147            super(message);
148        }
149    }
150
151    /**
152     * Base class for all actions that can be performed on an
153     * inflated view.
154     *
155     *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
156     */
157    private abstract static class Action implements Parcelable {
158        public abstract void apply(View root, ViewGroup rootParent) throws ActionException;
159
160        public int describeContents() {
161            return 0;
162        }
163
164        /**
165         * Overridden by each class to report on it's own memory usage
166         */
167        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
168            // We currently only calculate Bitmap memory usage, so by default, don't do anything
169            // here
170            return;
171        }
172
173        protected boolean startIntentSafely(View view, PendingIntent pendingIntent,
174                Intent fillInIntent) {
175            try {
176                // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
177                Context context = view.getContext();
178                ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view,
179                        0, 0,
180                        view.getMeasuredWidth(), view.getMeasuredHeight());
181                context.startIntentSender(
182                        pendingIntent.getIntentSender(), fillInIntent,
183                        Intent.FLAG_ACTIVITY_NEW_TASK,
184                        Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle());
185            } catch (IntentSender.SendIntentException e) {
186                android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
187                return false;
188            } catch (Exception e) {
189                android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " +
190                        "unknown exception: ", e);
191                return false;
192            }
193            return true;
194        }
195
196        public void setBitmapCache(BitmapCache bitmapCache) {
197            // Do nothing
198        }
199    }
200
201    private class SetEmptyView extends Action {
202        int viewId;
203        int emptyViewId;
204
205        public final static int TAG = 6;
206
207        SetEmptyView(int viewId, int emptyViewId) {
208            this.viewId = viewId;
209            this.emptyViewId = emptyViewId;
210        }
211
212        SetEmptyView(Parcel in) {
213            this.viewId = in.readInt();
214            this.emptyViewId = in.readInt();
215        }
216
217        public void writeToParcel(Parcel out, int flags) {
218            out.writeInt(TAG);
219            out.writeInt(this.viewId);
220            out.writeInt(this.emptyViewId);
221        }
222
223        @Override
224        public void apply(View root, ViewGroup rootParent) {
225            final View view = root.findViewById(viewId);
226            if (!(view instanceof AdapterView<?>)) return;
227
228            AdapterView<?> adapterView = (AdapterView<?>) view;
229
230            final View emptyView = root.findViewById(emptyViewId);
231            if (emptyView == null) return;
232
233            adapterView.setEmptyView(emptyView);
234        }
235    }
236
237    private class SetOnClickFillInIntent extends Action {
238        public SetOnClickFillInIntent(int id, Intent fillInIntent) {
239            this.viewId = id;
240            this.fillInIntent = fillInIntent;
241        }
242
243        public SetOnClickFillInIntent(Parcel parcel) {
244            viewId = parcel.readInt();
245            fillInIntent = Intent.CREATOR.createFromParcel(parcel);
246        }
247
248        public void writeToParcel(Parcel dest, int flags) {
249            dest.writeInt(TAG);
250            dest.writeInt(viewId);
251            fillInIntent.writeToParcel(dest, 0 /* no flags */);
252        }
253
254        @Override
255        public void apply(View root, ViewGroup rootParent) {
256            final View target = root.findViewById(viewId);
257            if (target == null) return;
258
259            if (!mIsWidgetCollectionChild) {
260                Log.e("RemoteViews", "The method setOnClickFillInIntent is available " +
261                        "only from RemoteViewsFactory (ie. on collection items).");
262                return;
263            }
264            if (target == root) {
265                target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent);
266            } else if (target != null && fillInIntent != null) {
267                OnClickListener listener = new OnClickListener() {
268                    public void onClick(View v) {
269                        // Insure that this view is a child of an AdapterView
270                        View parent = (View) v.getParent();
271                        while (!(parent instanceof AdapterView<?>)
272                                && !(parent instanceof AppWidgetHostView)) {
273                            parent = (View) parent.getParent();
274                        }
275
276                        if (parent instanceof AppWidgetHostView) {
277                            // Somehow they've managed to get this far without having
278                            // and AdapterView as a parent.
279                            Log.e("RemoteViews", "Collection item doesn't have AdapterView parent");
280                            return;
281                        }
282
283                        // Insure that a template pending intent has been set on an ancestor
284                        if (!(parent.getTag() instanceof PendingIntent)) {
285                            Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" +
286                                    " calling setPendingIntentTemplate on parent.");
287                            return;
288                        }
289
290                        PendingIntent pendingIntent = (PendingIntent) parent.getTag();
291
292                        final float appScale = v.getContext().getResources()
293                                .getCompatibilityInfo().applicationScale;
294                        final int[] pos = new int[2];
295                        v.getLocationOnScreen(pos);
296
297                        final Rect rect = new Rect();
298                        rect.left = (int) (pos[0] * appScale + 0.5f);
299                        rect.top = (int) (pos[1] * appScale + 0.5f);
300                        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
301                        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
302
303                        fillInIntent.setSourceBounds(rect);
304                        startIntentSafely(v, pendingIntent, fillInIntent);
305                    }
306
307                };
308                target.setOnClickListener(listener);
309            }
310        }
311
312        int viewId;
313        Intent fillInIntent;
314
315        public final static int TAG = 9;
316    }
317
318    private class SetPendingIntentTemplate extends Action {
319        public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
320            this.viewId = id;
321            this.pendingIntentTemplate = pendingIntentTemplate;
322        }
323
324        public SetPendingIntentTemplate(Parcel parcel) {
325            viewId = parcel.readInt();
326            pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
327        }
328
329        public void writeToParcel(Parcel dest, int flags) {
330            dest.writeInt(TAG);
331            dest.writeInt(viewId);
332            pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
333        }
334
335        @Override
336        public void apply(View root, ViewGroup rootParent) {
337            final View target = root.findViewById(viewId);
338            if (target == null) return;
339
340            // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense
341            if (target instanceof AdapterView<?>) {
342                AdapterView<?> av = (AdapterView<?>) target;
343                // The PendingIntent template is stored in the view's tag.
344                OnItemClickListener listener = new OnItemClickListener() {
345                    public void onItemClick(AdapterView<?> parent, View view,
346                            int position, long id) {
347                        // The view should be a frame layout
348                        if (view instanceof ViewGroup) {
349                            ViewGroup vg = (ViewGroup) view;
350
351                            // AdapterViews contain their children in a frame
352                            // so we need to go one layer deeper here.
353                            if (parent instanceof AdapterViewAnimator) {
354                                vg = (ViewGroup) vg.getChildAt(0);
355                            }
356                            if (vg == null) return;
357
358                            Intent fillInIntent = null;
359                            int childCount = vg.getChildCount();
360                            for (int i = 0; i < childCount; i++) {
361                                Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent);
362                                if (tag instanceof Intent) {
363                                    fillInIntent = (Intent) tag;
364                                    break;
365                                }
366                            }
367                            if (fillInIntent == null) return;
368
369                            final float appScale = view.getContext().getResources()
370                                    .getCompatibilityInfo().applicationScale;
371                            final int[] pos = new int[2];
372                            view.getLocationOnScreen(pos);
373
374                            final Rect rect = new Rect();
375                            rect.left = (int) (pos[0] * appScale + 0.5f);
376                            rect.top = (int) (pos[1] * appScale + 0.5f);
377                            rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f);
378                            rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f);
379
380                            final Intent intent = new Intent();
381                            intent.setSourceBounds(rect);
382                            startIntentSafely(view, pendingIntentTemplate, fillInIntent);
383                        }
384                    }
385                };
386                av.setOnItemClickListener(listener);
387                av.setTag(pendingIntentTemplate);
388            } else {
389                Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" +
390                        "an AdapterView (id: " + viewId + ")");
391                return;
392            }
393        }
394
395        int viewId;
396        PendingIntent pendingIntentTemplate;
397
398        public final static int TAG = 8;
399    }
400
401    private class SetRemoteViewsAdapterIntent extends Action {
402        public SetRemoteViewsAdapterIntent(int id, Intent intent) {
403            this.viewId = id;
404            this.intent = intent;
405        }
406
407        public SetRemoteViewsAdapterIntent(Parcel parcel) {
408            viewId = parcel.readInt();
409            intent = Intent.CREATOR.createFromParcel(parcel);
410        }
411
412        public void writeToParcel(Parcel dest, int flags) {
413            dest.writeInt(TAG);
414            dest.writeInt(viewId);
415            intent.writeToParcel(dest, flags);
416        }
417
418        @Override
419        public void apply(View root, ViewGroup rootParent) {
420            final View target = root.findViewById(viewId);
421            if (target == null) return;
422
423            // Ensure that we are applying to an AppWidget root
424            if (!(rootParent instanceof AppWidgetHostView)) {
425                Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " +
426                        "AppWidgets (root id: " + viewId + ")");
427                return;
428            }
429            // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
430            if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
431                Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " +
432                        "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
433                return;
434            }
435
436            // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
437            // RemoteViewsService
438            AppWidgetHostView host = (AppWidgetHostView) rootParent;
439            intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
440            if (target instanceof AbsListView) {
441                AbsListView v = (AbsListView) target;
442                v.setRemoteViewsAdapter(intent);
443            } else if (target instanceof AdapterViewAnimator) {
444                AdapterViewAnimator v = (AdapterViewAnimator) target;
445                v.setRemoteViewsAdapter(intent);
446            }
447        }
448
449        int viewId;
450        Intent intent;
451
452        public final static int TAG = 10;
453    }
454
455    /**
456     * Equivalent to calling
457     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
458     * to launch the provided {@link PendingIntent}.
459     */
460    private class SetOnClickPendingIntent extends Action {
461        public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
462            this.viewId = id;
463            this.pendingIntent = pendingIntent;
464        }
465
466        public SetOnClickPendingIntent(Parcel parcel) {
467            viewId = parcel.readInt();
468
469            // We check a flag to determine if the parcel contains a PendingIntent.
470            if (parcel.readInt() != 0) {
471                pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
472            }
473        }
474
475        public void writeToParcel(Parcel dest, int flags) {
476            dest.writeInt(TAG);
477            dest.writeInt(viewId);
478
479            // We use a flag to indicate whether the parcel contains a valid object.
480            dest.writeInt(pendingIntent != null ? 1 : 0);
481            if (pendingIntent != null) {
482                pendingIntent.writeToParcel(dest, 0 /* no flags */);
483            }
484        }
485
486        @Override
487        public void apply(View root, ViewGroup rootParent) {
488            final View target = root.findViewById(viewId);
489            if (target == null) return;
490
491            // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
492            // sense, do they mean to set a PendingIntent template for the AdapterView's children?
493            if (mIsWidgetCollectionChild) {
494                Log.e("RemoteViews", "Cannot setOnClickPendingIntent for collection item " +
495                        "(id: " + viewId + ")");
496                ApplicationInfo appInfo = root.getContext().getApplicationInfo();
497
498                // We let this slide for HC and ICS so as to not break compatibility. It should have
499                // been disabled from the outset, but was left open by accident.
500                if (appInfo != null &&
501                        appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
502                    return;
503                }
504            }
505
506            if (target != null) {
507                // If the pendingIntent is null, we clear the onClickListener
508                OnClickListener listener = null;
509                if (pendingIntent != null) {
510                    listener = new OnClickListener() {
511                        public void onClick(View v) {
512                            // Find target view location in screen coordinates and
513                            // fill into PendingIntent before sending.
514                            final float appScale = v.getContext().getResources()
515                                    .getCompatibilityInfo().applicationScale;
516                            final int[] pos = new int[2];
517                            v.getLocationOnScreen(pos);
518
519                            final Rect rect = new Rect();
520                            rect.left = (int) (pos[0] * appScale + 0.5f);
521                            rect.top = (int) (pos[1] * appScale + 0.5f);
522                            rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
523                            rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
524
525                            final Intent intent = new Intent();
526                            intent.setSourceBounds(rect);
527                            startIntentSafely(v, pendingIntent, intent);
528                        }
529                    };
530                }
531                target.setOnClickListener(listener);
532            }
533        }
534
535        int viewId;
536        PendingIntent pendingIntent;
537
538        public final static int TAG = 1;
539    }
540
541    /**
542     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
543     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
544     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
545     * <p>
546     * These operations will be performed on the {@link Drawable} returned by the
547     * target {@link View#getBackground()} by default.  If targetBackground is false,
548     * we assume the target is an {@link ImageView} and try applying the operations
549     * to {@link ImageView#getDrawable()}.
550     * <p>
551     * You can omit specific calls by marking their values with null or -1.
552     */
553    private class SetDrawableParameters extends Action {
554        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
555                int colorFilter, PorterDuff.Mode mode, int level) {
556            this.viewId = id;
557            this.targetBackground = targetBackground;
558            this.alpha = alpha;
559            this.colorFilter = colorFilter;
560            this.filterMode = mode;
561            this.level = level;
562        }
563
564        public SetDrawableParameters(Parcel parcel) {
565            viewId = parcel.readInt();
566            targetBackground = parcel.readInt() != 0;
567            alpha = parcel.readInt();
568            colorFilter = parcel.readInt();
569            boolean hasMode = parcel.readInt() != 0;
570            if (hasMode) {
571                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
572            } else {
573                filterMode = null;
574            }
575            level = parcel.readInt();
576        }
577
578        public void writeToParcel(Parcel dest, int flags) {
579            dest.writeInt(TAG);
580            dest.writeInt(viewId);
581            dest.writeInt(targetBackground ? 1 : 0);
582            dest.writeInt(alpha);
583            dest.writeInt(colorFilter);
584            if (filterMode != null) {
585                dest.writeInt(1);
586                dest.writeString(filterMode.toString());
587            } else {
588                dest.writeInt(0);
589            }
590            dest.writeInt(level);
591        }
592
593        @Override
594        public void apply(View root, ViewGroup rootParent) {
595            final View target = root.findViewById(viewId);
596            if (target == null) return;
597
598            // Pick the correct drawable to modify for this view
599            Drawable targetDrawable = null;
600            if (targetBackground) {
601                targetDrawable = target.getBackground();
602            } else if (target instanceof ImageView) {
603                ImageView imageView = (ImageView) target;
604                targetDrawable = imageView.getDrawable();
605            }
606
607            if (targetDrawable != null) {
608                // Perform modifications only if values are set correctly
609                if (alpha != -1) {
610                    targetDrawable.setAlpha(alpha);
611                }
612                if (colorFilter != -1 && filterMode != null) {
613                    targetDrawable.setColorFilter(colorFilter, filterMode);
614                }
615                if (level != -1) {
616                    targetDrawable.setLevel(level);
617                }
618            }
619        }
620
621        int viewId;
622        boolean targetBackground;
623        int alpha;
624        int colorFilter;
625        PorterDuff.Mode filterMode;
626        int level;
627
628        public final static int TAG = 3;
629    }
630
631    private class ReflectionActionWithoutParams extends Action {
632        int viewId;
633        String methodName;
634
635        public final static int TAG = 5;
636
637        ReflectionActionWithoutParams(int viewId, String methodName) {
638            this.viewId = viewId;
639            this.methodName = methodName;
640        }
641
642        ReflectionActionWithoutParams(Parcel in) {
643            this.viewId = in.readInt();
644            this.methodName = in.readString();
645        }
646
647        public void writeToParcel(Parcel out, int flags) {
648            out.writeInt(TAG);
649            out.writeInt(this.viewId);
650            out.writeString(this.methodName);
651        }
652
653        @Override
654        public void apply(View root, ViewGroup rootParent) {
655            final View view = root.findViewById(viewId);
656            if (view == null) return;
657
658            Class klass = view.getClass();
659            Method method;
660            try {
661                method = klass.getMethod(this.methodName);
662            } catch (NoSuchMethodException ex) {
663                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
664                        + this.methodName + "()");
665            }
666
667            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
668                throw new ActionException("view: " + klass.getName()
669                        + " can't use method with RemoteViews: "
670                        + this.methodName + "()");
671            }
672
673            try {
674                //noinspection ConstantIfStatement
675                if (false) {
676                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
677                        + this.methodName + "()");
678                }
679                method.invoke(view);
680            } catch (Exception ex) {
681                throw new ActionException(ex);
682            }
683        }
684    }
685
686    private static class BitmapCache {
687        ArrayList<Bitmap> mBitmaps;
688
689        public BitmapCache() {
690            mBitmaps = new ArrayList<Bitmap>();
691        }
692
693        public BitmapCache(Parcel source) {
694            int count = source.readInt();
695            mBitmaps = new ArrayList<Bitmap>();
696            for (int i = 0; i < count; i++) {
697                Bitmap b = Bitmap.CREATOR.createFromParcel(source);
698                mBitmaps.add(b);
699            }
700        }
701
702        public int getBitmapId(Bitmap b) {
703            if (b == null) {
704                return -1;
705            } else {
706                if (mBitmaps.contains(b)) {
707                    return mBitmaps.indexOf(b);
708                } else {
709                    mBitmaps.add(b);
710                    return (mBitmaps.size() - 1);
711                }
712            }
713        }
714
715        public Bitmap getBitmapForId(int id) {
716            if (id == -1 || id >= mBitmaps.size()) {
717                return null;
718            } else {
719                return mBitmaps.get(id);
720            }
721        }
722
723        public void writeBitmapsToParcel(Parcel dest, int flags) {
724            int count = mBitmaps.size();
725            dest.writeInt(count);
726            for (int i = 0; i < count; i++) {
727                mBitmaps.get(i).writeToParcel(dest, flags);
728            }
729        }
730
731        public void assimilate(BitmapCache bitmapCache) {
732            ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps;
733            int count = bitmapsToBeAdded.size();
734            for (int i = 0; i < count; i++) {
735                Bitmap b = bitmapsToBeAdded.get(i);
736                if (!mBitmaps.contains(b)) {
737                    mBitmaps.add(b);
738                }
739            }
740        }
741
742        public void addBitmapMemory(MemoryUsageCounter memoryCounter) {
743            for (int i = 0; i < mBitmaps.size(); i++) {
744                memoryCounter.addBitmapMemory(mBitmaps.get(i));
745            }
746        }
747    }
748
749    private class BitmapReflectionAction extends Action {
750        int bitmapId;
751        int viewId;
752        Bitmap bitmap;
753        String methodName;
754
755        BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {
756            this.bitmap = bitmap;
757            this.viewId = viewId;
758            this.methodName = methodName;
759            bitmapId = mBitmapCache.getBitmapId(bitmap);
760        }
761
762        BitmapReflectionAction(Parcel in) {
763            viewId = in.readInt();
764            methodName = in.readString();
765            bitmapId = in.readInt();
766            bitmap = mBitmapCache.getBitmapForId(bitmapId);
767        }
768
769        @Override
770        public void writeToParcel(Parcel dest, int flags) {
771            dest.writeInt(TAG);
772            dest.writeInt(viewId);
773            dest.writeString(methodName);
774            dest.writeInt(bitmapId);
775        }
776
777        @Override
778        public void apply(View root, ViewGroup rootParent) throws ActionException {
779            ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
780                    bitmap);
781            ra.apply(root, rootParent);
782        }
783
784        @Override
785        public void setBitmapCache(BitmapCache bitmapCache) {
786            bitmapId = bitmapCache.getBitmapId(bitmap);
787        }
788
789        public final static int TAG = 12;
790    }
791
792    /**
793     * Base class for the reflection actions.
794     */
795    private class ReflectionAction extends Action {
796        static final int TAG = 2;
797
798        static final int BOOLEAN = 1;
799        static final int BYTE = 2;
800        static final int SHORT = 3;
801        static final int INT = 4;
802        static final int LONG = 5;
803        static final int FLOAT = 6;
804        static final int DOUBLE = 7;
805        static final int CHAR = 8;
806        static final int STRING = 9;
807        static final int CHAR_SEQUENCE = 10;
808        static final int URI = 11;
809        static final int BITMAP = 12;
810        static final int BUNDLE = 13;
811        static final int INTENT = 14;
812
813        int viewId;
814        String methodName;
815        int type;
816        Object value;
817
818        ReflectionAction(int viewId, String methodName, int type, Object value) {
819            this.viewId = viewId;
820            this.methodName = methodName;
821            this.type = type;
822            this.value = value;
823        }
824
825        ReflectionAction(Parcel in) {
826            this.viewId = in.readInt();
827            this.methodName = in.readString();
828            this.type = in.readInt();
829            //noinspection ConstantIfStatement
830            if (false) {
831                Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
832                        + " methodName=" + this.methodName + " type=" + this.type);
833            }
834
835            // For some values that may have been null, we first check a flag to see if they were
836            // written to the parcel.
837            switch (this.type) {
838                case BOOLEAN:
839                    this.value = in.readInt() != 0;
840                    break;
841                case BYTE:
842                    this.value = in.readByte();
843                    break;
844                case SHORT:
845                    this.value = (short)in.readInt();
846                    break;
847                case INT:
848                    this.value = in.readInt();
849                    break;
850                case LONG:
851                    this.value = in.readLong();
852                    break;
853                case FLOAT:
854                    this.value = in.readFloat();
855                    break;
856                case DOUBLE:
857                    this.value = in.readDouble();
858                    break;
859                case CHAR:
860                    this.value = (char)in.readInt();
861                    break;
862                case STRING:
863                    this.value = in.readString();
864                    break;
865                case CHAR_SEQUENCE:
866                    this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
867                    break;
868                case URI:
869                    if (in.readInt() != 0) {
870                        this.value = Uri.CREATOR.createFromParcel(in);
871                    }
872                    break;
873                case BITMAP:
874                    if (in.readInt() != 0) {
875                        this.value = Bitmap.CREATOR.createFromParcel(in);
876                    }
877                    break;
878                case BUNDLE:
879                    this.value = in.readBundle();
880                    break;
881                case INTENT:
882                    if (in.readInt() != 0) {
883                        this.value = Intent.CREATOR.createFromParcel(in);
884                    }
885                    break;
886                default:
887                    break;
888            }
889        }
890
891        public void writeToParcel(Parcel out, int flags) {
892            out.writeInt(TAG);
893            out.writeInt(this.viewId);
894            out.writeString(this.methodName);
895            out.writeInt(this.type);
896            //noinspection ConstantIfStatement
897            if (false) {
898                Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
899                        + " methodName=" + this.methodName + " type=" + this.type);
900            }
901
902            // For some values which are null, we record an integer flag to indicate whether
903            // we have written a valid value to the parcel.
904            switch (this.type) {
905                case BOOLEAN:
906                    out.writeInt((Boolean) this.value ? 1 : 0);
907                    break;
908                case BYTE:
909                    out.writeByte((Byte) this.value);
910                    break;
911                case SHORT:
912                    out.writeInt((Short) this.value);
913                    break;
914                case INT:
915                    out.writeInt((Integer) this.value);
916                    break;
917                case LONG:
918                    out.writeLong((Long) this.value);
919                    break;
920                case FLOAT:
921                    out.writeFloat((Float) this.value);
922                    break;
923                case DOUBLE:
924                    out.writeDouble((Double) this.value);
925                    break;
926                case CHAR:
927                    out.writeInt((int)((Character)this.value).charValue());
928                    break;
929                case STRING:
930                    out.writeString((String)this.value);
931                    break;
932                case CHAR_SEQUENCE:
933                    TextUtils.writeToParcel((CharSequence)this.value, out, flags);
934                    break;
935                case URI:
936                    out.writeInt(this.value != null ? 1 : 0);
937                    if (this.value != null) {
938                        ((Uri)this.value).writeToParcel(out, flags);
939                    }
940                    break;
941                case BITMAP:
942                    out.writeInt(this.value != null ? 1 : 0);
943                    if (this.value != null) {
944                        ((Bitmap)this.value).writeToParcel(out, flags);
945                    }
946                    break;
947                case BUNDLE:
948                    out.writeBundle((Bundle) this.value);
949                    break;
950                case INTENT:
951                    out.writeInt(this.value != null ? 1 : 0);
952                    if (this.value != null) {
953                        ((Intent)this.value).writeToParcel(out, flags);
954                    }
955                    break;
956                default:
957                    break;
958            }
959        }
960
961        private Class getParameterType() {
962            switch (this.type) {
963                case BOOLEAN:
964                    return boolean.class;
965                case BYTE:
966                    return byte.class;
967                case SHORT:
968                    return short.class;
969                case INT:
970                    return int.class;
971                case LONG:
972                    return long.class;
973                case FLOAT:
974                    return float.class;
975                case DOUBLE:
976                    return double.class;
977                case CHAR:
978                    return char.class;
979                case STRING:
980                    return String.class;
981                case CHAR_SEQUENCE:
982                    return CharSequence.class;
983                case URI:
984                    return Uri.class;
985                case BITMAP:
986                    return Bitmap.class;
987                case BUNDLE:
988                    return Bundle.class;
989                case INTENT:
990                    return Intent.class;
991                default:
992                    return null;
993            }
994        }
995
996        @Override
997        public void apply(View root, ViewGroup rootParent) {
998            final View view = root.findViewById(viewId);
999            if (view == null) return;
1000
1001            Class param = getParameterType();
1002            if (param == null) {
1003                throw new ActionException("bad type: " + this.type);
1004            }
1005
1006            Class klass = view.getClass();
1007            Method method;
1008            try {
1009                method = klass.getMethod(this.methodName, getParameterType());
1010            }
1011            catch (NoSuchMethodException ex) {
1012                throw new ActionException("view: " + klass.getName() + " doesn't have method: "
1013                        + this.methodName + "(" + param.getName() + ")");
1014            }
1015
1016            if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
1017                throw new ActionException("view: " + klass.getName()
1018                        + " can't use method with RemoteViews: "
1019                        + this.methodName + "(" + param.getName() + ")");
1020            }
1021
1022            try {
1023                //noinspection ConstantIfStatement
1024                if (false) {
1025                    Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
1026                        + this.methodName + "(" + param.getName() + ") with "
1027                        + (this.value == null ? "null" : this.value.getClass().getName()));
1028                }
1029                method.invoke(view, this.value);
1030            }
1031            catch (Exception ex) {
1032                throw new ActionException(ex);
1033            }
1034        }
1035
1036        @Override
1037        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
1038            // We currently only calculate Bitmap memory usage
1039            switch (this.type) {
1040                case BITMAP:
1041                    if (this.value != null) {
1042                        final Bitmap b = (Bitmap) this.value;
1043                        counter.addBitmapMemory(b);
1044                    }
1045                    break;
1046                default:
1047                    break;
1048            }
1049        }
1050    }
1051
1052    private void configureRemoteViewsAsChild(RemoteViews rv) {
1053        mBitmapCache.assimilate(rv.mBitmapCache);
1054        rv.setBitmapCache(mBitmapCache);
1055        rv.setNotRoot();
1056    }
1057
1058    void setNotRoot() {
1059        mIsRoot = false;
1060    }
1061
1062    /**
1063     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
1064     * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
1065     * when null. This allows users to build "nested" {@link RemoteViews}.
1066     */
1067    private class ViewGroupAction extends Action {
1068        public ViewGroupAction(int viewId, RemoteViews nestedViews) {
1069            this.viewId = viewId;
1070            this.nestedViews = nestedViews;
1071            if (nestedViews != null) {
1072                configureRemoteViewsAsChild(nestedViews);
1073            }
1074        }
1075
1076        public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) {
1077            viewId = parcel.readInt();
1078            boolean nestedViewsNull = parcel.readInt() == 0;
1079            if (!nestedViewsNull) {
1080                nestedViews = new RemoteViews(parcel, bitmapCache);
1081            } else {
1082                nestedViews = null;
1083            }
1084        }
1085
1086        public void writeToParcel(Parcel dest, int flags) {
1087            dest.writeInt(TAG);
1088            dest.writeInt(viewId);
1089            if (nestedViews != null) {
1090                dest.writeInt(1);
1091                nestedViews.writeToParcel(dest, flags);
1092            } else {
1093                // signifies null
1094                dest.writeInt(0);
1095            }
1096        }
1097
1098        @Override
1099        public void apply(View root, ViewGroup rootParent) {
1100            final Context context = root.getContext();
1101            final ViewGroup target = (ViewGroup) root.findViewById(viewId);
1102            if (target == null) return;
1103            if (nestedViews != null) {
1104                // Inflate nested views and add as children
1105                target.addView(nestedViews.apply(context, target));
1106            } else {
1107                // Clear all children when nested views omitted
1108                target.removeAllViews();
1109            }
1110        }
1111
1112        @Override
1113        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
1114            if (nestedViews != null) {
1115                counter.increment(nestedViews.estimateMemoryUsage());
1116            }
1117        }
1118
1119        @Override
1120        public void setBitmapCache(BitmapCache bitmapCache) {
1121            if (nestedViews != null) {
1122                nestedViews.setBitmapCache(bitmapCache);
1123            }
1124        }
1125
1126        int viewId;
1127        RemoteViews nestedViews;
1128
1129        public final static int TAG = 4;
1130    }
1131
1132    /**
1133     * Helper action to set compound drawables on a TextView. Supports relative
1134     * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
1135     */
1136    private class TextViewDrawableAction extends Action {
1137        public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) {
1138            this.viewId = viewId;
1139            this.isRelative = isRelative;
1140            this.d1 = d1;
1141            this.d2 = d2;
1142            this.d3 = d3;
1143            this.d4 = d4;
1144        }
1145
1146        public TextViewDrawableAction(Parcel parcel) {
1147            viewId = parcel.readInt();
1148            isRelative = (parcel.readInt() != 0);
1149            d1 = parcel.readInt();
1150            d2 = parcel.readInt();
1151            d3 = parcel.readInt();
1152            d4 = parcel.readInt();
1153        }
1154
1155        public void writeToParcel(Parcel dest, int flags) {
1156            dest.writeInt(TAG);
1157            dest.writeInt(viewId);
1158            dest.writeInt(isRelative ? 1 : 0);
1159            dest.writeInt(d1);
1160            dest.writeInt(d2);
1161            dest.writeInt(d3);
1162            dest.writeInt(d4);
1163        }
1164
1165        @Override
1166        public void apply(View root, ViewGroup rootParent) {
1167            final Context context = root.getContext();
1168            final TextView target = (TextView) root.findViewById(viewId);
1169            if (target == null) return;
1170            if (isRelative) {
1171                target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
1172            } else {
1173                target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
1174            }
1175        }
1176
1177        int viewId;
1178        boolean isRelative = false;
1179        int d1, d2, d3, d4;
1180
1181        public final static int TAG = 11;
1182    }
1183
1184    /**
1185     * Simple class used to keep track of memory usage in a RemoteViews.
1186     *
1187     */
1188    private class MemoryUsageCounter {
1189        public void clear() {
1190            mMemoryUsage = 0;
1191        }
1192
1193        public void increment(int numBytes) {
1194            mMemoryUsage += numBytes;
1195        }
1196
1197        public int getMemoryUsage() {
1198            return mMemoryUsage;
1199        }
1200
1201        public void addBitmapMemory(Bitmap b) {
1202            final Bitmap.Config c = b.getConfig();
1203            // If we don't know, be pessimistic and assume 4
1204            int bpp = 4;
1205            if (c != null) {
1206                switch (c) {
1207                case ALPHA_8:
1208                    bpp = 1;
1209                    break;
1210                case RGB_565:
1211                case ARGB_4444:
1212                    bpp = 2;
1213                    break;
1214                case ARGB_8888:
1215                    bpp = 4;
1216                    break;
1217                }
1218            }
1219            increment(b.getWidth() * b.getHeight() * bpp);
1220        }
1221
1222        int mMemoryUsage;
1223    }
1224
1225    /**
1226     * Create a new RemoteViews object that will display the views contained
1227     * in the specified layout file.
1228     *
1229     * @param packageName Name of the package that contains the layout resource
1230     * @param layoutId The id of the layout resource
1231     */
1232    public RemoteViews(String packageName, int layoutId) {
1233        mPackage = packageName;
1234        mLayoutId = layoutId;
1235        mBitmapCache = new BitmapCache();
1236
1237        // setup the memory usage statistics
1238        mMemoryUsageCounter = new MemoryUsageCounter();
1239        recalculateMemoryUsage();
1240    }
1241
1242    private boolean hasLandscapeAndPortraitLayouts() {
1243        return (mLandscape != null) && (mPortrait != null);
1244    }
1245
1246     /**
1247     * Create a new RemoteViews object that will inflate as the specified
1248     * landspace or portrait RemoteViews, depending on the current configuration.
1249     *
1250     * @param landscape The RemoteViews to inflate in landscape configuration
1251     * @param portrait The RemoteViews to inflate in portrait configuration
1252     */
1253    public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
1254        if (landscape == null || portrait == null) {
1255            throw new RuntimeException("Both RemoteViews must be non-null");
1256        }
1257        if (landscape.getPackage().compareTo(portrait.getPackage()) != 0) {
1258            throw new RuntimeException("Both RemoteViews must share the same package");
1259        }
1260        mPackage = portrait.getPackage();
1261        mLayoutId = portrait.getLayoutId();
1262
1263        mLandscape = landscape;
1264        mPortrait = portrait;
1265
1266        // setup the memory usage statistics
1267        mMemoryUsageCounter = new MemoryUsageCounter();
1268
1269        mBitmapCache = new BitmapCache();
1270        configureRemoteViewsAsChild(landscape);
1271        configureRemoteViewsAsChild(portrait);
1272
1273        recalculateMemoryUsage();
1274    }
1275
1276    /**
1277     * Reads a RemoteViews object from a parcel.
1278     *
1279     * @param parcel
1280     */
1281    public RemoteViews(Parcel parcel) {
1282        this(parcel, null);
1283    }
1284
1285    private RemoteViews(Parcel parcel, BitmapCache bitmapCache) {
1286        int mode = parcel.readInt();
1287
1288        // We only store a bitmap cache in the root of the RemoteViews.
1289        if (bitmapCache == null) {
1290            mBitmapCache = new BitmapCache(parcel);
1291        } else {
1292            setBitmapCache(bitmapCache);
1293            setNotRoot();
1294        }
1295
1296        if (mode == MODE_NORMAL) {
1297            mPackage = parcel.readString();
1298            mLayoutId = parcel.readInt();
1299            mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false;
1300
1301            int count = parcel.readInt();
1302            if (count > 0) {
1303                mActions = new ArrayList<Action>(count);
1304                for (int i=0; i<count; i++) {
1305                    int tag = parcel.readInt();
1306                    switch (tag) {
1307                    case SetOnClickPendingIntent.TAG:
1308                        mActions.add(new SetOnClickPendingIntent(parcel));
1309                        break;
1310                    case SetDrawableParameters.TAG:
1311                        mActions.add(new SetDrawableParameters(parcel));
1312                        break;
1313                    case ReflectionAction.TAG:
1314                        mActions.add(new ReflectionAction(parcel));
1315                        break;
1316                    case ViewGroupAction.TAG:
1317                        mActions.add(new ViewGroupAction(parcel, mBitmapCache));
1318                        break;
1319                    case ReflectionActionWithoutParams.TAG:
1320                        mActions.add(new ReflectionActionWithoutParams(parcel));
1321                        break;
1322                    case SetEmptyView.TAG:
1323                        mActions.add(new SetEmptyView(parcel));
1324                        break;
1325                    case SetPendingIntentTemplate.TAG:
1326                        mActions.add(new SetPendingIntentTemplate(parcel));
1327                        break;
1328                    case SetOnClickFillInIntent.TAG:
1329                        mActions.add(new SetOnClickFillInIntent(parcel));
1330                        break;
1331                    case SetRemoteViewsAdapterIntent.TAG:
1332                        mActions.add(new SetRemoteViewsAdapterIntent(parcel));
1333                        break;
1334                    case TextViewDrawableAction.TAG:
1335                        mActions.add(new TextViewDrawableAction(parcel));
1336                        break;
1337                    case BitmapReflectionAction.TAG:
1338                        mActions.add(new BitmapReflectionAction(parcel));
1339                        break;
1340                    default:
1341                        throw new ActionException("Tag " + tag + " not found");
1342                    }
1343                }
1344            }
1345        } else {
1346            // MODE_HAS_LANDSCAPE_AND_PORTRAIT
1347            mLandscape = new RemoteViews(parcel, mBitmapCache);
1348            mPortrait = new RemoteViews(parcel, mBitmapCache);
1349            mPackage = mPortrait.getPackage();
1350            mLayoutId = mPortrait.getLayoutId();
1351        }
1352
1353        // setup the memory usage statistics
1354        mMemoryUsageCounter = new MemoryUsageCounter();
1355        recalculateMemoryUsage();
1356    }
1357
1358    @Override
1359    public RemoteViews clone() {
1360        RemoteViews that;
1361        if (!hasLandscapeAndPortraitLayouts()) {
1362            that = new RemoteViews(mPackage, mLayoutId);
1363
1364            if (mActions != null) {
1365                that.mActions = (ArrayList<Action>)mActions.clone();
1366            }
1367        } else {
1368            RemoteViews land = mLandscape.clone();
1369            RemoteViews port = mPortrait.clone();
1370            that = new RemoteViews(land, port);
1371        }
1372        // update the memory usage stats of the cloned RemoteViews
1373        that.recalculateMemoryUsage();
1374        return that;
1375    }
1376
1377    public String getPackage() {
1378        return mPackage;
1379    }
1380
1381    /**
1382     * Reutrns the layout id of the root layout associated with this RemoteViews. In the case
1383     * that the RemoteViews has both a landscape and portrait root, this will return the layout
1384     * id associated with the portrait layout.
1385     *
1386     * @return the layout id.
1387     */
1388    public int getLayoutId() {
1389        return mLayoutId;
1390    }
1391
1392    /*
1393     * This flag indicates whether this RemoteViews object is being created from a
1394     * RemoteViewsService for use as a child of a widget collection. This flag is used
1395     * to determine whether or not certain features are available, in particular,
1396     * setting on click extras and setting on click pending intents. The former is enabled,
1397     * and the latter disabled when this flag is true.
1398     */
1399    void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
1400        mIsWidgetCollectionChild = isWidgetCollectionChild;
1401    }
1402
1403    /**
1404     * Updates the memory usage statistics.
1405     */
1406    private void recalculateMemoryUsage() {
1407        mMemoryUsageCounter.clear();
1408
1409        if (!hasLandscapeAndPortraitLayouts()) {
1410            // Accumulate the memory usage for each action
1411            if (mActions != null) {
1412                final int count = mActions.size();
1413                for (int i= 0; i < count; ++i) {
1414                    mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
1415                }
1416            }
1417            if (mIsRoot) {
1418                mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
1419            }
1420        } else {
1421            mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage());
1422            mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage());
1423            mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
1424        }
1425    }
1426
1427    /**
1428     * Recursively sets BitmapCache in the hierarchy and update the bitmap ids.
1429     */
1430    private void setBitmapCache(BitmapCache bitmapCache) {
1431        mBitmapCache = bitmapCache;
1432        if (!hasLandscapeAndPortraitLayouts()) {
1433            if (mActions != null) {
1434                final int count = mActions.size();
1435                for (int i= 0; i < count; ++i) {
1436                    mActions.get(i).setBitmapCache(bitmapCache);
1437                }
1438            }
1439        } else {
1440            mLandscape.setBitmapCache(bitmapCache);
1441            mPortrait.setBitmapCache(bitmapCache);
1442        }
1443    }
1444
1445    /**
1446     * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
1447     */
1448    /** @hide */
1449    public int estimateMemoryUsage() {
1450        return mMemoryUsageCounter.getMemoryUsage();
1451    }
1452
1453    /**
1454     * Add an action to be executed on the remote side when apply is called.
1455     *
1456     * @param a The action to add
1457     */
1458    private void addAction(Action a) {
1459        if (hasLandscapeAndPortraitLayouts()) {
1460            throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
1461                    " layouts cannot be modified. Instead, fully configure the landscape and" +
1462                    " portrait layouts individually before constructing the combined layout.");
1463        }
1464        if (mActions == null) {
1465            mActions = new ArrayList<Action>();
1466        }
1467        mActions.add(a);
1468
1469        // update the memory usage stats
1470        a.updateMemoryUsageEstimate(mMemoryUsageCounter);
1471    }
1472
1473    /**
1474     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
1475     * given {@link RemoteViews}. This allows users to build "nested"
1476     * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
1477     * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
1478     * children.
1479     *
1480     * @param viewId The id of the parent {@link ViewGroup} to add child into.
1481     * @param nestedView {@link RemoteViews} that describes the child.
1482     */
1483    public void addView(int viewId, RemoteViews nestedView) {
1484        addAction(new ViewGroupAction(viewId, nestedView));
1485    }
1486
1487    /**
1488     * Equivalent to calling {@link ViewGroup#removeAllViews()}.
1489     *
1490     * @param viewId The id of the parent {@link ViewGroup} to remove all
1491     *            children from.
1492     */
1493    public void removeAllViews(int viewId) {
1494        addAction(new ViewGroupAction(viewId, null));
1495    }
1496
1497    /**
1498     * Equivalent to calling {@link AdapterViewAnimator#showNext()}
1499     *
1500     * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
1501     */
1502    public void showNext(int viewId) {
1503        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
1504    }
1505
1506    /**
1507     * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
1508     *
1509     * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
1510     */
1511    public void showPrevious(int viewId) {
1512        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
1513    }
1514
1515    /**
1516     * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)}
1517     *
1518     * @param viewId The id of the view on which to call
1519     *               {@link AdapterViewAnimator#setDisplayedChild(int)}
1520     */
1521    public void setDisplayedChild(int viewId, int childIndex) {
1522        setInt(viewId, "setDisplayedChild", childIndex);
1523    }
1524
1525    /**
1526     * Equivalent to calling View.setVisibility
1527     *
1528     * @param viewId The id of the view whose visibility should change
1529     * @param visibility The new visibility for the view
1530     */
1531    public void setViewVisibility(int viewId, int visibility) {
1532        setInt(viewId, "setVisibility", visibility);
1533    }
1534
1535    /**
1536     * Equivalent to calling TextView.setText
1537     *
1538     * @param viewId The id of the view whose text should change
1539     * @param text The new text for the view
1540     */
1541    public void setTextViewText(int viewId, CharSequence text) {
1542        setCharSequence(viewId, "setText", text);
1543    }
1544
1545    /**
1546     * Equivalent to calling
1547     * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
1548     *
1549     * @param viewId The id of the view whose text should change
1550     * @param left The id of a drawable to place to the left of the text, or 0
1551     * @param top The id of a drawable to place above the text, or 0
1552     * @param right The id of a drawable to place to the right of the text, or 0
1553     * @param bottom The id of a drawable to place below the text, or 0
1554     */
1555    public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
1556        addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
1557    }
1558
1559    /**
1560     * @param viewId The id of the view whose text should change
1561     * @param start The id of a drawable to place before the text (relative to the
1562     * layout direction), or 0
1563     * @param top The id of a drawable to place above the text, or 0
1564     * @param end The id of a drawable to place after the text, or 0
1565     * @param bottom The id of a drawable to place below the text, or 0
1566     * @hide
1567     */
1568    public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) {
1569        addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
1570    }
1571
1572    /**
1573     * Equivalent to calling ImageView.setImageResource
1574     *
1575     * @param viewId The id of the view whose drawable should change
1576     * @param srcId The new resource id for the drawable
1577     */
1578    public void setImageViewResource(int viewId, int srcId) {
1579        setInt(viewId, "setImageResource", srcId);
1580    }
1581
1582    /**
1583     * Equivalent to calling ImageView.setImageURI
1584     *
1585     * @param viewId The id of the view whose drawable should change
1586     * @param uri The Uri for the image
1587     */
1588    public void setImageViewUri(int viewId, Uri uri) {
1589        setUri(viewId, "setImageURI", uri);
1590    }
1591
1592    /**
1593     * Equivalent to calling ImageView.setImageBitmap
1594     *
1595     * @param viewId The id of the view whose bitmap should change
1596     * @param bitmap The new Bitmap for the drawable
1597     */
1598    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
1599        setBitmap(viewId, "setImageBitmap", bitmap);
1600    }
1601
1602    /**
1603     * Equivalent to calling AdapterView.setEmptyView
1604     *
1605     * @param viewId The id of the view on which to set the empty view
1606     * @param emptyViewId The view id of the empty view
1607     */
1608    public void setEmptyView(int viewId, int emptyViewId) {
1609        addAction(new SetEmptyView(viewId, emptyViewId));
1610    }
1611
1612    /**
1613     * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
1614     * {@link Chronometer#setFormat Chronometer.setFormat},
1615     * and {@link Chronometer#start Chronometer.start()} or
1616     * {@link Chronometer#stop Chronometer.stop()}.
1617     *
1618     * @param viewId The id of the {@link Chronometer} to change
1619     * @param base The time at which the timer would have read 0:00.  This
1620     *             time should be based off of
1621     *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
1622     * @param format The Chronometer format string, or null to
1623     *               simply display the timer value.
1624     * @param started True if you want the clock to be started, false if not.
1625     */
1626    public void setChronometer(int viewId, long base, String format, boolean started) {
1627        setLong(viewId, "setBase", base);
1628        setString(viewId, "setFormat", format);
1629        setBoolean(viewId, "setStarted", started);
1630    }
1631
1632    /**
1633     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
1634     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
1635     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
1636     *
1637     * If indeterminate is true, then the values for max and progress are ignored.
1638     *
1639     * @param viewId The id of the {@link ProgressBar} to change
1640     * @param max The 100% value for the progress bar
1641     * @param progress The current value of the progress bar.
1642     * @param indeterminate True if the progress bar is indeterminate,
1643     *                false if not.
1644     */
1645    public void setProgressBar(int viewId, int max, int progress,
1646            boolean indeterminate) {
1647        setBoolean(viewId, "setIndeterminate", indeterminate);
1648        if (!indeterminate) {
1649            setInt(viewId, "setMax", max);
1650            setInt(viewId, "setProgress", progress);
1651        }
1652    }
1653
1654    /**
1655     * Equivalent to calling
1656     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
1657     * to launch the provided {@link PendingIntent}.
1658     *
1659     * When setting the on-click action of items within collections (eg. {@link ListView},
1660     * {@link StackView} etc.), this method will not work. Instead, use {@link
1661     * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
1662     * RemoteViews#setOnClickFillInIntent(int, Intent).
1663     *
1664     * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
1665     * @param pendingIntent The {@link PendingIntent} to send when user clicks
1666     */
1667    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
1668        addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
1669    }
1670
1671    /**
1672     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
1673     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
1674     * this method should be used to set a single PendingIntent template on the collection, and
1675     * individual items can differentiate their on-click behavior using
1676     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
1677     *
1678     * @param viewId The id of the collection who's children will use this PendingIntent template
1679     *          when clicked
1680     * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
1681     *          by a child of viewId and executed when that child is clicked
1682     */
1683    public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
1684        addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
1685    }
1686
1687    /**
1688     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
1689     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
1690     * a single PendingIntent template can be set on the collection, see {@link
1691     * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
1692     * action of a given item can be distinguished by setting a fillInIntent on that item. The
1693     * fillInIntent is then combined with the PendingIntent template in order to determine the final
1694     * intent which will be executed when the item is clicked. This works as follows: any fields
1695     * which are left blank in the PendingIntent template, but are provided by the fillInIntent
1696     * will be overwritten, and the resulting PendingIntent will be used.
1697     *
1698     *
1699     * of the PendingIntent template will then be filled in with the associated fields that are
1700     * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
1701     *
1702     * @param viewId The id of the view on which to set the fillInIntent
1703     * @param fillInIntent The intent which will be combined with the parent's PendingIntent
1704     *        in order to determine the on-click behavior of the view specified by viewId
1705     */
1706    public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
1707        addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
1708    }
1709
1710    /**
1711     * @hide
1712     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
1713     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
1714     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
1715     * view.
1716     * <p>
1717     * You can omit specific calls by marking their values with null or -1.
1718     *
1719     * @param viewId The id of the view that contains the target
1720     *            {@link Drawable}
1721     * @param targetBackground If true, apply these parameters to the
1722     *            {@link Drawable} returned by
1723     *            {@link android.view.View#getBackground()}. Otherwise, assume
1724     *            the target view is an {@link ImageView} and apply them to
1725     *            {@link ImageView#getDrawable()}.
1726     * @param alpha Specify an alpha value for the drawable, or -1 to leave
1727     *            unchanged.
1728     * @param colorFilter Specify a color for a
1729     *            {@link android.graphics.ColorFilter} for this drawable, or -1
1730     *            to leave unchanged.
1731     * @param mode Specify a PorterDuff mode for this drawable, or null to leave
1732     *            unchanged.
1733     * @param level Specify the level for the drawable, or -1 to leave
1734     *            unchanged.
1735     */
1736    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
1737            int colorFilter, PorterDuff.Mode mode, int level) {
1738        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
1739                colorFilter, mode, level));
1740    }
1741
1742    /**
1743     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
1744     *
1745     * @param viewId The id of the view whose text color should change
1746     * @param color Sets the text color for all the states (normal, selected,
1747     *            focused) to be this color.
1748     */
1749    public void setTextColor(int viewId, int color) {
1750        setInt(viewId, "setTextColor", color);
1751    }
1752
1753    /**
1754     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
1755     *
1756     * @param appWidgetId The id of the app widget which contains the specified view. (This
1757     *      parameter is ignored in this deprecated method)
1758     * @param viewId The id of the {@link AbsListView}
1759     * @param intent The intent of the service which will be
1760     *            providing data to the RemoteViewsAdapter
1761     * @deprecated This method has been deprecated. See
1762     *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
1763     */
1764    @Deprecated
1765    public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
1766        setRemoteAdapter(viewId, intent);
1767    }
1768
1769    /**
1770     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
1771     * Can only be used for App Widgets.
1772     *
1773     * @param viewId The id of the {@link AbsListView}
1774     * @param intent The intent of the service which will be
1775     *            providing data to the RemoteViewsAdapter
1776     */
1777    public void setRemoteAdapter(int viewId, Intent intent) {
1778        addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
1779    }
1780
1781    /**
1782     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
1783     *
1784     * @param viewId The id of the view to change
1785     * @param position Scroll to this adapter position
1786     */
1787    public void setScrollPosition(int viewId, int position) {
1788        setInt(viewId, "smoothScrollToPosition", position);
1789    }
1790
1791    /**
1792     * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
1793     *
1794     * @param viewId The id of the view to change
1795     * @param offset Scroll by this adapter position offset
1796     */
1797    public void setRelativeScrollPosition(int viewId, int offset) {
1798        setInt(viewId, "smoothScrollByOffset", offset);
1799    }
1800
1801    /**
1802     * Call a method taking one boolean on a view in the layout for this RemoteViews.
1803     *
1804     * @param viewId The id of the view on which to call the method.
1805     * @param methodName The name of the method to call.
1806     * @param value The value to pass to the method.
1807     */
1808    public void setBoolean(int viewId, String methodName, boolean value) {
1809        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
1810    }
1811
1812    /**
1813     * Call a method taking one byte on a view in the layout for this RemoteViews.
1814     *
1815     * @param viewId The id of the view on which to call the method.
1816     * @param methodName The name of the method to call.
1817     * @param value The value to pass to the method.
1818     */
1819    public void setByte(int viewId, String methodName, byte value) {
1820        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
1821    }
1822
1823    /**
1824     * Call a method taking one short on a view in the layout for this RemoteViews.
1825     *
1826     * @param viewId The id of the view on which to call the method.
1827     * @param methodName The name of the method to call.
1828     * @param value The value to pass to the method.
1829     */
1830    public void setShort(int viewId, String methodName, short value) {
1831        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
1832    }
1833
1834    /**
1835     * Call a method taking one int on a view in the layout for this RemoteViews.
1836     *
1837     * @param viewId The id of the view on which to call the method.
1838     * @param methodName The name of the method to call.
1839     * @param value The value to pass to the method.
1840     */
1841    public void setInt(int viewId, String methodName, int value) {
1842        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
1843    }
1844
1845    /**
1846     * Call a method taking one long on a view in the layout for this RemoteViews.
1847     *
1848     * @param viewId The id of the view on which to call the method.
1849     * @param methodName The name of the method to call.
1850     * @param value The value to pass to the method.
1851     */
1852    public void setLong(int viewId, String methodName, long value) {
1853        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
1854    }
1855
1856    /**
1857     * Call a method taking one float on a view in the layout for this RemoteViews.
1858     *
1859     * @param viewId The id of the view on which to call the method.
1860     * @param methodName The name of the method to call.
1861     * @param value The value to pass to the method.
1862     */
1863    public void setFloat(int viewId, String methodName, float value) {
1864        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
1865    }
1866
1867    /**
1868     * Call a method taking one double on a view in the layout for this RemoteViews.
1869     *
1870     * @param viewId The id of the view on which to call the method.
1871     * @param methodName The name of the method to call.
1872     * @param value The value to pass to the method.
1873     */
1874    public void setDouble(int viewId, String methodName, double value) {
1875        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
1876    }
1877
1878    /**
1879     * Call a method taking one char on a view in the layout for this RemoteViews.
1880     *
1881     * @param viewId The id of the view on which to call the method.
1882     * @param methodName The name of the method to call.
1883     * @param value The value to pass to the method.
1884     */
1885    public void setChar(int viewId, String methodName, char value) {
1886        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
1887    }
1888
1889    /**
1890     * Call a method taking one String on a view in the layout for this RemoteViews.
1891     *
1892     * @param viewId The id of the view on which to call the method.
1893     * @param methodName The name of the method to call.
1894     * @param value The value to pass to the method.
1895     */
1896    public void setString(int viewId, String methodName, String value) {
1897        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
1898    }
1899
1900    /**
1901     * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
1902     *
1903     * @param viewId The id of the view on which to call the method.
1904     * @param methodName The name of the method to call.
1905     * @param value The value to pass to the method.
1906     */
1907    public void setCharSequence(int viewId, String methodName, CharSequence value) {
1908        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
1909    }
1910
1911    /**
1912     * Call a method taking one Uri on a view in the layout for this RemoteViews.
1913     *
1914     * @param viewId The id of the view on which to call the method.
1915     * @param methodName The name of the method to call.
1916     * @param value The value to pass to the method.
1917     */
1918    public void setUri(int viewId, String methodName, Uri value) {
1919        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
1920    }
1921
1922    /**
1923     * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
1924     * @more
1925     * <p class="note">The bitmap will be flattened into the parcel if this object is
1926     * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
1927     *
1928     * @param viewId The id of the view on which to call the method.
1929     * @param methodName The name of the method to call.
1930     * @param value The value to pass to the method.
1931     */
1932    public void setBitmap(int viewId, String methodName, Bitmap value) {
1933        addAction(new BitmapReflectionAction(viewId, methodName, value));
1934    }
1935
1936    /**
1937     * Call a method taking one Bundle on a view in the layout for this RemoteViews.
1938     *
1939     * @param viewId The id of the view on which to call the method.
1940     * @param methodName The name of the method to call.
1941     * @param value The value to pass to the method.
1942     */
1943    public void setBundle(int viewId, String methodName, Bundle value) {
1944        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
1945    }
1946
1947    /**
1948     * Call a method taking one Intent on a view in the layout for this RemoteViews.
1949     *
1950     * @param viewId The id of the view on which to call the method.
1951     * @param methodName The name of the method to call.
1952     * @param value The {@link android.content.Intent} to pass the method.
1953     */
1954    public void setIntent(int viewId, String methodName, Intent value) {
1955        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
1956    }
1957
1958    /**
1959     * Equivalent to calling View.setContentDescription
1960     *
1961     * @param viewId The id of the view whose content description should change
1962     * @param contentDescription The new content description for the view
1963     */
1964    public void setContentDescription(int viewId, CharSequence contentDescription) {
1965        setCharSequence(viewId, "setContentDescription", contentDescription);
1966    }
1967
1968    private RemoteViews getRemoteViewsToApply(Context context) {
1969        if (hasLandscapeAndPortraitLayouts()) {
1970            int orientation = context.getResources().getConfiguration().orientation;
1971            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
1972                return mLandscape;
1973            } else {
1974                return mPortrait;
1975            }
1976        }
1977        return this;
1978    }
1979
1980    /**
1981     * Inflates the view hierarchy represented by this object and applies
1982     * all of the actions.
1983     *
1984     * <p><strong>Caller beware: this may throw</strong>
1985     *
1986     * @param context Default context to use
1987     * @param parent Parent that the resulting view hierarchy will be attached to. This method
1988     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
1989     * @return The inflated view hierarchy
1990     */
1991    public View apply(Context context, ViewGroup parent) {
1992        RemoteViews rvToApply = getRemoteViewsToApply(context);
1993
1994        View result;
1995
1996        Context c = prepareContext(context);
1997
1998        LayoutInflater inflater = (LayoutInflater)
1999                c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2000
2001        inflater = inflater.cloneInContext(c);
2002        inflater.setFilter(this);
2003
2004        result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
2005
2006        rvToApply.performApply(result, parent);
2007
2008        return result;
2009    }
2010
2011    /**
2012     * Applies all of the actions to the provided view.
2013     *
2014     * <p><strong>Caller beware: this may throw</strong>
2015     *
2016     * @param v The view to apply the actions to.  This should be the result of
2017     * the {@link #apply(Context,ViewGroup)} call.
2018     */
2019    public void reapply(Context context, View v) {
2020        RemoteViews rvToApply = getRemoteViewsToApply(context);
2021
2022        // In the case that a view has this RemoteViews applied in one orientation, is persisted
2023        // across orientation change, and has the RemoteViews re-applied in the new orientation,
2024        // we throw an exception, since the layouts may be completely unrelated.
2025        if (hasLandscapeAndPortraitLayouts()) {
2026            if (v.getId() != rvToApply.getLayoutId()) {
2027                throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
2028                        " that does not share the same root layout id.");
2029            }
2030        }
2031
2032        prepareContext(context);
2033        rvToApply.performApply(v, (ViewGroup) v.getParent());
2034    }
2035
2036    private void performApply(View v, ViewGroup parent) {
2037        if (mActions != null) {
2038            final int count = mActions.size();
2039            for (int i = 0; i < count; i++) {
2040                Action a = mActions.get(i);
2041                a.apply(v, parent);
2042            }
2043        }
2044    }
2045
2046    private Context prepareContext(Context context) {
2047        Context c;
2048        String packageName = mPackage;
2049
2050        if (packageName != null) {
2051            try {
2052                c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED);
2053            } catch (NameNotFoundException e) {
2054                Log.e(LOG_TAG, "Package name " + packageName + " not found");
2055                c = context;
2056            }
2057        } else {
2058            c = context;
2059        }
2060
2061        return c;
2062    }
2063
2064    /* (non-Javadoc)
2065     * Used to restrict the views which can be inflated
2066     *
2067     * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
2068     */
2069    public boolean onLoadClass(Class clazz) {
2070        return clazz.isAnnotationPresent(RemoteView.class);
2071    }
2072
2073    public int describeContents() {
2074        return 0;
2075    }
2076
2077    public void writeToParcel(Parcel dest, int flags) {
2078        if (!hasLandscapeAndPortraitLayouts()) {
2079            dest.writeInt(MODE_NORMAL);
2080            // We only write the bitmap cache if we are the root RemoteViews, as this cache
2081            // is shared by all children.
2082            if (mIsRoot) {
2083                mBitmapCache.writeBitmapsToParcel(dest, flags);
2084            }
2085            dest.writeString(mPackage);
2086            dest.writeInt(mLayoutId);
2087            dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
2088            int count;
2089            if (mActions != null) {
2090                count = mActions.size();
2091            } else {
2092                count = 0;
2093            }
2094            dest.writeInt(count);
2095            for (int i=0; i<count; i++) {
2096                Action a = mActions.get(i);
2097                a.writeToParcel(dest, 0);
2098            }
2099        } else {
2100            dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
2101            // We only write the bitmap cache if we are the root RemoteViews, as this cache
2102            // is shared by all children.
2103            if (mIsRoot) {
2104                mBitmapCache.writeBitmapsToParcel(dest, flags);
2105            }
2106            mLandscape.writeToParcel(dest, flags);
2107            mPortrait.writeToParcel(dest, flags);
2108        }
2109    }
2110
2111    /**
2112     * Parcelable.Creator that instantiates RemoteViews objects
2113     */
2114    public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
2115        public RemoteViews createFromParcel(Parcel parcel) {
2116            return new RemoteViews(parcel);
2117        }
2118
2119        public RemoteViews[] newArray(int size) {
2120            return new RemoteViews[size];
2121        }
2122    };
2123}
2124