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