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