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