RemoteViews.java revision 90396365357c745070145a87b6036e6bb07cbae8
1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package android.widget;
18
19import android.annotation.ColorInt;
20import android.annotation.DimenRes;
21import android.app.ActivityManager.StackId;
22import android.app.ActivityOptions;
23import android.app.ActivityThread;
24import android.app.Application;
25import android.app.PendingIntent;
26import android.app.RemoteInput;
27import android.appwidget.AppWidgetHostView;
28import android.content.Context;
29import android.content.ContextWrapper;
30import android.content.Intent;
31import android.content.IntentSender;
32import android.content.pm.ApplicationInfo;
33import android.content.pm.PackageManager.NameNotFoundException;
34import android.content.res.ColorStateList;
35import android.content.res.Configuration;
36import android.content.res.Resources;
37import android.content.res.TypedArray;
38import android.graphics.Bitmap;
39import android.graphics.PorterDuff;
40import android.graphics.Rect;
41import android.graphics.drawable.Drawable;
42import android.graphics.drawable.Icon;
43import android.net.Uri;
44import android.os.AsyncTask;
45import android.os.Binder;
46import android.os.Build;
47import android.os.Bundle;
48import android.os.CancellationSignal;
49import android.os.Parcel;
50import android.os.Parcelable;
51import android.os.Process;
52import android.os.StrictMode;
53import android.os.UserHandle;
54import android.text.TextUtils;
55import android.util.ArrayMap;
56import android.util.Log;
57import android.view.LayoutInflater;
58import android.view.LayoutInflater.Filter;
59import android.view.RemotableViewMethod;
60import android.view.View;
61import android.view.View.OnClickListener;
62import android.view.ViewGroup;
63import android.view.ViewStub;
64import android.widget.AdapterView.OnItemClickListener;
65
66import com.android.internal.R;
67import com.android.internal.util.Preconditions;
68
69import libcore.util.Objects;
70
71import java.lang.annotation.ElementType;
72import java.lang.annotation.Retention;
73import java.lang.annotation.RetentionPolicy;
74import java.lang.annotation.Target;
75import java.lang.reflect.Method;
76import java.util.ArrayList;
77import java.util.HashMap;
78import java.util.concurrent.Executor;
79
80/**
81 * A class that describes a view hierarchy that can be displayed in
82 * another process. The hierarchy is inflated from a layout resource
83 * file, and this class provides some basic operations for modifying
84 * the content of the inflated hierarchy.
85 */
86public class RemoteViews implements Parcelable, Filter {
87
88    private static final String LOG_TAG = "RemoteViews";
89
90    /**
91     * The intent extra that contains the appWidgetId.
92     * @hide
93     */
94    static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
95
96    /**
97     * Maximum depth of nested views calls from {@link #addView(int, RemoteViews)} and
98     * {@link #RemoteViews(RemoteViews, RemoteViews)}.
99     */
100    private static final int MAX_NESTED_VIEWS = 10;
101
102    // The unique identifiers for each custom {@link Action}.
103    private static final int SET_ON_CLICK_PENDING_INTENT_TAG = 1;
104    private static final int REFLECTION_ACTION_TAG = 2;
105    private static final int SET_DRAWABLE_PARAMETERS_TAG = 3;
106    private static final int VIEW_GROUP_ACTION_ADD_TAG = 4;
107    private static final int SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG = 5;
108    private static final int SET_EMPTY_VIEW_ACTION_TAG = 6;
109    private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7;
110    private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8;
111    private static final int SET_ON_CLICK_FILL_IN_INTENT_TAG = 9;
112    private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10;
113    private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11;
114    private static final int BITMAP_REFLECTION_ACTION_TAG = 12;
115    private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13;
116    private static final int VIEW_PADDING_ACTION_TAG = 14;
117    private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15;
118    private static final int TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG = 17;
119    private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18;
120    private static final int LAYOUT_PARAM_ACTION_TAG = 19;
121
122    /**
123     * Application that hosts the remote views.
124     *
125     * @hide
126     */
127    private ApplicationInfo mApplication;
128
129    /**
130     * The resource ID of the layout file. (Added to the parcel)
131     */
132    private final int mLayoutId;
133
134    /**
135     * An array of actions to perform on the view tree once it has been
136     * inflated
137     */
138    private ArrayList<Action> mActions;
139
140    /**
141     * A class to keep track of memory usage by this RemoteViews
142     */
143    private MemoryUsageCounter mMemoryUsageCounter;
144
145    /**
146     * Maps bitmaps to unique indicies to avoid Bitmap duplication.
147     */
148    private BitmapCache mBitmapCache;
149
150    /**
151     * Indicates whether or not this RemoteViews object is contained as a child of any other
152     * RemoteViews.
153     */
154    private boolean mIsRoot = true;
155
156    /**
157     * Constants to whether or not this RemoteViews is composed of a landscape and portrait
158     * RemoteViews.
159     */
160    private static final int MODE_NORMAL = 0;
161    private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
162
163    /**
164     * Used in conjunction with the special constructor
165     * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait
166     * RemoteViews.
167     */
168    private RemoteViews mLandscape = null;
169    private RemoteViews mPortrait = null;
170
171    /**
172     * This flag indicates whether this RemoteViews object is being created from a
173     * RemoteViewsService for use as a child of a widget collection. This flag is used
174     * to determine whether or not certain features are available, in particular,
175     * setting on click extras and setting on click pending intents. The former is enabled,
176     * and the latter disabled when this flag is true.
177     */
178    private boolean mIsWidgetCollectionChild = false;
179
180    private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler();
181
182    private static final Object[] sMethodsLock = new Object[0];
183    private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods =
184            new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>();
185    private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>();
186
187    private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() {
188        @Override
189        protected Object[] initialValue() {
190            return new Object[1];
191        }
192    };
193
194    /**
195     * @hide
196     */
197    public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) {
198        mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
199    }
200
201    /**
202     * Handle with care!
203     */
204    static class MutablePair<F, S> {
205        F first;
206        S second;
207
208        MutablePair(F first, S second) {
209            this.first = first;
210            this.second = second;
211        }
212
213        @Override
214        public boolean equals(Object o) {
215            if (!(o instanceof MutablePair)) {
216                return false;
217            }
218            MutablePair<?, ?> p = (MutablePair<?, ?>) o;
219            return Objects.equal(p.first, first) && Objects.equal(p.second, second);
220        }
221
222        @Override
223        public int hashCode() {
224            return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
225        }
226    }
227
228    /**
229     * This pair is used to perform lookups in sMethods without causing allocations.
230     */
231    private final MutablePair<String, Class<?>> mPair =
232            new MutablePair<String, Class<?>>(null, null);
233
234    /**
235     * This annotation indicates that a subclass of View is allowed to be used
236     * with the {@link RemoteViews} mechanism.
237     */
238    @Target({ ElementType.TYPE })
239    @Retention(RetentionPolicy.RUNTIME)
240    public @interface RemoteView {
241    }
242
243    /**
244     * Exception to send when something goes wrong executing an action
245     *
246     */
247    public static class ActionException extends RuntimeException {
248        public ActionException(Exception ex) {
249            super(ex);
250        }
251        public ActionException(String message) {
252            super(message);
253        }
254    }
255
256    /** @hide */
257    public static class OnClickHandler {
258
259        private int mEnterAnimationId;
260
261        public boolean onClickHandler(View view, PendingIntent pendingIntent,
262                Intent fillInIntent) {
263            return onClickHandler(view, pendingIntent, fillInIntent, StackId.INVALID_STACK_ID);
264        }
265
266        public boolean onClickHandler(View view, PendingIntent pendingIntent,
267                Intent fillInIntent, int launchStackId) {
268            try {
269                // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
270                Context context = view.getContext();
271                ActivityOptions opts;
272                if (mEnterAnimationId != 0) {
273                    opts = ActivityOptions.makeCustomAnimation(context, mEnterAnimationId, 0);
274                } else {
275                    opts = ActivityOptions.makeBasic();
276                }
277
278                if (launchStackId != StackId.INVALID_STACK_ID) {
279                    opts.setLaunchStackId(launchStackId);
280                }
281                context.startIntentSender(
282                        pendingIntent.getIntentSender(), fillInIntent,
283                        Intent.FLAG_ACTIVITY_NEW_TASK,
284                        Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle());
285            } catch (IntentSender.SendIntentException e) {
286                android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
287                return false;
288            } catch (Exception e) {
289                android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " +
290                        "unknown exception: ", e);
291                return false;
292            }
293            return true;
294        }
295
296        public void setEnterAnimationId(int enterAnimationId) {
297            mEnterAnimationId = enterAnimationId;
298        }
299    }
300
301    /**
302     * Base class for all actions that can be performed on an
303     * inflated view.
304     *
305     *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
306     */
307    private abstract static class Action implements Parcelable {
308        public abstract void apply(View root, ViewGroup rootParent,
309                OnClickHandler handler) throws ActionException;
310
311        public static final int MERGE_REPLACE = 0;
312        public static final int MERGE_APPEND = 1;
313        public static final int MERGE_IGNORE = 2;
314
315        public int describeContents() {
316            return 0;
317        }
318
319        /**
320         * Overridden by each class to report on it's own memory usage
321         */
322        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
323            // We currently only calculate Bitmap memory usage, so by default,
324            // don't do anything here
325        }
326
327        public void setBitmapCache(BitmapCache bitmapCache) {
328            // Do nothing
329        }
330
331        public int mergeBehavior() {
332            return MERGE_REPLACE;
333        }
334
335        public abstract String getActionName();
336
337        public String getUniqueKey() {
338            return (getActionName() + viewId);
339        }
340
341        /**
342         * This is called on the background thread. It should perform any non-ui computations
343         * and return the final action which will run on the UI thread.
344         * Override this if some of the tasks can be performed async.
345         */
346        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
347            return this;
348        }
349
350        public boolean prefersAsyncApply() {
351            return false;
352        }
353
354        /**
355         * Overridden by subclasses which have (or inherit) an ApplicationInfo instance
356         * as member variable
357         */
358        public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
359            return true;
360        }
361
362        int viewId;
363    }
364
365    /**
366     * Action class used during async inflation of RemoteViews. Subclasses are not parcelable.
367     */
368    private static abstract class RuntimeAction extends Action {
369        @Override
370        public final String getActionName() {
371            return "RuntimeAction";
372        }
373
374        @Override
375        public final void writeToParcel(Parcel dest, int flags) {
376            throw new UnsupportedOperationException();
377        }
378    }
379
380    // Constant used during async execution. It is not parcelable.
381    private static final Action ACTION_NOOP = new RuntimeAction() {
382        @Override
383        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { }
384    };
385
386    /**
387     * Merges the passed RemoteViews actions with this RemoteViews actions according to
388     * action-specific merge rules.
389     *
390     * @param newRv
391     *
392     * @hide
393     */
394    public void mergeRemoteViews(RemoteViews newRv) {
395        if (newRv == null) return;
396        // We first copy the new RemoteViews, as the process of merging modifies the way the actions
397        // reference the bitmap cache. We don't want to modify the object as it may need to
398        // be merged and applied multiple times.
399        RemoteViews copy = newRv.clone();
400
401        HashMap<String, Action> map = new HashMap<String, Action>();
402        if (mActions == null) {
403            mActions = new ArrayList<Action>();
404        }
405
406        int count = mActions.size();
407        for (int i = 0; i < count; i++) {
408            Action a = mActions.get(i);
409            map.put(a.getUniqueKey(), a);
410        }
411
412        ArrayList<Action> newActions = copy.mActions;
413        if (newActions == null) return;
414        count = newActions.size();
415        for (int i = 0; i < count; i++) {
416            Action a = newActions.get(i);
417            String key = newActions.get(i).getUniqueKey();
418            int mergeBehavior = newActions.get(i).mergeBehavior();
419            if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) {
420                mActions.remove(map.get(key));
421                map.remove(key);
422            }
423
424            // If the merge behavior is ignore, we don't bother keeping the extra action
425            if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) {
426                mActions.add(a);
427            }
428        }
429
430        // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache
431        mBitmapCache = new BitmapCache();
432        setBitmapCache(mBitmapCache);
433        recalculateMemoryUsage();
434    }
435
436    private static class RemoteViewsContextWrapper extends ContextWrapper {
437        private final Context mContextForResources;
438
439        RemoteViewsContextWrapper(Context context, Context contextForResources) {
440            super(context);
441            mContextForResources = contextForResources;
442        }
443
444        @Override
445        public Resources getResources() {
446            return mContextForResources.getResources();
447        }
448
449        @Override
450        public Resources.Theme getTheme() {
451            return mContextForResources.getTheme();
452        }
453
454        @Override
455        public String getPackageName() {
456            return mContextForResources.getPackageName();
457        }
458    }
459
460    private class SetEmptyView extends Action {
461        int viewId;
462        int emptyViewId;
463
464        SetEmptyView(int viewId, int emptyViewId) {
465            this.viewId = viewId;
466            this.emptyViewId = emptyViewId;
467        }
468
469        SetEmptyView(Parcel in) {
470            this.viewId = in.readInt();
471            this.emptyViewId = in.readInt();
472        }
473
474        public void writeToParcel(Parcel out, int flags) {
475            out.writeInt(SET_EMPTY_VIEW_ACTION_TAG);
476            out.writeInt(this.viewId);
477            out.writeInt(this.emptyViewId);
478        }
479
480        @Override
481        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
482            final View view = root.findViewById(viewId);
483            if (!(view instanceof AdapterView<?>)) return;
484
485            AdapterView<?> adapterView = (AdapterView<?>) view;
486
487            final View emptyView = root.findViewById(emptyViewId);
488            if (emptyView == null) return;
489
490            adapterView.setEmptyView(emptyView);
491        }
492
493        public String getActionName() {
494            return "SetEmptyView";
495        }
496    }
497
498    private class SetOnClickFillInIntent extends Action {
499        public SetOnClickFillInIntent(int id, Intent fillInIntent) {
500            this.viewId = id;
501            this.fillInIntent = fillInIntent;
502        }
503
504        public SetOnClickFillInIntent(Parcel parcel) {
505            viewId = parcel.readInt();
506            fillInIntent = Intent.CREATOR.createFromParcel(parcel);
507        }
508
509        public void writeToParcel(Parcel dest, int flags) {
510            dest.writeInt(SET_ON_CLICK_FILL_IN_INTENT_TAG);
511            dest.writeInt(viewId);
512            fillInIntent.writeToParcel(dest, 0 /* no flags */);
513        }
514
515        @Override
516        public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
517            final View target = root.findViewById(viewId);
518            if (target == null) return;
519
520            if (!mIsWidgetCollectionChild) {
521                Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " +
522                        "only from RemoteViewsFactory (ie. on collection items).");
523                return;
524            }
525            if (target == root) {
526                target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent);
527            } else if (fillInIntent != null) {
528                OnClickListener listener = new OnClickListener() {
529                    public void onClick(View v) {
530                        // Insure that this view is a child of an AdapterView
531                        View parent = (View) v.getParent();
532                        // Break the for loop on the first encounter of:
533                        //    1) an AdapterView,
534                        //    2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or
535                        //    3) a null parent.
536                        // 2) and 3) are unexpected and catch the case where a child is not
537                        // correctly parented in an AdapterView.
538                        while (parent != null && !(parent instanceof AdapterView<?>)
539                                && !((parent instanceof AppWidgetHostView) &&
540                                    !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) {
541                            parent = (View) parent.getParent();
542                        }
543
544                        if (!(parent instanceof AdapterView<?>)) {
545                            // Somehow they've managed to get this far without having
546                            // and AdapterView as a parent.
547                            Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent");
548                            return;
549                        }
550
551                        // Insure that a template pending intent has been set on an ancestor
552                        if (!(parent.getTag() instanceof PendingIntent)) {
553                            Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" +
554                                    " calling setPendingIntentTemplate on parent.");
555                            return;
556                        }
557
558                        PendingIntent pendingIntent = (PendingIntent) parent.getTag();
559
560                        final Rect rect = getSourceBounds(v);
561
562                        fillInIntent.setSourceBounds(rect);
563                        handler.onClickHandler(v, pendingIntent, fillInIntent);
564                    }
565
566                };
567                target.setOnClickListener(listener);
568            }
569        }
570
571        public String getActionName() {
572            return "SetOnClickFillInIntent";
573        }
574
575        Intent fillInIntent;
576    }
577
578    private class SetPendingIntentTemplate extends Action {
579        public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
580            this.viewId = id;
581            this.pendingIntentTemplate = pendingIntentTemplate;
582        }
583
584        public SetPendingIntentTemplate(Parcel parcel) {
585            viewId = parcel.readInt();
586            pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
587        }
588
589        public void writeToParcel(Parcel dest, int flags) {
590            dest.writeInt(SET_PENDING_INTENT_TEMPLATE_TAG);
591            dest.writeInt(viewId);
592            pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
593        }
594
595        @Override
596        public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
597            final View target = root.findViewById(viewId);
598            if (target == null) return;
599
600            // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense
601            if (target instanceof AdapterView<?>) {
602                AdapterView<?> av = (AdapterView<?>) target;
603                // The PendingIntent template is stored in the view's tag.
604                OnItemClickListener listener = new OnItemClickListener() {
605                    public void onItemClick(AdapterView<?> parent, View view,
606                            int position, long id) {
607                        // The view should be a frame layout
608                        if (view instanceof ViewGroup) {
609                            ViewGroup vg = (ViewGroup) view;
610
611                            // AdapterViews contain their children in a frame
612                            // so we need to go one layer deeper here.
613                            if (parent instanceof AdapterViewAnimator) {
614                                vg = (ViewGroup) vg.getChildAt(0);
615                            }
616                            if (vg == null) return;
617
618                            Intent fillInIntent = null;
619                            int childCount = vg.getChildCount();
620                            for (int i = 0; i < childCount; i++) {
621                                Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent);
622                                if (tag instanceof Intent) {
623                                    fillInIntent = (Intent) tag;
624                                    break;
625                                }
626                            }
627                            if (fillInIntent == null) return;
628
629                            final Rect rect = getSourceBounds(view);
630
631                            final Intent intent = new Intent();
632                            intent.setSourceBounds(rect);
633                            handler.onClickHandler(view, pendingIntentTemplate, fillInIntent);
634                        }
635                    }
636                };
637                av.setOnItemClickListener(listener);
638                av.setTag(pendingIntentTemplate);
639            } else {
640                Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" +
641                        "an AdapterView (id: " + viewId + ")");
642                return;
643            }
644        }
645
646        public String getActionName() {
647            return "SetPendingIntentTemplate";
648        }
649
650        PendingIntent pendingIntentTemplate;
651    }
652
653    private class SetRemoteViewsAdapterList extends Action {
654        public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
655            this.viewId = id;
656            this.list = list;
657            this.viewTypeCount = viewTypeCount;
658        }
659
660        public SetRemoteViewsAdapterList(Parcel parcel) {
661            viewId = parcel.readInt();
662            viewTypeCount = parcel.readInt();
663            int count = parcel.readInt();
664            list = new ArrayList<RemoteViews>();
665
666            for (int i = 0; i < count; i++) {
667                RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
668                list.add(rv);
669            }
670        }
671
672        public void writeToParcel(Parcel dest, int flags) {
673            dest.writeInt(SET_REMOTE_VIEW_ADAPTER_LIST_TAG);
674            dest.writeInt(viewId);
675            dest.writeInt(viewTypeCount);
676
677            if (list == null || list.size() == 0) {
678                dest.writeInt(0);
679            } else {
680                int count = list.size();
681                dest.writeInt(count);
682                for (int i = 0; i < count; i++) {
683                    RemoteViews rv = list.get(i);
684                    rv.writeToParcel(dest, flags);
685                }
686            }
687        }
688
689        @Override
690        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
691            final View target = root.findViewById(viewId);
692            if (target == null) return;
693
694            // Ensure that we are applying to an AppWidget root
695            if (!(rootParent instanceof AppWidgetHostView)) {
696                Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
697                        "AppWidgets (root id: " + viewId + ")");
698                return;
699            }
700            // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
701            if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
702                Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
703                        "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
704                return;
705            }
706
707            if (target instanceof AbsListView) {
708                AbsListView v = (AbsListView) target;
709                Adapter a = v.getAdapter();
710                if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
711                    ((RemoteViewsListAdapter) a).setViewsList(list);
712                } else {
713                    v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
714                }
715            } else if (target instanceof AdapterViewAnimator) {
716                AdapterViewAnimator v = (AdapterViewAnimator) target;
717                Adapter a = v.getAdapter();
718                if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
719                    ((RemoteViewsListAdapter) a).setViewsList(list);
720                } else {
721                    v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
722                }
723            }
724        }
725
726        public String getActionName() {
727            return "SetRemoteViewsAdapterList";
728        }
729
730        int viewTypeCount;
731        ArrayList<RemoteViews> list;
732    }
733
734    private class SetRemoteViewsAdapterIntent extends Action {
735        public SetRemoteViewsAdapterIntent(int id, Intent intent) {
736            this.viewId = id;
737            this.intent = intent;
738        }
739
740        public SetRemoteViewsAdapterIntent(Parcel parcel) {
741            viewId = parcel.readInt();
742            intent = Intent.CREATOR.createFromParcel(parcel);
743        }
744
745        public void writeToParcel(Parcel dest, int flags) {
746            dest.writeInt(SET_REMOTE_VIEW_ADAPTER_INTENT_TAG);
747            dest.writeInt(viewId);
748            intent.writeToParcel(dest, flags);
749        }
750
751        @Override
752        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
753            final View target = root.findViewById(viewId);
754            if (target == null) return;
755
756            // Ensure that we are applying to an AppWidget root
757            if (!(rootParent instanceof AppWidgetHostView)) {
758                Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
759                        "AppWidgets (root id: " + viewId + ")");
760                return;
761            }
762            // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
763            if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
764                Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
765                        "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
766                return;
767            }
768
769            // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
770            // RemoteViewsService
771            AppWidgetHostView host = (AppWidgetHostView) rootParent;
772            intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
773            if (target instanceof AbsListView) {
774                AbsListView v = (AbsListView) target;
775                v.setRemoteViewsAdapter(intent, isAsync);
776                v.setRemoteViewsOnClickHandler(handler);
777            } else if (target instanceof AdapterViewAnimator) {
778                AdapterViewAnimator v = (AdapterViewAnimator) target;
779                v.setRemoteViewsAdapter(intent, isAsync);
780                v.setRemoteViewsOnClickHandler(handler);
781            }
782        }
783
784        @Override
785        public Action initActionAsync(ViewTree root, ViewGroup rootParent,
786                OnClickHandler handler) {
787            SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent);
788            copy.isAsync = true;
789            return copy;
790        }
791
792        public String getActionName() {
793            return "SetRemoteViewsAdapterIntent";
794        }
795
796        Intent intent;
797        boolean isAsync = false;
798    }
799
800    /**
801     * Equivalent to calling
802     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
803     * to launch the provided {@link PendingIntent}.
804     */
805    private class SetOnClickPendingIntent extends Action {
806        public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
807            this.viewId = id;
808            this.pendingIntent = pendingIntent;
809        }
810
811        public SetOnClickPendingIntent(Parcel parcel) {
812            viewId = parcel.readInt();
813
814            // We check a flag to determine if the parcel contains a PendingIntent.
815            if (parcel.readInt() != 0) {
816                pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
817            }
818        }
819
820        public void writeToParcel(Parcel dest, int flags) {
821            dest.writeInt(SET_ON_CLICK_PENDING_INTENT_TAG);
822            dest.writeInt(viewId);
823
824            // We use a flag to indicate whether the parcel contains a valid object.
825            dest.writeInt(pendingIntent != null ? 1 : 0);
826            if (pendingIntent != null) {
827                pendingIntent.writeToParcel(dest, 0 /* no flags */);
828            }
829        }
830
831        @Override
832        public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
833            final View target = root.findViewById(viewId);
834            if (target == null) return;
835
836            // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
837            // sense, do they mean to set a PendingIntent template for the AdapterView's children?
838            if (mIsWidgetCollectionChild) {
839                Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
840                        "(id: " + viewId + ")");
841                ApplicationInfo appInfo = root.getContext().getApplicationInfo();
842
843                // We let this slide for HC and ICS so as to not break compatibility. It should have
844                // been disabled from the outset, but was left open by accident.
845                if (appInfo != null &&
846                        appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
847                    return;
848                }
849            }
850
851            // If the pendingIntent is null, we clear the onClickListener
852            OnClickListener listener = null;
853            if (pendingIntent != null) {
854                listener = new OnClickListener() {
855                    public void onClick(View v) {
856                        // Find target view location in screen coordinates and
857                        // fill into PendingIntent before sending.
858                        final Rect rect = getSourceBounds(v);
859
860                        final Intent intent = new Intent();
861                        intent.setSourceBounds(rect);
862                        handler.onClickHandler(v, pendingIntent, intent);
863                    }
864                };
865            }
866            target.setOnClickListener(listener);
867        }
868
869        public String getActionName() {
870            return "SetOnClickPendingIntent";
871        }
872
873        PendingIntent pendingIntent;
874    }
875
876    private static Rect getSourceBounds(View v) {
877        final float appScale = v.getContext().getResources()
878                .getCompatibilityInfo().applicationScale;
879        final int[] pos = new int[2];
880        v.getLocationOnScreen(pos);
881
882        final Rect rect = new Rect();
883        rect.left = (int) (pos[0] * appScale + 0.5f);
884        rect.top = (int) (pos[1] * appScale + 0.5f);
885        rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
886        rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
887        return rect;
888    }
889
890    private Method getMethod(View view, String methodName, Class<?> paramType) {
891        Method method;
892        Class<? extends View> klass = view.getClass();
893
894        synchronized (sMethodsLock) {
895            ArrayMap<MutablePair<String, Class<?>>, Method> methods = sMethods.get(klass);
896            if (methods == null) {
897                methods = new ArrayMap<MutablePair<String, Class<?>>, Method>();
898                sMethods.put(klass, methods);
899            }
900
901            mPair.first = methodName;
902            mPair.second = paramType;
903
904            method = methods.get(mPair);
905            if (method == null) {
906                try {
907                    if (paramType == null) {
908                        method = klass.getMethod(methodName);
909                    } else {
910                        method = klass.getMethod(methodName, paramType);
911                    }
912                } catch (NoSuchMethodException ex) {
913                    throw new ActionException("view: " + klass.getName() + " doesn't have method: "
914                            + methodName + getParameters(paramType));
915                }
916
917                if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
918                    throw new ActionException("view: " + klass.getName()
919                            + " can't use method with RemoteViews: "
920                            + methodName + getParameters(paramType));
921                }
922
923                methods.put(new MutablePair<String, Class<?>>(methodName, paramType), method);
924            }
925        }
926
927        return method;
928    }
929
930    /**
931     * @return the async implementation of the provided method.
932     */
933    private Method getAsyncMethod(Method method) {
934        synchronized (sAsyncMethods) {
935            int valueIndex = sAsyncMethods.indexOfKey(method);
936            if (valueIndex >= 0) {
937                return sAsyncMethods.valueAt(valueIndex);
938            }
939
940            RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class);
941            Method asyncMethod = null;
942            if (!annotation.asyncImpl().isEmpty()) {
943                try {
944                    asyncMethod = method.getDeclaringClass()
945                            .getMethod(annotation.asyncImpl(), method.getParameterTypes());
946                    if (!asyncMethod.getReturnType().equals(Runnable.class)) {
947                        throw new ActionException("Async implementation for " + method.getName() +
948                            " does not return a Runnable");
949                    }
950                } catch (NoSuchMethodException ex) {
951                    throw new ActionException("Async implementation declared but not defined for " +
952                            method.getName());
953                }
954            }
955            sAsyncMethods.put(method, asyncMethod);
956            return asyncMethod;
957        }
958    }
959
960    private static String getParameters(Class<?> paramType) {
961        if (paramType == null) return "()";
962        return "(" + paramType + ")";
963    }
964
965    private static Object[] wrapArg(Object value) {
966        Object[] args = sInvokeArgsTls.get();
967        args[0] = value;
968        return args;
969    }
970
971    /**
972     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
973     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
974     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
975     * <p>
976     * These operations will be performed on the {@link Drawable} returned by the
977     * target {@link View#getBackground()} by default.  If targetBackground is false,
978     * we assume the target is an {@link ImageView} and try applying the operations
979     * to {@link ImageView#getDrawable()}.
980     * <p>
981     * You can omit specific calls by marking their values with null or -1.
982     */
983    private class SetDrawableParameters extends Action {
984        public SetDrawableParameters(int id, boolean targetBackground, int alpha,
985                int colorFilter, PorterDuff.Mode mode, int level) {
986            this.viewId = id;
987            this.targetBackground = targetBackground;
988            this.alpha = alpha;
989            this.colorFilter = colorFilter;
990            this.filterMode = mode;
991            this.level = level;
992        }
993
994        public SetDrawableParameters(Parcel parcel) {
995            viewId = parcel.readInt();
996            targetBackground = parcel.readInt() != 0;
997            alpha = parcel.readInt();
998            colorFilter = parcel.readInt();
999            boolean hasMode = parcel.readInt() != 0;
1000            if (hasMode) {
1001                filterMode = PorterDuff.Mode.valueOf(parcel.readString());
1002            } else {
1003                filterMode = null;
1004            }
1005            level = parcel.readInt();
1006        }
1007
1008        public void writeToParcel(Parcel dest, int flags) {
1009            dest.writeInt(SET_DRAWABLE_PARAMETERS_TAG);
1010            dest.writeInt(viewId);
1011            dest.writeInt(targetBackground ? 1 : 0);
1012            dest.writeInt(alpha);
1013            dest.writeInt(colorFilter);
1014            if (filterMode != null) {
1015                dest.writeInt(1);
1016                dest.writeString(filterMode.toString());
1017            } else {
1018                dest.writeInt(0);
1019            }
1020            dest.writeInt(level);
1021        }
1022
1023        @Override
1024        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1025            final View target = root.findViewById(viewId);
1026            if (target == null) return;
1027
1028            // Pick the correct drawable to modify for this view
1029            Drawable targetDrawable = null;
1030            if (targetBackground) {
1031                targetDrawable = target.getBackground();
1032            } else if (target instanceof ImageView) {
1033                ImageView imageView = (ImageView) target;
1034                targetDrawable = imageView.getDrawable();
1035            }
1036
1037            if (targetDrawable != null) {
1038                // Perform modifications only if values are set correctly
1039                if (alpha != -1) {
1040                    targetDrawable.mutate().setAlpha(alpha);
1041                }
1042                if (filterMode != null) {
1043                    targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
1044                }
1045                if (level != -1) {
1046                    targetDrawable.mutate().setLevel(level);
1047                }
1048            }
1049        }
1050
1051        public String getActionName() {
1052            return "SetDrawableParameters";
1053        }
1054
1055        boolean targetBackground;
1056        int alpha;
1057        int colorFilter;
1058        PorterDuff.Mode filterMode;
1059        int level;
1060    }
1061
1062    private final class ReflectionActionWithoutParams extends Action {
1063        final String methodName;
1064
1065        ReflectionActionWithoutParams(int viewId, String methodName) {
1066            this.viewId = viewId;
1067            this.methodName = methodName;
1068        }
1069
1070        ReflectionActionWithoutParams(Parcel in) {
1071            this.viewId = in.readInt();
1072            this.methodName = in.readString();
1073        }
1074
1075        public void writeToParcel(Parcel out, int flags) {
1076            out.writeInt(SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG);
1077            out.writeInt(this.viewId);
1078            out.writeString(this.methodName);
1079        }
1080
1081        @Override
1082        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1083            final View view = root.findViewById(viewId);
1084            if (view == null) return;
1085
1086            try {
1087                getMethod(view, this.methodName, null).invoke(view);
1088            } catch (ActionException e) {
1089                throw e;
1090            } catch (Exception ex) {
1091                throw new ActionException(ex);
1092            }
1093        }
1094
1095        public int mergeBehavior() {
1096            // we don't need to build up showNext or showPrevious calls
1097            if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
1098                return MERGE_IGNORE;
1099            } else {
1100                return MERGE_REPLACE;
1101            }
1102        }
1103
1104        public String getActionName() {
1105            return "ReflectionActionWithoutParams";
1106        }
1107    }
1108
1109    private static class BitmapCache {
1110        ArrayList<Bitmap> mBitmaps;
1111
1112        public BitmapCache() {
1113            mBitmaps = new ArrayList<Bitmap>();
1114        }
1115
1116        public BitmapCache(Parcel source) {
1117            int count = source.readInt();
1118            mBitmaps = new ArrayList<Bitmap>();
1119            for (int i = 0; i < count; i++) {
1120                Bitmap b = Bitmap.CREATOR.createFromParcel(source);
1121                mBitmaps.add(b);
1122            }
1123        }
1124
1125        public int getBitmapId(Bitmap b) {
1126            if (b == null) {
1127                return -1;
1128            } else {
1129                if (mBitmaps.contains(b)) {
1130                    return mBitmaps.indexOf(b);
1131                } else {
1132                    mBitmaps.add(b);
1133                    return (mBitmaps.size() - 1);
1134                }
1135            }
1136        }
1137
1138        public Bitmap getBitmapForId(int id) {
1139            if (id == -1 || id >= mBitmaps.size()) {
1140                return null;
1141            } else {
1142                return mBitmaps.get(id);
1143            }
1144        }
1145
1146        public void writeBitmapsToParcel(Parcel dest, int flags) {
1147            int count = mBitmaps.size();
1148            dest.writeInt(count);
1149            for (int i = 0; i < count; i++) {
1150                mBitmaps.get(i).writeToParcel(dest, flags);
1151            }
1152        }
1153
1154        public void assimilate(BitmapCache bitmapCache) {
1155            ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps;
1156            int count = bitmapsToBeAdded.size();
1157            for (int i = 0; i < count; i++) {
1158                Bitmap b = bitmapsToBeAdded.get(i);
1159                if (!mBitmaps.contains(b)) {
1160                    mBitmaps.add(b);
1161                }
1162            }
1163        }
1164
1165        public void addBitmapMemory(MemoryUsageCounter memoryCounter) {
1166            for (int i = 0; i < mBitmaps.size(); i++) {
1167                memoryCounter.addBitmapMemory(mBitmaps.get(i));
1168            }
1169        }
1170
1171        @Override
1172        protected BitmapCache clone() {
1173            BitmapCache bitmapCache = new BitmapCache();
1174            bitmapCache.mBitmaps.addAll(mBitmaps);
1175            return bitmapCache;
1176        }
1177    }
1178
1179    private class BitmapReflectionAction extends Action {
1180        int bitmapId;
1181        Bitmap bitmap;
1182        String methodName;
1183
1184        BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {
1185            this.bitmap = bitmap;
1186            this.viewId = viewId;
1187            this.methodName = methodName;
1188            bitmapId = mBitmapCache.getBitmapId(bitmap);
1189        }
1190
1191        BitmapReflectionAction(Parcel in) {
1192            viewId = in.readInt();
1193            methodName = in.readString();
1194            bitmapId = in.readInt();
1195            bitmap = mBitmapCache.getBitmapForId(bitmapId);
1196        }
1197
1198        @Override
1199        public void writeToParcel(Parcel dest, int flags) {
1200            dest.writeInt(BITMAP_REFLECTION_ACTION_TAG);
1201            dest.writeInt(viewId);
1202            dest.writeString(methodName);
1203            dest.writeInt(bitmapId);
1204        }
1205
1206        @Override
1207        public void apply(View root, ViewGroup rootParent,
1208                OnClickHandler handler) throws ActionException {
1209            ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
1210                    bitmap);
1211            ra.apply(root, rootParent, handler);
1212        }
1213
1214        @Override
1215        public void setBitmapCache(BitmapCache bitmapCache) {
1216            bitmapId = bitmapCache.getBitmapId(bitmap);
1217        }
1218
1219        public String getActionName() {
1220            return "BitmapReflectionAction";
1221        }
1222    }
1223
1224    /**
1225     * Base class for the reflection actions.
1226     */
1227    private final class ReflectionAction extends Action {
1228        static final int BOOLEAN = 1;
1229        static final int BYTE = 2;
1230        static final int SHORT = 3;
1231        static final int INT = 4;
1232        static final int LONG = 5;
1233        static final int FLOAT = 6;
1234        static final int DOUBLE = 7;
1235        static final int CHAR = 8;
1236        static final int STRING = 9;
1237        static final int CHAR_SEQUENCE = 10;
1238        static final int URI = 11;
1239        // BITMAP actions are never stored in the list of actions. They are only used locally
1240        // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache.
1241        static final int BITMAP = 12;
1242        static final int BUNDLE = 13;
1243        static final int INTENT = 14;
1244        static final int COLOR_STATE_LIST = 15;
1245        static final int ICON = 16;
1246
1247        String methodName;
1248        int type;
1249        Object value;
1250
1251        ReflectionAction(int viewId, String methodName, int type, Object value) {
1252            this.viewId = viewId;
1253            this.methodName = methodName;
1254            this.type = type;
1255            this.value = value;
1256        }
1257
1258        ReflectionAction(Parcel in) {
1259            this.viewId = in.readInt();
1260            this.methodName = in.readString();
1261            this.type = in.readInt();
1262            //noinspection ConstantIfStatement
1263            if (false) {
1264                Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
1265                        + " methodName=" + this.methodName + " type=" + this.type);
1266            }
1267
1268            // For some values that may have been null, we first check a flag to see if they were
1269            // written to the parcel.
1270            switch (this.type) {
1271                case BOOLEAN:
1272                    this.value = in.readInt() != 0;
1273                    break;
1274                case BYTE:
1275                    this.value = in.readByte();
1276                    break;
1277                case SHORT:
1278                    this.value = (short)in.readInt();
1279                    break;
1280                case INT:
1281                    this.value = in.readInt();
1282                    break;
1283                case LONG:
1284                    this.value = in.readLong();
1285                    break;
1286                case FLOAT:
1287                    this.value = in.readFloat();
1288                    break;
1289                case DOUBLE:
1290                    this.value = in.readDouble();
1291                    break;
1292                case CHAR:
1293                    this.value = (char)in.readInt();
1294                    break;
1295                case STRING:
1296                    this.value = in.readString();
1297                    break;
1298                case CHAR_SEQUENCE:
1299                    this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1300                    break;
1301                case URI:
1302                    if (in.readInt() != 0) {
1303                        this.value = Uri.CREATOR.createFromParcel(in);
1304                    }
1305                    break;
1306                case BITMAP:
1307                    if (in.readInt() != 0) {
1308                        this.value = Bitmap.CREATOR.createFromParcel(in);
1309                    }
1310                    break;
1311                case BUNDLE:
1312                    this.value = in.readBundle();
1313                    break;
1314                case INTENT:
1315                    if (in.readInt() != 0) {
1316                        this.value = Intent.CREATOR.createFromParcel(in);
1317                    }
1318                    break;
1319                case COLOR_STATE_LIST:
1320                    if (in.readInt() != 0) {
1321                        this.value = ColorStateList.CREATOR.createFromParcel(in);
1322                    }
1323                    break;
1324                case ICON:
1325                    if (in.readInt() != 0) {
1326                        this.value = Icon.CREATOR.createFromParcel(in);
1327                    }
1328                default:
1329                    break;
1330            }
1331        }
1332
1333        public void writeToParcel(Parcel out, int flags) {
1334            out.writeInt(REFLECTION_ACTION_TAG);
1335            out.writeInt(this.viewId);
1336            out.writeString(this.methodName);
1337            out.writeInt(this.type);
1338            //noinspection ConstantIfStatement
1339            if (false) {
1340                Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
1341                        + " methodName=" + this.methodName + " type=" + this.type);
1342            }
1343
1344            // For some values which are null, we record an integer flag to indicate whether
1345            // we have written a valid value to the parcel.
1346            switch (this.type) {
1347                case BOOLEAN:
1348                    out.writeInt((Boolean) this.value ? 1 : 0);
1349                    break;
1350                case BYTE:
1351                    out.writeByte((Byte) this.value);
1352                    break;
1353                case SHORT:
1354                    out.writeInt((Short) this.value);
1355                    break;
1356                case INT:
1357                    out.writeInt((Integer) this.value);
1358                    break;
1359                case LONG:
1360                    out.writeLong((Long) this.value);
1361                    break;
1362                case FLOAT:
1363                    out.writeFloat((Float) this.value);
1364                    break;
1365                case DOUBLE:
1366                    out.writeDouble((Double) this.value);
1367                    break;
1368                case CHAR:
1369                    out.writeInt((int)((Character)this.value).charValue());
1370                    break;
1371                case STRING:
1372                    out.writeString((String)this.value);
1373                    break;
1374                case CHAR_SEQUENCE:
1375                    TextUtils.writeToParcel((CharSequence)this.value, out, flags);
1376                    break;
1377                case URI:
1378                    out.writeInt(this.value != null ? 1 : 0);
1379                    if (this.value != null) {
1380                        ((Uri)this.value).writeToParcel(out, flags);
1381                    }
1382                    break;
1383                case BITMAP:
1384                    out.writeInt(this.value != null ? 1 : 0);
1385                    if (this.value != null) {
1386                        ((Bitmap)this.value).writeToParcel(out, flags);
1387                    }
1388                    break;
1389                case BUNDLE:
1390                    out.writeBundle((Bundle) this.value);
1391                    break;
1392                case INTENT:
1393                    out.writeInt(this.value != null ? 1 : 0);
1394                    if (this.value != null) {
1395                        ((Intent)this.value).writeToParcel(out, flags);
1396                    }
1397                    break;
1398                case COLOR_STATE_LIST:
1399                    out.writeInt(this.value != null ? 1 : 0);
1400                    if (this.value != null) {
1401                        ((ColorStateList)this.value).writeToParcel(out, flags);
1402                    }
1403                    break;
1404                case ICON:
1405                    out.writeInt(this.value != null ? 1 : 0);
1406                    if (this.value != null) {
1407                        ((Icon)this.value).writeToParcel(out, flags);
1408                    }
1409                    break;
1410                default:
1411                    break;
1412            }
1413        }
1414
1415        private Class<?> getParameterType() {
1416            switch (this.type) {
1417                case BOOLEAN:
1418                    return boolean.class;
1419                case BYTE:
1420                    return byte.class;
1421                case SHORT:
1422                    return short.class;
1423                case INT:
1424                    return int.class;
1425                case LONG:
1426                    return long.class;
1427                case FLOAT:
1428                    return float.class;
1429                case DOUBLE:
1430                    return double.class;
1431                case CHAR:
1432                    return char.class;
1433                case STRING:
1434                    return String.class;
1435                case CHAR_SEQUENCE:
1436                    return CharSequence.class;
1437                case URI:
1438                    return Uri.class;
1439                case BITMAP:
1440                    return Bitmap.class;
1441                case BUNDLE:
1442                    return Bundle.class;
1443                case INTENT:
1444                    return Intent.class;
1445                case COLOR_STATE_LIST:
1446                    return ColorStateList.class;
1447                case ICON:
1448                    return Icon.class;
1449                default:
1450                    return null;
1451            }
1452        }
1453
1454        @Override
1455        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1456            final View view = root.findViewById(viewId);
1457            if (view == null) return;
1458
1459            Class<?> param = getParameterType();
1460            if (param == null) {
1461                throw new ActionException("bad type: " + this.type);
1462            }
1463
1464            try {
1465                getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
1466            } catch (ActionException e) {
1467                throw e;
1468            } catch (Exception ex) {
1469                throw new ActionException(ex);
1470            }
1471        }
1472
1473        @Override
1474        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
1475            final View view = root.findViewById(viewId);
1476            if (view == null) return ACTION_NOOP;
1477
1478            Class<?> param = getParameterType();
1479            if (param == null) {
1480                throw new ActionException("bad type: " + this.type);
1481            }
1482
1483            try {
1484                Method method = getMethod(view, this.methodName, param);
1485                Method asyncMethod = getAsyncMethod(method);
1486
1487                if (asyncMethod != null) {
1488                    Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value));
1489                    if (endAction == null) {
1490                        return ACTION_NOOP;
1491                    } else {
1492                        // Special case view stub
1493                        if (endAction instanceof ViewStub.ViewReplaceRunnable) {
1494                            root.createTree();
1495                            // Replace child tree
1496                            root.findViewTreeById(viewId).replaceView(
1497                                    ((ViewStub.ViewReplaceRunnable) endAction).view);
1498                        }
1499                        return new RunnableAction(endAction);
1500                    }
1501                }
1502            } catch (ActionException e) {
1503                throw e;
1504            } catch (Exception ex) {
1505                throw new ActionException(ex);
1506            }
1507
1508            return this;
1509        }
1510
1511        public int mergeBehavior() {
1512            // smoothScrollBy is cumulative, everything else overwites.
1513            if (methodName.equals("smoothScrollBy")) {
1514                return MERGE_APPEND;
1515            } else {
1516                return MERGE_REPLACE;
1517            }
1518        }
1519
1520        public String getActionName() {
1521            // Each type of reflection action corresponds to a setter, so each should be seen as
1522            // unique from the standpoint of merging.
1523            return "ReflectionAction" + this.methodName + this.type;
1524        }
1525
1526        @Override
1527        public boolean prefersAsyncApply() {
1528            return this.type == URI || this.type == ICON;
1529        }
1530    }
1531
1532    /**
1533     * This is only used for async execution of actions and it not parcelable.
1534     */
1535    private static final class RunnableAction extends RuntimeAction {
1536        private final Runnable mRunnable;
1537
1538        RunnableAction(Runnable r) {
1539            mRunnable = r;
1540        }
1541
1542        @Override
1543        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1544            mRunnable.run();
1545        }
1546    }
1547
1548    private void configureRemoteViewsAsChild(RemoteViews rv) {
1549        mBitmapCache.assimilate(rv.mBitmapCache);
1550        rv.setBitmapCache(mBitmapCache);
1551        rv.setNotRoot();
1552    }
1553
1554    void setNotRoot() {
1555        mIsRoot = false;
1556    }
1557
1558    /**
1559     * ViewGroup methods that are related to adding Views.
1560     */
1561    private class ViewGroupActionAdd extends Action {
1562        private RemoteViews mNestedViews;
1563        private int mIndex;
1564
1565        ViewGroupActionAdd(int viewId, RemoteViews nestedViews) {
1566            this(viewId, nestedViews, -1 /* index */);
1567        }
1568
1569        ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index) {
1570            this.viewId = viewId;
1571            mNestedViews = nestedViews;
1572            mIndex = index;
1573            if (nestedViews != null) {
1574                configureRemoteViewsAsChild(nestedViews);
1575            }
1576        }
1577
1578        ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
1579                int depth) {
1580            viewId = parcel.readInt();
1581            mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth);
1582        }
1583
1584        public void writeToParcel(Parcel dest, int flags) {
1585            dest.writeInt(VIEW_GROUP_ACTION_ADD_TAG);
1586            dest.writeInt(viewId);
1587            mNestedViews.writeToParcel(dest, flags);
1588        }
1589
1590        @Override
1591        public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
1592            return mNestedViews.mApplication.packageName.equals(parentInfo.packageName)
1593                    && mNestedViews.mApplication.uid == parentInfo.uid;
1594        }
1595
1596        @Override
1597        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1598            final Context context = root.getContext();
1599            final ViewGroup target = root.findViewById(viewId);
1600
1601            if (target == null) {
1602                return;
1603            }
1604
1605            // Inflate nested views and add as children
1606            target.addView(mNestedViews.apply(context, target, handler), mIndex);
1607        }
1608
1609        @Override
1610        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
1611            // In the async implementation, update the view tree so that subsequent calls to
1612            // findViewById return the current view.
1613            root.createTree();
1614            ViewTree target = root.findViewTreeById(viewId);
1615            if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
1616                return ACTION_NOOP;
1617            }
1618            final ViewGroup targetVg = (ViewGroup) target.mRoot;
1619
1620            // Inflate nested views and perform all the async tasks for the child remoteView.
1621            final Context context = root.mRoot.getContext();
1622            final AsyncApplyTask task = mNestedViews.getAsyncApplyTask(
1623                    context, targetVg, null, handler);
1624            final ViewTree tree = task.doInBackground();
1625
1626            if (tree == null) {
1627                throw new ActionException(task.mError);
1628            }
1629
1630            // Update the global view tree, so that next call to findViewTreeById
1631            // goes through the subtree as well.
1632            target.addChild(tree, mIndex);
1633
1634            return new RuntimeAction() {
1635                @Override
1636                public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
1637                        throws ActionException {
1638                    task.onPostExecute(tree);
1639                    targetVg.addView(task.mResult, mIndex);
1640                }
1641            };
1642        }
1643
1644        @Override
1645        public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
1646            counter.increment(mNestedViews.estimateMemoryUsage());
1647        }
1648
1649        @Override
1650        public void setBitmapCache(BitmapCache bitmapCache) {
1651            mNestedViews.setBitmapCache(bitmapCache);
1652        }
1653
1654        @Override
1655        public int mergeBehavior() {
1656            return MERGE_APPEND;
1657        }
1658
1659        @Override
1660        public boolean prefersAsyncApply() {
1661            return mNestedViews.prefersAsyncApply();
1662        }
1663
1664
1665        @Override
1666        public String getActionName() {
1667            return "ViewGroupActionAdd";
1668        }
1669    }
1670
1671    /**
1672     * ViewGroup methods related to removing child views.
1673     */
1674    private class ViewGroupActionRemove extends Action {
1675        /**
1676         * Id that indicates that all child views of the affected ViewGroup should be removed.
1677         *
1678         * <p>Using -2 because the default id is -1. This avoids accidentally matching that.
1679         */
1680        private static final int REMOVE_ALL_VIEWS_ID = -2;
1681
1682        private int mViewIdToKeep;
1683
1684        ViewGroupActionRemove(int viewId) {
1685            this(viewId, REMOVE_ALL_VIEWS_ID);
1686        }
1687
1688        ViewGroupActionRemove(int viewId, int viewIdToKeep) {
1689            this.viewId = viewId;
1690            mViewIdToKeep = viewIdToKeep;
1691        }
1692
1693        ViewGroupActionRemove(Parcel parcel) {
1694            viewId = parcel.readInt();
1695            mViewIdToKeep = parcel.readInt();
1696        }
1697
1698        public void writeToParcel(Parcel dest, int flags) {
1699            dest.writeInt(VIEW_GROUP_ACTION_REMOVE_TAG);
1700            dest.writeInt(viewId);
1701            dest.writeInt(mViewIdToKeep);
1702        }
1703
1704        @Override
1705        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1706            final ViewGroup target = root.findViewById(viewId);
1707
1708            if (target == null) {
1709                return;
1710            }
1711
1712            if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
1713                target.removeAllViews();
1714                return;
1715            }
1716
1717            removeAllViewsExceptIdToKeep(target);
1718        }
1719
1720        @Override
1721        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
1722            // In the async implementation, update the view tree so that subsequent calls to
1723            // findViewById return the current view.
1724            root.createTree();
1725            ViewTree target = root.findViewTreeById(viewId);
1726
1727            if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
1728                return ACTION_NOOP;
1729            }
1730
1731            final ViewGroup targetVg = (ViewGroup) target.mRoot;
1732
1733            // Clear all children when nested views omitted
1734            target.mChildren = null;
1735            return new RuntimeAction() {
1736                @Override
1737                public void apply(View root, ViewGroup rootParent, OnClickHandler handler)
1738                        throws ActionException {
1739                    if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
1740                        targetVg.removeAllViews();
1741                        return;
1742                    }
1743
1744                    removeAllViewsExceptIdToKeep(targetVg);
1745                }
1746            };
1747        }
1748
1749        /**
1750         * Iterates through the children in the given ViewGroup and removes all the views that
1751         * do not have an id of {@link #mViewIdToKeep}.
1752         */
1753        private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) {
1754            // Otherwise, remove all the views that do not match the id to keep.
1755            int index = viewGroup.getChildCount() - 1;
1756            while (index >= 0) {
1757                if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) {
1758                    viewGroup.removeViewAt(index);
1759                }
1760                index--;
1761            }
1762        }
1763
1764        @Override
1765        public String getActionName() {
1766            return "ViewGroupActionRemove";
1767        }
1768
1769        @Override
1770        public int mergeBehavior() {
1771            return MERGE_APPEND;
1772        }
1773    }
1774
1775    /**
1776     * Helper action to set compound drawables on a TextView. Supports relative
1777     * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
1778     */
1779    private class TextViewDrawableAction extends Action {
1780        public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) {
1781            this.viewId = viewId;
1782            this.isRelative = isRelative;
1783            this.useIcons = false;
1784            this.d1 = d1;
1785            this.d2 = d2;
1786            this.d3 = d3;
1787            this.d4 = d4;
1788        }
1789
1790        public TextViewDrawableAction(int viewId, boolean isRelative,
1791                Icon i1, Icon i2, Icon i3, Icon i4) {
1792            this.viewId = viewId;
1793            this.isRelative = isRelative;
1794            this.useIcons = true;
1795            this.i1 = i1;
1796            this.i2 = i2;
1797            this.i3 = i3;
1798            this.i4 = i4;
1799        }
1800
1801        public TextViewDrawableAction(Parcel parcel) {
1802            viewId = parcel.readInt();
1803            isRelative = (parcel.readInt() != 0);
1804            useIcons = (parcel.readInt() != 0);
1805            if (useIcons) {
1806                if (parcel.readInt() != 0) {
1807                    i1 = Icon.CREATOR.createFromParcel(parcel);
1808                }
1809                if (parcel.readInt() != 0) {
1810                    i2 = Icon.CREATOR.createFromParcel(parcel);
1811                }
1812                if (parcel.readInt() != 0) {
1813                    i3 = Icon.CREATOR.createFromParcel(parcel);
1814                }
1815                if (parcel.readInt() != 0) {
1816                    i4 = Icon.CREATOR.createFromParcel(parcel);
1817                }
1818            } else {
1819                d1 = parcel.readInt();
1820                d2 = parcel.readInt();
1821                d3 = parcel.readInt();
1822                d4 = parcel.readInt();
1823            }
1824        }
1825
1826        public void writeToParcel(Parcel dest, int flags) {
1827            dest.writeInt(TEXT_VIEW_DRAWABLE_ACTION_TAG);
1828            dest.writeInt(viewId);
1829            dest.writeInt(isRelative ? 1 : 0);
1830            dest.writeInt(useIcons ? 1 : 0);
1831            if (useIcons) {
1832                if (i1 != null) {
1833                    dest.writeInt(1);
1834                    i1.writeToParcel(dest, 0);
1835                } else {
1836                    dest.writeInt(0);
1837                }
1838                if (i2 != null) {
1839                    dest.writeInt(1);
1840                    i2.writeToParcel(dest, 0);
1841                } else {
1842                    dest.writeInt(0);
1843                }
1844                if (i3 != null) {
1845                    dest.writeInt(1);
1846                    i3.writeToParcel(dest, 0);
1847                } else {
1848                    dest.writeInt(0);
1849                }
1850                if (i4 != null) {
1851                    dest.writeInt(1);
1852                    i4.writeToParcel(dest, 0);
1853                } else {
1854                    dest.writeInt(0);
1855                }
1856            } else {
1857                dest.writeInt(d1);
1858                dest.writeInt(d2);
1859                dest.writeInt(d3);
1860                dest.writeInt(d4);
1861            }
1862        }
1863
1864        @Override
1865        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1866            final TextView target = root.findViewById(viewId);
1867            if (target == null) return;
1868            if (drawablesLoaded) {
1869                if (isRelative) {
1870                    target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
1871                } else {
1872                    target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
1873                }
1874            } else if (useIcons) {
1875                final Context ctx = target.getContext();
1876                final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx);
1877                final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx);
1878                final Drawable id3 = i3 == null ? null : i3.loadDrawable(ctx);
1879                final Drawable id4 = i4 == null ? null : i4.loadDrawable(ctx);
1880                if (isRelative) {
1881                    target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
1882                } else {
1883                    target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
1884                }
1885            } else {
1886                if (isRelative) {
1887                    target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
1888                } else {
1889                    target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
1890                }
1891            }
1892        }
1893
1894        @Override
1895        public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
1896            final TextView target = root.findViewById(viewId);
1897            if (target == null) return ACTION_NOOP;
1898
1899            TextViewDrawableAction copy = useIcons ?
1900                    new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) :
1901                    new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4);
1902
1903            // Load the drawables on the background thread.
1904            copy.drawablesLoaded = true;
1905            final Context ctx = target.getContext();
1906
1907            if (useIcons) {
1908                copy.id1 = i1 == null ? null : i1.loadDrawable(ctx);
1909                copy.id2 = i2 == null ? null : i2.loadDrawable(ctx);
1910                copy.id3 = i3 == null ? null : i3.loadDrawable(ctx);
1911                copy.id4 = i4 == null ? null : i4.loadDrawable(ctx);
1912            } else {
1913                copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1);
1914                copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2);
1915                copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3);
1916                copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4);
1917            }
1918            return copy;
1919        }
1920
1921        @Override
1922        public boolean prefersAsyncApply() {
1923            return useIcons;
1924        }
1925
1926        public String getActionName() {
1927            return "TextViewDrawableAction";
1928        }
1929
1930        boolean isRelative = false;
1931        boolean useIcons = false;
1932        int d1, d2, d3, d4;
1933        Icon i1, i2, i3, i4;
1934
1935        boolean drawablesLoaded = false;
1936        Drawable id1, id2, id3, id4;
1937    }
1938
1939    /**
1940     * Helper action to set text size on a TextView in any supported units.
1941     */
1942    private class TextViewSizeAction extends Action {
1943        public TextViewSizeAction(int viewId, int units, float size) {
1944            this.viewId = viewId;
1945            this.units = units;
1946            this.size = size;
1947        }
1948
1949        public TextViewSizeAction(Parcel parcel) {
1950            viewId = parcel.readInt();
1951            units = parcel.readInt();
1952            size  = parcel.readFloat();
1953        }
1954
1955        public void writeToParcel(Parcel dest, int flags) {
1956            dest.writeInt(TEXT_VIEW_SIZE_ACTION_TAG);
1957            dest.writeInt(viewId);
1958            dest.writeInt(units);
1959            dest.writeFloat(size);
1960        }
1961
1962        @Override
1963        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1964            final TextView target = root.findViewById(viewId);
1965            if (target == null) return;
1966            target.setTextSize(units, size);
1967        }
1968
1969        public String getActionName() {
1970            return "TextViewSizeAction";
1971        }
1972
1973        int units;
1974        float size;
1975    }
1976
1977    /**
1978     * Helper action to set padding on a View.
1979     */
1980    private class ViewPaddingAction extends Action {
1981        public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) {
1982            this.viewId = viewId;
1983            this.left = left;
1984            this.top = top;
1985            this.right = right;
1986            this.bottom = bottom;
1987        }
1988
1989        public ViewPaddingAction(Parcel parcel) {
1990            viewId = parcel.readInt();
1991            left = parcel.readInt();
1992            top = parcel.readInt();
1993            right = parcel.readInt();
1994            bottom = parcel.readInt();
1995        }
1996
1997        public void writeToParcel(Parcel dest, int flags) {
1998            dest.writeInt(VIEW_PADDING_ACTION_TAG);
1999            dest.writeInt(viewId);
2000            dest.writeInt(left);
2001            dest.writeInt(top);
2002            dest.writeInt(right);
2003            dest.writeInt(bottom);
2004        }
2005
2006        @Override
2007        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
2008            final View target = root.findViewById(viewId);
2009            if (target == null) return;
2010            target.setPadding(left, top, right, bottom);
2011        }
2012
2013        public String getActionName() {
2014            return "ViewPaddingAction";
2015        }
2016
2017        int left, top, right, bottom;
2018    }
2019
2020    /**
2021     * Helper action to set layout params on a View.
2022     */
2023    private static class LayoutParamAction extends Action {
2024
2025        /** Set marginEnd */
2026        public static final int LAYOUT_MARGIN_END_DIMEN = 1;
2027        /** Set width */
2028        public static final int LAYOUT_WIDTH = 2;
2029        public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3;
2030
2031        /**
2032         * @param viewId ID of the view alter
2033         * @param property which layout parameter to alter
2034         * @param value new value of the layout parameter
2035         */
2036        public LayoutParamAction(int viewId, int property, int value) {
2037            this.viewId = viewId;
2038            this.property = property;
2039            this.value = value;
2040        }
2041
2042        public LayoutParamAction(Parcel parcel) {
2043            viewId = parcel.readInt();
2044            property = parcel.readInt();
2045            value = parcel.readInt();
2046        }
2047
2048        public void writeToParcel(Parcel dest, int flags) {
2049            dest.writeInt(LAYOUT_PARAM_ACTION_TAG);
2050            dest.writeInt(viewId);
2051            dest.writeInt(property);
2052            dest.writeInt(value);
2053        }
2054
2055        @Override
2056        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
2057            final View target = root.findViewById(viewId);
2058            if (target == null) {
2059                return;
2060            }
2061            ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
2062            if (layoutParams == null) {
2063                return;
2064            }
2065            switch (property) {
2066                case LAYOUT_MARGIN_END_DIMEN:
2067                    if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
2068                        int resolved = resolveDimenPixelOffset(target, value);
2069                        ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(resolved);
2070                        target.setLayoutParams(layoutParams);
2071                    }
2072                    break;
2073                case LAYOUT_MARGIN_BOTTOM_DIMEN:
2074                    if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
2075                        int resolved = resolveDimenPixelOffset(target, value);
2076                        ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved;
2077                        target.setLayoutParams(layoutParams);
2078                    }
2079                    break;
2080                case LAYOUT_WIDTH:
2081                    layoutParams.width = value;
2082                    target.setLayoutParams(layoutParams);
2083                    break;
2084                default:
2085                    throw new IllegalArgumentException("Unknown property " + property);
2086            }
2087        }
2088
2089        private static int resolveDimenPixelOffset(View target, int value) {
2090            if (value == 0) {
2091                return 0;
2092            }
2093            return target.getContext().getResources().getDimensionPixelOffset(value);
2094        }
2095
2096        public String getActionName() {
2097            return "LayoutParamAction" + property + ".";
2098        }
2099
2100        int property;
2101        int value;
2102    }
2103
2104    /**
2105     * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
2106     * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
2107     */
2108    private class TextViewDrawableColorFilterAction extends Action {
2109        public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index,
2110                int color, PorterDuff.Mode mode) {
2111            this.viewId = viewId;
2112            this.isRelative = isRelative;
2113            this.index = index;
2114            this.color = color;
2115            this.mode = mode;
2116        }
2117
2118        public TextViewDrawableColorFilterAction(Parcel parcel) {
2119            viewId = parcel.readInt();
2120            isRelative = (parcel.readInt() != 0);
2121            index = parcel.readInt();
2122            color = parcel.readInt();
2123            mode = readPorterDuffMode(parcel);
2124        }
2125
2126        private PorterDuff.Mode readPorterDuffMode(Parcel parcel) {
2127            int mode = parcel.readInt();
2128            if (mode >= 0 && mode < PorterDuff.Mode.values().length) {
2129                return PorterDuff.Mode.values()[mode];
2130            } else {
2131                return PorterDuff.Mode.CLEAR;
2132            }
2133        }
2134
2135        public void writeToParcel(Parcel dest, int flags) {
2136            dest.writeInt(TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG);
2137            dest.writeInt(viewId);
2138            dest.writeInt(isRelative ? 1 : 0);
2139            dest.writeInt(index);
2140            dest.writeInt(color);
2141            dest.writeInt(mode.ordinal());
2142        }
2143
2144        @Override
2145        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
2146            final TextView target = root.findViewById(viewId);
2147            if (target == null) return;
2148            Drawable[] drawables = isRelative
2149                    ? target.getCompoundDrawablesRelative()
2150                    : target.getCompoundDrawables();
2151            if (index < 0 || index >= 4) {
2152                throw new IllegalStateException("index must be in range [0, 3].");
2153            }
2154            Drawable d = drawables[index];
2155            if (d != null) {
2156                d.mutate();
2157                d.setColorFilter(color, mode);
2158            }
2159        }
2160
2161        public String getActionName() {
2162            return "TextViewDrawableColorFilterAction";
2163        }
2164
2165        final boolean isRelative;
2166        final int index;
2167        final int color;
2168        final PorterDuff.Mode mode;
2169    }
2170
2171    /**
2172     * Helper action to add a view tag with RemoteInputs.
2173     */
2174    private class SetRemoteInputsAction extends Action {
2175
2176        public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) {
2177            this.viewId = viewId;
2178            this.remoteInputs = remoteInputs;
2179        }
2180
2181        public SetRemoteInputsAction(Parcel parcel) {
2182            viewId = parcel.readInt();
2183            remoteInputs = parcel.createTypedArray(RemoteInput.CREATOR);
2184        }
2185
2186        public void writeToParcel(Parcel dest, int flags) {
2187            dest.writeInt(SET_REMOTE_INPUTS_ACTION_TAG);
2188            dest.writeInt(viewId);
2189            dest.writeTypedArray(remoteInputs, flags);
2190        }
2191
2192        @Override
2193        public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
2194            final View target = root.findViewById(viewId);
2195            if (target == null) return;
2196
2197            target.setTagInternal(R.id.remote_input_tag, remoteInputs);
2198        }
2199
2200        public String getActionName() {
2201            return "SetRemoteInputsAction";
2202        }
2203
2204        final Parcelable[] remoteInputs;
2205    }
2206
2207    /**
2208     * Simple class used to keep track of memory usage in a RemoteViews.
2209     *
2210     */
2211    private class MemoryUsageCounter {
2212        public void clear() {
2213            mMemoryUsage = 0;
2214        }
2215
2216        public void increment(int numBytes) {
2217            mMemoryUsage += numBytes;
2218        }
2219
2220        public int getMemoryUsage() {
2221            return mMemoryUsage;
2222        }
2223
2224        public void addBitmapMemory(Bitmap b) {
2225            increment(b.getAllocationByteCount());
2226        }
2227
2228        int mMemoryUsage;
2229    }
2230
2231    /**
2232     * Create a new RemoteViews object that will display the views contained
2233     * in the specified layout file.
2234     *
2235     * @param packageName Name of the package that contains the layout resource
2236     * @param layoutId The id of the layout resource
2237     */
2238    public RemoteViews(String packageName, int layoutId) {
2239        this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
2240    }
2241
2242    /**
2243     * Create a new RemoteViews object that will display the views contained
2244     * in the specified layout file.
2245     *
2246     * @param packageName Name of the package that contains the layout resource.
2247     * @param userId The user under which the package is running.
2248     * @param layoutId The id of the layout resource.
2249     *
2250     * @hide
2251     */
2252    public RemoteViews(String packageName, int userId, int layoutId) {
2253        this(getApplicationInfo(packageName, userId), layoutId);
2254    }
2255
2256    /**
2257     * Create a new RemoteViews object that will display the views contained
2258     * in the specified layout file.
2259     *
2260     * @param application The application whose content is shown by the views.
2261     * @param layoutId The id of the layout resource.
2262     *
2263     * @hide
2264     */
2265    protected RemoteViews(ApplicationInfo application, int layoutId) {
2266        mApplication = application;
2267        mLayoutId = layoutId;
2268        mBitmapCache = new BitmapCache();
2269        // setup the memory usage statistics
2270        mMemoryUsageCounter = new MemoryUsageCounter();
2271        recalculateMemoryUsage();
2272    }
2273
2274    private boolean hasLandscapeAndPortraitLayouts() {
2275        return (mLandscape != null) && (mPortrait != null);
2276    }
2277
2278    /**
2279     * Create a new RemoteViews object that will inflate as the specified
2280     * landspace or portrait RemoteViews, depending on the current configuration.
2281     *
2282     * @param landscape The RemoteViews to inflate in landscape configuration
2283     * @param portrait The RemoteViews to inflate in portrait configuration
2284     */
2285    public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
2286        if (landscape == null || portrait == null) {
2287            throw new RuntimeException("Both RemoteViews must be non-null");
2288        }
2289        if (landscape.mApplication.uid != portrait.mApplication.uid
2290                || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) {
2291            throw new RuntimeException("Both RemoteViews must share the same package and user");
2292        }
2293        mApplication = portrait.mApplication;
2294        mLayoutId = portrait.getLayoutId();
2295
2296        mLandscape = landscape;
2297        mPortrait = portrait;
2298
2299        // setup the memory usage statistics
2300        mMemoryUsageCounter = new MemoryUsageCounter();
2301
2302        mBitmapCache = new BitmapCache();
2303        configureRemoteViewsAsChild(landscape);
2304        configureRemoteViewsAsChild(portrait);
2305
2306        recalculateMemoryUsage();
2307    }
2308
2309    /**
2310     * Reads a RemoteViews object from a parcel.
2311     *
2312     * @param parcel
2313     */
2314    public RemoteViews(Parcel parcel) {
2315        this(parcel, null, null, 0);
2316    }
2317
2318    private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth) {
2319        if (depth > MAX_NESTED_VIEWS
2320                && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) {
2321            throw new IllegalArgumentException("Too many nested views.");
2322        }
2323        depth++;
2324
2325        int mode = parcel.readInt();
2326
2327        // We only store a bitmap cache in the root of the RemoteViews.
2328        if (bitmapCache == null) {
2329            mBitmapCache = new BitmapCache(parcel);
2330        } else {
2331            setBitmapCache(bitmapCache);
2332            setNotRoot();
2333        }
2334
2335        if (mode == MODE_NORMAL) {
2336            mApplication = parcel.readInt() == 0 ? info :
2337                    ApplicationInfo.CREATOR.createFromParcel(parcel);
2338            mLayoutId = parcel.readInt();
2339            mIsWidgetCollectionChild = parcel.readInt() == 1;
2340
2341            int count = parcel.readInt();
2342            if (count > 0) {
2343                mActions = new ArrayList<Action>(count);
2344                for (int i=0; i<count; i++) {
2345                    int tag = parcel.readInt();
2346                    switch (tag) {
2347                        case SET_ON_CLICK_PENDING_INTENT_TAG:
2348                            mActions.add(new SetOnClickPendingIntent(parcel));
2349                            break;
2350                        case SET_DRAWABLE_PARAMETERS_TAG:
2351                            mActions.add(new SetDrawableParameters(parcel));
2352                            break;
2353                        case REFLECTION_ACTION_TAG:
2354                            mActions.add(new ReflectionAction(parcel));
2355                            break;
2356                        case VIEW_GROUP_ACTION_ADD_TAG:
2357                            mActions.add(new ViewGroupActionAdd(parcel, mBitmapCache, mApplication,
2358                                    depth));
2359                            break;
2360                        case VIEW_GROUP_ACTION_REMOVE_TAG:
2361                            mActions.add(new ViewGroupActionRemove(parcel));
2362                            break;
2363                        case SET_REFLECTION_ACTION_WITHOUT_PARAMS_TAG:
2364                            mActions.add(new ReflectionActionWithoutParams(parcel));
2365                            break;
2366                        case SET_EMPTY_VIEW_ACTION_TAG:
2367                            mActions.add(new SetEmptyView(parcel));
2368                            break;
2369                        case SET_PENDING_INTENT_TEMPLATE_TAG:
2370                            mActions.add(new SetPendingIntentTemplate(parcel));
2371                            break;
2372                        case SET_ON_CLICK_FILL_IN_INTENT_TAG:
2373                            mActions.add(new SetOnClickFillInIntent(parcel));
2374                            break;
2375                        case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG:
2376                            mActions.add(new SetRemoteViewsAdapterIntent(parcel));
2377                            break;
2378                        case TEXT_VIEW_DRAWABLE_ACTION_TAG:
2379                            mActions.add(new TextViewDrawableAction(parcel));
2380                            break;
2381                        case TEXT_VIEW_SIZE_ACTION_TAG:
2382                            mActions.add(new TextViewSizeAction(parcel));
2383                            break;
2384                        case VIEW_PADDING_ACTION_TAG:
2385                            mActions.add(new ViewPaddingAction(parcel));
2386                            break;
2387                        case BITMAP_REFLECTION_ACTION_TAG:
2388                            mActions.add(new BitmapReflectionAction(parcel));
2389                            break;
2390                        case SET_REMOTE_VIEW_ADAPTER_LIST_TAG:
2391                            mActions.add(new SetRemoteViewsAdapterList(parcel));
2392                            break;
2393                        case TEXT_VIEW_DRAWABLE_COLOR_FILTER_ACTION_TAG:
2394                            mActions.add(new TextViewDrawableColorFilterAction(parcel));
2395                            break;
2396                        case SET_REMOTE_INPUTS_ACTION_TAG:
2397                            mActions.add(new SetRemoteInputsAction(parcel));
2398                            break;
2399                        case LAYOUT_PARAM_ACTION_TAG:
2400                            mActions.add(new LayoutParamAction(parcel));
2401                            break;
2402                        default:
2403                            throw new ActionException("Tag " + tag + " not found");
2404                    }
2405                }
2406            }
2407        } else {
2408            // MODE_HAS_LANDSCAPE_AND_PORTRAIT
2409            mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth);
2410            mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth);
2411            mApplication = mPortrait.mApplication;
2412            mLayoutId = mPortrait.getLayoutId();
2413        }
2414
2415        // setup the memory usage statistics
2416        mMemoryUsageCounter = new MemoryUsageCounter();
2417        recalculateMemoryUsage();
2418    }
2419
2420
2421    public synchronized RemoteViews clone() {
2422        Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. "
2423                + "May only clone the root of a RemoteView hierarchy.");
2424
2425        Parcel p = Parcel.obtain();
2426
2427        // Do not parcel the Bitmap cache - doing so creates an expensive copy of all bitmaps.
2428        // Instead pretend we're not owning the cache while parceling.
2429        mIsRoot = false;
2430        writeToParcel(p, PARCELABLE_ELIDE_DUPLICATES);
2431        p.setDataPosition(0);
2432        mIsRoot = true;
2433
2434        RemoteViews rv = new RemoteViews(p, mBitmapCache.clone(), mApplication, 0);
2435        rv.mIsRoot = true;
2436
2437        p.recycle();
2438        return rv;
2439    }
2440
2441    public String getPackage() {
2442        return (mApplication != null) ? mApplication.packageName : null;
2443    }
2444
2445    /**
2446     * Returns the layout id of the root layout associated with this RemoteViews. In the case
2447     * that the RemoteViews has both a landscape and portrait root, this will return the layout
2448     * id associated with the portrait layout.
2449     *
2450     * @return the layout id.
2451     */
2452    public int getLayoutId() {
2453        return mLayoutId;
2454    }
2455
2456    /*
2457     * This flag indicates whether this RemoteViews object is being created from a
2458     * RemoteViewsService for use as a child of a widget collection. This flag is used
2459     * to determine whether or not certain features are available, in particular,
2460     * setting on click extras and setting on click pending intents. The former is enabled,
2461     * and the latter disabled when this flag is true.
2462     */
2463    void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
2464        mIsWidgetCollectionChild = isWidgetCollectionChild;
2465    }
2466
2467    /**
2468     * Updates the memory usage statistics.
2469     */
2470    private void recalculateMemoryUsage() {
2471        mMemoryUsageCounter.clear();
2472
2473        if (!hasLandscapeAndPortraitLayouts()) {
2474            // Accumulate the memory usage for each action
2475            if (mActions != null) {
2476                final int count = mActions.size();
2477                for (int i= 0; i < count; ++i) {
2478                    mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
2479                }
2480            }
2481            if (mIsRoot) {
2482                mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
2483            }
2484        } else {
2485            mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage());
2486            mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage());
2487            mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
2488        }
2489    }
2490
2491    /**
2492     * Recursively sets BitmapCache in the hierarchy and update the bitmap ids.
2493     */
2494    private void setBitmapCache(BitmapCache bitmapCache) {
2495        mBitmapCache = bitmapCache;
2496        if (!hasLandscapeAndPortraitLayouts()) {
2497            if (mActions != null) {
2498                final int count = mActions.size();
2499                for (int i= 0; i < count; ++i) {
2500                    mActions.get(i).setBitmapCache(bitmapCache);
2501                }
2502            }
2503        } else {
2504            mLandscape.setBitmapCache(bitmapCache);
2505            mPortrait.setBitmapCache(bitmapCache);
2506        }
2507    }
2508
2509    /**
2510     * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
2511     */
2512    /** @hide */
2513    public int estimateMemoryUsage() {
2514        return mMemoryUsageCounter.getMemoryUsage();
2515    }
2516
2517    /**
2518     * Add an action to be executed on the remote side when apply is called.
2519     *
2520     * @param a The action to add
2521     */
2522    private void addAction(Action a) {
2523        if (hasLandscapeAndPortraitLayouts()) {
2524            throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
2525                    " layouts cannot be modified. Instead, fully configure the landscape and" +
2526                    " portrait layouts individually before constructing the combined layout.");
2527        }
2528        if (mActions == null) {
2529            mActions = new ArrayList<Action>();
2530        }
2531        mActions.add(a);
2532
2533        // update the memory usage stats
2534        a.updateMemoryUsageEstimate(mMemoryUsageCounter);
2535    }
2536
2537    /**
2538     * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
2539     * given {@link RemoteViews}. This allows users to build "nested"
2540     * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
2541     * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
2542     * children.
2543     *
2544     * @param viewId The id of the parent {@link ViewGroup} to add child into.
2545     * @param nestedView {@link RemoteViews} that describes the child.
2546     */
2547    public void addView(int viewId, RemoteViews nestedView) {
2548        addAction(nestedView == null
2549                ? new ViewGroupActionRemove(viewId)
2550                : new ViewGroupActionAdd(viewId, nestedView));
2551    }
2552
2553    /**
2554     * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the
2555     * given {@link RemoteViews}.
2556     *
2557     * @param viewId The id of the parent {@link ViewGroup} to add the child into.
2558     * @param nestedView {@link RemoveViews} of the child to add.
2559     * @param index The position at which to add the child.
2560     *
2561     * @hide
2562     */
2563    public void addView(int viewId, RemoteViews nestedView, int index) {
2564        addAction(new ViewGroupActionAdd(viewId, nestedView, index));
2565    }
2566
2567    /**
2568     * Equivalent to calling {@link ViewGroup#removeAllViews()}.
2569     *
2570     * @param viewId The id of the parent {@link ViewGroup} to remove all
2571     *            children from.
2572     */
2573    public void removeAllViews(int viewId) {
2574        addAction(new ViewGroupActionRemove(viewId));
2575    }
2576
2577    /**
2578     * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any
2579     * child that has the {@code viewIdToKeep} as its id.
2580     *
2581     * @param viewId The id of the parent {@link ViewGroup} to remove children from.
2582     * @param viewIdToKeep The id of a child that should not be removed.
2583     *
2584     * @hide
2585     */
2586    public void removeAllViewsExceptId(int viewId, int viewIdToKeep) {
2587        addAction(new ViewGroupActionRemove(viewId, viewIdToKeep));
2588    }
2589
2590    /**
2591     * Equivalent to calling {@link AdapterViewAnimator#showNext()}
2592     *
2593     * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
2594     */
2595    public void showNext(int viewId) {
2596        addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
2597    }
2598
2599    /**
2600     * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
2601     *
2602     * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
2603     */
2604    public void showPrevious(int viewId) {
2605        addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
2606    }
2607
2608    /**
2609     * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)}
2610     *
2611     * @param viewId The id of the view on which to call
2612     *               {@link AdapterViewAnimator#setDisplayedChild(int)}
2613     */
2614    public void setDisplayedChild(int viewId, int childIndex) {
2615        setInt(viewId, "setDisplayedChild", childIndex);
2616    }
2617
2618    /**
2619     * Equivalent to calling {@link View#setVisibility(int)}
2620     *
2621     * @param viewId The id of the view whose visibility should change
2622     * @param visibility The new visibility for the view
2623     */
2624    public void setViewVisibility(int viewId, int visibility) {
2625        setInt(viewId, "setVisibility", visibility);
2626    }
2627
2628    /**
2629     * Equivalent to calling {@link TextView#setText(CharSequence)}
2630     *
2631     * @param viewId The id of the view whose text should change
2632     * @param text The new text for the view
2633     */
2634    public void setTextViewText(int viewId, CharSequence text) {
2635        setCharSequence(viewId, "setText", text);
2636    }
2637
2638    /**
2639     * Equivalent to calling {@link TextView#setTextSize(int, float)}
2640     *
2641     * @param viewId The id of the view whose text size should change
2642     * @param units The units of size (e.g. COMPLEX_UNIT_SP)
2643     * @param size The size of the text
2644     */
2645    public void setTextViewTextSize(int viewId, int units, float size) {
2646        addAction(new TextViewSizeAction(viewId, units, size));
2647    }
2648
2649    /**
2650     * Equivalent to calling
2651     * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
2652     *
2653     * @param viewId The id of the view whose text should change
2654     * @param left The id of a drawable to place to the left of the text, or 0
2655     * @param top The id of a drawable to place above the text, or 0
2656     * @param right The id of a drawable to place to the right of the text, or 0
2657     * @param bottom The id of a drawable to place below the text, or 0
2658     */
2659    public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
2660        addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
2661    }
2662
2663    /**
2664     * Equivalent to calling {@link
2665     * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}.
2666     *
2667     * @param viewId The id of the view whose text should change
2668     * @param start The id of a drawable to place before the text (relative to the
2669     * layout direction), or 0
2670     * @param top The id of a drawable to place above the text, or 0
2671     * @param end The id of a drawable to place after the text, or 0
2672     * @param bottom The id of a drawable to place below the text, or 0
2673     */
2674    public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) {
2675        addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
2676    }
2677
2678    /**
2679     * Equivalent to applying a color filter on one of the drawables in
2680     * {@link android.widget.TextView#getCompoundDrawablesRelative()}.
2681     *
2682     * @param viewId The id of the view whose text should change.
2683     * @param index  The index of the drawable in the array of
2684     *               {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color
2685     *               filter on. Must be in [0, 3].
2686     * @param color  The color of the color filter. See
2687     *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
2688     * @param mode   The mode of the color filter. See
2689     *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
2690     * @hide
2691     */
2692    public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId,
2693            int index, int color, PorterDuff.Mode mode) {
2694        if (index < 0 || index >= 4) {
2695            throw new IllegalArgumentException("index must be in range [0, 3].");
2696        }
2697        addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode));
2698    }
2699
2700    /**
2701     * Equivalent to calling {@link
2702     * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
2703     * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
2704     *
2705     * @param viewId The id of the view whose text should change
2706     * @param left an Icon to place to the left of the text, or 0
2707     * @param top an Icon to place above the text, or 0
2708     * @param right an Icon to place to the right of the text, or 0
2709     * @param bottom an Icon to place below the text, or 0
2710     *
2711     * @hide
2712     */
2713    public void setTextViewCompoundDrawables(int viewId, Icon left, Icon top, Icon right, Icon bottom) {
2714        addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
2715    }
2716
2717    /**
2718     * Equivalent to calling {@link
2719     * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
2720     * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
2721     *
2722     * @param viewId The id of the view whose text should change
2723     * @param start an Icon to place before the text (relative to the
2724     * layout direction), or 0
2725     * @param top an Icon to place above the text, or 0
2726     * @param end an Icon to place after the text, or 0
2727     * @param bottom an Icon to place below the text, or 0
2728     *
2729     * @hide
2730     */
2731    public void setTextViewCompoundDrawablesRelative(int viewId, Icon start, Icon top, Icon end, Icon bottom) {
2732        addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
2733    }
2734
2735    /**
2736     * Equivalent to calling {@link ImageView#setImageResource(int)}
2737     *
2738     * @param viewId The id of the view whose drawable should change
2739     * @param srcId The new resource id for the drawable
2740     */
2741    public void setImageViewResource(int viewId, int srcId) {
2742        setInt(viewId, "setImageResource", srcId);
2743    }
2744
2745    /**
2746     * Equivalent to calling {@link ImageView#setImageURI(Uri)}
2747     *
2748     * @param viewId The id of the view whose drawable should change
2749     * @param uri The Uri for the image
2750     */
2751    public void setImageViewUri(int viewId, Uri uri) {
2752        setUri(viewId, "setImageURI", uri);
2753    }
2754
2755    /**
2756     * Equivalent to calling {@link ImageView#setImageBitmap(Bitmap)}
2757     *
2758     * @param viewId The id of the view whose bitmap should change
2759     * @param bitmap The new Bitmap for the drawable
2760     */
2761    public void setImageViewBitmap(int viewId, Bitmap bitmap) {
2762        setBitmap(viewId, "setImageBitmap", bitmap);
2763    }
2764
2765    /**
2766     * Equivalent to calling {@link ImageView#setImageIcon(Icon)}
2767     *
2768     * @param viewId The id of the view whose bitmap should change
2769     * @param icon The new Icon for the ImageView
2770     */
2771    public void setImageViewIcon(int viewId, Icon icon) {
2772        setIcon(viewId, "setImageIcon", icon);
2773    }
2774
2775    /**
2776     * Equivalent to calling {@link AdapterView#setEmptyView(View)}
2777     *
2778     * @param viewId The id of the view on which to set the empty view
2779     * @param emptyViewId The view id of the empty view
2780     */
2781    public void setEmptyView(int viewId, int emptyViewId) {
2782        addAction(new SetEmptyView(viewId, emptyViewId));
2783    }
2784
2785    /**
2786     * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
2787     * {@link Chronometer#setFormat Chronometer.setFormat},
2788     * and {@link Chronometer#start Chronometer.start()} or
2789     * {@link Chronometer#stop Chronometer.stop()}.
2790     *
2791     * @param viewId The id of the {@link Chronometer} to change
2792     * @param base The time at which the timer would have read 0:00.  This
2793     *             time should be based off of
2794     *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
2795     * @param format The Chronometer format string, or null to
2796     *               simply display the timer value.
2797     * @param started True if you want the clock to be started, false if not.
2798     *
2799     * @see #setChronometerCountDown(int, boolean)
2800     */
2801    public void setChronometer(int viewId, long base, String format, boolean started) {
2802        setLong(viewId, "setBase", base);
2803        setString(viewId, "setFormat", format);
2804        setBoolean(viewId, "setStarted", started);
2805    }
2806
2807    /**
2808     * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on
2809     * the chronometer with the given viewId.
2810     *
2811     * @param viewId The id of the {@link Chronometer} to change
2812     * @param isCountDown True if you want the chronometer to count down to base instead of
2813     *                    counting up.
2814     */
2815    public void setChronometerCountDown(int viewId, boolean isCountDown) {
2816        setBoolean(viewId, "setCountDown", isCountDown);
2817    }
2818
2819    /**
2820     * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
2821     * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
2822     * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
2823     *
2824     * If indeterminate is true, then the values for max and progress are ignored.
2825     *
2826     * @param viewId The id of the {@link ProgressBar} to change
2827     * @param max The 100% value for the progress bar
2828     * @param progress The current value of the progress bar.
2829     * @param indeterminate True if the progress bar is indeterminate,
2830     *                false if not.
2831     */
2832    public void setProgressBar(int viewId, int max, int progress,
2833            boolean indeterminate) {
2834        setBoolean(viewId, "setIndeterminate", indeterminate);
2835        if (!indeterminate) {
2836            setInt(viewId, "setMax", max);
2837            setInt(viewId, "setProgress", progress);
2838        }
2839    }
2840
2841    /**
2842     * Equivalent to calling
2843     * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
2844     * to launch the provided {@link PendingIntent}.
2845     *
2846     * When setting the on-click action of items within collections (eg. {@link ListView},
2847     * {@link StackView} etc.), this method will not work. Instead, use {@link
2848     * RemoteViews#setPendingIntentTemplate(int, PendingIntent)} in conjunction with
2849     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
2850     *
2851     * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
2852     * @param pendingIntent The {@link PendingIntent} to send when user clicks
2853     */
2854    public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
2855        addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
2856    }
2857
2858    /**
2859     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
2860     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
2861     * this method should be used to set a single PendingIntent template on the collection, and
2862     * individual items can differentiate their on-click behavior using
2863     * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
2864     *
2865     * @param viewId The id of the collection who's children will use this PendingIntent template
2866     *          when clicked
2867     * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
2868     *          by a child of viewId and executed when that child is clicked
2869     */
2870    public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
2871        addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
2872    }
2873
2874    /**
2875     * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
2876     * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
2877     * a single PendingIntent template can be set on the collection, see {@link
2878     * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
2879     * action of a given item can be distinguished by setting a fillInIntent on that item. The
2880     * fillInIntent is then combined with the PendingIntent template in order to determine the final
2881     * intent which will be executed when the item is clicked. This works as follows: any fields
2882     * which are left blank in the PendingIntent template, but are provided by the fillInIntent
2883     * will be overwritten, and the resulting PendingIntent will be used. The rest
2884     * of the PendingIntent template will then be filled in with the associated fields that are
2885     * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
2886     *
2887     * @param viewId The id of the view on which to set the fillInIntent
2888     * @param fillInIntent The intent which will be combined with the parent's PendingIntent
2889     *        in order to determine the on-click behavior of the view specified by viewId
2890     */
2891    public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
2892        addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
2893    }
2894
2895    /**
2896     * @hide
2897     * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
2898     * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
2899     * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
2900     * view.
2901     * <p>
2902     * You can omit specific calls by marking their values with null or -1.
2903     *
2904     * @param viewId The id of the view that contains the target
2905     *            {@link Drawable}
2906     * @param targetBackground If true, apply these parameters to the
2907     *            {@link Drawable} returned by
2908     *            {@link android.view.View#getBackground()}. Otherwise, assume
2909     *            the target view is an {@link ImageView} and apply them to
2910     *            {@link ImageView#getDrawable()}.
2911     * @param alpha Specify an alpha value for the drawable, or -1 to leave
2912     *            unchanged.
2913     * @param colorFilter Specify a color for a
2914     *            {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
2915     *            {@code mode} is {@code null}.
2916     * @param mode Specify a PorterDuff mode for this drawable, or null to leave
2917     *            unchanged.
2918     * @param level Specify the level for the drawable, or -1 to leave
2919     *            unchanged.
2920     */
2921    public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
2922            int colorFilter, PorterDuff.Mode mode, int level) {
2923        addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
2924                colorFilter, mode, level));
2925    }
2926
2927    /**
2928     * @hide
2929     * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}.
2930     *
2931     * @param viewId The id of the view whose tint should change
2932     * @param tint the tint to apply, may be {@code null} to clear tint
2933     */
2934    public void setProgressTintList(int viewId, ColorStateList tint) {
2935        addAction(new ReflectionAction(viewId, "setProgressTintList",
2936                ReflectionAction.COLOR_STATE_LIST, tint));
2937    }
2938
2939    /**
2940     * @hide
2941     * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}.
2942     *
2943     * @param viewId The id of the view whose tint should change
2944     * @param tint the tint to apply, may be {@code null} to clear tint
2945     */
2946    public void setProgressBackgroundTintList(int viewId, ColorStateList tint) {
2947        addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
2948                ReflectionAction.COLOR_STATE_LIST, tint));
2949    }
2950
2951    /**
2952     * @hide
2953     * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}.
2954     *
2955     * @param viewId The id of the view whose tint should change
2956     * @param tint the tint to apply, may be {@code null} to clear tint
2957     */
2958    public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) {
2959        addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
2960                ReflectionAction.COLOR_STATE_LIST, tint));
2961    }
2962
2963    /**
2964     * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
2965     *
2966     * @param viewId The id of the view whose text color should change
2967     * @param color Sets the text color for all the states (normal, selected,
2968     *            focused) to be this color.
2969     */
2970    public void setTextColor(int viewId, @ColorInt int color) {
2971        setInt(viewId, "setTextColor", color);
2972    }
2973
2974    /**
2975     * @hide
2976     * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}.
2977     *
2978     * @param viewId The id of the view whose text color should change
2979     * @param colors the text colors to set
2980     */
2981    public void setTextColor(int viewId, @ColorInt ColorStateList colors) {
2982        addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST,
2983                colors));
2984    }
2985
2986    /**
2987     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
2988     *
2989     * @param appWidgetId The id of the app widget which contains the specified view. (This
2990     *      parameter is ignored in this deprecated method)
2991     * @param viewId The id of the {@link AdapterView}
2992     * @param intent The intent of the service which will be
2993     *            providing data to the RemoteViewsAdapter
2994     * @deprecated This method has been deprecated. See
2995     *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
2996     */
2997    @Deprecated
2998    public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
2999        setRemoteAdapter(viewId, intent);
3000    }
3001
3002    /**
3003     * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
3004     * Can only be used for App Widgets.
3005     *
3006     * @param viewId The id of the {@link AdapterView}
3007     * @param intent The intent of the service which will be
3008     *            providing data to the RemoteViewsAdapter
3009     */
3010    public void setRemoteAdapter(int viewId, Intent intent) {
3011        addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
3012    }
3013
3014    /**
3015     * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
3016     * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
3017     * This is a simpler but less flexible approach to populating collection widgets. Its use is
3018     * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
3019     * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
3020     * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
3021     * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
3022     *
3023     * This API is supported in the compatibility library for previous API levels, see
3024     * RemoteViewsCompat.
3025     *
3026     * @param viewId The id of the {@link AdapterView}
3027     * @param list The list of RemoteViews which will populate the view specified by viewId.
3028     * @param viewTypeCount The maximum number of unique layout id's used to construct the list of
3029     *      RemoteViews. This count cannot change during the life-cycle of a given widget, so this
3030     *      parameter should account for the maximum possible number of types that may appear in the
3031     *      See {@link Adapter#getViewTypeCount()}.
3032     *
3033     * @hide
3034     */
3035    public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
3036        addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
3037    }
3038
3039    /**
3040     * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}.
3041     *
3042     * @param viewId The id of the view to change
3043     * @param position Scroll to this adapter position
3044     */
3045    public void setScrollPosition(int viewId, int position) {
3046        setInt(viewId, "smoothScrollToPosition", position);
3047    }
3048
3049    /**
3050     * Equivalent to calling {@link ListView#smoothScrollByOffset(int)}.
3051     *
3052     * @param viewId The id of the view to change
3053     * @param offset Scroll by this adapter position offset
3054     */
3055    public void setRelativeScrollPosition(int viewId, int offset) {
3056        setInt(viewId, "smoothScrollByOffset", offset);
3057    }
3058
3059    /**
3060     * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}.
3061     *
3062     * @param viewId The id of the view to change
3063     * @param left the left padding in pixels
3064     * @param top the top padding in pixels
3065     * @param right the right padding in pixels
3066     * @param bottom the bottom padding in pixels
3067     */
3068    public void setViewPadding(int viewId, int left, int top, int right, int bottom) {
3069        addAction(new ViewPaddingAction(viewId, left, top, right, bottom));
3070    }
3071
3072    /**
3073     * @hide
3074     * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}.
3075     * Only works if the {@link View#getLayoutParams()} supports margins.
3076     * Hidden for now since we don't want to support this for all different layout margins yet.
3077     *
3078     * @param viewId The id of the view to change
3079     * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin.
3080     */
3081    public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) {
3082        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN,
3083                endMarginDimen));
3084    }
3085
3086    /**
3087     * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}.
3088     *
3089     * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin.
3090     * @hide
3091     */
3092    public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) {
3093        addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN,
3094                bottomMarginDimen));
3095    }
3096
3097    /**
3098     * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}.
3099     *
3100     * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed
3101     *                    because they behave poorly when the density changes.
3102     * @hide
3103     */
3104    public void setViewLayoutWidth(int viewId, int layoutWidth) {
3105        if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT
3106                && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) {
3107            throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT");
3108        }
3109        mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth));
3110    }
3111
3112    /**
3113     * Call a method taking one boolean on a view in the layout for this RemoteViews.
3114     *
3115     * @param viewId The id of the view on which to call the method.
3116     * @param methodName The name of the method to call.
3117     * @param value The value to pass to the method.
3118     */
3119    public void setBoolean(int viewId, String methodName, boolean value) {
3120        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
3121    }
3122
3123    /**
3124     * Call a method taking one byte on a view in the layout for this RemoteViews.
3125     *
3126     * @param viewId The id of the view on which to call the method.
3127     * @param methodName The name of the method to call.
3128     * @param value The value to pass to the method.
3129     */
3130    public void setByte(int viewId, String methodName, byte value) {
3131        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
3132    }
3133
3134    /**
3135     * Call a method taking one short on a view in the layout for this RemoteViews.
3136     *
3137     * @param viewId The id of the view on which to call the method.
3138     * @param methodName The name of the method to call.
3139     * @param value The value to pass to the method.
3140     */
3141    public void setShort(int viewId, String methodName, short value) {
3142        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
3143    }
3144
3145    /**
3146     * Call a method taking one int on a view in the layout for this RemoteViews.
3147     *
3148     * @param viewId The id of the view on which to call the method.
3149     * @param methodName The name of the method to call.
3150     * @param value The value to pass to the method.
3151     */
3152    public void setInt(int viewId, String methodName, int value) {
3153        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
3154    }
3155
3156    /**
3157     * Call a method taking one long on a view in the layout for this RemoteViews.
3158     *
3159     * @param viewId The id of the view on which to call the method.
3160     * @param methodName The name of the method to call.
3161     * @param value The value to pass to the method.
3162     */
3163    public void setLong(int viewId, String methodName, long value) {
3164        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
3165    }
3166
3167    /**
3168     * Call a method taking one float on a view in the layout for this RemoteViews.
3169     *
3170     * @param viewId The id of the view on which to call the method.
3171     * @param methodName The name of the method to call.
3172     * @param value The value to pass to the method.
3173     */
3174    public void setFloat(int viewId, String methodName, float value) {
3175        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
3176    }
3177
3178    /**
3179     * Call a method taking one double on a view in the layout for this RemoteViews.
3180     *
3181     * @param viewId The id of the view on which to call the method.
3182     * @param methodName The name of the method to call.
3183     * @param value The value to pass to the method.
3184     */
3185    public void setDouble(int viewId, String methodName, double value) {
3186        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
3187    }
3188
3189    /**
3190     * Call a method taking one char on a view in the layout for this RemoteViews.
3191     *
3192     * @param viewId The id of the view on which to call the method.
3193     * @param methodName The name of the method to call.
3194     * @param value The value to pass to the method.
3195     */
3196    public void setChar(int viewId, String methodName, char value) {
3197        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
3198    }
3199
3200    /**
3201     * Call a method taking one String on a view in the layout for this RemoteViews.
3202     *
3203     * @param viewId The id of the view on which to call the method.
3204     * @param methodName The name of the method to call.
3205     * @param value The value to pass to the method.
3206     */
3207    public void setString(int viewId, String methodName, String value) {
3208        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
3209    }
3210
3211    /**
3212     * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
3213     *
3214     * @param viewId The id of the view on which to call the method.
3215     * @param methodName The name of the method to call.
3216     * @param value The value to pass to the method.
3217     */
3218    public void setCharSequence(int viewId, String methodName, CharSequence value) {
3219        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
3220    }
3221
3222    /**
3223     * Call a method taking one Uri on a view in the layout for this RemoteViews.
3224     *
3225     * @param viewId The id of the view on which to call the method.
3226     * @param methodName The name of the method to call.
3227     * @param value The value to pass to the method.
3228     */
3229    public void setUri(int viewId, String methodName, Uri value) {
3230        if (value != null) {
3231            // Resolve any filesystem path before sending remotely
3232            value = value.getCanonicalUri();
3233            if (StrictMode.vmFileUriExposureEnabled()) {
3234                value.checkFileUriExposed("RemoteViews.setUri()");
3235            }
3236        }
3237        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
3238    }
3239
3240    /**
3241     * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
3242     * @more
3243     * <p class="note">The bitmap will be flattened into the parcel if this object is
3244     * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
3245     *
3246     * @param viewId The id of the view on which to call the method.
3247     * @param methodName The name of the method to call.
3248     * @param value The value to pass to the method.
3249     */
3250    public void setBitmap(int viewId, String methodName, Bitmap value) {
3251        addAction(new BitmapReflectionAction(viewId, methodName, value));
3252    }
3253
3254    /**
3255     * Call a method taking one Bundle on a view in the layout for this RemoteViews.
3256     *
3257     * @param viewId The id of the view on which to call the method.
3258     * @param methodName The name of the method to call.
3259     * @param value The value to pass to the method.
3260     */
3261    public void setBundle(int viewId, String methodName, Bundle value) {
3262        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
3263    }
3264
3265    /**
3266     * Call a method taking one Intent on a view in the layout for this RemoteViews.
3267     *
3268     * @param viewId The id of the view on which to call the method.
3269     * @param methodName The name of the method to call.
3270     * @param value The {@link android.content.Intent} to pass the method.
3271     */
3272    public void setIntent(int viewId, String methodName, Intent value) {
3273        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
3274    }
3275
3276    /**
3277     * Call a method taking one Icon on a view in the layout for this RemoteViews.
3278     *
3279     * @param viewId The id of the view on which to call the method.
3280     * @param methodName The name of the method to call.
3281     * @param value The {@link android.graphics.drawable.Icon} to pass the method.
3282     */
3283    public void setIcon(int viewId, String methodName, Icon value) {
3284        addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value));
3285    }
3286
3287    /**
3288     * Equivalent to calling View.setContentDescription(CharSequence).
3289     *
3290     * @param viewId The id of the view whose content description should change.
3291     * @param contentDescription The new content description for the view.
3292     */
3293    public void setContentDescription(int viewId, CharSequence contentDescription) {
3294        setCharSequence(viewId, "setContentDescription", contentDescription);
3295    }
3296
3297    /**
3298     * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}.
3299     *
3300     * @param viewId The id of the view whose before view in accessibility traversal to set.
3301     * @param nextId The id of the next in the accessibility traversal.
3302     **/
3303    public void setAccessibilityTraversalBefore(int viewId, int nextId) {
3304        setInt(viewId, "setAccessibilityTraversalBefore", nextId);
3305    }
3306
3307    /**
3308     * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}.
3309     *
3310     * @param viewId The id of the view whose after view in accessibility traversal to set.
3311     * @param nextId The id of the next in the accessibility traversal.
3312     **/
3313    public void setAccessibilityTraversalAfter(int viewId, int nextId) {
3314        setInt(viewId, "setAccessibilityTraversalAfter", nextId);
3315    }
3316
3317    /**
3318     * Equivalent to calling {@link View#setLabelFor(int)}.
3319     *
3320     * @param viewId The id of the view whose property to set.
3321     * @param labeledId The id of a view for which this view serves as a label.
3322     */
3323    public void setLabelFor(int viewId, int labeledId) {
3324        setInt(viewId, "setLabelFor", labeledId);
3325    }
3326
3327    private RemoteViews getRemoteViewsToApply(Context context) {
3328        if (hasLandscapeAndPortraitLayouts()) {
3329            int orientation = context.getResources().getConfiguration().orientation;
3330            if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
3331                return mLandscape;
3332            } else {
3333                return mPortrait;
3334            }
3335        }
3336        return this;
3337    }
3338
3339    /**
3340     * Inflates the view hierarchy represented by this object and applies
3341     * all of the actions.
3342     *
3343     * <p><strong>Caller beware: this may throw</strong>
3344     *
3345     * @param context Default context to use
3346     * @param parent Parent that the resulting view hierarchy will be attached to. This method
3347     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
3348     * @return The inflated view hierarchy
3349     */
3350    public View apply(Context context, ViewGroup parent) {
3351        return apply(context, parent, null);
3352    }
3353
3354    /** @hide */
3355    public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
3356        RemoteViews rvToApply = getRemoteViewsToApply(context);
3357
3358        View result = inflateView(context, rvToApply, parent);
3359        loadTransitionOverride(context, handler);
3360
3361        rvToApply.performApply(result, parent, handler);
3362
3363        return result;
3364    }
3365
3366    private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
3367        // RemoteViews may be built by an application installed in another
3368        // user. So build a context that loads resources from that user but
3369        // still returns the current users userId so settings like data / time formats
3370        // are loaded without requiring cross user persmissions.
3371        final Context contextForResources = getContextForResources(context);
3372        Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources);
3373
3374        LayoutInflater inflater = (LayoutInflater)
3375                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
3376
3377        // Clone inflater so we load resources from correct context and
3378        // we don't add a filter to the static version returned by getSystemService.
3379        inflater = inflater.cloneInContext(inflationContext);
3380        inflater.setFilter(this);
3381        View v = inflater.inflate(rv.getLayoutId(), parent, false);
3382        v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
3383        return v;
3384    }
3385
3386    private static void loadTransitionOverride(Context context,
3387            RemoteViews.OnClickHandler handler) {
3388        if (handler != null && context.getResources().getBoolean(
3389                com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) {
3390            TypedArray windowStyle = context.getTheme().obtainStyledAttributes(
3391                    com.android.internal.R.styleable.Window);
3392            int windowAnimations = windowStyle.getResourceId(
3393                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
3394            TypedArray windowAnimationStyle = context.obtainStyledAttributes(
3395                    windowAnimations, com.android.internal.R.styleable.WindowAnimation);
3396            handler.setEnterAnimationId(windowAnimationStyle.getResourceId(
3397                    com.android.internal.R.styleable.
3398                            WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0));
3399            windowStyle.recycle();
3400            windowAnimationStyle.recycle();
3401        }
3402    }
3403
3404    /**
3405     * Implement this interface to receive a callback when
3406     * {@link #applyAsync} or {@link #reapplyAsync} is finished.
3407     * @hide
3408     */
3409    public interface OnViewAppliedListener {
3410        void onViewApplied(View v);
3411
3412        void onError(Exception e);
3413    }
3414
3415    /**
3416     * Applies the views asynchronously, moving as much of the task on the background
3417     * thread as possible.
3418     *
3419     * @see #apply(Context, ViewGroup)
3420     * @param context Default context to use
3421     * @param parent Parent that the resulting view hierarchy will be attached to. This method
3422     * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
3423     * @param listener the callback to run when all actions have been applied. May be null.
3424     * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used.
3425     * @return CancellationSignal
3426     * @hide
3427     */
3428    public CancellationSignal applyAsync(
3429            Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) {
3430        return applyAsync(context, parent, executor, listener, null);
3431    }
3432
3433    private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
3434        CancellationSignal cancelSignal = new CancellationSignal();
3435        cancelSignal.setOnCancelListener(task);
3436
3437        task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
3438        return cancelSignal;
3439    }
3440
3441    /** @hide */
3442    public CancellationSignal applyAsync(Context context, ViewGroup parent,
3443            Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
3444        return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
3445    }
3446
3447    private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
3448            OnViewAppliedListener listener, OnClickHandler handler) {
3449        return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
3450                handler, null);
3451    }
3452
3453    private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
3454            implements CancellationSignal.OnCancelListener {
3455        final RemoteViews mRV;
3456        final ViewGroup mParent;
3457        final Context mContext;
3458        final OnViewAppliedListener mListener;
3459        final OnClickHandler mHandler;
3460
3461        private View mResult;
3462        private ViewTree mTree;
3463        private Action[] mActions;
3464        private Exception mError;
3465
3466        private AsyncApplyTask(
3467                RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener,
3468                OnClickHandler handler, View result) {
3469            mRV = rv;
3470            mParent = parent;
3471            mContext = context;
3472            mListener = listener;
3473            mHandler = handler;
3474
3475            mResult = result;
3476            loadTransitionOverride(context, handler);
3477        }
3478
3479        @Override
3480        protected ViewTree doInBackground(Void... params) {
3481            try {
3482                if (mResult == null) {
3483                    mResult = inflateView(mContext, mRV, mParent);
3484                }
3485
3486                mTree = new ViewTree(mResult);
3487                if (mRV.mActions != null) {
3488                    int count = mRV.mActions.size();
3489                    mActions = new Action[count];
3490                    for (int i = 0; i < count && !isCancelled(); i++) {
3491                        // TODO: check if isCancelled in nested views.
3492                        mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler);
3493                    }
3494                } else {
3495                    mActions = null;
3496                }
3497                return mTree;
3498            } catch (Exception e) {
3499                mError = e;
3500                return null;
3501            }
3502        }
3503
3504        @Override
3505        protected void onPostExecute(ViewTree viewTree) {
3506            if (mError == null) {
3507                try {
3508                    if (mActions != null) {
3509                        OnClickHandler handler = mHandler == null
3510                                ? DEFAULT_ON_CLICK_HANDLER : mHandler;
3511                        for (Action a : mActions) {
3512                            a.apply(viewTree.mRoot, mParent, handler);
3513                        }
3514                    }
3515                } catch (Exception e) {
3516                    mError = e;
3517                }
3518            }
3519
3520            if (mListener != null) {
3521                if (mError != null) {
3522                    mListener.onError(mError);
3523                } else {
3524                    mListener.onViewApplied(viewTree.mRoot);
3525                }
3526            } else if (mError != null) {
3527                if (mError instanceof ActionException) {
3528                    throw (ActionException) mError;
3529                } else {
3530                    throw new ActionException(mError);
3531                }
3532            }
3533        }
3534
3535        @Override
3536        public void onCancel() {
3537            cancel(true);
3538        }
3539    }
3540
3541    /**
3542     * Applies all of the actions to the provided view.
3543     *
3544     * <p><strong>Caller beware: this may throw</strong>
3545     *
3546     * @param v The view to apply the actions to.  This should be the result of
3547     * the {@link #apply(Context,ViewGroup)} call.
3548     */
3549    public void reapply(Context context, View v) {
3550        reapply(context, v, null);
3551    }
3552
3553    /** @hide */
3554    public void reapply(Context context, View v, OnClickHandler handler) {
3555        RemoteViews rvToApply = getRemoteViewsToApply(context);
3556
3557        // In the case that a view has this RemoteViews applied in one orientation, is persisted
3558        // across orientation change, and has the RemoteViews re-applied in the new orientation,
3559        // we throw an exception, since the layouts may be completely unrelated.
3560        if (hasLandscapeAndPortraitLayouts()) {
3561            if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
3562                throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
3563                        " that does not share the same root layout id.");
3564            }
3565        }
3566
3567        rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
3568    }
3569
3570    /**
3571     * Applies all the actions to the provided view, moving as much of the task on the background
3572     * thread as possible.
3573     *
3574     * @see #reapply(Context, View)
3575     * @param context Default context to use
3576     * @param v The view to apply the actions to.  This should be the result of
3577     * the {@link #apply(Context,ViewGroup)} call.
3578     * @param listener the callback to run when all actions have been applied. May be null.
3579     * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used
3580     * @return CancellationSignal
3581     * @hide
3582     */
3583    public CancellationSignal reapplyAsync(
3584            Context context, View v, Executor executor, OnViewAppliedListener listener) {
3585        return reapplyAsync(context, v, executor, listener, null);
3586    }
3587
3588    /** @hide */
3589    public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
3590            OnViewAppliedListener listener, OnClickHandler handler) {
3591        RemoteViews rvToApply = getRemoteViewsToApply(context);
3592
3593        // In the case that a view has this RemoteViews applied in one orientation, is persisted
3594        // across orientation change, and has the RemoteViews re-applied in the new orientation,
3595        // we throw an exception, since the layouts may be completely unrelated.
3596        if (hasLandscapeAndPortraitLayouts()) {
3597            if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
3598                throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
3599                        " that does not share the same root layout id.");
3600            }
3601        }
3602
3603        return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
3604                context, listener, handler, v), executor);
3605    }
3606
3607    private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
3608        if (mActions != null) {
3609            handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
3610            final int count = mActions.size();
3611            for (int i = 0; i < count; i++) {
3612                Action a = mActions.get(i);
3613                a.apply(v, parent, handler);
3614            }
3615        }
3616    }
3617
3618    /**
3619     * Returns true if the RemoteViews contains potentially costly operations and should be
3620     * applied asynchronously.
3621     *
3622     * @hide
3623     */
3624    public boolean prefersAsyncApply() {
3625        if (mActions != null) {
3626            final int count = mActions.size();
3627            for (int i = 0; i < count; i++) {
3628                if (mActions.get(i).prefersAsyncApply()) {
3629                    return true;
3630                }
3631            }
3632        }
3633        return false;
3634    }
3635
3636    private Context getContextForResources(Context context) {
3637        if (mApplication != null) {
3638            if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
3639                    && context.getPackageName().equals(mApplication.packageName)) {
3640                return context;
3641            }
3642            try {
3643                return context.createApplicationContext(mApplication,
3644                        Context.CONTEXT_RESTRICTED);
3645            } catch (NameNotFoundException e) {
3646                Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found");
3647            }
3648        }
3649
3650        return context;
3651    }
3652
3653    /**
3654     * Returns the number of actions in this RemoteViews. Can be used as a sequence number.
3655     *
3656     * @hide
3657     */
3658    public int getSequenceNumber() {
3659        return (mActions == null) ? 0 : mActions.size();
3660    }
3661
3662    /* (non-Javadoc)
3663     * Used to restrict the views which can be inflated
3664     *
3665     * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
3666     */
3667    public boolean onLoadClass(Class clazz) {
3668        return clazz.isAnnotationPresent(RemoteView.class);
3669    }
3670
3671    public int describeContents() {
3672        return 0;
3673    }
3674
3675    public void writeToParcel(Parcel dest, int flags) {
3676        if (!hasLandscapeAndPortraitLayouts()) {
3677            dest.writeInt(MODE_NORMAL);
3678            // We only write the bitmap cache if we are the root RemoteViews, as this cache
3679            // is shared by all children.
3680            if (mIsRoot) {
3681                mBitmapCache.writeBitmapsToParcel(dest, flags);
3682            }
3683            if (!mIsRoot && (flags & PARCELABLE_ELIDE_DUPLICATES) != 0) {
3684                dest.writeInt(0);
3685            } else {
3686                dest.writeInt(1);
3687                mApplication.writeToParcel(dest, flags);
3688            }
3689            dest.writeInt(mLayoutId);
3690            dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
3691            int count;
3692            if (mActions != null) {
3693                count = mActions.size();
3694            } else {
3695                count = 0;
3696            }
3697            dest.writeInt(count);
3698            for (int i=0; i<count; i++) {
3699                Action a = mActions.get(i);
3700                a.writeToParcel(dest, a.hasSameAppInfo(mApplication)
3701                        ? PARCELABLE_ELIDE_DUPLICATES : 0);
3702            }
3703        } else {
3704            dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
3705            // We only write the bitmap cache if we are the root RemoteViews, as this cache
3706            // is shared by all children.
3707            if (mIsRoot) {
3708                mBitmapCache.writeBitmapsToParcel(dest, flags);
3709            }
3710            mLandscape.writeToParcel(dest, flags);
3711            // Both RemoteViews already share the same package and user
3712            mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
3713        }
3714    }
3715
3716    private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
3717        if (packageName == null) {
3718            return null;
3719        }
3720
3721        // Get the application for the passed in package and user.
3722        Application application = ActivityThread.currentApplication();
3723        if (application == null) {
3724            throw new IllegalStateException("Cannot create remote views out of an aplication.");
3725        }
3726
3727        ApplicationInfo applicationInfo = application.getApplicationInfo();
3728        if (UserHandle.getUserId(applicationInfo.uid) != userId
3729                || !applicationInfo.packageName.equals(packageName)) {
3730            try {
3731                Context context = application.getBaseContext().createPackageContextAsUser(
3732                        packageName, 0, new UserHandle(userId));
3733                applicationInfo = context.getApplicationInfo();
3734            } catch (NameNotFoundException nnfe) {
3735                throw new IllegalArgumentException("No such package " + packageName);
3736            }
3737        }
3738
3739        return applicationInfo;
3740    }
3741
3742    /**
3743     * Parcelable.Creator that instantiates RemoteViews objects
3744     */
3745    public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
3746        public RemoteViews createFromParcel(Parcel parcel) {
3747            return new RemoteViews(parcel);
3748        }
3749
3750        public RemoteViews[] newArray(int size) {
3751            return new RemoteViews[size];
3752        }
3753    };
3754
3755    /**
3756     * A representation of the view hierarchy. Only views which have a valid ID are added
3757     * and can be searched.
3758     */
3759    private static class ViewTree {
3760        private static final int INSERT_AT_END_INDEX = -1;
3761        private View mRoot;
3762        private ArrayList<ViewTree> mChildren;
3763
3764        private ViewTree(View root) {
3765            mRoot = root;
3766        }
3767
3768        public void createTree() {
3769            if (mChildren != null) {
3770                return;
3771            }
3772
3773            mChildren = new ArrayList<>();
3774            if (mRoot instanceof ViewGroup) {
3775                ViewGroup vg = (ViewGroup) mRoot;
3776                int count = vg.getChildCount();
3777                for (int i = 0; i < count; i++) {
3778                    addViewChild(vg.getChildAt(i));
3779                }
3780            }
3781        }
3782
3783        public ViewTree findViewTreeById(int id) {
3784            if (mRoot.getId() == id) {
3785                return this;
3786            }
3787            if (mChildren == null) {
3788                return null;
3789            }
3790            for (ViewTree tree : mChildren) {
3791                ViewTree result = tree.findViewTreeById(id);
3792                if (result != null) {
3793                    return result;
3794                }
3795            }
3796            return null;
3797        }
3798
3799        public void replaceView(View v) {
3800            mRoot = v;
3801            mChildren = null;
3802            createTree();
3803        }
3804
3805        public <T extends View> T findViewById(int id) {
3806            if (mChildren == null) {
3807                return mRoot.findViewById(id);
3808            }
3809            ViewTree tree = findViewTreeById(id);
3810            return tree == null ? null : (T) tree.mRoot;
3811        }
3812
3813        public void addChild(ViewTree child) {
3814            addChild(child, INSERT_AT_END_INDEX);
3815        }
3816
3817        /**
3818         * Adds the given {@link ViewTree} as a child at the given index.
3819         *
3820         * @param index The position at which to add the child or -1 to add last.
3821         */
3822        public void addChild(ViewTree child, int index) {
3823            if (mChildren == null) {
3824                mChildren = new ArrayList<>();
3825            }
3826            child.createTree();
3827
3828            if (index == INSERT_AT_END_INDEX) {
3829                mChildren.add(child);
3830                return;
3831            }
3832
3833            mChildren.add(index, child);
3834        }
3835
3836        private void addViewChild(View v) {
3837            // ViewTree only contains Views which can be found using findViewById.
3838            // If isRootNamespace is true, this view is skipped.
3839            // @see ViewGroup#findViewTraversal(int)
3840            if (v.isRootNamespace()) {
3841                return;
3842            }
3843            final ViewTree target;
3844
3845            // If the view has a valid id, i.e., if can be found using findViewById, add it to the
3846            // tree, otherwise skip this view and add its children instead.
3847            if (v.getId() != 0) {
3848                ViewTree tree = new ViewTree(v);
3849                mChildren.add(tree);
3850                target = tree;
3851            } else {
3852                target = this;
3853            }
3854
3855            if (v instanceof ViewGroup) {
3856                if (target.mChildren == null) {
3857                    target.mChildren = new ArrayList<>();
3858                    ViewGroup vg = (ViewGroup) v;
3859                    int count = vg.getChildCount();
3860                    for (int i = 0; i < count; i++) {
3861                        target.addViewChild(vg.getChildAt(i));
3862                    }
3863                }
3864            }
3865        }
3866    }
3867}
3868