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