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