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