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 
17 package android.widget;
18 
19 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
20 
21 import android.annotation.ColorInt;
22 import android.annotation.DimenRes;
23 import android.annotation.NonNull;
24 import android.annotation.StyleRes;
25 import android.app.ActivityOptions;
26 import android.app.ActivityThread;
27 import android.app.Application;
28 import android.app.PendingIntent;
29 import android.app.RemoteInput;
30 import android.appwidget.AppWidgetHostView;
31 import android.content.Context;
32 import android.content.ContextWrapper;
33 import android.content.Intent;
34 import android.content.IntentSender;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.PackageManager.NameNotFoundException;
37 import android.content.res.ColorStateList;
38 import android.content.res.Configuration;
39 import android.content.res.Resources;
40 import android.content.res.TypedArray;
41 import android.graphics.Bitmap;
42 import android.graphics.PorterDuff;
43 import android.graphics.Rect;
44 import android.graphics.drawable.Drawable;
45 import android.graphics.drawable.Icon;
46 import android.net.Uri;
47 import android.os.AsyncTask;
48 import android.os.Binder;
49 import android.os.Build;
50 import android.os.Bundle;
51 import android.os.CancellationSignal;
52 import android.os.Parcel;
53 import android.os.Parcelable;
54 import android.os.Process;
55 import android.os.StrictMode;
56 import android.os.UserHandle;
57 import android.text.TextUtils;
58 import android.util.ArrayMap;
59 import android.util.Log;
60 import android.view.ContextThemeWrapper;
61 import android.view.LayoutInflater;
62 import android.view.LayoutInflater.Filter;
63 import android.view.RemotableViewMethod;
64 import android.view.View;
65 import android.view.View.OnClickListener;
66 import android.view.ViewGroup;
67 import android.view.ViewStub;
68 import android.widget.AdapterView.OnItemClickListener;
69 
70 import com.android.internal.R;
71 import com.android.internal.util.NotificationColorUtil;
72 import com.android.internal.util.Preconditions;
73 
74 import java.lang.annotation.ElementType;
75 import java.lang.annotation.Retention;
76 import java.lang.annotation.RetentionPolicy;
77 import java.lang.annotation.Target;
78 import java.lang.invoke.MethodHandle;
79 import java.lang.invoke.MethodHandles;
80 import java.lang.invoke.MethodType;
81 import java.lang.reflect.Method;
82 import java.util.ArrayList;
83 import java.util.HashMap;
84 import java.util.Map;
85 import java.util.Objects;
86 import java.util.Stack;
87 import java.util.concurrent.Executor;
88 import 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  */
121 public 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      */
setRemoteInputs(int viewId, RemoteInput[] remoteInputs)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      */
reduceImageSizes(int maxWidth, int maxHeight)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      */
overrideTextColors(int textColor)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      */
setReapplyDisallowed()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      */
isReapplyDisallowed()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
equals(Object o)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
hashCode()308         public int hashCode() {
309             return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass)
310                     ^ Objects.hashCode(methodName);
311         }
312 
set(Class targetClass, Class paramClass, String methodName)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 {
ActionException(Exception ex)344         public ActionException(Exception ex) {
345             super(ex);
346         }
ActionException(String message)347         public ActionException(String message) {
348             super(message);
349         }
350         /**
351          * @hide
352          */
ActionException(Throwable t)353         public ActionException(Throwable t) {
354             super(t);
355         }
356     }
357 
358     /** @hide */
359     public static class OnClickHandler {
360 
361         private int mEnterAnimationId;
362 
onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent)363         public boolean onClickHandler(View view, PendingIntent pendingIntent,
364                 Intent fillInIntent) {
365             return onClickHandler(view, pendingIntent, fillInIntent, WINDOWING_MODE_UNDEFINED);
366         }
367 
onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent, int windowingMode)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 
setEnterAnimationId(int enterAnimationId)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 {
apply(View root, ViewGroup rootParent, OnClickHandler handler)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 
describeContents()417         public int describeContents() {
418             return 0;
419         }
420 
setBitmapCache(BitmapCache bitmapCache)421         public void setBitmapCache(BitmapCache bitmapCache) {
422             // Do nothing
423         }
424 
mergeBehavior()425         public int mergeBehavior() {
426             return MERGE_REPLACE;
427         }
428 
getActionTag()429         public abstract int getActionTag();
430 
getUniqueKey()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          */
initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)440         public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
441             return this;
442         }
443 
prefersAsyncApply()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          */
hasSameAppInfo(ApplicationInfo parentInfo)452         public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
453             return true;
454         }
455 
visitUris(@onNull Consumer<Uri> visitor)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
getActionTag()468         public final int getActionTag() {
469             return 0;
470         }
471 
472         @Override
writeToParcel(Parcel dest, int flags)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      */
mergeRemoteViews(RemoteViews newRv)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      */
visitUris(@onNull Consumer<Uri> visitor)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 
visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor)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 
RemoteViewsContextWrapper(Context context, Context contextForResources)557         RemoteViewsContextWrapper(Context context, Context contextForResources) {
558             super(context);
559             mContextForResources = contextForResources;
560         }
561 
562         @Override
getResources()563         public Resources getResources() {
564             return mContextForResources.getResources();
565         }
566 
567         @Override
getTheme()568         public Resources.Theme getTheme() {
569             return mContextForResources.getTheme();
570         }
571 
572         @Override
getPackageName()573         public String getPackageName() {
574             return mContextForResources.getPackageName();
575         }
576     }
577 
578     private class SetEmptyView extends Action {
579         int emptyViewId;
580 
SetEmptyView(int viewId, int emptyViewId)581         SetEmptyView(int viewId, int emptyViewId) {
582             this.viewId = viewId;
583             this.emptyViewId = emptyViewId;
584         }
585 
SetEmptyView(Parcel in)586         SetEmptyView(Parcel in) {
587             this.viewId = in.readInt();
588             this.emptyViewId = in.readInt();
589         }
590 
writeToParcel(Parcel out, int flags)591         public void writeToParcel(Parcel out, int flags) {
592             out.writeInt(this.viewId);
593             out.writeInt(this.emptyViewId);
594         }
595 
596         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)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
getActionTag()610         public int getActionTag() {
611             return SET_EMPTY_VIEW_ACTION_TAG;
612         }
613     }
614 
615     private class SetOnClickFillInIntent extends Action {
SetOnClickFillInIntent(int id, Intent fillInIntent)616         public SetOnClickFillInIntent(int id, Intent fillInIntent) {
617             this.viewId = id;
618             this.fillInIntent = fillInIntent;
619         }
620 
SetOnClickFillInIntent(Parcel parcel)621         public SetOnClickFillInIntent(Parcel parcel) {
622             viewId = parcel.readInt();
623             fillInIntent = parcel.readTypedObject(Intent.CREATOR);
624         }
625 
writeToParcel(Parcel dest, int flags)626         public void writeToParcel(Parcel dest, int flags) {
627             dest.writeInt(viewId);
628             dest.writeTypedObject(fillInIntent, 0 /* no flags */);
629         }
630 
631         @Override
apply(View root, ViewGroup rootParent, final OnClickHandler handler)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
getActionTag()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 {
SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate)696         public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
697             this.viewId = id;
698             this.pendingIntentTemplate = pendingIntentTemplate;
699         }
700 
SetPendingIntentTemplate(Parcel parcel)701         public SetPendingIntentTemplate(Parcel parcel) {
702             viewId = parcel.readInt();
703             pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
704         }
705 
writeToParcel(Parcel dest, int flags)706         public void writeToParcel(Parcel dest, int flags) {
707             dest.writeInt(viewId);
708             PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest);
709         }
710 
711         @Override
apply(View root, ViewGroup rootParent, final OnClickHandler handler)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
getActionTag()763         public int getActionTag() {
764             return SET_PENDING_INTENT_TEMPLATE_TAG;
765         }
766 
767         PendingIntent pendingIntentTemplate;
768     }
769 
770     private class SetRemoteViewsAdapterList extends Action {
SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount)771         public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
772             this.viewId = id;
773             this.list = list;
774             this.viewTypeCount = viewTypeCount;
775         }
776 
SetRemoteViewsAdapterList(Parcel parcel)777         public SetRemoteViewsAdapterList(Parcel parcel) {
778             viewId = parcel.readInt();
779             viewTypeCount = parcel.readInt();
780             list = parcel.createTypedArrayList(RemoteViews.CREATOR);
781         }
782 
writeToParcel(Parcel dest, int flags)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
apply(View root, ViewGroup rootParent, OnClickHandler handler)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
getActionTag()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 {
SetRemoteViewsAdapterIntent(int id, Intent intent)836         public SetRemoteViewsAdapterIntent(int id, Intent intent) {
837             this.viewId = id;
838             this.intent = intent;
839         }
840 
SetRemoteViewsAdapterIntent(Parcel parcel)841         public SetRemoteViewsAdapterIntent(Parcel parcel) {
842             viewId = parcel.readInt();
843             intent = parcel.readTypedObject(Intent.CREATOR);
844         }
845 
writeToParcel(Parcel dest, int flags)846         public void writeToParcel(Parcel dest, int flags) {
847             dest.writeInt(viewId);
848             dest.writeTypedObject(intent, flags);
849         }
850 
851         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)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
initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)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
getActionTag()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 {
SetOnClickPendingIntent(int id, PendingIntent pendingIntent)907         public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
908             this.viewId = id;
909             this.pendingIntent = pendingIntent;
910         }
911 
SetOnClickPendingIntent(Parcel parcel)912         public SetOnClickPendingIntent(Parcel parcel) {
913             viewId = parcel.readInt();
914             pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
915         }
916 
writeToParcel(Parcel dest, int flags)917         public void writeToParcel(Parcel dest, int flags) {
918             dest.writeInt(viewId);
919             PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, dest);
920         }
921 
922         @Override
apply(View root, ViewGroup rootParent, final OnClickHandler handler)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
getActionTag()962         public int getActionTag() {
963             return SET_ON_CLICK_PENDING_INTENT_TAG;
964         }
965 
966         PendingIntent pendingIntent;
967     }
968 
getSourceBounds(View v)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 
getMethod(View view, String methodName, Class<?> paramType, boolean async)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 
getParameters(Class<?> paramType)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 {
SetDrawableTint(int id, boolean targetBackground, int colorFilter, @NonNull PorterDuff.Mode mode)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 
SetDrawableTint(Parcel parcel)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 
writeToParcel(Parcel dest, int flags)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
apply(View root, ViewGroup rootParent, OnClickHandler handler)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
getActionTag()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 
ViewContentNavigation(int viewId, boolean next)1117         ViewContentNavigation(int viewId, boolean next) {
1118             this.viewId = viewId;
1119             this.mNext = next;
1120         }
1121 
ViewContentNavigation(Parcel in)1122         ViewContentNavigation(Parcel in) {
1123             this.viewId = in.readInt();
1124             this.mNext = in.readBoolean();
1125         }
1126 
writeToParcel(Parcel out, int flags)1127         public void writeToParcel(Parcel out, int flags) {
1128             out.writeInt(this.viewId);
1129             out.writeBoolean(this.mNext);
1130         }
1131 
1132         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)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 
mergeBehavior()1145         public int mergeBehavior() {
1146             return MERGE_IGNORE;
1147         }
1148 
1149         @Override
getActionTag()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 
BitmapCache()1160         public BitmapCache() {
1161             mBitmaps = new ArrayList<>();
1162         }
1163 
BitmapCache(Parcel source)1164         public BitmapCache(Parcel source) {
1165             mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
1166         }
1167 
getBitmapId(Bitmap b)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 
getBitmapForId(int id)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 
writeBitmapsToParcel(Parcel dest, int flags)1190         public void writeBitmapsToParcel(Parcel dest, int flags) {
1191             dest.writeTypedList(mBitmaps, flags);
1192         }
1193 
getBitmapMemory()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 
BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap)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 
BitmapReflectionAction(Parcel in)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
writeToParcel(Parcel dest, int flags)1226         public void writeToParcel(Parcel dest, int flags) {
1227             dest.writeInt(viewId);
1228             dest.writeString(methodName);
1229             dest.writeInt(bitmapId);
1230         }
1231 
1232         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)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
setBitmapCache(BitmapCache bitmapCache)1241         public void setBitmapCache(BitmapCache bitmapCache) {
1242             bitmapId = bitmapCache.getBitmapId(bitmap);
1243         }
1244 
1245         @Override
getActionTag()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 
ReflectionAction(int viewId, String methodName, int type, Object value)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 
ReflectionAction(Parcel in)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 
writeToParcel(Parcel out, int flags)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 
getParameterType()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
apply(View root, ViewGroup rootParent, OnClickHandler handler)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
initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)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 
mergeBehavior()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
getActionTag()1508         public int getActionTag() {
1509             return REFLECTION_ACTION_TAG;
1510         }
1511 
1512         @Override
getUniqueKey()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
prefersAsyncApply()1520         public boolean prefersAsyncApply() {
1521             return this.type == URI || this.type == ICON;
1522         }
1523 
1524         @Override
visitUris(@onNull Consumer<Uri> visitor)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 
RunnableAction(Runnable r)1545         RunnableAction(Runnable r) {
1546             mRunnable = r;
1547         }
1548 
1549         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)1550         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1551             mRunnable.run();
1552         }
1553     }
1554 
configureRemoteViewsAsChild(RemoteViews rv)1555     private void configureRemoteViewsAsChild(RemoteViews rv) {
1556         rv.setBitmapCache(mBitmapCache);
1557         rv.setNotRoot();
1558     }
1559 
setNotRoot()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 
ViewGroupActionAdd(int viewId, RemoteViews nestedViews)1571         ViewGroupActionAdd(int viewId, RemoteViews nestedViews) {
1572             this(viewId, nestedViews, -1 /* index */);
1573         }
1574 
ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index)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 
ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, Map<Class, Object> classCookies)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 
writeToParcel(Parcel dest, int flags)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
hasSameAppInfo(ApplicationInfo parentInfo)1598         public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
1599             return mNestedViews.hasSameAppInfo(parentInfo);
1600         }
1601 
1602         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)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
initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)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
setBitmapCache(BitmapCache bitmapCache)1651         public void setBitmapCache(BitmapCache bitmapCache) {
1652             mNestedViews.setBitmapCache(bitmapCache);
1653         }
1654 
1655         @Override
mergeBehavior()1656         public int mergeBehavior() {
1657             return MERGE_APPEND;
1658         }
1659 
1660         @Override
prefersAsyncApply()1661         public boolean prefersAsyncApply() {
1662             return mNestedViews.prefersAsyncApply();
1663         }
1664 
1665         @Override
getActionTag()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