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