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