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