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