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