• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 android.annotation.AttrRes;
20 import android.annotation.ColorInt;
21 import android.annotation.ColorRes;
22 import android.annotation.DimenRes;
23 import android.annotation.DrawableRes;
24 import android.annotation.IdRes;
25 import android.annotation.IntDef;
26 import android.annotation.LayoutRes;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.annotation.Px;
30 import android.annotation.StringRes;
31 import android.annotation.StyleRes;
32 import android.annotation.SuppressLint;
33 import android.app.Activity;
34 import android.app.ActivityOptions;
35 import android.app.ActivityThread;
36 import android.app.Application;
37 import android.app.PendingIntent;
38 import android.app.RemoteInput;
39 import android.appwidget.AppWidgetHostView;
40 import android.compat.annotation.UnsupportedAppUsage;
41 import android.content.Context;
42 import android.content.ContextWrapper;
43 import android.content.Intent;
44 import android.content.IntentSender;
45 import android.content.pm.ApplicationInfo;
46 import android.content.pm.PackageManager.NameNotFoundException;
47 import android.content.res.ColorStateList;
48 import android.content.res.Configuration;
49 import android.content.res.Resources;
50 import android.content.res.TypedArray;
51 import android.content.res.loader.ResourcesLoader;
52 import android.content.res.loader.ResourcesProvider;
53 import android.graphics.Bitmap;
54 import android.graphics.BlendMode;
55 import android.graphics.Outline;
56 import android.graphics.PorterDuff;
57 import android.graphics.Rect;
58 import android.graphics.drawable.Drawable;
59 import android.graphics.drawable.Icon;
60 import android.graphics.drawable.RippleDrawable;
61 import android.net.Uri;
62 import android.os.AsyncTask;
63 import android.os.Binder;
64 import android.os.Build;
65 import android.os.Bundle;
66 import android.os.CancellationSignal;
67 import android.os.Parcel;
68 import android.os.ParcelFileDescriptor;
69 import android.os.Parcelable;
70 import android.os.Process;
71 import android.os.StrictMode;
72 import android.os.UserHandle;
73 import android.system.Os;
74 import android.text.TextUtils;
75 import android.util.ArrayMap;
76 import android.util.DisplayMetrics;
77 import android.util.IntArray;
78 import android.util.Log;
79 import android.util.LongArray;
80 import android.util.Pair;
81 import android.util.SizeF;
82 import android.util.SparseIntArray;
83 import android.util.TypedValue;
84 import android.util.TypedValue.ComplexDimensionUnit;
85 import android.view.ContextThemeWrapper;
86 import android.view.LayoutInflater;
87 import android.view.LayoutInflater.Filter;
88 import android.view.RemotableViewMethod;
89 import android.view.View;
90 import android.view.ViewGroup;
91 import android.view.ViewGroup.MarginLayoutParams;
92 import android.view.ViewManager;
93 import android.view.ViewOutlineProvider;
94 import android.view.ViewParent;
95 import android.view.ViewStub;
96 import android.widget.AdapterView.OnItemClickListener;
97 import android.widget.CompoundButton.OnCheckedChangeListener;
98 
99 import com.android.internal.R;
100 import com.android.internal.util.ContrastColorUtil;
101 import com.android.internal.util.Preconditions;
102 
103 import java.io.ByteArrayOutputStream;
104 import java.io.FileDescriptor;
105 import java.io.FileOutputStream;
106 import java.io.IOException;
107 import java.io.InputStream;
108 import java.io.OutputStream;
109 import java.lang.annotation.ElementType;
110 import java.lang.annotation.Retention;
111 import java.lang.annotation.RetentionPolicy;
112 import java.lang.annotation.Target;
113 import java.lang.invoke.MethodHandle;
114 import java.lang.invoke.MethodHandles;
115 import java.lang.invoke.MethodType;
116 import java.lang.reflect.Method;
117 import java.util.ArrayDeque;
118 import java.util.ArrayList;
119 import java.util.Arrays;
120 import java.util.HashMap;
121 import java.util.Iterator;
122 import java.util.List;
123 import java.util.Map;
124 import java.util.Objects;
125 import java.util.Stack;
126 import java.util.concurrent.Executor;
127 import java.util.function.Consumer;
128 import java.util.function.Predicate;
129 
130 /**
131  * A class that describes a view hierarchy that can be displayed in
132  * another process. The hierarchy is inflated from a layout resource
133  * file, and this class provides some basic operations for modifying
134  * the content of the inflated hierarchy.
135  *
136  * <p>{@code RemoteViews} is limited to support for the following layouts:</p>
137  * <ul>
138  *   <li>{@link android.widget.AdapterViewFlipper}</li>
139  *   <li>{@link android.widget.FrameLayout}</li>
140  *   <li>{@link android.widget.GridLayout}</li>
141  *   <li>{@link android.widget.GridView}</li>
142  *   <li>{@link android.widget.LinearLayout}</li>
143  *   <li>{@link android.widget.ListView}</li>
144  *   <li>{@link android.widget.RelativeLayout}</li>
145  *   <li>{@link android.widget.StackView}</li>
146  *   <li>{@link android.widget.ViewFlipper}</li>
147  * </ul>
148  * <p>And the following widgets:</p>
149  * <ul>
150  *   <li>{@link android.widget.AnalogClock}</li>
151  *   <li>{@link android.widget.Button}</li>
152  *   <li>{@link android.widget.Chronometer}</li>
153  *   <li>{@link android.widget.ImageButton}</li>
154  *   <li>{@link android.widget.ImageView}</li>
155  *   <li>{@link android.widget.ProgressBar}</li>
156  *   <li>{@link android.widget.TextClock}</li>
157  *   <li>{@link android.widget.TextView}</li>
158  * </ul>
159  * <p>As of API 31, the following widgets and layouts may also be used:</p>
160  * <ul>
161  *     <li>{@link android.widget.CheckBox}</li>
162  *     <li>{@link android.widget.RadioButton}</li>
163  *     <li>{@link android.widget.RadioGroup}</li>
164  *     <li>{@link android.widget.Switch}</li>
165  * </ul>
166  * <p>Descendants of these classes are not supported.</p>
167  */
168 public class RemoteViews implements Parcelable, Filter {
169 
170     private static final String LOG_TAG = "RemoteViews";
171 
172     /** The intent extra for whether the view whose checked state changed is currently checked. */
173     public static final String EXTRA_CHECKED = "android.widget.extra.CHECKED";
174 
175     /**
176      * The intent extra that contains the appWidgetId.
177      * @hide
178      */
179     static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
180 
181     /**
182      * The intent extra that contains {@code true} if inflating as dak text theme.
183      * @hide
184      */
185     static final String EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND = "remoteAdapterOnLightBackground";
186 
187     /**
188      * The intent extra that contains the bounds for all shared elements.
189      */
190     public static final String EXTRA_SHARED_ELEMENT_BOUNDS =
191             "android.widget.extra.SHARED_ELEMENT_BOUNDS";
192 
193     /**
194      * Maximum depth of nested views calls from {@link #addView(int, RemoteViews)} and
195      * {@link #RemoteViews(RemoteViews, RemoteViews)}.
196      */
197     private static final int MAX_NESTED_VIEWS = 10;
198 
199     /**
200      * Maximum number of RemoteViews that can be specified in constructor.
201      */
202     private static final int MAX_INIT_VIEW_COUNT = 16;
203 
204     // The unique identifiers for each custom {@link Action}.
205     private static final int SET_ON_CLICK_RESPONSE_TAG = 1;
206     private static final int REFLECTION_ACTION_TAG = 2;
207     private static final int SET_DRAWABLE_TINT_TAG = 3;
208     private static final int VIEW_GROUP_ACTION_ADD_TAG = 4;
209     private static final int VIEW_CONTENT_NAVIGATION_TAG = 5;
210     private static final int SET_EMPTY_VIEW_ACTION_TAG = 6;
211     private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7;
212     private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8;
213     private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10;
214     private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11;
215     private static final int BITMAP_REFLECTION_ACTION_TAG = 12;
216     private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13;
217     private static final int VIEW_PADDING_ACTION_TAG = 14;
218     private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15;
219     private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18;
220     private static final int LAYOUT_PARAM_ACTION_TAG = 19;
221     private static final int OVERRIDE_TEXT_COLORS_TAG = 20;
222     private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21;
223     private static final int SET_INT_TAG_TAG = 22;
224     private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23;
225     private static final int RESOURCE_REFLECTION_ACTION_TAG = 24;
226     private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25;
227     private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26;
228     private static final int SET_RADIO_GROUP_CHECKED = 27;
229     private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28;
230     private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29;
231     private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30;
232     private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31;
233     private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32;
234 
235     /** @hide **/
236     @IntDef(prefix = "MARGIN_", value = {
237             MARGIN_LEFT,
238             MARGIN_TOP,
239             MARGIN_RIGHT,
240             MARGIN_BOTTOM,
241             MARGIN_START,
242             MARGIN_END
243     })
244     @Retention(RetentionPolicy.SOURCE)
245     public @interface MarginType {}
246     /** The value will apply to the marginLeft. */
247     public static final int MARGIN_LEFT = 0;
248     /** The value will apply to the marginTop. */
249     public static final int MARGIN_TOP = 1;
250     /** The value will apply to the marginRight. */
251     public static final int MARGIN_RIGHT = 2;
252     /** The value will apply to the marginBottom. */
253     public static final int MARGIN_BOTTOM = 3;
254     /** The value will apply to the marginStart. */
255     public static final int MARGIN_START = 4;
256     /** The value will apply to the marginEnd. */
257     public static final int MARGIN_END = 5;
258 
259     @IntDef(prefix = "VALUE_TYPE_", value = {
260             VALUE_TYPE_RAW,
261             VALUE_TYPE_COMPLEX_UNIT,
262             VALUE_TYPE_RESOURCE,
263             VALUE_TYPE_ATTRIBUTE
264     })
265     @Retention(RetentionPolicy.SOURCE)
266     @interface ValueType {}
267     static final int VALUE_TYPE_RAW = 1;
268     static final int VALUE_TYPE_COMPLEX_UNIT = 2;
269     static final int VALUE_TYPE_RESOURCE = 3;
270     static final int VALUE_TYPE_ATTRIBUTE = 4;
271 
272     /** @hide **/
273     @IntDef(flag = true, value = {
274             FLAG_REAPPLY_DISALLOWED,
275             FLAG_WIDGET_IS_COLLECTION_CHILD,
276             FLAG_USE_LIGHT_BACKGROUND_LAYOUT
277     })
278     @Retention(RetentionPolicy.SOURCE)
279     public @interface ApplyFlags {}
280     /**
281      * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify
282      * the layout in a way that isn't recoverable, since views are being removed.
283      * @hide
284      */
285     public static final int FLAG_REAPPLY_DISALLOWED = 1;
286     /**
287      * This flag indicates whether this RemoteViews object is being created from a
288      * RemoteViewsService for use as a child of a widget collection. This flag is used
289      * to determine whether or not certain features are available, in particular,
290      * setting on click extras and setting on click pending intents. The former is enabled,
291      * and the latter disabled when this flag is true.
292      * @hide
293      */
294     public static final int FLAG_WIDGET_IS_COLLECTION_CHILD = 2;
295     /**
296      * When this flag is set, the views is inflated with {@link #mLightBackgroundLayoutId} instead
297      * of {link #mLayoutId}
298      * @hide
299      */
300     public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4;
301 
302     /**
303      * Used to restrict the views which can be inflated
304      *
305      * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
306      */
307     private static final LayoutInflater.Filter INFLATER_FILTER =
308             (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class);
309 
310     /**
311      * Application that hosts the remote views.
312      *
313      * @hide
314      */
315     @UnsupportedAppUsage
316     public ApplicationInfo mApplication;
317 
318     /**
319      * The resource ID of the layout file. (Added to the parcel)
320      */
321     @UnsupportedAppUsage
322     private int mLayoutId;
323 
324     /**
325      * The resource ID of the layout file in dark text mode. (Added to the parcel)
326      */
327     private int mLightBackgroundLayoutId = 0;
328 
329     /**
330      * An array of actions to perform on the view tree once it has been
331      * inflated
332      */
333     @UnsupportedAppUsage
334     private ArrayList<Action> mActions;
335 
336     /**
337      * Maps bitmaps to unique indicies to avoid Bitmap duplication.
338      */
339     @UnsupportedAppUsage
340     private BitmapCache mBitmapCache;
341 
342     /**
343      * Indicates whether or not this RemoteViews object is contained as a child of any other
344      * RemoteViews.
345      */
346     private boolean mIsRoot = true;
347 
348     /**
349      * Constants to whether or not this RemoteViews is composed of a landscape and portrait
350      * RemoteViews.
351      */
352     private static final int MODE_NORMAL = 0;
353     private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
354     private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2;
355 
356     /**
357      * Used in conjunction with the special constructor
358      * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait
359      * RemoteViews.
360      */
361     private RemoteViews mLandscape = null;
362     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
363     private RemoteViews mPortrait = null;
364     /**
365      * List of RemoteViews with their ideal size. There must be at least two if the map is not null.
366      *
367      * The smallest remote view is always the last element in the list.
368      */
369     private List<RemoteViews> mSizedRemoteViews = null;
370 
371     /**
372      * Ideal size for this RemoteViews.
373      *
374      * Only to be used on children views used in a {@link RemoteViews} with
375      * {@link RemoteViews#hasSizedRemoteViews()}.
376      */
377     private SizeF mIdealSize = null;
378 
379     @ApplyFlags
380     private int mApplyFlags = 0;
381 
382     /**
383      * Id to use to override the ID of the top-level view in this RemoteViews.
384      *
385      * Only used if this RemoteViews is defined from a XML layout value.
386      */
387     private int mViewId = View.NO_ID;
388 
389     /**
390      * Id used to uniquely identify a {@link RemoteViews} instance coming from a given provider.
391      */
392     private long mProviderInstanceId = -1;
393 
394     /** Class cookies of the Parcel this instance was read from. */
395     private Map<Class, Object> mClassCookies;
396 
397     private static final InteractionHandler DEFAULT_INTERACTION_HANDLER =
398             (view, pendingIntent, response) ->
399                     startPendingIntent(view, pendingIntent, response.getLaunchOptions(view));
400 
401     private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>();
402 
403     /**
404      * This key is used to perform lookups in sMethods without causing allocations.
405      */
406     private static final MethodKey sLookupKey = new MethodKey();
407 
408     /**
409      * @hide
410      */
setRemoteInputs(@dRes int viewId, RemoteInput[] remoteInputs)411     public void setRemoteInputs(@IdRes int viewId, RemoteInput[] remoteInputs) {
412         mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
413     }
414 
415     /**
416      * Reduces all images and ensures that they are all below the given sizes.
417      *
418      * @param maxWidth the maximum width allowed
419      * @param maxHeight the maximum height allowed
420      *
421      * @hide
422      */
reduceImageSizes(int maxWidth, int maxHeight)423     public void reduceImageSizes(int maxWidth, int maxHeight) {
424         ArrayList<Bitmap> cache = mBitmapCache.mBitmaps;
425         for (int i = 0; i < cache.size(); i++) {
426             Bitmap bitmap = cache.get(i);
427             cache.set(i, Icon.scaleDownIfNecessary(bitmap, maxWidth, maxHeight));
428         }
429     }
430 
431     /**
432      * Override all text colors in this layout and replace them by the given text color.
433      *
434      * @param textColor The color to use.
435      *
436      * @hide
437      */
overrideTextColors(int textColor)438     public void overrideTextColors(int textColor) {
439         addAction(new OverrideTextColorsAction(textColor));
440     }
441 
442     /**
443      * Sets an integer tag to the view.
444      *
445      * @hide
446      */
setIntTag(@dRes int viewId, @IdRes int key, int tag)447     public void setIntTag(@IdRes int viewId, @IdRes int key, int tag) {
448         addAction(new SetIntTagAction(viewId, key, tag));
449     }
450 
451     /**
452      * Set that it is disallowed to reapply another remoteview with the same layout as this view.
453      * This should be done if an action is destroying the view tree of the base layout.
454      *
455      * @hide
456      */
addFlags(@pplyFlags int flags)457     public void addFlags(@ApplyFlags int flags) {
458         mApplyFlags = mApplyFlags | flags;
459     }
460 
461     /**
462      * @hide
463      */
hasFlags(@pplyFlags int flag)464     public boolean hasFlags(@ApplyFlags int flag) {
465         return (mApplyFlags & flag) == flag;
466     }
467 
468     /**
469      * Stores information related to reflection method lookup.
470      */
471     static class MethodKey {
472         public Class targetClass;
473         public Class paramClass;
474         public String methodName;
475 
476         @Override
equals(@ullable Object o)477         public boolean equals(@Nullable Object o) {
478             if (!(o instanceof MethodKey)) {
479                 return false;
480             }
481             MethodKey p = (MethodKey) o;
482             return Objects.equals(p.targetClass, targetClass)
483                     && Objects.equals(p.paramClass, paramClass)
484                     && Objects.equals(p.methodName, methodName);
485         }
486 
487         @Override
hashCode()488         public int hashCode() {
489             return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass)
490                     ^ Objects.hashCode(methodName);
491         }
492 
set(Class targetClass, Class paramClass, String methodName)493         public void set(Class targetClass, Class paramClass, String methodName) {
494             this.targetClass = targetClass;
495             this.paramClass = paramClass;
496             this.methodName = methodName;
497         }
498     }
499 
500 
501     /**
502      * Stores information related to reflection method lookup result.
503      */
504     static class MethodArgs {
505         public MethodHandle syncMethod;
506         public MethodHandle asyncMethod;
507         public String asyncMethodName;
508     }
509 
510     /**
511      * This annotation indicates that a subclass of View is allowed to be used
512      * with the {@link RemoteViews} mechanism.
513      */
514     @Target({ ElementType.TYPE })
515     @Retention(RetentionPolicy.RUNTIME)
516     public @interface RemoteView {
517     }
518 
519     /**
520      * Exception to send when something goes wrong executing an action
521      *
522      */
523     public static class ActionException extends RuntimeException {
ActionException(Exception ex)524         public ActionException(Exception ex) {
525             super(ex);
526         }
ActionException(String message)527         public ActionException(String message) {
528             super(message);
529         }
530         /**
531          * @hide
532          */
ActionException(Throwable t)533         public ActionException(Throwable t) {
534             super(t);
535         }
536     }
537 
538     /**
539      * Handler for view interactions (such as clicks) within a RemoteViews.
540      *
541      * @hide
542      */
543     public interface InteractionHandler {
544         /**
545          * Invoked when the user performs an interaction on the View.
546          *
547          * @param view the View with which the user interacted
548          * @param pendingIntent the base PendingIntent associated with the view
549          * @param response the response to the interaction, which knows how to fill in the
550          *                 attached PendingIntent
551          *
552          * @hide
553          */
onInteraction( View view, PendingIntent pendingIntent, RemoteResponse response)554         boolean onInteraction(
555                 View view,
556                 PendingIntent pendingIntent,
557                 RemoteResponse response);
558     }
559 
560     /**
561      * Base class for all actions that can be performed on an
562      * inflated view.
563      *
564      *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
565      */
566     private abstract static class Action implements Parcelable {
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)567         public abstract void apply(View root, ViewGroup rootParent, InteractionHandler handler,
568                 ColorResources colorResources) throws ActionException;
569 
570         public static final int MERGE_REPLACE = 0;
571         public static final int MERGE_APPEND = 1;
572         public static final int MERGE_IGNORE = 2;
573 
describeContents()574         public int describeContents() {
575             return 0;
576         }
577 
setBitmapCache(BitmapCache bitmapCache)578         public void setBitmapCache(BitmapCache bitmapCache) {
579             // Do nothing
580         }
581 
582         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
mergeBehavior()583         public int mergeBehavior() {
584             return MERGE_REPLACE;
585         }
586 
getActionTag()587         public abstract int getActionTag();
588 
getUniqueKey()589         public String getUniqueKey() {
590             return (getActionTag() + "_" + viewId);
591         }
592 
593         /**
594          * This is called on the background thread. It should perform any non-ui computations
595          * and return the final action which will run on the UI thread.
596          * Override this if some of the tasks can be performed async.
597          */
initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)598         public Action initActionAsync(ViewTree root, ViewGroup rootParent,
599                 InteractionHandler handler, ColorResources colorResources) {
600             return this;
601         }
602 
prefersAsyncApply()603         public boolean prefersAsyncApply() {
604             return false;
605         }
606 
607         /**
608          * Overridden by subclasses which have (or inherit) an ApplicationInfo instance
609          * as member variable
610          */
hasSameAppInfo(ApplicationInfo parentInfo)611         public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
612             return true;
613         }
614 
visitUris(@onNull Consumer<Uri> visitor)615         public void visitUris(@NonNull Consumer<Uri> visitor) {
616             // Nothing to visit by default
617         }
618 
619         @IdRes
620         @UnsupportedAppUsage
621         int viewId;
622     }
623 
624     /**
625      * Action class used during async inflation of RemoteViews. Subclasses are not parcelable.
626      */
627     private static abstract class RuntimeAction extends Action {
628         @Override
getActionTag()629         public final int getActionTag() {
630             return 0;
631         }
632 
633         @Override
writeToParcel(Parcel dest, int flags)634         public final void writeToParcel(Parcel dest, int flags) {
635             throw new UnsupportedOperationException();
636         }
637     }
638 
639     // Constant used during async execution. It is not parcelable.
640     private static final Action ACTION_NOOP = new RuntimeAction() {
641         @Override
642         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
643                 ColorResources colorResources) {
644         }
645     };
646 
647     /**
648      * Merges the passed RemoteViews actions with this RemoteViews actions according to
649      * action-specific merge rules.
650      *
651      * @param newRv
652      *
653      * @hide
654      */
655     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
mergeRemoteViews(RemoteViews newRv)656     public void mergeRemoteViews(RemoteViews newRv) {
657         if (newRv == null) return;
658         // We first copy the new RemoteViews, as the process of merging modifies the way the actions
659         // reference the bitmap cache. We don't want to modify the object as it may need to
660         // be merged and applied multiple times.
661         RemoteViews copy = new RemoteViews(newRv);
662 
663         HashMap<String, Action> map = new HashMap<String, Action>();
664         if (mActions == null) {
665             mActions = new ArrayList<Action>();
666         }
667 
668         int count = mActions.size();
669         for (int i = 0; i < count; i++) {
670             Action a = mActions.get(i);
671             map.put(a.getUniqueKey(), a);
672         }
673 
674         ArrayList<Action> newActions = copy.mActions;
675         if (newActions == null) return;
676         count = newActions.size();
677         for (int i = 0; i < count; i++) {
678             Action a = newActions.get(i);
679             String key = newActions.get(i).getUniqueKey();
680             int mergeBehavior = newActions.get(i).mergeBehavior();
681             if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) {
682                 mActions.remove(map.get(key));
683                 map.remove(key);
684             }
685 
686             // If the merge behavior is ignore, we don't bother keeping the extra action
687             if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) {
688                 mActions.add(a);
689             }
690         }
691 
692         // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache
693         mBitmapCache = new BitmapCache();
694         setBitmapCache(mBitmapCache);
695     }
696 
697     /**
698      * Note all {@link Uri} that are referenced internally, with the expectation
699      * that Uri permission grants will need to be issued to ensure the recipient
700      * of this object is able to render its contents.
701      *
702      * @hide
703      */
visitUris(@onNull Consumer<Uri> visitor)704     public void visitUris(@NonNull Consumer<Uri> visitor) {
705         if (mActions != null) {
706             for (int i = 0; i < mActions.size(); i++) {
707                 mActions.get(i).visitUris(visitor);
708             }
709         }
710     }
711 
visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor)712     private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) {
713         if (icon != null && (icon.getType() == Icon.TYPE_URI
714                 || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) {
715             visitor.accept(icon.getUri());
716         }
717     }
718 
719     private static class RemoteViewsContextWrapper extends ContextWrapper {
720         private final Context mContextForResources;
721 
RemoteViewsContextWrapper(Context context, Context contextForResources)722         RemoteViewsContextWrapper(Context context, Context contextForResources) {
723             super(context);
724             mContextForResources = contextForResources;
725         }
726 
727         @Override
getResources()728         public Resources getResources() {
729             return mContextForResources.getResources();
730         }
731 
732         @Override
getTheme()733         public Resources.Theme getTheme() {
734             return mContextForResources.getTheme();
735         }
736 
737         @Override
getPackageName()738         public String getPackageName() {
739             return mContextForResources.getPackageName();
740         }
741 
742         @Override
isRestricted()743         public boolean isRestricted() {
744             // Override isRestricted and direct to resource's implementation. The isRestricted is
745             // used for determining the risky resources loading, e.g. fonts, thus direct to context
746             // for resource.
747             return mContextForResources.isRestricted();
748         }
749     }
750 
751     private class SetEmptyView extends Action {
752         int emptyViewId;
753 
SetEmptyView(@dRes int viewId, @IdRes int emptyViewId)754         SetEmptyView(@IdRes int viewId, @IdRes int emptyViewId) {
755             this.viewId = viewId;
756             this.emptyViewId = emptyViewId;
757         }
758 
SetEmptyView(Parcel in)759         SetEmptyView(Parcel in) {
760             this.viewId = in.readInt();
761             this.emptyViewId = in.readInt();
762         }
763 
writeToParcel(Parcel out, int flags)764         public void writeToParcel(Parcel out, int flags) {
765             out.writeInt(this.viewId);
766             out.writeInt(this.emptyViewId);
767         }
768 
769         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)770         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
771                 ColorResources colorResources) {
772             final View view = root.findViewById(viewId);
773             if (!(view instanceof AdapterView<?>)) return;
774 
775             AdapterView<?> adapterView = (AdapterView<?>) view;
776 
777             final View emptyView = root.findViewById(emptyViewId);
778             if (emptyView == null) return;
779 
780             adapterView.setEmptyView(emptyView);
781         }
782 
783         @Override
getActionTag()784         public int getActionTag() {
785             return SET_EMPTY_VIEW_ACTION_TAG;
786         }
787     }
788 
789     private class SetPendingIntentTemplate extends Action {
SetPendingIntentTemplate(@dRes int id, PendingIntent pendingIntentTemplate)790         public SetPendingIntentTemplate(@IdRes int id, PendingIntent pendingIntentTemplate) {
791             this.viewId = id;
792             this.pendingIntentTemplate = pendingIntentTemplate;
793         }
794 
SetPendingIntentTemplate(Parcel parcel)795         public SetPendingIntentTemplate(Parcel parcel) {
796             viewId = parcel.readInt();
797             pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
798         }
799 
writeToParcel(Parcel dest, int flags)800         public void writeToParcel(Parcel dest, int flags) {
801             dest.writeInt(viewId);
802             PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest);
803         }
804 
805         @Override
apply(View root, ViewGroup rootParent, final InteractionHandler handler, ColorResources colorResources)806         public void apply(View root, ViewGroup rootParent, final InteractionHandler handler,
807                 ColorResources colorResources) {
808             final View target = root.findViewById(viewId);
809             if (target == null) return;
810 
811             // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense
812             if (target instanceof AdapterView<?>) {
813                 AdapterView<?> av = (AdapterView<?>) target;
814                 // The PendingIntent template is stored in the view's tag.
815                 OnItemClickListener listener = (parent, view, position, id) -> {
816                     RemoteResponse response = findRemoteResponseTag(view);
817                     if (response != null) {
818                         response.handleViewInteraction(view, handler);
819                     }
820                 };
821                 av.setOnItemClickListener(listener);
822                 av.setTag(pendingIntentTemplate);
823             } else {
824                 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" +
825                         "an AdapterView (id: " + viewId + ")");
826                 return;
827             }
828         }
829 
830         @Nullable
findRemoteResponseTag(@ullable View rootView)831         private RemoteResponse findRemoteResponseTag(@Nullable View rootView) {
832             if (rootView == null) return null;
833 
834             ArrayDeque<View> viewsToCheck = new ArrayDeque<>();
835             viewsToCheck.addLast(rootView);
836 
837             while (!viewsToCheck.isEmpty()) {
838                 View view = viewsToCheck.removeFirst();
839                 Object tag = view.getTag(R.id.fillInIntent);
840                 if (tag instanceof RemoteResponse) return (RemoteResponse) tag;
841                 if (!(view instanceof ViewGroup)) continue;
842 
843                 ViewGroup viewGroup = (ViewGroup) view;
844                 for (int i = 0; i < viewGroup.getChildCount(); i++) {
845                     viewsToCheck.addLast(viewGroup.getChildAt(i));
846                 }
847             }
848 
849             return null;
850         }
851 
852         @Override
getActionTag()853         public int getActionTag() {
854             return SET_PENDING_INTENT_TEMPLATE_TAG;
855         }
856 
857         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
858         PendingIntent pendingIntentTemplate;
859     }
860 
861     private class SetRemoteViewsAdapterList extends Action {
SetRemoteViewsAdapterList(@dRes int id, ArrayList<RemoteViews> list, int viewTypeCount)862         public SetRemoteViewsAdapterList(@IdRes int id, ArrayList<RemoteViews> list,
863                 int viewTypeCount) {
864             this.viewId = id;
865             this.list = list;
866             this.viewTypeCount = viewTypeCount;
867         }
868 
SetRemoteViewsAdapterList(Parcel parcel)869         public SetRemoteViewsAdapterList(Parcel parcel) {
870             viewId = parcel.readInt();
871             viewTypeCount = parcel.readInt();
872             list = parcel.createTypedArrayList(RemoteViews.CREATOR);
873         }
874 
writeToParcel(Parcel dest, int flags)875         public void writeToParcel(Parcel dest, int flags) {
876             dest.writeInt(viewId);
877             dest.writeInt(viewTypeCount);
878             dest.writeTypedList(list, flags);
879         }
880 
881         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)882         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
883                 ColorResources colorResources) {
884             final View target = root.findViewById(viewId);
885             if (target == null) return;
886 
887             // Ensure that we are applying to an AppWidget root
888             if (!(rootParent instanceof AppWidgetHostView)) {
889                 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
890                         "AppWidgets (root id: " + viewId + ")");
891                 return;
892             }
893             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
894             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
895                 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
896                         "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
897                 return;
898             }
899 
900             if (target instanceof AbsListView) {
901                 AbsListView v = (AbsListView) target;
902                 Adapter a = v.getAdapter();
903                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
904                     ((RemoteViewsListAdapter) a).setViewsList(list);
905                 } else {
906                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount,
907                             colorResources));
908                 }
909             } else if (target instanceof AdapterViewAnimator) {
910                 AdapterViewAnimator v = (AdapterViewAnimator) target;
911                 Adapter a = v.getAdapter();
912                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
913                     ((RemoteViewsListAdapter) a).setViewsList(list);
914                 } else {
915                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount,
916                             colorResources));
917                 }
918             }
919         }
920 
921         @Override
getActionTag()922         public int getActionTag() {
923             return SET_REMOTE_VIEW_ADAPTER_LIST_TAG;
924         }
925 
926         int viewTypeCount;
927         ArrayList<RemoteViews> list;
928     }
929 
930     private static class SetRemoteCollectionItemListAdapterAction extends Action {
931         private final RemoteCollectionItems mItems;
932 
SetRemoteCollectionItemListAdapterAction(@dRes int id, RemoteCollectionItems items)933         SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) {
934             viewId = id;
935             mItems = items;
936         }
937 
SetRemoteCollectionItemListAdapterAction(Parcel parcel)938         SetRemoteCollectionItemListAdapterAction(Parcel parcel) {
939             viewId = parcel.readInt();
940             mItems = parcel.readTypedObject(RemoteCollectionItems.CREATOR);
941         }
942 
943         @Override
writeToParcel(Parcel dest, int flags)944         public void writeToParcel(Parcel dest, int flags) {
945             dest.writeInt(viewId);
946             dest.writeTypedObject(mItems, flags);
947         }
948 
949         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)950         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
951                 ColorResources colorResources) throws ActionException {
952             View target = root.findViewById(viewId);
953             if (target == null) return;
954 
955             // Ensure that we are applying to an AppWidget root
956             if (!(rootParent instanceof AppWidgetHostView)) {
957                 Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
958                         + "AppWidgets (root id: " + viewId + ")");
959                 return;
960             }
961 
962             if (!(target instanceof AdapterView)) {
963                 Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not "
964                         + "an AdapterView (id: " + viewId + ")");
965                 return;
966             }
967 
968             AdapterView adapterView = (AdapterView) target;
969             Adapter adapter = adapterView.getAdapter();
970             // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type
971             // count hasn't increased. Note that AbsListView allocates a fixed size array for view
972             // recycling in setAdapter, so we must call setAdapter again if the number of view types
973             // increases.
974             if (adapter instanceof RemoteCollectionItemsAdapter
975                     && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) {
976                 try {
977                     ((RemoteCollectionItemsAdapter) adapter).setData(
978                             mItems, handler, colorResources);
979                 } catch (Throwable throwable) {
980                     // setData should never failed with the validation in the items builder, but if
981                     // it does, catch and rethrow.
982                     throw new ActionException(throwable);
983                 }
984                 return;
985             }
986 
987             try {
988                 adapterView.setAdapter(
989                         new RemoteCollectionItemsAdapter(mItems, handler, colorResources));
990             } catch (Throwable throwable) {
991                 // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to
992                 // a type error.
993                 throw new ActionException(throwable);
994             }
995         }
996 
997         @Override
getActionTag()998         public int getActionTag() {
999             return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG;
1000         }
1001     }
1002 
1003     private class SetRemoteViewsAdapterIntent extends Action {
SetRemoteViewsAdapterIntent(@dRes int id, Intent intent)1004         public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) {
1005             this.viewId = id;
1006             this.intent = intent;
1007         }
1008 
SetRemoteViewsAdapterIntent(Parcel parcel)1009         public SetRemoteViewsAdapterIntent(Parcel parcel) {
1010             viewId = parcel.readInt();
1011             intent = parcel.readTypedObject(Intent.CREATOR);
1012         }
1013 
writeToParcel(Parcel dest, int flags)1014         public void writeToParcel(Parcel dest, int flags) {
1015             dest.writeInt(viewId);
1016             dest.writeTypedObject(intent, flags);
1017         }
1018 
1019         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1020         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
1021                 ColorResources colorResources) {
1022             final View target = root.findViewById(viewId);
1023             if (target == null) return;
1024 
1025             // Ensure that we are applying to an AppWidget root
1026             if (!(rootParent instanceof AppWidgetHostView)) {
1027                 Log.e(LOG_TAG, "setRemoteAdapter can only be used for "
1028                         + "AppWidgets (root id: " + viewId + ")");
1029                 return;
1030             }
1031 
1032             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
1033             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
1034                 Log.e(LOG_TAG, "Cannot setRemoteAdapter on a view which is not "
1035                         + "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
1036                 return;
1037             }
1038 
1039             // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
1040             // RemoteViewsService
1041             AppWidgetHostView host = (AppWidgetHostView) rootParent;
1042             intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId())
1043                     .putExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND,
1044                             hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT));
1045 
1046             if (target instanceof AbsListView) {
1047                 AbsListView v = (AbsListView) target;
1048                 v.setRemoteViewsAdapter(intent, isAsync);
1049                 v.setRemoteViewsInteractionHandler(handler);
1050             } else if (target instanceof AdapterViewAnimator) {
1051                 AdapterViewAnimator v = (AdapterViewAnimator) target;
1052                 v.setRemoteViewsAdapter(intent, isAsync);
1053                 v.setRemoteViewsOnClickHandler(handler);
1054             }
1055         }
1056 
1057         @Override
initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1058         public Action initActionAsync(ViewTree root, ViewGroup rootParent,
1059                 InteractionHandler handler, ColorResources colorResources) {
1060             SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent);
1061             copy.isAsync = true;
1062             return copy;
1063         }
1064 
1065         @Override
getActionTag()1066         public int getActionTag() {
1067             return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG;
1068         }
1069 
1070         Intent intent;
1071         boolean isAsync = false;
1072     }
1073 
1074     /**
1075      * Equivalent to calling
1076      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
1077      * to launch the provided {@link PendingIntent}.
1078      */
1079     private class SetOnClickResponse extends Action {
1080 
SetOnClickResponse(@dRes int id, RemoteResponse response)1081         SetOnClickResponse(@IdRes int id, RemoteResponse response) {
1082             this.viewId = id;
1083             this.mResponse = response;
1084         }
1085 
SetOnClickResponse(Parcel parcel)1086         SetOnClickResponse(Parcel parcel) {
1087             viewId = parcel.readInt();
1088             mResponse = new RemoteResponse();
1089             mResponse.readFromParcel(parcel);
1090         }
1091 
writeToParcel(Parcel dest, int flags)1092         public void writeToParcel(Parcel dest, int flags) {
1093             dest.writeInt(viewId);
1094             mResponse.writeToParcel(dest, flags);
1095         }
1096 
1097         @Override
apply(View root, ViewGroup rootParent, final InteractionHandler handler, ColorResources colorResources)1098         public void apply(View root, ViewGroup rootParent, final InteractionHandler handler,
1099                 ColorResources colorResources) {
1100             final View target = root.findViewById(viewId);
1101             if (target == null) return;
1102 
1103             if (mResponse.mPendingIntent != null) {
1104                 // If the view is an AdapterView, setting a PendingIntent on click doesn't make
1105                 // much sense, do they mean to set a PendingIntent template for the
1106                 // AdapterView's children?
1107                 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
1108                     Log.w(LOG_TAG, "Cannot SetOnClickResponse for collection item "
1109                             + "(id: " + viewId + ")");
1110                     ApplicationInfo appInfo = root.getContext().getApplicationInfo();
1111 
1112                     // We let this slide for HC and ICS so as to not break compatibility. It should
1113                     // have been disabled from the outset, but was left open by accident.
1114                     if (appInfo != null
1115                             && appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
1116                         return;
1117                     }
1118                 }
1119                 target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent);
1120             } else if (mResponse.mFillIntent != null) {
1121                 if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
1122                     Log.e(LOG_TAG, "The method setOnClickFillInIntent is available "
1123                             + "only from RemoteViewsFactory (ie. on collection items).");
1124                     return;
1125                 }
1126                 if (target == root) {
1127                     // Target is a root node of an AdapterView child. Set the response in the tag.
1128                     // Actual click handling is done by OnItemClickListener in
1129                     // SetPendingIntentTemplate, which uses this tag information.
1130                     target.setTagInternal(com.android.internal.R.id.fillInIntent, mResponse);
1131                     return;
1132                 }
1133             } else {
1134                 // No intent to apply, clear the listener and any tags that were previously set.
1135                 target.setOnClickListener(null);
1136                 target.setTagInternal(R.id.pending_intent_tag, null);
1137                 target.setTagInternal(com.android.internal.R.id.fillInIntent, null);
1138                 return;
1139             }
1140             target.setOnClickListener(v -> mResponse.handleViewInteraction(v, handler));
1141         }
1142 
1143         @Override
getActionTag()1144         public int getActionTag() {
1145             return SET_ON_CLICK_RESPONSE_TAG;
1146         }
1147 
1148         final RemoteResponse mResponse;
1149     }
1150 
1151     /**
1152      * Equivalent to calling
1153      * {@link android.widget.CompoundButton#setOnCheckedChangeListener(
1154      * android.widget.CompoundButton.OnCheckedChangeListener)}
1155      * to launch the provided {@link PendingIntent}.
1156      */
1157     private class SetOnCheckedChangeResponse extends Action {
1158 
1159         private final RemoteResponse mResponse;
1160 
SetOnCheckedChangeResponse(@dRes int id, RemoteResponse response)1161         SetOnCheckedChangeResponse(@IdRes int id, RemoteResponse response) {
1162             this.viewId = id;
1163             this.mResponse = response;
1164         }
1165 
SetOnCheckedChangeResponse(Parcel parcel)1166         SetOnCheckedChangeResponse(Parcel parcel) {
1167             viewId = parcel.readInt();
1168             mResponse = new RemoteResponse();
1169             mResponse.readFromParcel(parcel);
1170         }
1171 
writeToParcel(Parcel dest, int flags)1172         public void writeToParcel(Parcel dest, int flags) {
1173             dest.writeInt(viewId);
1174             mResponse.writeToParcel(dest, flags);
1175         }
1176 
1177         @Override
apply(View root, ViewGroup rootParent, final InteractionHandler handler, ColorResources colorResources)1178         public void apply(View root, ViewGroup rootParent, final InteractionHandler handler,
1179                 ColorResources colorResources) {
1180             final View target = root.findViewById(viewId);
1181             if (target == null) return;
1182             if (!(target instanceof CompoundButton)) {
1183                 Log.w(LOG_TAG, "setOnCheckedChange methods cannot be used on "
1184                         + "non-CompoundButton child (id: " + viewId + ")");
1185                 return;
1186             }
1187             CompoundButton button = (CompoundButton) target;
1188 
1189             if (mResponse.mPendingIntent != null) {
1190                 // setOnCheckedChangePendingIntent cannot be used with collection children, which
1191                 // must use setOnCheckedChangeFillInIntent instead.
1192                 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
1193                     Log.w(LOG_TAG, "Cannot setOnCheckedChangePendingIntent for collection item "
1194                             + "(id: " + viewId + ")");
1195                     return;
1196                 }
1197                 target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent);
1198             } else if (mResponse.mFillIntent != null) {
1199                 if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) {
1200                     Log.e(LOG_TAG, "The method setOnCheckedChangeFillInIntent is available "
1201                             + "only from RemoteViewsFactory (ie. on collection items).");
1202                     return;
1203                 }
1204             } else {
1205                 // No intent to apply, clear any existing listener or tag.
1206                 button.setOnCheckedChangeListener(null);
1207                 button.setTagInternal(R.id.remote_checked_change_listener_tag, null);
1208                 return;
1209             }
1210 
1211             OnCheckedChangeListener onCheckedChangeListener =
1212                     (v, isChecked) -> mResponse.handleViewInteraction(v, handler);
1213             button.setTagInternal(R.id.remote_checked_change_listener_tag, onCheckedChangeListener);
1214             button.setOnCheckedChangeListener(onCheckedChangeListener);
1215         }
1216 
1217         @Override
getActionTag()1218         public int getActionTag() {
1219             return SET_ON_CHECKED_CHANGE_RESPONSE_TAG;
1220         }
1221     }
1222 
1223     /** @hide **/
getSourceBounds(View v)1224     public static Rect getSourceBounds(View v) {
1225         final float appScale = v.getContext().getResources()
1226                 .getCompatibilityInfo().applicationScale;
1227         final int[] pos = new int[2];
1228         v.getLocationOnScreen(pos);
1229 
1230         final Rect rect = new Rect();
1231         rect.left = (int) (pos[0] * appScale + 0.5f);
1232         rect.top = (int) (pos[1] * appScale + 0.5f);
1233         rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
1234         rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
1235         return rect;
1236     }
1237 
1238     @Nullable
getParameterType(int type)1239     private static Class<?> getParameterType(int type) {
1240         switch (type) {
1241             case BaseReflectionAction.BOOLEAN:
1242                 return boolean.class;
1243             case BaseReflectionAction.BYTE:
1244                 return byte.class;
1245             case BaseReflectionAction.SHORT:
1246                 return short.class;
1247             case BaseReflectionAction.INT:
1248                 return int.class;
1249             case BaseReflectionAction.LONG:
1250                 return long.class;
1251             case BaseReflectionAction.FLOAT:
1252                 return float.class;
1253             case BaseReflectionAction.DOUBLE:
1254                 return double.class;
1255             case BaseReflectionAction.CHAR:
1256                 return char.class;
1257             case BaseReflectionAction.STRING:
1258                 return String.class;
1259             case BaseReflectionAction.CHAR_SEQUENCE:
1260                 return CharSequence.class;
1261             case BaseReflectionAction.URI:
1262                 return Uri.class;
1263             case BaseReflectionAction.BITMAP:
1264                 return Bitmap.class;
1265             case BaseReflectionAction.BUNDLE:
1266                 return Bundle.class;
1267             case BaseReflectionAction.INTENT:
1268                 return Intent.class;
1269             case BaseReflectionAction.COLOR_STATE_LIST:
1270                 return ColorStateList.class;
1271             case BaseReflectionAction.ICON:
1272                 return Icon.class;
1273             case BaseReflectionAction.BLEND_MODE:
1274                 return BlendMode.class;
1275             default:
1276                 return null;
1277         }
1278     }
1279 
1280     @Nullable
getMethod(View view, String methodName, Class<?> paramType, boolean async)1281     private MethodHandle getMethod(View view, String methodName, Class<?> paramType,
1282             boolean async) {
1283         MethodArgs result;
1284         Class<? extends View> klass = view.getClass();
1285 
1286         synchronized (sMethods) {
1287             // The key is defined by the view class, param class and method name.
1288             sLookupKey.set(klass, paramType, methodName);
1289             result = sMethods.get(sLookupKey);
1290 
1291             if (result == null) {
1292                 Method method;
1293                 try {
1294                     if (paramType == null) {
1295                         method = klass.getMethod(methodName);
1296                     } else {
1297                         method = klass.getMethod(methodName, paramType);
1298                     }
1299                     if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
1300                         throw new ActionException("view: " + klass.getName()
1301                                 + " can't use method with RemoteViews: "
1302                                 + methodName + getParameters(paramType));
1303                     }
1304 
1305                     result = new MethodArgs();
1306                     result.syncMethod = MethodHandles.publicLookup().unreflect(method);
1307                     result.asyncMethodName =
1308                             method.getAnnotation(RemotableViewMethod.class).asyncImpl();
1309                 } catch (NoSuchMethodException | IllegalAccessException ex) {
1310                     throw new ActionException("view: " + klass.getName() + " doesn't have method: "
1311                             + methodName + getParameters(paramType));
1312                 }
1313 
1314                 MethodKey key = new MethodKey();
1315                 key.set(klass, paramType, methodName);
1316                 sMethods.put(key, result);
1317             }
1318 
1319             if (!async) {
1320                 return result.syncMethod;
1321             }
1322             // Check this so see if async method is implemented or not.
1323             if (result.asyncMethodName.isEmpty()) {
1324                 return null;
1325             }
1326             // Async method is lazily loaded. If it is not yet loaded, load now.
1327             if (result.asyncMethod == null) {
1328                 MethodType asyncType = result.syncMethod.type()
1329                         .dropParameterTypes(0, 1).changeReturnType(Runnable.class);
1330                 try {
1331                     result.asyncMethod = MethodHandles.publicLookup().findVirtual(
1332                             klass, result.asyncMethodName, asyncType);
1333                 } catch (NoSuchMethodException | IllegalAccessException ex) {
1334                     throw new ActionException("Async implementation declared as "
1335                             + result.asyncMethodName + " but not defined for " + methodName
1336                             + ": public Runnable " + result.asyncMethodName + " ("
1337                             + TextUtils.join(",", asyncType.parameterArray()) + ")");
1338                 }
1339             }
1340             return result.asyncMethod;
1341         }
1342     }
1343 
getParameters(Class<?> paramType)1344     private static String getParameters(Class<?> paramType) {
1345         if (paramType == null) return "()";
1346         return "(" + paramType + ")";
1347     }
1348 
1349     /**
1350      * Equivalent to calling
1351      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
1352      * on the {@link Drawable} of a given view.
1353      * <p>
1354      * The operation will be performed on the {@link Drawable} returned by the
1355      * target {@link View#getBackground()} by default.  If targetBackground is false,
1356      * we assume the target is an {@link ImageView} and try applying the operations
1357      * to {@link ImageView#getDrawable()}.
1358      * <p>
1359      */
1360     private class SetDrawableTint extends Action {
SetDrawableTint(@dRes int id, boolean targetBackground, @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode)1361         SetDrawableTint(@IdRes int id, boolean targetBackground,
1362                 @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) {
1363             this.viewId = id;
1364             this.targetBackground = targetBackground;
1365             this.colorFilter = colorFilter;
1366             this.filterMode = mode;
1367         }
1368 
SetDrawableTint(Parcel parcel)1369         SetDrawableTint(Parcel parcel) {
1370             viewId = parcel.readInt();
1371             targetBackground = parcel.readInt() != 0;
1372             colorFilter = parcel.readInt();
1373             filterMode = PorterDuff.intToMode(parcel.readInt());
1374         }
1375 
writeToParcel(Parcel dest, int flags)1376         public void writeToParcel(Parcel dest, int flags) {
1377             dest.writeInt(viewId);
1378             dest.writeInt(targetBackground ? 1 : 0);
1379             dest.writeInt(colorFilter);
1380             dest.writeInt(PorterDuff.modeToInt(filterMode));
1381         }
1382 
1383         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1384         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
1385                 ColorResources colorResources) {
1386             final View target = root.findViewById(viewId);
1387             if (target == null) return;
1388 
1389             // Pick the correct drawable to modify for this view
1390             Drawable targetDrawable = null;
1391             if (targetBackground) {
1392                 targetDrawable = target.getBackground();
1393             } else if (target instanceof ImageView) {
1394                 ImageView imageView = (ImageView) target;
1395                 targetDrawable = imageView.getDrawable();
1396             }
1397 
1398             if (targetDrawable != null) {
1399                 targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
1400             }
1401         }
1402 
1403         @Override
getActionTag()1404         public int getActionTag() {
1405             return SET_DRAWABLE_TINT_TAG;
1406         }
1407 
1408         boolean targetBackground;
1409         @ColorInt int colorFilter;
1410         PorterDuff.Mode filterMode;
1411     }
1412 
1413     /**
1414      * Equivalent to calling
1415      * {@link RippleDrawable#setColor(ColorStateList)},
1416      * on the {@link Drawable} of a given view.
1417      * <p>
1418      * The operation will be performed on the {@link Drawable} returned by the
1419      * target {@link View#getBackground()}.
1420      * <p>
1421      */
1422     private class SetRippleDrawableColor extends Action {
1423 
1424         ColorStateList mColorStateList;
1425 
SetRippleDrawableColor(@dRes int id, ColorStateList colorStateList)1426         SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) {
1427             this.viewId = id;
1428             this.mColorStateList = colorStateList;
1429         }
1430 
SetRippleDrawableColor(Parcel parcel)1431         SetRippleDrawableColor(Parcel parcel) {
1432             viewId = parcel.readInt();
1433             mColorStateList = parcel.readParcelable(null);
1434         }
1435 
writeToParcel(Parcel dest, int flags)1436         public void writeToParcel(Parcel dest, int flags) {
1437             dest.writeInt(viewId);
1438             dest.writeParcelable(mColorStateList, 0);
1439         }
1440 
1441         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1442         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
1443                 ColorResources colorResources) {
1444             final View target = root.findViewById(viewId);
1445             if (target == null) return;
1446 
1447             // Pick the correct drawable to modify for this view
1448             Drawable targetDrawable = target.getBackground();
1449 
1450             if (targetDrawable instanceof RippleDrawable) {
1451                 ((RippleDrawable) targetDrawable.mutate()).setColor(mColorStateList);
1452             }
1453         }
1454 
1455         @Override
getActionTag()1456         public int getActionTag() {
1457             return SET_RIPPLE_DRAWABLE_COLOR_TAG;
1458         }
1459     }
1460 
1461     private final class ViewContentNavigation extends Action {
1462         final boolean mNext;
1463 
ViewContentNavigation(@dRes int viewId, boolean next)1464         ViewContentNavigation(@IdRes int viewId, boolean next) {
1465             this.viewId = viewId;
1466             this.mNext = next;
1467         }
1468 
ViewContentNavigation(Parcel in)1469         ViewContentNavigation(Parcel in) {
1470             this.viewId = in.readInt();
1471             this.mNext = in.readBoolean();
1472         }
1473 
writeToParcel(Parcel out, int flags)1474         public void writeToParcel(Parcel out, int flags) {
1475             out.writeInt(this.viewId);
1476             out.writeBoolean(this.mNext);
1477         }
1478 
1479         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1480         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
1481                 ColorResources colorResources) {
1482             final View view = root.findViewById(viewId);
1483             if (view == null) return;
1484 
1485             try {
1486                 getMethod(view,
1487                         mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view);
1488             } catch (Throwable ex) {
1489                 throw new ActionException(ex);
1490             }
1491         }
1492 
mergeBehavior()1493         public int mergeBehavior() {
1494             return MERGE_IGNORE;
1495         }
1496 
1497         @Override
getActionTag()1498         public int getActionTag() {
1499             return VIEW_CONTENT_NAVIGATION_TAG;
1500         }
1501     }
1502 
1503     private static class BitmapCache {
1504 
1505         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
1506         ArrayList<Bitmap> mBitmaps;
1507         int mBitmapMemory = -1;
1508 
BitmapCache()1509         public BitmapCache() {
1510             mBitmaps = new ArrayList<>();
1511         }
1512 
BitmapCache(Parcel source)1513         public BitmapCache(Parcel source) {
1514             mBitmaps = source.createTypedArrayList(Bitmap.CREATOR);
1515         }
1516 
getBitmapId(Bitmap b)1517         public int getBitmapId(Bitmap b) {
1518             if (b == null) {
1519                 return -1;
1520             } else {
1521                 if (mBitmaps.contains(b)) {
1522                     return mBitmaps.indexOf(b);
1523                 } else {
1524                     mBitmaps.add(b);
1525                     mBitmapMemory = -1;
1526                     return (mBitmaps.size() - 1);
1527                 }
1528             }
1529         }
1530 
1531         @Nullable
getBitmapForId(int id)1532         public Bitmap getBitmapForId(int id) {
1533             if (id == -1 || id >= mBitmaps.size()) {
1534                 return null;
1535             } else {
1536                 return mBitmaps.get(id);
1537             }
1538         }
1539 
writeBitmapsToParcel(Parcel dest, int flags)1540         public void writeBitmapsToParcel(Parcel dest, int flags) {
1541             dest.writeTypedList(mBitmaps, flags);
1542         }
1543 
getBitmapMemory()1544         public int getBitmapMemory() {
1545             if (mBitmapMemory < 0) {
1546                 mBitmapMemory = 0;
1547                 int count = mBitmaps.size();
1548                 for (int i = 0; i < count; i++) {
1549                     mBitmapMemory += mBitmaps.get(i).getAllocationByteCount();
1550                 }
1551             }
1552             return mBitmapMemory;
1553         }
1554     }
1555 
1556     private class BitmapReflectionAction extends Action {
1557         int bitmapId;
1558         @UnsupportedAppUsage
1559         Bitmap bitmap;
1560         @UnsupportedAppUsage
1561         String methodName;
1562 
BitmapReflectionAction(@dRes int viewId, String methodName, Bitmap bitmap)1563         BitmapReflectionAction(@IdRes int viewId, String methodName, Bitmap bitmap) {
1564             this.bitmap = bitmap;
1565             this.viewId = viewId;
1566             this.methodName = methodName;
1567             bitmapId = mBitmapCache.getBitmapId(bitmap);
1568         }
1569 
BitmapReflectionAction(Parcel in)1570         BitmapReflectionAction(Parcel in) {
1571             viewId = in.readInt();
1572             methodName = in.readString8();
1573             bitmapId = in.readInt();
1574             bitmap = mBitmapCache.getBitmapForId(bitmapId);
1575         }
1576 
1577         @Override
writeToParcel(Parcel dest, int flags)1578         public void writeToParcel(Parcel dest, int flags) {
1579             dest.writeInt(viewId);
1580             dest.writeString8(methodName);
1581             dest.writeInt(bitmapId);
1582         }
1583 
1584         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1585         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
1586                 ColorResources colorResources) throws ActionException {
1587             ReflectionAction ra = new ReflectionAction(viewId, methodName,
1588                     BaseReflectionAction.BITMAP,
1589                     bitmap);
1590             ra.apply(root, rootParent, handler, colorResources);
1591         }
1592 
1593         @Override
setBitmapCache(BitmapCache bitmapCache)1594         public void setBitmapCache(BitmapCache bitmapCache) {
1595             bitmapId = bitmapCache.getBitmapId(bitmap);
1596         }
1597 
1598         @Override
getActionTag()1599         public int getActionTag() {
1600             return BITMAP_REFLECTION_ACTION_TAG;
1601         }
1602     }
1603 
1604     /**
1605      * Base class for the reflection actions.
1606      */
1607     private abstract class BaseReflectionAction extends Action {
1608         static final int BOOLEAN = 1;
1609         static final int BYTE = 2;
1610         static final int SHORT = 3;
1611         static final int INT = 4;
1612         static final int LONG = 5;
1613         static final int FLOAT = 6;
1614         static final int DOUBLE = 7;
1615         static final int CHAR = 8;
1616         static final int STRING = 9;
1617         static final int CHAR_SEQUENCE = 10;
1618         static final int URI = 11;
1619         // BITMAP actions are never stored in the list of actions. They are only used locally
1620         // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache.
1621         static final int BITMAP = 12;
1622         static final int BUNDLE = 13;
1623         static final int INTENT = 14;
1624         static final int COLOR_STATE_LIST = 15;
1625         static final int ICON = 16;
1626         static final int BLEND_MODE = 17;
1627 
1628         @UnsupportedAppUsage
1629         String methodName;
1630         int type;
1631 
BaseReflectionAction(@dRes int viewId, String methodName, int type)1632         BaseReflectionAction(@IdRes int viewId, String methodName, int type) {
1633             this.viewId = viewId;
1634             this.methodName = methodName;
1635             this.type = type;
1636         }
1637 
BaseReflectionAction(Parcel in)1638         BaseReflectionAction(Parcel in) {
1639             this.viewId = in.readInt();
1640             this.methodName = in.readString8();
1641             this.type = in.readInt();
1642             //noinspection ConstantIfStatement
1643             if (false) {
1644                 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
1645                         + " methodName=" + this.methodName + " type=" + this.type);
1646             }
1647         }
1648 
writeToParcel(Parcel out, int flags)1649         public void writeToParcel(Parcel out, int flags) {
1650             out.writeInt(this.viewId);
1651             out.writeString8(this.methodName);
1652             out.writeInt(this.type);
1653         }
1654 
1655         /**
1656          * Returns the value to use as parameter for the method.
1657          *
1658          * The view might be passed as {@code null} if the parameter value is requested outside of
1659          * inflation. If the parameter cannot be determined at that time, the method should return
1660          * {@code null} but not raise any exception.
1661          */
1662         @Nullable
getParameterValue(@ullable View view)1663         protected abstract Object getParameterValue(@Nullable View view) throws ActionException;
1664 
1665         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1666         public final void apply(View root, ViewGroup rootParent, InteractionHandler handler,
1667                 ColorResources colorResources) {
1668             final View view = root.findViewById(viewId);
1669             if (view == null) return;
1670 
1671             Class<?> param = getParameterType(this.type);
1672             if (param == null) {
1673                 throw new ActionException("bad type: " + this.type);
1674             }
1675             Object value = getParameterValue(view);
1676             try {
1677                 getMethod(view, this.methodName, param, false /* async */).invoke(view, value);
1678             } catch (Throwable ex) {
1679                 throw new ActionException(ex);
1680             }
1681         }
1682 
1683         @Override
initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1684         public final Action initActionAsync(ViewTree root, ViewGroup rootParent,
1685                 InteractionHandler handler, ColorResources colorResources) {
1686             final View view = root.findViewById(viewId);
1687             if (view == null) return ACTION_NOOP;
1688 
1689             Class<?> param = getParameterType(this.type);
1690             if (param == null) {
1691                 throw new ActionException("bad type: " + this.type);
1692             }
1693 
1694             Object value = getParameterValue(view);
1695             try {
1696                 MethodHandle method = getMethod(view, this.methodName, param, true /* async */);
1697 
1698                 if (method != null) {
1699                     Runnable endAction = (Runnable) method.invoke(view, value);
1700                     if (endAction == null) {
1701                         return ACTION_NOOP;
1702                     }
1703                     // Special case view stub
1704                     if (endAction instanceof ViewStub.ViewReplaceRunnable) {
1705                         root.createTree();
1706                         // Replace child tree
1707                         root.findViewTreeById(viewId).replaceView(
1708                                 ((ViewStub.ViewReplaceRunnable) endAction).view);
1709                     }
1710                     return new RunnableAction(endAction);
1711                 }
1712             } catch (Throwable ex) {
1713                 throw new ActionException(ex);
1714             }
1715 
1716             return this;
1717         }
1718 
mergeBehavior()1719         public final int mergeBehavior() {
1720             // smoothScrollBy is cumulative, everything else overwites.
1721             if (methodName.equals("smoothScrollBy")) {
1722                 return MERGE_APPEND;
1723             } else {
1724                 return MERGE_REPLACE;
1725             }
1726         }
1727 
1728         @Override
getUniqueKey()1729         public final String getUniqueKey() {
1730             // Each type of reflection action corresponds to a setter, so each should be seen as
1731             // unique from the standpoint of merging.
1732             return super.getUniqueKey() + this.methodName + this.type;
1733         }
1734 
1735         @Override
prefersAsyncApply()1736         public final boolean prefersAsyncApply() {
1737             return this.type == URI || this.type == ICON;
1738         }
1739 
1740         @Override
visitUris(@onNull Consumer<Uri> visitor)1741         public final void visitUris(@NonNull Consumer<Uri> visitor) {
1742             switch (this.type) {
1743                 case URI:
1744                     final Uri uri = (Uri) getParameterValue(null);
1745                     if (uri != null) visitor.accept(uri);
1746                     break;
1747                 case ICON:
1748                     final Icon icon = (Icon) getParameterValue(null);
1749                     if (icon != null) visitIconUri(icon, visitor);
1750                     break;
1751             }
1752         }
1753     }
1754 
1755     /** Class for the reflection actions. */
1756     private final class ReflectionAction extends BaseReflectionAction {
1757         @UnsupportedAppUsage
1758         Object value;
1759 
ReflectionAction(@dRes int viewId, String methodName, int type, Object value)1760         ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) {
1761             super(viewId, methodName, type);
1762             this.value = value;
1763         }
1764 
ReflectionAction(Parcel in)1765         ReflectionAction(Parcel in) {
1766             super(in);
1767             // For some values that may have been null, we first check a flag to see if they were
1768             // written to the parcel.
1769             switch (this.type) {
1770                 case BOOLEAN:
1771                     this.value = in.readBoolean();
1772                     break;
1773                 case BYTE:
1774                     this.value = in.readByte();
1775                     break;
1776                 case SHORT:
1777                     this.value = (short) in.readInt();
1778                     break;
1779                 case INT:
1780                     this.value = in.readInt();
1781                     break;
1782                 case LONG:
1783                     this.value = in.readLong();
1784                     break;
1785                 case FLOAT:
1786                     this.value = in.readFloat();
1787                     break;
1788                 case DOUBLE:
1789                     this.value = in.readDouble();
1790                     break;
1791                 case CHAR:
1792                     this.value = (char) in.readInt();
1793                     break;
1794                 case STRING:
1795                     this.value = in.readString8();
1796                     break;
1797                 case CHAR_SEQUENCE:
1798                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1799                     break;
1800                 case URI:
1801                     this.value = in.readTypedObject(Uri.CREATOR);
1802                     break;
1803                 case BITMAP:
1804                     this.value = in.readTypedObject(Bitmap.CREATOR);
1805                     break;
1806                 case BUNDLE:
1807                     this.value = in.readBundle();
1808                     break;
1809                 case INTENT:
1810                     this.value = in.readTypedObject(Intent.CREATOR);
1811                     break;
1812                 case COLOR_STATE_LIST:
1813                     this.value = in.readTypedObject(ColorStateList.CREATOR);
1814                     break;
1815                 case ICON:
1816                     this.value = in.readTypedObject(Icon.CREATOR);
1817                     break;
1818                 case BLEND_MODE:
1819                     this.value = BlendMode.fromValue(in.readInt());
1820                     break;
1821                 default:
1822                     break;
1823             }
1824         }
1825 
writeToParcel(Parcel out, int flags)1826         public void writeToParcel(Parcel out, int flags) {
1827             super.writeToParcel(out, flags);
1828             // For some values which are null, we record an integer flag to indicate whether
1829             // we have written a valid value to the parcel.
1830             switch (this.type) {
1831                 case BOOLEAN:
1832                     out.writeBoolean((Boolean) this.value);
1833                     break;
1834                 case BYTE:
1835                     out.writeByte((Byte) this.value);
1836                     break;
1837                 case SHORT:
1838                     out.writeInt((Short) this.value);
1839                     break;
1840                 case INT:
1841                     out.writeInt((Integer) this.value);
1842                     break;
1843                 case LONG:
1844                     out.writeLong((Long) this.value);
1845                     break;
1846                 case FLOAT:
1847                     out.writeFloat((Float) this.value);
1848                     break;
1849                 case DOUBLE:
1850                     out.writeDouble((Double) this.value);
1851                     break;
1852                 case CHAR:
1853                     out.writeInt((int) ((Character) this.value).charValue());
1854                     break;
1855                 case STRING:
1856                     out.writeString8((String) this.value);
1857                     break;
1858                 case CHAR_SEQUENCE:
1859                     TextUtils.writeToParcel((CharSequence) this.value, out, flags);
1860                     break;
1861                 case BUNDLE:
1862                     out.writeBundle((Bundle) this.value);
1863                     break;
1864                 case BLEND_MODE:
1865                     out.writeInt(BlendMode.toValue((BlendMode) this.value));
1866                     break;
1867                 case URI:
1868                 case BITMAP:
1869                 case INTENT:
1870                 case COLOR_STATE_LIST:
1871                 case ICON:
1872                     out.writeTypedObject((Parcelable) this.value, flags);
1873                     break;
1874                 default:
1875                     break;
1876             }
1877         }
1878 
1879         @Nullable
1880         @Override
getParameterValue(@ullable View view)1881         protected Object getParameterValue(@Nullable View view) throws ActionException {
1882             return this.value;
1883         }
1884 
1885         @Override
getActionTag()1886         public int getActionTag() {
1887             return REFLECTION_ACTION_TAG;
1888         }
1889     }
1890 
1891     private final class ResourceReflectionAction extends BaseReflectionAction {
1892 
1893         static final int DIMEN_RESOURCE = 1;
1894         static final int COLOR_RESOURCE = 2;
1895         static final int STRING_RESOURCE = 3;
1896 
1897         private final int mResourceType;
1898         private final int mResId;
1899 
ResourceReflectionAction(@dRes int viewId, String methodName, int parameterType, int resourceType, int resId)1900         ResourceReflectionAction(@IdRes int viewId, String methodName, int parameterType,
1901                 int resourceType, int resId) {
1902             super(viewId, methodName, parameterType);
1903             this.mResourceType = resourceType;
1904             this.mResId = resId;
1905         }
1906 
ResourceReflectionAction(Parcel in)1907         ResourceReflectionAction(Parcel in) {
1908             super(in);
1909             this.mResourceType = in.readInt();
1910             this.mResId = in.readInt();
1911         }
1912 
1913         @Override
writeToParcel(Parcel dest, int flags)1914         public void writeToParcel(Parcel dest, int flags) {
1915             super.writeToParcel(dest, flags);
1916             dest.writeInt(this.mResourceType);
1917             dest.writeInt(this.mResId);
1918         }
1919 
1920         @Nullable
1921         @Override
getParameterValue(@ullable View view)1922         protected Object getParameterValue(@Nullable View view) throws ActionException {
1923             if (view == null) return null;
1924 
1925             Resources resources = view.getContext().getResources();
1926             try {
1927                 switch (this.mResourceType) {
1928                     case DIMEN_RESOURCE:
1929                         switch (this.type) {
1930                             case BaseReflectionAction.INT:
1931                                 return mResId == 0 ? 0 : resources.getDimensionPixelSize(mResId);
1932                             case BaseReflectionAction.FLOAT:
1933                                 return mResId == 0 ? 0f : resources.getDimension(mResId);
1934                             default:
1935                                 throw new ActionException(
1936                                         "dimen resources must be used as INT or FLOAT, "
1937                                                 + "not " + this.type);
1938                         }
1939                     case COLOR_RESOURCE:
1940                         switch (this.type) {
1941                             case BaseReflectionAction.INT:
1942                                 return mResId == 0 ? 0 : view.getContext().getColor(mResId);
1943                             case BaseReflectionAction.COLOR_STATE_LIST:
1944                                 return mResId == 0
1945                                         ? null : view.getContext().getColorStateList(mResId);
1946                             default:
1947                                 throw new ActionException(
1948                                         "color resources must be used as INT or COLOR_STATE_LIST,"
1949                                                 + " not " + this.type);
1950                         }
1951                     case STRING_RESOURCE:
1952                         switch (this.type) {
1953                             case BaseReflectionAction.CHAR_SEQUENCE:
1954                                 return mResId == 0 ? null : resources.getText(mResId);
1955                             case BaseReflectionAction.STRING:
1956                                 return mResId == 0 ? null : resources.getString(mResId);
1957                             default:
1958                                 throw new ActionException(
1959                                         "string resources must be used as STRING or CHAR_SEQUENCE,"
1960                                                 + " not " + this.type);
1961                         }
1962                     default:
1963                         throw new ActionException("unknown resource type: " + this.mResourceType);
1964                 }
1965             } catch (ActionException ex) {
1966                 throw ex;
1967             } catch (Throwable t) {
1968                 throw new ActionException(t);
1969             }
1970         }
1971 
1972         @Override
getActionTag()1973         public int getActionTag() {
1974             return RESOURCE_REFLECTION_ACTION_TAG;
1975         }
1976     }
1977 
1978     private final class AttributeReflectionAction extends BaseReflectionAction {
1979 
1980         static final int DIMEN_RESOURCE = 1;
1981         static final int COLOR_RESOURCE = 2;
1982         static final int STRING_RESOURCE = 3;
1983 
1984         private final int mResourceType;
1985         private final int mAttrId;
1986 
AttributeReflectionAction(@dRes int viewId, String methodName, int parameterType, int resourceType, int attrId)1987         AttributeReflectionAction(@IdRes int viewId, String methodName, int parameterType,
1988                 int resourceType, int attrId) {
1989             super(viewId, methodName, parameterType);
1990             this.mResourceType = resourceType;
1991             this.mAttrId = attrId;
1992         }
1993 
AttributeReflectionAction(Parcel in)1994         AttributeReflectionAction(Parcel in) {
1995             super(in);
1996             this.mResourceType = in.readInt();
1997             this.mAttrId = in.readInt();
1998         }
1999 
2000         @Override
writeToParcel(Parcel dest, int flags)2001         public void writeToParcel(Parcel dest, int flags) {
2002             super.writeToParcel(dest, flags);
2003             dest.writeInt(this.mResourceType);
2004             dest.writeInt(this.mAttrId);
2005         }
2006 
2007         @Override
getParameterValue(View view)2008         protected Object getParameterValue(View view) throws ActionException {
2009             TypedArray typedArray = view.getContext().obtainStyledAttributes(new int[]{mAttrId});
2010             try {
2011                 // When mAttrId == 0, we will depend on the default values below
2012                 if (mAttrId != 0 && typedArray.getType(0) == TypedValue.TYPE_NULL) {
2013                     throw new ActionException("Attribute 0x" + Integer.toHexString(this.mAttrId)
2014                             + " is not defined");
2015                 }
2016                 switch (this.mResourceType) {
2017                     case DIMEN_RESOURCE:
2018                         switch (this.type) {
2019                             case BaseReflectionAction.INT:
2020                                 return typedArray.getDimensionPixelSize(0, 0);
2021                             case BaseReflectionAction.FLOAT:
2022                                 return typedArray.getDimension(0, 0);
2023                             default:
2024                                 throw new ActionException(
2025                                         "dimen attribute 0x" + Integer.toHexString(this.mAttrId)
2026                                                 + " must be used as INT or FLOAT,"
2027                                                 + " not " + this.type);
2028                         }
2029                     case COLOR_RESOURCE:
2030                         switch (this.type) {
2031                             case BaseReflectionAction.INT:
2032                                 return typedArray.getColor(0, 0);
2033                             case BaseReflectionAction.COLOR_STATE_LIST:
2034                                 return typedArray.getColorStateList(0);
2035                             default:
2036                                 throw new ActionException(
2037                                         "color attribute 0x" + Integer.toHexString(this.mAttrId)
2038                                                 + " must be used as INT or COLOR_STATE_LIST,"
2039                                                 + " not " + this.type);
2040                         }
2041                     case STRING_RESOURCE:
2042                         switch (this.type) {
2043                             case BaseReflectionAction.CHAR_SEQUENCE:
2044                                 return typedArray.getText(0);
2045                             case BaseReflectionAction.STRING:
2046                                 return typedArray.getString(0);
2047                             default:
2048                                 throw new ActionException(
2049                                         "string attribute 0x" + Integer.toHexString(this.mAttrId)
2050                                                 + " must be used as STRING or CHAR_SEQUENCE,"
2051                                                 + " not " + this.type);
2052                         }
2053                     default:
2054                         // Note: This can only be an implementation error.
2055                         throw new ActionException(
2056                                 "Unknown resource type: " + this.mResourceType);
2057                 }
2058             } catch (ActionException ex) {
2059                 throw ex;
2060             } catch (Throwable t) {
2061                 throw new ActionException(t);
2062             } finally {
2063                 typedArray.recycle();
2064             }
2065         }
2066 
2067         @Override
getActionTag()2068         public int getActionTag() {
2069             return ATTRIBUTE_REFLECTION_ACTION_TAG;
2070         }
2071     }
2072     private final class ComplexUnitDimensionReflectionAction extends BaseReflectionAction {
2073 
2074         private final float mValue;
2075         @ComplexDimensionUnit
2076         private final int mUnit;
2077 
ComplexUnitDimensionReflectionAction(int viewId, String methodName, int parameterType, float value, @ComplexDimensionUnit int unit)2078         ComplexUnitDimensionReflectionAction(int viewId, String methodName, int parameterType,
2079                 float value, @ComplexDimensionUnit int unit) {
2080             super(viewId, methodName, parameterType);
2081             this.mValue = value;
2082             this.mUnit = unit;
2083         }
2084 
ComplexUnitDimensionReflectionAction(Parcel in)2085         ComplexUnitDimensionReflectionAction(Parcel in) {
2086             super(in);
2087             this.mValue = in.readFloat();
2088             this.mUnit = in.readInt();
2089         }
2090 
2091         @Override
writeToParcel(Parcel dest, int flags)2092         public void writeToParcel(Parcel dest, int flags) {
2093             super.writeToParcel(dest, flags);
2094             dest.writeFloat(this.mValue);
2095             dest.writeInt(this.mUnit);
2096         }
2097 
2098         @Nullable
2099         @Override
getParameterValue(@ullable View view)2100         protected Object getParameterValue(@Nullable View view) throws ActionException {
2101             if (view == null) return null;
2102 
2103             DisplayMetrics dm = view.getContext().getResources().getDisplayMetrics();
2104             try {
2105                 int data = TypedValue.createComplexDimension(this.mValue, this.mUnit);
2106                 switch (this.type) {
2107                     case ReflectionAction.INT:
2108                         return TypedValue.complexToDimensionPixelSize(data, dm);
2109                     case ReflectionAction.FLOAT:
2110                         return TypedValue.complexToDimension(data, dm);
2111                     default:
2112                         throw new ActionException(
2113                                 "parameter type must be INT or FLOAT, not " + this.type);
2114                 }
2115             } catch (ActionException ex) {
2116                 throw ex;
2117             } catch (Throwable t) {
2118                 throw new ActionException(t);
2119             }
2120         }
2121 
2122         @Override
getActionTag()2123         public int getActionTag() {
2124             return COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG;
2125         }
2126     }
2127 
2128     private final class NightModeReflectionAction extends BaseReflectionAction {
2129 
2130         private final Object mLightValue;
2131         private final Object mDarkValue;
2132 
NightModeReflectionAction( @dRes int viewId, String methodName, int type, Object lightValue, Object darkValue)2133         NightModeReflectionAction(
2134                 @IdRes int viewId,
2135                 String methodName,
2136                 int type,
2137                 Object lightValue,
2138                 Object darkValue) {
2139             super(viewId, methodName, type);
2140             mLightValue = lightValue;
2141             mDarkValue = darkValue;
2142         }
2143 
NightModeReflectionAction(Parcel in)2144         NightModeReflectionAction(Parcel in) {
2145             super(in);
2146             switch (this.type) {
2147                 case ICON:
2148                     mLightValue = in.readTypedObject(Icon.CREATOR);
2149                     mDarkValue = in.readTypedObject(Icon.CREATOR);
2150                     break;
2151                 case COLOR_STATE_LIST:
2152                     mLightValue = in.readTypedObject(ColorStateList.CREATOR);
2153                     mDarkValue = in.readTypedObject(ColorStateList.CREATOR);
2154                     break;
2155                 case INT:
2156                     mLightValue = in.readInt();
2157                     mDarkValue = in.readInt();
2158                     break;
2159                 default:
2160                     throw new ActionException("Unexpected night mode action type: " + this.type);
2161             }
2162         }
2163 
2164         @Override
writeToParcel(Parcel out, int flags)2165         public void writeToParcel(Parcel out, int flags) {
2166             super.writeToParcel(out, flags);
2167             switch (this.type) {
2168                 case ICON:
2169                 case COLOR_STATE_LIST:
2170                     out.writeTypedObject((Parcelable) mLightValue, flags);
2171                     out.writeTypedObject((Parcelable) mDarkValue, flags);
2172                     break;
2173                 case INT:
2174                     out.writeInt((int) mLightValue);
2175                     out.writeInt((int) mDarkValue);
2176                     break;
2177             }
2178         }
2179 
2180         @Nullable
2181         @Override
getParameterValue(@ullable View view)2182         protected Object getParameterValue(@Nullable View view) throws ActionException {
2183             if (view == null) return null;
2184 
2185             Configuration configuration = view.getResources().getConfiguration();
2186             return configuration.isNightModeActive() ? mDarkValue : mLightValue;
2187         }
2188 
2189         @Override
getActionTag()2190         public int getActionTag() {
2191             return NIGHT_MODE_REFLECTION_ACTION_TAG;
2192         }
2193     }
2194 
2195     /**
2196      * This is only used for async execution of actions and it not parcelable.
2197      */
2198     private static final class RunnableAction extends RuntimeAction {
2199         private final Runnable mRunnable;
2200 
RunnableAction(Runnable r)2201         RunnableAction(Runnable r) {
2202             mRunnable = r;
2203         }
2204 
2205         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)2206         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2207                 ColorResources colorResources) {
2208             mRunnable.run();
2209         }
2210     }
2211 
configureRemoteViewsAsChild(RemoteViews rv)2212     private void configureRemoteViewsAsChild(RemoteViews rv) {
2213         rv.setBitmapCache(mBitmapCache);
2214         rv.setNotRoot();
2215     }
2216 
setNotRoot()2217     void setNotRoot() {
2218         mIsRoot = false;
2219     }
2220 
hasStableId(View view)2221     private static boolean hasStableId(View view) {
2222         Object tag = view.getTag(com.android.internal.R.id.remote_views_stable_id);
2223         return tag != null;
2224     }
2225 
getStableId(View view)2226     private static int getStableId(View view) {
2227         Integer id = (Integer) view.getTag(com.android.internal.R.id.remote_views_stable_id);
2228         return id == null ? ViewGroupActionAdd.NO_ID : id;
2229     }
2230 
setStableId(View view, int stableId)2231     private static void setStableId(View view, int stableId) {
2232         view.setTagInternal(com.android.internal.R.id.remote_views_stable_id, stableId);
2233     }
2234 
2235     // Returns the next recyclable child of the view group, or -1 if there are none.
getNextRecyclableChild(ViewGroup vg)2236     private static int getNextRecyclableChild(ViewGroup vg) {
2237         Integer tag = (Integer) vg.getTag(com.android.internal.R.id.remote_views_next_child);
2238         return tag == null ? -1 : tag;
2239     }
2240 
getViewLayoutId(View v)2241     private static int getViewLayoutId(View v) {
2242         return (Integer) v.getTag(R.id.widget_frame);
2243     }
2244 
setNextRecyclableChild(ViewGroup vg, int nextChild, int numChildren)2245     private static void setNextRecyclableChild(ViewGroup vg, int nextChild, int numChildren) {
2246         if (nextChild < 0 || nextChild >= numChildren) {
2247             vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, -1);
2248         } else {
2249             vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, nextChild);
2250         }
2251     }
2252 
finalizeViewRecycling(ViewGroup root)2253     private void finalizeViewRecycling(ViewGroup root) {
2254         // Remove any recyclable children that were not used. nextChild should either be -1 or point
2255         // to the next recyclable child that hasn't been recycled.
2256         int nextChild = getNextRecyclableChild(root);
2257         if (nextChild >= 0 && nextChild < root.getChildCount()) {
2258             root.removeViews(nextChild, root.getChildCount() - nextChild);
2259         }
2260         // Make sure on the next round, we don't try to recycle if removeAllViews is not called.
2261         setNextRecyclableChild(root, -1, 0);
2262         // Traverse the view tree.
2263         for (int i = 0; i < root.getChildCount(); i++) {
2264             View child = root.getChildAt(i);
2265             if (child instanceof ViewGroup && !child.isRootNamespace()) {
2266                 finalizeViewRecycling((ViewGroup) child);
2267             }
2268         }
2269     }
2270 
2271     /**
2272      * ViewGroup methods that are related to adding Views.
2273      */
2274     private class ViewGroupActionAdd extends Action {
2275         static final int NO_ID = -1;
2276         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
2277         private RemoteViews mNestedViews;
2278         private int mIndex;
2279         private int mStableId;
2280 
ViewGroupActionAdd(@dRes int viewId, RemoteViews nestedViews)2281         ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews) {
2282             this(viewId, nestedViews, -1 /* index */, NO_ID /* nestedViewId */);
2283         }
2284 
ViewGroupActionAdd(@dRes int viewId, RemoteViews nestedViews, int index)2285         ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index) {
2286             this(viewId, nestedViews, index, NO_ID /* nestedViewId */);
2287         }
2288 
ViewGroupActionAdd(@dRes int viewId, RemoteViews nestedViews, int index, int stableId)2289         ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index, int stableId) {
2290             this.viewId = viewId;
2291             mNestedViews = nestedViews;
2292             mIndex = index;
2293             mStableId = stableId;
2294             if (nestedViews != null) {
2295                 configureRemoteViewsAsChild(nestedViews);
2296             }
2297         }
2298 
ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, Map<Class, Object> classCookies)2299         ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info,
2300                 int depth, Map<Class, Object> classCookies) {
2301             viewId = parcel.readInt();
2302             mIndex = parcel.readInt();
2303             mStableId = parcel.readInt();
2304             mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies);
2305             mNestedViews.addFlags(mApplyFlags);
2306         }
2307 
writeToParcel(Parcel dest, int flags)2308         public void writeToParcel(Parcel dest, int flags) {
2309             dest.writeInt(viewId);
2310             dest.writeInt(mIndex);
2311             dest.writeInt(mStableId);
2312             mNestedViews.writeToParcel(dest, flags);
2313         }
2314 
2315         @Override
hasSameAppInfo(ApplicationInfo parentInfo)2316         public boolean hasSameAppInfo(ApplicationInfo parentInfo) {
2317             return mNestedViews.hasSameAppInfo(parentInfo);
2318         }
2319 
findViewIndexToRecycle(ViewGroup target, RemoteViews newContent)2320         private int findViewIndexToRecycle(ViewGroup target, RemoteViews newContent) {
2321             for (int nextChild = getNextRecyclableChild(target); nextChild < target.getChildCount();
2322                     nextChild++) {
2323                 View child = target.getChildAt(nextChild);
2324                 if (getStableId(child) == mStableId) {
2325                     return nextChild;
2326                 }
2327             }
2328             return -1;
2329         }
2330 
2331         @Override
apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)2332         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2333                 ColorResources colorResources) {
2334             final Context context = root.getContext();
2335             final ViewGroup target = root.findViewById(viewId);
2336 
2337             if (target == null) {
2338                 return;
2339             }
2340 
2341             // If removeAllViews was called, this returns the next potential recycled view.
2342             // If there are no more views to recycle (or removeAllViews was not called), this
2343             // will return -1.
2344             final int nextChild = getNextRecyclableChild(target);
2345             RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context);
2346             if (nextChild >= 0 && mStableId != NO_ID) {
2347                 // At that point, the views starting at index nextChild are the ones recyclable but
2348                 // not yet recycled. All views added on that round of application are placed before.
2349                 // Find the next view with the same stable id, or -1.
2350                 int recycledViewIndex = findViewIndexToRecycle(target, rvToApply);
2351                 if (recycledViewIndex >= 0) {
2352                     View child = target.getChildAt(recycledViewIndex);
2353                     if (rvToApply.canRecycleView(child)) {
2354                         if (nextChild < recycledViewIndex) {
2355                             target.removeViews(nextChild, recycledViewIndex - nextChild);
2356                         }
2357                         setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
2358                         rvToApply.reapply(context, child, handler, null /* size */, colorResources,
2359                                 false /* topLevel */);
2360                         return;
2361                     }
2362                     // If we cannot recycle the views, we still remove all views in between to
2363                     // avoid weird behaviors and insert the new view in place of the old one.
2364                     target.removeViews(nextChild, recycledViewIndex - nextChild + 1);
2365                 }
2366             }
2367             // If we cannot recycle, insert the new view before the next recyclable child.
2368 
2369             // Inflate nested views and add as children
2370             View nestedView = rvToApply.apply(context, target, handler, null /* size */,
2371                     colorResources);
2372             if (mStableId != NO_ID) {
2373                 setStableId(nestedView, mStableId);
2374             }
2375             target.addView(nestedView, mIndex >= 0 ? mIndex : nextChild);
2376             if (nextChild >= 0) {
2377                 // If we are at the end, there is no reason to try to recycle anymore
2378                 setNextRecyclableChild(target, nextChild + 1, target.getChildCount());
2379             }
2380         }
2381 
2382         @Override
initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)2383         public Action initActionAsync(ViewTree root, ViewGroup rootParent,
2384                 InteractionHandler handler, ColorResources colorResources) {
2385             // In the async implementation, update the view tree so that subsequent calls to
2386             // findViewById return the current view.
2387             root.createTree();
2388             ViewTree target = root.findViewTreeById(viewId);
2389             if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
2390                 return ACTION_NOOP;
2391             }
2392             final ViewGroup targetVg = (ViewGroup) target.mRoot;
2393 
2394             // Inflate nested views and perform all the async tasks for the child remoteView.
2395             final Context context = root.mRoot.getContext();
2396 
2397             // If removeAllViews was called, this returns the next potential recycled view.
2398             // If there are no more views to recycle (or removeAllViews was not called), this
2399             // will return -1.
2400             final int nextChild = getNextRecyclableChild(targetVg);
2401             if (nextChild >= 0 && mStableId != NO_ID) {
2402                 RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context);
2403                 final int recycledViewIndex = target.findChildIndex(nextChild,
2404                         view -> getStableId(view) == mStableId);
2405                 if (recycledViewIndex >= 0) {
2406                     // At that point, the views starting at index nextChild are the ones
2407                     // recyclable but not yet recycled. All views added on that round of
2408                     // application are placed before.
2409                     ViewTree recycled = target.mChildren.get(recycledViewIndex);
2410                     // We can only recycle the view if the layout id is the same.
2411                     if (rvToApply.canRecycleView(recycled.mRoot)) {
2412                         if (recycledViewIndex > nextChild) {
2413                             target.removeChildren(nextChild, recycledViewIndex - nextChild);
2414                         }
2415                         setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size());
2416                         final AsyncApplyTask reapplyTask = rvToApply.getInternalAsyncApplyTask(
2417                                 context,
2418                                 targetVg, null /* listener */, handler, null /* size */,
2419                                 colorResources,
2420                                 recycled.mRoot);
2421                         final ViewTree tree = reapplyTask.doInBackground();
2422                         if (tree == null) {
2423                             throw new ActionException(reapplyTask.mError);
2424                         }
2425                         return new RuntimeAction() {
2426                             @Override
2427                             public void apply(View root, ViewGroup rootParent,
2428                                     InteractionHandler handler, ColorResources colorResources)
2429                                     throws ActionException {
2430                                 reapplyTask.onPostExecute(tree);
2431                                 if (recycledViewIndex > nextChild) {
2432                                     targetVg.removeViews(nextChild, recycledViewIndex - nextChild);
2433                                 }
2434                             }
2435                         };
2436                     }
2437                     // If the layout id is different, still remove the children as if we recycled
2438                     // the view, to insert at the same place.
2439                     target.removeChildren(nextChild, recycledViewIndex - nextChild + 1);
2440                     return insertNewView(context, target, handler, colorResources,
2441                             () -> targetVg.removeViews(nextChild,
2442                                     recycledViewIndex - nextChild + 1));
2443 
2444                 }
2445             }
2446             // If we cannot recycle, simply add the view at the same available slot.
2447             return insertNewView(context, target, handler, colorResources, () -> {});
2448         }
2449 
insertNewView(Context context, ViewTree target, InteractionHandler handler, ColorResources colorResources, Runnable finalizeAction)2450         private Action insertNewView(Context context, ViewTree target, InteractionHandler handler,
2451                 ColorResources colorResources, Runnable finalizeAction) {
2452             ViewGroup targetVg = (ViewGroup) target.mRoot;
2453             int nextChild = getNextRecyclableChild(targetVg);
2454             final AsyncApplyTask task = mNestedViews.getInternalAsyncApplyTask(context, targetVg,
2455                     null /* listener */, handler, null /* size */, colorResources,
2456                     null /* result */);
2457             final ViewTree tree = task.doInBackground();
2458 
2459             if (tree == null) {
2460                 throw new ActionException(task.mError);
2461             }
2462             if (mStableId != NO_ID) {
2463                 setStableId(task.mResult, mStableId);
2464             }
2465 
2466             // Update the global view tree, so that next call to findViewTreeById
2467             // goes through the subtree as well.
2468             final int insertIndex = mIndex >= 0 ? mIndex : nextChild;
2469             target.addChild(tree, insertIndex);
2470             if (nextChild >= 0) {
2471                 setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size());
2472             }
2473 
2474             return new RuntimeAction() {
2475                 @Override
2476                 public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2477                         ColorResources colorResources) throws ActionException {
2478                     task.onPostExecute(tree);
2479                     finalizeAction.run();
2480                     targetVg.addView(task.mResult, insertIndex);
2481                 }
2482             };
2483         }
2484 
2485         @Override
2486         public void setBitmapCache(BitmapCache bitmapCache) {
2487             mNestedViews.setBitmapCache(bitmapCache);
2488         }
2489 
2490         @Override
2491         public int mergeBehavior() {
2492             return MERGE_APPEND;
2493         }
2494 
2495         @Override
2496         public boolean prefersAsyncApply() {
2497             return mNestedViews.prefersAsyncApply();
2498         }
2499 
2500         @Override
2501         public int getActionTag() {
2502             return VIEW_GROUP_ACTION_ADD_TAG;
2503         }
2504     }
2505 
2506     /**
2507      * ViewGroup methods related to removing child views.
2508      */
2509     private class ViewGroupActionRemove extends Action {
2510         /**
2511          * Id that indicates that all child views of the affected ViewGroup should be removed.
2512          *
2513          * <p>Using -2 because the default id is -1. This avoids accidentally matching that.
2514          */
2515         private static final int REMOVE_ALL_VIEWS_ID = -2;
2516 
2517         private int mViewIdToKeep;
2518 
2519         ViewGroupActionRemove(@IdRes int viewId) {
2520             this(viewId, REMOVE_ALL_VIEWS_ID);
2521         }
2522 
2523         ViewGroupActionRemove(@IdRes int viewId, @IdRes int viewIdToKeep) {
2524             this.viewId = viewId;
2525             mViewIdToKeep = viewIdToKeep;
2526         }
2527 
2528         ViewGroupActionRemove(Parcel parcel) {
2529             viewId = parcel.readInt();
2530             mViewIdToKeep = parcel.readInt();
2531         }
2532 
2533         public void writeToParcel(Parcel dest, int flags) {
2534             dest.writeInt(viewId);
2535             dest.writeInt(mViewIdToKeep);
2536         }
2537 
2538         @Override
2539         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2540                 ColorResources colorResources) {
2541             final ViewGroup target = root.findViewById(viewId);
2542 
2543             if (target == null) {
2544                 return;
2545             }
2546 
2547             if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
2548                 // Remote any view without a stable id
2549                 for (int i = target.getChildCount() - 1; i >= 0; i--) {
2550                     if (!hasStableId(target.getChildAt(i))) {
2551                         target.removeViewAt(i);
2552                     }
2553                 }
2554                 // In the end, only children with a stable id (i.e. recyclable) are left.
2555                 setNextRecyclableChild(target, 0, target.getChildCount());
2556                 return;
2557             }
2558 
2559             removeAllViewsExceptIdToKeep(target);
2560         }
2561 
2562         @Override
2563         public Action initActionAsync(ViewTree root, ViewGroup rootParent,
2564                 InteractionHandler handler, ColorResources colorResources) {
2565             // In the async implementation, update the view tree so that subsequent calls to
2566             // findViewById return the current view.
2567             root.createTree();
2568             ViewTree target = root.findViewTreeById(viewId);
2569 
2570             if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
2571                 return ACTION_NOOP;
2572             }
2573 
2574             final ViewGroup targetVg = (ViewGroup) target.mRoot;
2575 
2576             if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
2577                 target.mChildren.removeIf(childTree -> !hasStableId(childTree.mRoot));
2578                 setNextRecyclableChild(targetVg, 0, target.mChildren.size());
2579             } else {
2580                 // Remove just the children which don't match the excepted view
2581                 target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep);
2582                 if (target.mChildren.isEmpty()) {
2583                     target.mChildren = null;
2584                 }
2585             }
2586             return new RuntimeAction() {
2587                 @Override
2588                 public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2589                         ColorResources colorResources) throws ActionException {
2590                     if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) {
2591                         for (int i = targetVg.getChildCount() - 1; i >= 0; i--) {
2592                             if (!hasStableId(targetVg.getChildAt(i))) {
2593                                 targetVg.removeViewAt(i);
2594                             }
2595                         }
2596                         return;
2597                     }
2598 
2599                     removeAllViewsExceptIdToKeep(targetVg);
2600                 }
2601             };
2602         }
2603 
2604         /**
2605          * Iterates through the children in the given ViewGroup and removes all the views that
2606          * do not have an id of {@link #mViewIdToKeep}.
2607          */
2608         private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) {
2609             // Otherwise, remove all the views that do not match the id to keep.
2610             int index = viewGroup.getChildCount() - 1;
2611             while (index >= 0) {
2612                 if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) {
2613                     viewGroup.removeViewAt(index);
2614                 }
2615                 index--;
2616             }
2617         }
2618 
2619         @Override
2620         public int getActionTag() {
2621             return VIEW_GROUP_ACTION_REMOVE_TAG;
2622         }
2623 
2624         @Override
2625         public int mergeBehavior() {
2626             return MERGE_APPEND;
2627         }
2628     }
2629 
2630     /**
2631      * Action to remove a view from its parent.
2632      */
2633     private class RemoveFromParentAction extends Action {
2634 
2635         RemoveFromParentAction(@IdRes int viewId) {
2636             this.viewId = viewId;
2637         }
2638 
2639         RemoveFromParentAction(Parcel parcel) {
2640             viewId = parcel.readInt();
2641         }
2642 
2643         public void writeToParcel(Parcel dest, int flags) {
2644             dest.writeInt(viewId);
2645         }
2646 
2647         @Override
2648         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2649                 ColorResources colorResources) {
2650             final View target = root.findViewById(viewId);
2651 
2652             if (target == null || target == root) {
2653                 return;
2654             }
2655 
2656             ViewParent parent = target.getParent();
2657             if (parent instanceof ViewManager) {
2658                 ((ViewManager) parent).removeView(target);
2659             }
2660         }
2661 
2662         @Override
2663         public Action initActionAsync(ViewTree root, ViewGroup rootParent,
2664                 InteractionHandler handler, ColorResources colorResources) {
2665             // In the async implementation, update the view tree so that subsequent calls to
2666             // findViewById return the correct view.
2667             root.createTree();
2668             ViewTree target = root.findViewTreeById(viewId);
2669 
2670             if (target == null || target == root) {
2671                 return ACTION_NOOP;
2672             }
2673 
2674             ViewTree parent = root.findViewTreeParentOf(target);
2675             if (parent == null || !(parent.mRoot instanceof ViewManager)) {
2676                 return ACTION_NOOP;
2677             }
2678             final ViewManager parentVg = (ViewManager) parent.mRoot;
2679 
2680             parent.mChildren.remove(target);
2681             return new RuntimeAction() {
2682                 @Override
2683                 public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2684                         ColorResources colorResources) throws ActionException {
2685                     parentVg.removeView(target.mRoot);
2686                 }
2687             };
2688         }
2689 
2690         @Override
2691         public int getActionTag() {
2692             return REMOVE_FROM_PARENT_ACTION_TAG;
2693         }
2694 
2695         @Override
2696         public int mergeBehavior() {
2697             return MERGE_APPEND;
2698         }
2699     }
2700 
2701     /**
2702      * Helper action to set compound drawables on a TextView. Supports relative
2703      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
2704      */
2705     private class TextViewDrawableAction extends Action {
2706         public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, @DrawableRes int d1,
2707                 @DrawableRes int d2, @DrawableRes int d3, @DrawableRes int d4) {
2708             this.viewId = viewId;
2709             this.isRelative = isRelative;
2710             this.useIcons = false;
2711             this.d1 = d1;
2712             this.d2 = d2;
2713             this.d3 = d3;
2714             this.d4 = d4;
2715         }
2716 
2717         public TextViewDrawableAction(@IdRes int viewId, boolean isRelative,
2718                 Icon i1, Icon i2, Icon i3, Icon i4) {
2719             this.viewId = viewId;
2720             this.isRelative = isRelative;
2721             this.useIcons = true;
2722             this.i1 = i1;
2723             this.i2 = i2;
2724             this.i3 = i3;
2725             this.i4 = i4;
2726         }
2727 
2728         public TextViewDrawableAction(Parcel parcel) {
2729             viewId = parcel.readInt();
2730             isRelative = (parcel.readInt() != 0);
2731             useIcons = (parcel.readInt() != 0);
2732             if (useIcons) {
2733                 i1 = parcel.readTypedObject(Icon.CREATOR);
2734                 i2 = parcel.readTypedObject(Icon.CREATOR);
2735                 i3 = parcel.readTypedObject(Icon.CREATOR);
2736                 i4 = parcel.readTypedObject(Icon.CREATOR);
2737             } else {
2738                 d1 = parcel.readInt();
2739                 d2 = parcel.readInt();
2740                 d3 = parcel.readInt();
2741                 d4 = parcel.readInt();
2742             }
2743         }
2744 
2745         public void writeToParcel(Parcel dest, int flags) {
2746             dest.writeInt(viewId);
2747             dest.writeInt(isRelative ? 1 : 0);
2748             dest.writeInt(useIcons ? 1 : 0);
2749             if (useIcons) {
2750                 dest.writeTypedObject(i1, 0);
2751                 dest.writeTypedObject(i2, 0);
2752                 dest.writeTypedObject(i3, 0);
2753                 dest.writeTypedObject(i4, 0);
2754             } else {
2755                 dest.writeInt(d1);
2756                 dest.writeInt(d2);
2757                 dest.writeInt(d3);
2758                 dest.writeInt(d4);
2759             }
2760         }
2761 
2762         @Override
2763         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2764                 ColorResources colorResources) {
2765             final TextView target = root.findViewById(viewId);
2766             if (target == null) return;
2767             if (drawablesLoaded) {
2768                 if (isRelative) {
2769                     target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
2770                 } else {
2771                     target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
2772                 }
2773             } else if (useIcons) {
2774                 final Context ctx = target.getContext();
2775                 final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx);
2776                 final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx);
2777                 final Drawable id3 = i3 == null ? null : i3.loadDrawable(ctx);
2778                 final Drawable id4 = i4 == null ? null : i4.loadDrawable(ctx);
2779                 if (isRelative) {
2780                     target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
2781                 } else {
2782                     target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
2783                 }
2784             } else {
2785                 if (isRelative) {
2786                     target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
2787                 } else {
2788                     target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
2789                 }
2790             }
2791         }
2792 
2793         @Override
2794         public Action initActionAsync(ViewTree root, ViewGroup rootParent,
2795                 InteractionHandler handler, ColorResources colorResources) {
2796             final TextView target = root.findViewById(viewId);
2797             if (target == null) return ACTION_NOOP;
2798 
2799             TextViewDrawableAction copy = useIcons ?
2800                     new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) :
2801                     new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4);
2802 
2803             // Load the drawables on the background thread.
2804             copy.drawablesLoaded = true;
2805             final Context ctx = target.getContext();
2806 
2807             if (useIcons) {
2808                 copy.id1 = i1 == null ? null : i1.loadDrawable(ctx);
2809                 copy.id2 = i2 == null ? null : i2.loadDrawable(ctx);
2810                 copy.id3 = i3 == null ? null : i3.loadDrawable(ctx);
2811                 copy.id4 = i4 == null ? null : i4.loadDrawable(ctx);
2812             } else {
2813                 copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1);
2814                 copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2);
2815                 copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3);
2816                 copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4);
2817             }
2818             return copy;
2819         }
2820 
2821         @Override
2822         public boolean prefersAsyncApply() {
2823             return useIcons;
2824         }
2825 
2826         @Override
2827         public int getActionTag() {
2828             return TEXT_VIEW_DRAWABLE_ACTION_TAG;
2829         }
2830 
2831         @Override
2832         public void visitUris(@NonNull Consumer<Uri> visitor) {
2833             if (useIcons) {
2834                 visitIconUri(i1, visitor);
2835                 visitIconUri(i2, visitor);
2836                 visitIconUri(i3, visitor);
2837                 visitIconUri(i4, visitor);
2838             }
2839         }
2840 
2841         boolean isRelative = false;
2842         boolean useIcons = false;
2843         int d1, d2, d3, d4;
2844         Icon i1, i2, i3, i4;
2845 
2846         boolean drawablesLoaded = false;
2847         Drawable id1, id2, id3, id4;
2848     }
2849 
2850     /**
2851      * Helper action to set text size on a TextView in any supported units.
2852      */
2853     private class TextViewSizeAction extends Action {
2854         TextViewSizeAction(@IdRes int viewId, @ComplexDimensionUnit int units, float size) {
2855             this.viewId = viewId;
2856             this.units = units;
2857             this.size = size;
2858         }
2859 
2860         TextViewSizeAction(Parcel parcel) {
2861             viewId = parcel.readInt();
2862             units = parcel.readInt();
2863             size  = parcel.readFloat();
2864         }
2865 
2866         public void writeToParcel(Parcel dest, int flags) {
2867             dest.writeInt(viewId);
2868             dest.writeInt(units);
2869             dest.writeFloat(size);
2870         }
2871 
2872         @Override
2873         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2874                 ColorResources colorResources) {
2875             final TextView target = root.findViewById(viewId);
2876             if (target == null) return;
2877             target.setTextSize(units, size);
2878         }
2879 
2880         @Override
2881         public int getActionTag() {
2882             return TEXT_VIEW_SIZE_ACTION_TAG;
2883         }
2884 
2885         int units;
2886         float size;
2887     }
2888 
2889     /**
2890      * Helper action to set padding on a View.
2891      */
2892     private class ViewPaddingAction extends Action {
2893         public ViewPaddingAction(@IdRes int viewId, @Px int left, @Px int top,
2894                 @Px int right, @Px int bottom) {
2895             this.viewId = viewId;
2896             this.left = left;
2897             this.top = top;
2898             this.right = right;
2899             this.bottom = bottom;
2900         }
2901 
2902         public ViewPaddingAction(Parcel parcel) {
2903             viewId = parcel.readInt();
2904             left = parcel.readInt();
2905             top = parcel.readInt();
2906             right = parcel.readInt();
2907             bottom = parcel.readInt();
2908         }
2909 
2910         public void writeToParcel(Parcel dest, int flags) {
2911             dest.writeInt(viewId);
2912             dest.writeInt(left);
2913             dest.writeInt(top);
2914             dest.writeInt(right);
2915             dest.writeInt(bottom);
2916         }
2917 
2918         @Override
2919         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2920                 ColorResources colorResources) {
2921             final View target = root.findViewById(viewId);
2922             if (target == null) return;
2923             target.setPadding(left, top, right, bottom);
2924         }
2925 
2926         @Override
2927         public int getActionTag() {
2928             return VIEW_PADDING_ACTION_TAG;
2929         }
2930 
2931         @Px int left, top, right, bottom;
2932     }
2933 
2934     /**
2935      * Helper action to set layout params on a View.
2936      */
2937     private static class LayoutParamAction extends Action {
2938 
2939         static final int LAYOUT_MARGIN_LEFT = MARGIN_LEFT;
2940         static final int LAYOUT_MARGIN_TOP = MARGIN_TOP;
2941         static final int LAYOUT_MARGIN_RIGHT = MARGIN_RIGHT;
2942         static final int LAYOUT_MARGIN_BOTTOM = MARGIN_BOTTOM;
2943         static final int LAYOUT_MARGIN_START = MARGIN_START;
2944         static final int LAYOUT_MARGIN_END = MARGIN_END;
2945         static final int LAYOUT_WIDTH = 8;
2946         static final int LAYOUT_HEIGHT = 9;
2947 
2948         final int mProperty;
2949         final int mValueType;
2950         final int mValue;
2951 
2952         /**
2953          * @param viewId ID of the view alter
2954          * @param property which layout parameter to alter
2955          * @param value new value of the layout parameter
2956          * @param units the units of the given value
2957          */
2958         LayoutParamAction(@IdRes int viewId, int property, float value,
2959                 @ComplexDimensionUnit int units) {
2960             this.viewId = viewId;
2961             this.mProperty = property;
2962             this.mValueType = VALUE_TYPE_COMPLEX_UNIT;
2963             this.mValue = TypedValue.createComplexDimension(value, units);
2964         }
2965 
2966         /**
2967          * @param viewId ID of the view alter
2968          * @param property which layout parameter to alter
2969          * @param value value to set.
2970          * @param valueType must be one of {@link #VALUE_TYPE_COMPLEX_UNIT},
2971          *   {@link #VALUE_TYPE_RESOURCE}, {@link #VALUE_TYPE_ATTRIBUTE} or
2972          *   {@link #VALUE_TYPE_RAW}.
2973          */
2974         LayoutParamAction(@IdRes int viewId, int property, int value, @ValueType int valueType) {
2975             this.viewId = viewId;
2976             this.mProperty = property;
2977             this.mValueType = valueType;
2978             this.mValue = value;
2979         }
2980 
2981         public LayoutParamAction(Parcel parcel) {
2982             viewId = parcel.readInt();
2983             mProperty = parcel.readInt();
2984             mValueType = parcel.readInt();
2985             mValue = parcel.readInt();
2986         }
2987 
2988         public void writeToParcel(Parcel dest, int flags) {
2989             dest.writeInt(viewId);
2990             dest.writeInt(mProperty);
2991             dest.writeInt(mValueType);
2992             dest.writeInt(mValue);
2993         }
2994 
2995         @Override
2996         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
2997                 ColorResources colorResources) {
2998             final View target = root.findViewById(viewId);
2999             if (target == null) {
3000                 return;
3001             }
3002             ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
3003             if (layoutParams == null) {
3004                 return;
3005             }
3006             switch (mProperty) {
3007                 case LAYOUT_MARGIN_LEFT:
3008                     if (layoutParams instanceof MarginLayoutParams) {
3009                         ((MarginLayoutParams) layoutParams).leftMargin = getPixelOffset(target);
3010                         target.setLayoutParams(layoutParams);
3011                     }
3012                     break;
3013                 case LAYOUT_MARGIN_TOP:
3014                     if (layoutParams instanceof MarginLayoutParams) {
3015                         ((MarginLayoutParams) layoutParams).topMargin = getPixelOffset(target);
3016                         target.setLayoutParams(layoutParams);
3017                     }
3018                     break;
3019                 case LAYOUT_MARGIN_RIGHT:
3020                     if (layoutParams instanceof MarginLayoutParams) {
3021                         ((MarginLayoutParams) layoutParams).rightMargin = getPixelOffset(target);
3022                         target.setLayoutParams(layoutParams);
3023                     }
3024                     break;
3025                 case LAYOUT_MARGIN_BOTTOM:
3026                     if (layoutParams instanceof MarginLayoutParams) {
3027                         ((MarginLayoutParams) layoutParams).bottomMargin = getPixelOffset(target);
3028                         target.setLayoutParams(layoutParams);
3029                     }
3030                     break;
3031                 case LAYOUT_MARGIN_START:
3032                     if (layoutParams instanceof MarginLayoutParams) {
3033                         ((MarginLayoutParams) layoutParams).setMarginStart(getPixelOffset(target));
3034                         target.setLayoutParams(layoutParams);
3035                     }
3036                     break;
3037                 case LAYOUT_MARGIN_END:
3038                     if (layoutParams instanceof MarginLayoutParams) {
3039                         ((MarginLayoutParams) layoutParams).setMarginEnd(getPixelOffset(target));
3040                         target.setLayoutParams(layoutParams);
3041                     }
3042                     break;
3043                 case LAYOUT_WIDTH:
3044                     layoutParams.width = getPixelSize(target);
3045                     target.setLayoutParams(layoutParams);
3046                     break;
3047                 case LAYOUT_HEIGHT:
3048                     layoutParams.height = getPixelSize(target);
3049                     target.setLayoutParams(layoutParams);
3050                     break;
3051                 default:
3052                     throw new IllegalArgumentException("Unknown property " + mProperty);
3053             }
3054         }
3055 
3056         private int getPixelOffset(View target) {
3057             try {
3058                 switch (mValueType) {
3059                     case VALUE_TYPE_ATTRIBUTE:
3060                         TypedArray typedArray = target.getContext().obtainStyledAttributes(
3061                                 new int[]{this.mValue});
3062                         try {
3063                             return typedArray.getDimensionPixelOffset(0, 0);
3064                         } finally {
3065                             typedArray.recycle();
3066                         }
3067                     case VALUE_TYPE_RESOURCE:
3068                         if (mValue == 0) {
3069                             return 0;
3070                         }
3071                         return target.getResources().getDimensionPixelOffset(mValue);
3072                     case VALUE_TYPE_COMPLEX_UNIT:
3073                         return TypedValue.complexToDimensionPixelOffset(mValue,
3074                                 target.getResources().getDisplayMetrics());
3075                     default:
3076                         return mValue;
3077                 }
3078             } catch (Throwable t) {
3079                 throw new ActionException(t);
3080             }
3081         }
3082 
3083         private int getPixelSize(View target) {
3084             try {
3085                 switch (mValueType) {
3086                     case VALUE_TYPE_ATTRIBUTE:
3087                         TypedArray typedArray = target.getContext().obtainStyledAttributes(
3088                                 new int[]{this.mValue});
3089                         try {
3090                             return typedArray.getDimensionPixelSize(0, 0);
3091                         } finally {
3092                             typedArray.recycle();
3093                         }
3094                     case VALUE_TYPE_RESOURCE:
3095                         if (mValue == 0) {
3096                             return 0;
3097                         }
3098                         return target.getResources().getDimensionPixelSize(mValue);
3099                     case VALUE_TYPE_COMPLEX_UNIT:
3100                         return TypedValue.complexToDimensionPixelSize(mValue,
3101                                 target.getResources().getDisplayMetrics());
3102                     default:
3103                         return mValue;
3104                 }
3105             } catch (Throwable t) {
3106                 throw new ActionException(t);
3107             }
3108         }
3109 
3110         @Override
3111         public int getActionTag() {
3112             return LAYOUT_PARAM_ACTION_TAG;
3113         }
3114 
3115         @Override
3116         public String getUniqueKey() {
3117             return super.getUniqueKey() + mProperty;
3118         }
3119     }
3120 
3121     /**
3122      * Helper action to add a view tag with RemoteInputs.
3123      */
3124     private class SetRemoteInputsAction extends Action {
3125 
3126         public SetRemoteInputsAction(@IdRes int viewId, RemoteInput[] remoteInputs) {
3127             this.viewId = viewId;
3128             this.remoteInputs = remoteInputs;
3129         }
3130 
3131         public SetRemoteInputsAction(Parcel parcel) {
3132             viewId = parcel.readInt();
3133             remoteInputs = parcel.createTypedArray(RemoteInput.CREATOR);
3134         }
3135 
3136         public void writeToParcel(Parcel dest, int flags) {
3137             dest.writeInt(viewId);
3138             dest.writeTypedArray(remoteInputs, flags);
3139         }
3140 
3141         @Override
3142         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
3143                 ColorResources colorResources) {
3144             final View target = root.findViewById(viewId);
3145             if (target == null) return;
3146 
3147             target.setTagInternal(R.id.remote_input_tag, remoteInputs);
3148         }
3149 
3150         @Override
3151         public int getActionTag() {
3152             return SET_REMOTE_INPUTS_ACTION_TAG;
3153         }
3154 
3155         final Parcelable[] remoteInputs;
3156     }
3157 
3158     /**
3159      * Helper action to override all textViewColors
3160      */
3161     private class OverrideTextColorsAction extends Action {
3162 
3163         private final int textColor;
3164 
3165         public OverrideTextColorsAction(int textColor) {
3166             this.textColor = textColor;
3167         }
3168 
3169         public OverrideTextColorsAction(Parcel parcel) {
3170             textColor = parcel.readInt();
3171         }
3172 
3173         public void writeToParcel(Parcel dest, int flags) {
3174             dest.writeInt(textColor);
3175         }
3176 
3177         @Override
3178         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
3179                 ColorResources colorResources) {
3180             // Let's traverse the viewtree and override all textColors!
3181             Stack<View> viewsToProcess = new Stack<>();
3182             viewsToProcess.add(root);
3183             while (!viewsToProcess.isEmpty()) {
3184                 View v = viewsToProcess.pop();
3185                 if (v instanceof TextView) {
3186                     TextView textView = (TextView) v;
3187                     textView.setText(ContrastColorUtil.clearColorSpans(textView.getText()));
3188                     textView.setTextColor(textColor);
3189                 }
3190                 if (v instanceof ViewGroup) {
3191                     ViewGroup viewGroup = (ViewGroup) v;
3192                     for (int i = 0; i < viewGroup.getChildCount(); i++) {
3193                         viewsToProcess.push(viewGroup.getChildAt(i));
3194                     }
3195                 }
3196             }
3197         }
3198 
3199         @Override
3200         public int getActionTag() {
3201             return OVERRIDE_TEXT_COLORS_TAG;
3202         }
3203     }
3204 
3205     private class SetIntTagAction extends Action {
3206         @IdRes private final int mViewId;
3207         @IdRes private final int mKey;
3208         private final int mTag;
3209 
3210         SetIntTagAction(@IdRes int viewId, @IdRes int key, int tag) {
3211             mViewId = viewId;
3212             mKey = key;
3213             mTag = tag;
3214         }
3215 
3216         SetIntTagAction(Parcel parcel) {
3217             mViewId = parcel.readInt();
3218             mKey = parcel.readInt();
3219             mTag = parcel.readInt();
3220         }
3221 
3222         public void writeToParcel(Parcel dest, int flags) {
3223             dest.writeInt(mViewId);
3224             dest.writeInt(mKey);
3225             dest.writeInt(mTag);
3226         }
3227 
3228         @Override
3229         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
3230                 ColorResources colorResources) {
3231             final View target = root.findViewById(mViewId);
3232             if (target == null) return;
3233 
3234             target.setTagInternal(mKey, mTag);
3235         }
3236 
3237         @Override
3238         public int getActionTag() {
3239             return SET_INT_TAG_TAG;
3240         }
3241     }
3242 
3243     private static class SetCompoundButtonCheckedAction extends Action {
3244 
3245         private final boolean mChecked;
3246 
3247         SetCompoundButtonCheckedAction(@IdRes int viewId, boolean checked) {
3248             this.viewId = viewId;
3249             mChecked = checked;
3250         }
3251 
3252         SetCompoundButtonCheckedAction(Parcel in) {
3253             viewId = in.readInt();
3254             mChecked = in.readBoolean();
3255         }
3256 
3257         @Override
3258         public void writeToParcel(Parcel dest, int flags) {
3259             dest.writeInt(viewId);
3260             dest.writeBoolean(mChecked);
3261         }
3262 
3263         @Override
3264         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
3265                 ColorResources colorResources)
3266                 throws ActionException {
3267             final View target = root.findViewById(viewId);
3268             if (target == null) return;
3269 
3270             if (!(target instanceof CompoundButton)) {
3271                 Log.w(LOG_TAG, "Cannot set checked to view "
3272                         + viewId + " because it is not a CompoundButton");
3273                 return;
3274             }
3275 
3276             CompoundButton button = (CompoundButton) target;
3277             Object tag = button.getTag(R.id.remote_checked_change_listener_tag);
3278             // Temporarily unset the checked change listener so calling setChecked doesn't launch
3279             // the intent.
3280             if (tag instanceof OnCheckedChangeListener) {
3281                 button.setOnCheckedChangeListener(null);
3282                 button.setChecked(mChecked);
3283                 button.setOnCheckedChangeListener((OnCheckedChangeListener) tag);
3284             } else {
3285                 button.setChecked(mChecked);
3286             }
3287         }
3288 
3289         @Override
3290         public int getActionTag() {
3291             return SET_COMPOUND_BUTTON_CHECKED_TAG;
3292         }
3293     }
3294 
3295     private static class SetRadioGroupCheckedAction extends Action {
3296 
3297         @IdRes private final int mCheckedId;
3298 
3299         SetRadioGroupCheckedAction(@IdRes int viewId, @IdRes int checkedId) {
3300             this.viewId = viewId;
3301             mCheckedId = checkedId;
3302         }
3303 
3304         SetRadioGroupCheckedAction(Parcel in) {
3305             viewId = in.readInt();
3306             mCheckedId = in.readInt();
3307         }
3308 
3309         @Override
3310         public void writeToParcel(Parcel dest, int flags) {
3311             dest.writeInt(viewId);
3312             dest.writeInt(mCheckedId);
3313         }
3314 
3315         @Override
3316         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
3317                 ColorResources colorResources) throws ActionException {
3318             final View target = root.findViewById(viewId);
3319             if (target == null) return;
3320 
3321             if (!(target instanceof RadioGroup)) {
3322                 Log.w(LOG_TAG, "Cannot check " + viewId + " because it's not a RadioGroup");
3323                 return;
3324             }
3325 
3326             RadioGroup group = (RadioGroup) target;
3327 
3328             // Temporarily unset all the checked change listeners while we check the group.
3329             for (int i = 0; i < group.getChildCount(); i++) {
3330                 View child = group.getChildAt(i);
3331                 if (!(child instanceof CompoundButton)) continue;
3332 
3333                 Object tag = child.getTag(R.id.remote_checked_change_listener_tag);
3334                 if (!(tag instanceof OnCheckedChangeListener)) continue;
3335 
3336                 // Clear the checked change listener, we'll restore it after the check.
3337                 ((CompoundButton) child).setOnCheckedChangeListener(null);
3338             }
3339 
3340             group.check(mCheckedId);
3341 
3342             // Loop through the children again and restore the checked change listeners.
3343             for (int i = 0; i < group.getChildCount(); i++) {
3344                 View child = group.getChildAt(i);
3345                 if (!(child instanceof CompoundButton)) continue;
3346 
3347                 Object tag = child.getTag(R.id.remote_checked_change_listener_tag);
3348                 if (!(tag instanceof OnCheckedChangeListener)) continue;
3349 
3350                 ((CompoundButton) child).setOnCheckedChangeListener((OnCheckedChangeListener) tag);
3351             }
3352         }
3353 
3354         @Override
3355         public int getActionTag() {
3356             return SET_RADIO_GROUP_CHECKED;
3357         }
3358     }
3359 
3360     private static class SetViewOutlinePreferredRadiusAction extends Action {
3361 
3362         @ValueType
3363         private final int mValueType;
3364         private final int mValue;
3365 
3366         SetViewOutlinePreferredRadiusAction(@IdRes int viewId, int value,
3367                 @ValueType int valueType) {
3368             this.viewId = viewId;
3369             this.mValueType = valueType;
3370             this.mValue = value;
3371         }
3372 
3373         SetViewOutlinePreferredRadiusAction(
3374                 @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
3375             this.viewId = viewId;
3376             this.mValueType = VALUE_TYPE_COMPLEX_UNIT;
3377             this.mValue = TypedValue.createComplexDimension(radius, units);
3378 
3379         }
3380 
3381         SetViewOutlinePreferredRadiusAction(Parcel in) {
3382             viewId = in.readInt();
3383             mValueType = in.readInt();
3384             mValue = in.readInt();
3385         }
3386 
3387         @Override
3388         public void writeToParcel(Parcel dest, int flags) {
3389             dest.writeInt(viewId);
3390             dest.writeInt(mValueType);
3391             dest.writeInt(mValue);
3392         }
3393 
3394         @Override
3395         public void apply(View root, ViewGroup rootParent, InteractionHandler handler,
3396                 ColorResources colorResources) throws ActionException {
3397             final View target = root.findViewById(viewId);
3398             if (target == null) return;
3399 
3400             try {
3401                 float radius;
3402                 switch (mValueType) {
3403                     case VALUE_TYPE_ATTRIBUTE:
3404                         TypedArray typedArray = target.getContext().obtainStyledAttributes(
3405                                 new int[]{mValue});
3406                         try {
3407                             radius = typedArray.getDimension(0, 0);
3408                         } finally {
3409                             typedArray.recycle();
3410                         }
3411                         break;
3412                     case VALUE_TYPE_RESOURCE:
3413                         radius = mValue == 0 ? 0 : target.getResources().getDimension(mValue);
3414                         break;
3415                     case VALUE_TYPE_COMPLEX_UNIT:
3416                         radius = TypedValue.complexToDimension(mValue,
3417                                 target.getResources().getDisplayMetrics());
3418                         break;
3419                     default:
3420                         radius = mValue;
3421                 }
3422                 target.setOutlineProvider(new RemoteViewOutlineProvider(radius));
3423             } catch (Throwable t) {
3424                 throw new ActionException(t);
3425             }
3426         }
3427 
3428         @Override
3429         public int getActionTag() {
3430             return SET_VIEW_OUTLINE_RADIUS_TAG;
3431         }
3432     }
3433 
3434     /**
3435      * OutlineProvider for a view with a radius set by
3436      * {@link #setViewOutlinePreferredRadius(int, float, int)}.
3437      */
3438     public static final class RemoteViewOutlineProvider extends ViewOutlineProvider {
3439 
3440         private final float mRadius;
3441 
3442         public RemoteViewOutlineProvider(float radius) {
3443             mRadius = radius;
3444         }
3445 
3446         /** Returns the corner radius used when providing the view outline. */
3447         public float getRadius() {
3448             return mRadius;
3449         }
3450 
3451         @Override
3452         public void getOutline(@NonNull View view, @NonNull Outline outline) {
3453             outline.setRoundRect(
3454                     0 /*left*/,
3455                     0 /* top */,
3456                     view.getWidth() /* right */,
3457                     view.getHeight() /* bottom */,
3458                     mRadius);
3459         }
3460     }
3461 
3462     /**
3463      * Create a new RemoteViews object that will display the views contained
3464      * in the specified layout file.
3465      *
3466      * @param packageName Name of the package that contains the layout resource
3467      * @param layoutId The id of the layout resource
3468      */
3469     public RemoteViews(String packageName, int layoutId) {
3470         this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
3471     }
3472 
3473     /**
3474      * Create a new RemoteViews object that will display the views contained
3475      * in the specified layout file and change the id of the root view to the specified one.
3476      *
3477      * @param packageName Name of the package that contains the layout resource
3478      * @param layoutId The id of the layout resource
3479      */
3480     public RemoteViews(@NonNull String packageName, @LayoutRes int layoutId, @IdRes int viewId) {
3481         this(packageName, layoutId);
3482         this.mViewId = viewId;
3483     }
3484 
3485     /**
3486      * Create a new RemoteViews object that will display the views contained
3487      * in the specified layout file.
3488      *
3489      * @param application The application whose content is shown by the views.
3490      * @param layoutId The id of the layout resource.
3491      *
3492      * @hide
3493      */
3494     protected RemoteViews(ApplicationInfo application, @LayoutRes int layoutId) {
3495         mApplication = application;
3496         mLayoutId = layoutId;
3497         mBitmapCache = new BitmapCache();
3498         mClassCookies = null;
3499     }
3500 
3501     private boolean hasMultipleLayouts() {
3502         return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews();
3503     }
3504 
3505     private boolean hasLandscapeAndPortraitLayouts() {
3506         return (mLandscape != null) && (mPortrait != null);
3507     }
3508 
3509     private boolean hasSizedRemoteViews() {
3510         return mSizedRemoteViews != null;
3511     }
3512 
3513     private @Nullable SizeF getIdealSize() {
3514         return mIdealSize;
3515     }
3516 
3517     private void setIdealSize(@Nullable SizeF size) {
3518         mIdealSize = size;
3519     }
3520 
3521     /**
3522      * Finds the smallest view in {@code mSizedRemoteViews}.
3523      * This method must not be called if {@code mSizedRemoteViews} is null.
3524      */
3525     private RemoteViews findSmallestRemoteView() {
3526         return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1);
3527     }
3528 
3529     /**
3530      * Create a new RemoteViews object that will inflate as the specified
3531      * landspace or portrait RemoteViews, depending on the current configuration.
3532      *
3533      * @param landscape The RemoteViews to inflate in landscape configuration
3534      * @param portrait The RemoteViews to inflate in portrait configuration
3535      * @throws IllegalArgumentException if either landscape or portrait are null or if they are
3536      *   not from the same application
3537      */
3538     public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
3539         if (landscape == null || portrait == null) {
3540             throw new IllegalArgumentException("Both RemoteViews must be non-null");
3541         }
3542         if (!landscape.hasSameAppInfo(portrait.mApplication)) {
3543             throw new IllegalArgumentException(
3544                     "Both RemoteViews must share the same package and user");
3545         }
3546         mApplication = portrait.mApplication;
3547         mLayoutId = portrait.mLayoutId;
3548         mViewId = portrait.mViewId;
3549         mLightBackgroundLayoutId = portrait.mLightBackgroundLayoutId;
3550 
3551         mLandscape = landscape;
3552         mPortrait = portrait;
3553 
3554         mBitmapCache = new BitmapCache();
3555         configureRemoteViewsAsChild(landscape);
3556         configureRemoteViewsAsChild(portrait);
3557 
3558         mClassCookies = (portrait.mClassCookies != null)
3559                 ? portrait.mClassCookies : landscape.mClassCookies;
3560     }
3561 
3562     /**
3563      * Create a new RemoteViews object that will inflate the layout with the closest size
3564      * specification.
3565      *
3566      * The default remote views in that case is always the one with the smallest area.
3567      *
3568      * If the {@link RemoteViews} host provides the size of the view, the layout with the largest
3569      * area that fits entirely in the provided size will be used (i.e. the width and height of
3570      * the layout must be less than the size of the view, with a 1dp margin to account for
3571      * rounding). If no layout fits in the view, the layout with the smallest area will be used.
3572      *
3573      * @param remoteViews Mapping of size to layout.
3574      * @throws IllegalArgumentException if the map is empty, there are more than
3575      *   MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application.
3576      */
3577     public RemoteViews(@NonNull Map<SizeF, RemoteViews> remoteViews) {
3578         if (remoteViews.isEmpty()) {
3579             throw new IllegalArgumentException("The set of RemoteViews cannot be empty");
3580         }
3581         if (remoteViews.size() > MAX_INIT_VIEW_COUNT) {
3582             throw new IllegalArgumentException("Too many RemoteViews in constructor");
3583         }
3584         if (remoteViews.size() == 1) {
3585             initializeFrom(remoteViews.values().iterator().next());
3586             return;
3587         }
3588         mBitmapCache = new BitmapCache();
3589         mClassCookies = initializeSizedRemoteViews(
3590                 remoteViews.entrySet().stream().map(
3591                         entry -> {
3592                             entry.getValue().setIdealSize(entry.getKey());
3593                             return entry.getValue();
3594                         }
3595                 ).iterator()
3596         );
3597 
3598         RemoteViews smallestView = findSmallestRemoteView();
3599         mApplication = smallestView.mApplication;
3600         mLayoutId = smallestView.mLayoutId;
3601         mViewId = smallestView.mViewId;
3602         mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
3603     }
3604 
3605     // Initialize mSizedRemoteViews and return the class cookies.
3606     private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) {
3607         List<RemoteViews> sizedRemoteViews = new ArrayList<>();
3608         Map<Class, Object> classCookies = null;
3609         float viewArea = Float.MAX_VALUE;
3610         RemoteViews smallestView = null;
3611         while (remoteViews.hasNext()) {
3612             RemoteViews view = remoteViews.next();
3613             SizeF size = view.getIdealSize();
3614             if (size == null) {
3615                 throw new IllegalStateException("Expected RemoteViews to have ideal size");
3616             }
3617             float newViewArea = size.getWidth() * size.getHeight();
3618             if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) {
3619                 throw new IllegalArgumentException(
3620                         "All RemoteViews must share the same package and user");
3621             }
3622             if (smallestView == null || newViewArea < viewArea) {
3623                 if (smallestView != null) {
3624                     sizedRemoteViews.add(smallestView);
3625                 }
3626                 viewArea = newViewArea;
3627                 smallestView = view;
3628             } else {
3629                 sizedRemoteViews.add(view);
3630             }
3631             configureRemoteViewsAsChild(view);
3632             view.setIdealSize(size);
3633             if (classCookies == null) {
3634                 classCookies = view.mClassCookies;
3635             }
3636         }
3637         sizedRemoteViews.add(smallestView);
3638         mSizedRemoteViews = sizedRemoteViews;
3639         return classCookies;
3640     }
3641 
3642     /**
3643      * Creates a copy of another RemoteViews.
3644      */
3645     public RemoteViews(RemoteViews src) {
3646         initializeFrom(src);
3647     }
3648 
3649     private void initializeFrom(RemoteViews src) {
3650         mBitmapCache = src.mBitmapCache;
3651         mApplication = src.mApplication;
3652         mIsRoot = src.mIsRoot;
3653         mLayoutId = src.mLayoutId;
3654         mLightBackgroundLayoutId = src.mLightBackgroundLayoutId;
3655         mApplyFlags = src.mApplyFlags;
3656         mClassCookies = src.mClassCookies;
3657         mIdealSize = src.mIdealSize;
3658         mProviderInstanceId = src.mProviderInstanceId;
3659 
3660         if (src.hasLandscapeAndPortraitLayouts()) {
3661             mLandscape = new RemoteViews(src.mLandscape);
3662             mPortrait = new RemoteViews(src.mPortrait);
3663         }
3664 
3665         if (src.hasSizedRemoteViews()) {
3666             mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size());
3667             for (RemoteViews srcView : src.mSizedRemoteViews) {
3668                 mSizedRemoteViews.add(new RemoteViews(srcView));
3669             }
3670         }
3671 
3672         if (src.mActions != null) {
3673             Parcel p = Parcel.obtain();
3674             p.putClassCookies(mClassCookies);
3675             src.writeActionsToParcel(p);
3676             p.setDataPosition(0);
3677             // Since src is already in memory, we do not care about stack overflow as it has
3678             // already been read once.
3679             readActionsFromParcel(p, 0);
3680             p.recycle();
3681         }
3682 
3683         // Now that everything is initialized and duplicated, setting a new BitmapCache will
3684         // re-initialize the cache.
3685         setBitmapCache(new BitmapCache());
3686     }
3687 
3688     /**
3689      * Reads a RemoteViews object from a parcel.
3690      *
3691      * @param parcel
3692      */
3693     public RemoteViews(Parcel parcel) {
3694         this(parcel, null, null, 0, null);
3695     }
3696 
3697     private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth,
3698             Map<Class, Object> classCookies) {
3699         if (depth > MAX_NESTED_VIEWS
3700                 && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) {
3701             throw new IllegalArgumentException("Too many nested views.");
3702         }
3703         depth++;
3704 
3705         int mode = parcel.readInt();
3706 
3707         // We only store a bitmap cache in the root of the RemoteViews.
3708         if (bitmapCache == null) {
3709             mBitmapCache = new BitmapCache(parcel);
3710             // Store the class cookies such that they are available when we clone this RemoteView.
3711             mClassCookies = parcel.copyClassCookies();
3712         } else {
3713             setBitmapCache(bitmapCache);
3714             mClassCookies = classCookies;
3715             setNotRoot();
3716         }
3717 
3718         if (mode == MODE_NORMAL) {
3719             mApplication = parcel.readInt() == 0 ? info :
3720                     ApplicationInfo.CREATOR.createFromParcel(parcel);
3721             mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel);
3722             mLayoutId = parcel.readInt();
3723             mViewId = parcel.readInt();
3724             mLightBackgroundLayoutId = parcel.readInt();
3725 
3726             readActionsFromParcel(parcel, depth);
3727         } else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) {
3728             int numViews = parcel.readInt();
3729             if (numViews > MAX_INIT_VIEW_COUNT) {
3730                 throw new IllegalArgumentException(
3731                         "Too many views in mapping from size to RemoteViews.");
3732             }
3733             List<RemoteViews> remoteViews = new ArrayList<>(numViews);
3734             for (int i = 0; i < numViews; i++) {
3735                 RemoteViews view = new RemoteViews(parcel, mBitmapCache, info, depth,
3736                         mClassCookies);
3737                 info = view.mApplication;
3738                 remoteViews.add(view);
3739             }
3740             initializeSizedRemoteViews(remoteViews.iterator());
3741             RemoteViews smallestView = findSmallestRemoteView();
3742             mApplication = smallestView.mApplication;
3743             mLayoutId = smallestView.mLayoutId;
3744             mViewId = smallestView.mViewId;
3745             mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId;
3746         } else {
3747             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
3748             mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies);
3749             mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth,
3750                     mClassCookies);
3751             mApplication = mPortrait.mApplication;
3752             mLayoutId = mPortrait.mLayoutId;
3753             mViewId = mPortrait.mViewId;
3754             mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId;
3755         }
3756         mApplyFlags = parcel.readInt();
3757         mProviderInstanceId = parcel.readLong();
3758     }
3759 
3760     private void readActionsFromParcel(Parcel parcel, int depth) {
3761         int count = parcel.readInt();
3762         if (count > 0) {
3763             mActions = new ArrayList<>(count);
3764             for (int i = 0; i < count; i++) {
3765                 mActions.add(getActionFromParcel(parcel, depth));
3766             }
3767         }
3768     }
3769 
3770     private Action getActionFromParcel(Parcel parcel, int depth) {
3771         int tag = parcel.readInt();
3772         switch (tag) {
3773             case SET_ON_CLICK_RESPONSE_TAG:
3774                 return new SetOnClickResponse(parcel);
3775             case SET_DRAWABLE_TINT_TAG:
3776                 return new SetDrawableTint(parcel);
3777             case REFLECTION_ACTION_TAG:
3778                 return new ReflectionAction(parcel);
3779             case VIEW_GROUP_ACTION_ADD_TAG:
3780                 return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth,
3781                         mClassCookies);
3782             case VIEW_GROUP_ACTION_REMOVE_TAG:
3783                 return new ViewGroupActionRemove(parcel);
3784             case VIEW_CONTENT_NAVIGATION_TAG:
3785                 return new ViewContentNavigation(parcel);
3786             case SET_EMPTY_VIEW_ACTION_TAG:
3787                 return new SetEmptyView(parcel);
3788             case SET_PENDING_INTENT_TEMPLATE_TAG:
3789                 return new SetPendingIntentTemplate(parcel);
3790             case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG:
3791                 return new SetRemoteViewsAdapterIntent(parcel);
3792             case TEXT_VIEW_DRAWABLE_ACTION_TAG:
3793                 return new TextViewDrawableAction(parcel);
3794             case TEXT_VIEW_SIZE_ACTION_TAG:
3795                 return new TextViewSizeAction(parcel);
3796             case VIEW_PADDING_ACTION_TAG:
3797                 return new ViewPaddingAction(parcel);
3798             case BITMAP_REFLECTION_ACTION_TAG:
3799                 return new BitmapReflectionAction(parcel);
3800             case SET_REMOTE_VIEW_ADAPTER_LIST_TAG:
3801                 return new SetRemoteViewsAdapterList(parcel);
3802             case SET_REMOTE_INPUTS_ACTION_TAG:
3803                 return new SetRemoteInputsAction(parcel);
3804             case LAYOUT_PARAM_ACTION_TAG:
3805                 return new LayoutParamAction(parcel);
3806             case OVERRIDE_TEXT_COLORS_TAG:
3807                 return new OverrideTextColorsAction(parcel);
3808             case SET_RIPPLE_DRAWABLE_COLOR_TAG:
3809                 return new SetRippleDrawableColor(parcel);
3810             case SET_INT_TAG_TAG:
3811                 return new SetIntTagAction(parcel);
3812             case REMOVE_FROM_PARENT_ACTION_TAG:
3813                 return new RemoveFromParentAction(parcel);
3814             case RESOURCE_REFLECTION_ACTION_TAG:
3815                 return new ResourceReflectionAction(parcel);
3816             case COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG:
3817                 return new ComplexUnitDimensionReflectionAction(parcel);
3818             case SET_COMPOUND_BUTTON_CHECKED_TAG:
3819                 return new SetCompoundButtonCheckedAction(parcel);
3820             case SET_RADIO_GROUP_CHECKED:
3821                 return new SetRadioGroupCheckedAction(parcel);
3822             case SET_VIEW_OUTLINE_RADIUS_TAG:
3823                 return new SetViewOutlinePreferredRadiusAction(parcel);
3824             case SET_ON_CHECKED_CHANGE_RESPONSE_TAG:
3825                 return new SetOnCheckedChangeResponse(parcel);
3826             case NIGHT_MODE_REFLECTION_ACTION_TAG:
3827                 return new NightModeReflectionAction(parcel);
3828             case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG:
3829                 return new SetRemoteCollectionItemListAdapterAction(parcel);
3830             case ATTRIBUTE_REFLECTION_ACTION_TAG:
3831                 return new AttributeReflectionAction(parcel);
3832             default:
3833                 throw new ActionException("Tag " + tag + " not found");
3834         }
3835     };
3836 
3837     /**
3838      * Returns a deep copy of the RemoteViews object. The RemoteView may not be
3839      * attached to another RemoteView -- it must be the root of a hierarchy.
3840      *
3841      * @deprecated use {@link #RemoteViews(RemoteViews)} instead.
3842      * @throws IllegalStateException if this is not the root of a RemoteView
3843      *         hierarchy
3844      */
3845     @Override
3846     @Deprecated
3847     public RemoteViews clone() {
3848         Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. "
3849                 + "May only clone the root of a RemoteView hierarchy.");
3850 
3851         return new RemoteViews(this);
3852     }
3853 
3854     public String getPackage() {
3855         return (mApplication != null) ? mApplication.packageName : null;
3856     }
3857 
3858     /**
3859      * Returns the layout id of the root layout associated with this RemoteViews. In the case
3860      * that the RemoteViews has both a landscape and portrait root, this will return the layout
3861      * id associated with the portrait layout.
3862      *
3863      * @return the layout id.
3864      */
3865     public int getLayoutId() {
3866         return hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT) && (mLightBackgroundLayoutId != 0)
3867                 ? mLightBackgroundLayoutId : mLayoutId;
3868     }
3869 
3870     /**
3871      * Recursively sets BitmapCache in the hierarchy and update the bitmap ids.
3872      */
3873     private void setBitmapCache(BitmapCache bitmapCache) {
3874         mBitmapCache = bitmapCache;
3875         if (hasSizedRemoteViews()) {
3876             for (RemoteViews remoteView : mSizedRemoteViews) {
3877                 remoteView.setBitmapCache(bitmapCache);
3878             }
3879         } else if (hasLandscapeAndPortraitLayouts()) {
3880             mLandscape.setBitmapCache(bitmapCache);
3881             mPortrait.setBitmapCache(bitmapCache);
3882         } else {
3883             if (mActions != null) {
3884                 final int count = mActions.size();
3885                 for (int i = 0; i < count; ++i) {
3886                     mActions.get(i).setBitmapCache(bitmapCache);
3887                 }
3888             }
3889         }
3890     }
3891 
3892     /**
3893      * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
3894      */
3895     /** @hide */
3896     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
3897     public int estimateMemoryUsage() {
3898         return mBitmapCache.getBitmapMemory();
3899     }
3900 
3901     /**
3902      * Add an action to be executed on the remote side when apply is called.
3903      *
3904      * @param a The action to add
3905      */
3906     private void addAction(Action a) {
3907         if (hasMultipleLayouts()) {
3908             throw new RuntimeException("RemoteViews specifying separate layouts for orientation"
3909                     + " or size cannot be modified. Instead, fully configure each layouts"
3910                     + " individually before constructing the combined layout.");
3911         }
3912         if (mActions == null) {
3913             mActions = new ArrayList<>();
3914         }
3915         mActions.add(a);
3916     }
3917 
3918     /**
3919      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
3920      * given {@link RemoteViews}. This allows users to build "nested"
3921      * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
3922      * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
3923      * children.
3924      *
3925      * @param viewId The id of the parent {@link ViewGroup} to add child into.
3926      * @param nestedView {@link RemoteViews} that describes the child.
3927      */
3928     public void addView(@IdRes int viewId, RemoteViews nestedView) {
3929         // Clear all children when nested views omitted
3930         addAction(nestedView == null
3931                 ? new ViewGroupActionRemove(viewId)
3932                 : new ViewGroupActionAdd(viewId, nestedView));
3933     }
3934 
3935     /**
3936      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the given
3937      * {@link RemoteViews}. If the {@link RemoteViews} may be re-inflated or updated,
3938      * {@link #removeAllViews(int)} must be called on the same {@code viewId
3939      * } before the first call to this method for the behavior of this method to be predictable.
3940      *
3941      * The {@code stableId} will be used to identify a potential view to recycled when the remote
3942      * view is inflated. Views can be re-used if inserted in the same order, potentially with
3943      * some views appearing / disappearing. To be recycled the view must not change the layout
3944      * used to inflate it or its view id (see {@link RemoteViews#RemoteViews(String, int, int)}).
3945      *
3946      * Note: if a view is re-used, all the actions will be re-applied on it. However, its properties
3947      * are not reset, so what was applied in previous round will have an effect. As a view may be
3948      * re-created at any time by the host, the RemoteViews should not rely on keeping information
3949      * from previous applications and always re-set all the properties they need.
3950      *
3951      * @param viewId The id of the parent {@link ViewGroup} to add child into.
3952      * @param nestedView {@link RemoteViews} that describes the child.
3953      * @param stableId An id that is stable across different versions of RemoteViews.
3954      */
3955     public void addStableView(@IdRes int viewId, @NonNull RemoteViews nestedView, int stableId) {
3956         addAction(new ViewGroupActionAdd(viewId, nestedView, -1 /* index */, stableId));
3957     }
3958 
3959     /**
3960      * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the
3961      * given {@link RemoteViews}.
3962      *
3963      * @param viewId The id of the parent {@link ViewGroup} to add the child into.
3964      * @param nestedView {@link RemoteViews} of the child to add.
3965      * @param index The position at which to add the child.
3966      *
3967      * @hide
3968      */
3969     @UnsupportedAppUsage
3970     public void addView(@IdRes int viewId, RemoteViews nestedView, int index) {
3971         addAction(new ViewGroupActionAdd(viewId, nestedView, index));
3972     }
3973 
3974     /**
3975      * Equivalent to calling {@link ViewGroup#removeAllViews()}.
3976      *
3977      * @param viewId The id of the parent {@link ViewGroup} to remove all
3978      *            children from.
3979      */
3980     public void removeAllViews(@IdRes int viewId) {
3981         addAction(new ViewGroupActionRemove(viewId));
3982     }
3983 
3984     /**
3985      * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any
3986      * child that has the {@code viewIdToKeep} as its id.
3987      *
3988      * @param viewId The id of the parent {@link ViewGroup} to remove children from.
3989      * @param viewIdToKeep The id of a child that should not be removed.
3990      *
3991      * @hide
3992      */
3993     public void removeAllViewsExceptId(@IdRes int viewId, @IdRes int viewIdToKeep) {
3994         addAction(new ViewGroupActionRemove(viewId, viewIdToKeep));
3995     }
3996 
3997     /**
3998      * Removes the {@link View} specified by the {@code viewId} from its parent {@link ViewManager}.
3999      * This will do nothing if the viewId specifies the root view of this RemoteViews.
4000      *
4001      * @param viewId The id of the {@link View} to remove from its parent.
4002      *
4003      * @hide
4004      */
4005     public void removeFromParent(@IdRes int viewId) {
4006         addAction(new RemoveFromParentAction(viewId));
4007     }
4008 
4009     /**
4010      * Equivalent to calling {@link AdapterViewAnimator#showNext()}
4011      *
4012      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
4013      */
4014     public void showNext(@IdRes int viewId) {
4015         addAction(new ViewContentNavigation(viewId, true /* next */));
4016     }
4017 
4018     /**
4019      * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
4020      *
4021      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
4022      */
4023     public void showPrevious(@IdRes int viewId) {
4024         addAction(new ViewContentNavigation(viewId, false /* next */));
4025     }
4026 
4027     /**
4028      * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)}
4029      *
4030      * @param viewId The id of the view on which to call
4031      *               {@link AdapterViewAnimator#setDisplayedChild(int)}
4032      */
4033     public void setDisplayedChild(@IdRes int viewId, int childIndex) {
4034         setInt(viewId, "setDisplayedChild", childIndex);
4035     }
4036 
4037     /**
4038      * Equivalent to calling {@link View#setVisibility(int)}
4039      *
4040      * @param viewId The id of the view whose visibility should change
4041      * @param visibility The new visibility for the view
4042      */
4043     public void setViewVisibility(@IdRes int viewId, @View.Visibility int visibility) {
4044         setInt(viewId, "setVisibility", visibility);
4045     }
4046 
4047     /**
4048      * Equivalent to calling {@link TextView#setText(CharSequence)}
4049      *
4050      * @param viewId The id of the view whose text should change
4051      * @param text The new text for the view
4052      */
4053     public void setTextViewText(@IdRes int viewId, CharSequence text) {
4054         setCharSequence(viewId, "setText", text);
4055     }
4056 
4057     /**
4058      * Equivalent to calling {@link TextView#setTextSize(int, float)}
4059      *
4060      * @param viewId The id of the view whose text size should change
4061      * @param units The units of size (e.g. COMPLEX_UNIT_SP)
4062      * @param size The size of the text
4063      */
4064     public void setTextViewTextSize(@IdRes int viewId, int units, float size) {
4065         addAction(new TextViewSizeAction(viewId, units, size));
4066     }
4067 
4068     /**
4069      * Equivalent to calling
4070      * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
4071      *
4072      * @param viewId The id of the view whose text should change
4073      * @param left The id of a drawable to place to the left of the text, or 0
4074      * @param top The id of a drawable to place above the text, or 0
4075      * @param right The id of a drawable to place to the right of the text, or 0
4076      * @param bottom The id of a drawable to place below the text, or 0
4077      */
4078     public void setTextViewCompoundDrawables(@IdRes int viewId, @DrawableRes int left,
4079             @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) {
4080         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
4081     }
4082 
4083     /**
4084      * Equivalent to calling {@link
4085      * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}.
4086      *
4087      * @param viewId The id of the view whose text should change
4088      * @param start The id of a drawable to place before the text (relative to the
4089      * layout direction), or 0
4090      * @param top The id of a drawable to place above the text, or 0
4091      * @param end The id of a drawable to place after the text, or 0
4092      * @param bottom The id of a drawable to place below the text, or 0
4093      */
4094     public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, @DrawableRes int start,
4095             @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) {
4096         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
4097     }
4098 
4099     /**
4100      * Equivalent to calling {@link
4101      * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
4102      * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
4103      *
4104      * @param viewId The id of the view whose text should change
4105      * @param left an Icon to place to the left of the text, or 0
4106      * @param top an Icon to place above the text, or 0
4107      * @param right an Icon to place to the right of the text, or 0
4108      * @param bottom an Icon to place below the text, or 0
4109      *
4110      * @hide
4111      */
4112     public void setTextViewCompoundDrawables(@IdRes int viewId,
4113             Icon left, Icon top, Icon right, Icon bottom) {
4114         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
4115     }
4116 
4117     /**
4118      * Equivalent to calling {@link
4119      * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
4120      * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
4121      *
4122      * @param viewId The id of the view whose text should change
4123      * @param start an Icon to place before the text (relative to the
4124      * layout direction), or 0
4125      * @param top an Icon to place above the text, or 0
4126      * @param end an Icon to place after the text, or 0
4127      * @param bottom an Icon to place below the text, or 0
4128      *
4129      * @hide
4130      */
4131     public void setTextViewCompoundDrawablesRelative(@IdRes int viewId,
4132             Icon start, Icon top, Icon end, Icon bottom) {
4133         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
4134     }
4135 
4136     /**
4137      * Equivalent to calling {@link ImageView#setImageResource(int)}
4138      *
4139      * @param viewId The id of the view whose drawable should change
4140      * @param srcId The new resource id for the drawable
4141      */
4142     public void setImageViewResource(@IdRes int viewId, @DrawableRes int srcId) {
4143         setInt(viewId, "setImageResource", srcId);
4144     }
4145 
4146     /**
4147      * Equivalent to calling {@link ImageView#setImageURI(Uri)}
4148      *
4149      * @param viewId The id of the view whose drawable should change
4150      * @param uri The Uri for the image
4151      */
4152     public void setImageViewUri(@IdRes int viewId, Uri uri) {
4153         setUri(viewId, "setImageURI", uri);
4154     }
4155 
4156     /**
4157      * Equivalent to calling {@link ImageView#setImageBitmap(Bitmap)}
4158      *
4159      * @param viewId The id of the view whose bitmap should change
4160      * @param bitmap The new Bitmap for the drawable
4161      */
4162     public void setImageViewBitmap(@IdRes int viewId, Bitmap bitmap) {
4163         setBitmap(viewId, "setImageBitmap", bitmap);
4164     }
4165 
4166     /**
4167      * Equivalent to calling {@link ImageView#setImageIcon(Icon)}
4168      *
4169      * @param viewId The id of the view whose bitmap should change
4170      * @param icon The new Icon for the ImageView
4171      */
4172     public void setImageViewIcon(@IdRes int viewId, Icon icon) {
4173         setIcon(viewId, "setImageIcon", icon);
4174     }
4175 
4176     /**
4177      * Equivalent to calling {@link AdapterView#setEmptyView(View)}
4178      *
4179      * @param viewId The id of the view on which to set the empty view
4180      * @param emptyViewId The view id of the empty view
4181      */
4182     public void setEmptyView(@IdRes int viewId, @IdRes int emptyViewId) {
4183         addAction(new SetEmptyView(viewId, emptyViewId));
4184     }
4185 
4186     /**
4187      * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
4188      * {@link Chronometer#setFormat Chronometer.setFormat},
4189      * and {@link Chronometer#start Chronometer.start()} or
4190      * {@link Chronometer#stop Chronometer.stop()}.
4191      *
4192      * @param viewId The id of the {@link Chronometer} to change
4193      * @param base The time at which the timer would have read 0:00.  This
4194      *             time should be based off of
4195      *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
4196      * @param format The Chronometer format string, or null to
4197      *               simply display the timer value.
4198      * @param started True if you want the clock to be started, false if not.
4199      *
4200      * @see #setChronometerCountDown(int, boolean)
4201      */
4202     public void setChronometer(@IdRes int viewId, long base, String format, boolean started) {
4203         setLong(viewId, "setBase", base);
4204         setString(viewId, "setFormat", format);
4205         setBoolean(viewId, "setStarted", started);
4206     }
4207 
4208     /**
4209      * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on
4210      * the chronometer with the given viewId.
4211      *
4212      * @param viewId The id of the {@link Chronometer} to change
4213      * @param isCountDown True if you want the chronometer to count down to base instead of
4214      *                    counting up.
4215      */
4216     public void setChronometerCountDown(@IdRes int viewId, boolean isCountDown) {
4217         setBoolean(viewId, "setCountDown", isCountDown);
4218     }
4219 
4220     /**
4221      * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
4222      * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
4223      * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
4224      *
4225      * If indeterminate is true, then the values for max and progress are ignored.
4226      *
4227      * @param viewId The id of the {@link ProgressBar} to change
4228      * @param max The 100% value for the progress bar
4229      * @param progress The current value of the progress bar.
4230      * @param indeterminate True if the progress bar is indeterminate,
4231      *                false if not.
4232      */
4233     public void setProgressBar(@IdRes int viewId, int max, int progress,
4234             boolean indeterminate) {
4235         setBoolean(viewId, "setIndeterminate", indeterminate);
4236         if (!indeterminate) {
4237             setInt(viewId, "setMax", max);
4238             setInt(viewId, "setProgress", progress);
4239         }
4240     }
4241 
4242     /**
4243      * Equivalent to calling
4244      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
4245      * to launch the provided {@link PendingIntent}. The source bounds
4246      * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked
4247      * view in screen space.
4248      * Note that any activity options associated with the mPendingIntent may get overridden
4249      * before starting the intent.
4250      *
4251      * When setting the on-click action of items within collections (eg. {@link ListView},
4252      * {@link StackView} etc.), this method will not work. Instead, use {@link
4253      * RemoteViews#setPendingIntentTemplate(int, PendingIntent)} in conjunction with
4254      * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
4255      *
4256      * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
4257      * @param pendingIntent The {@link PendingIntent} to send when user clicks
4258      */
4259     public void setOnClickPendingIntent(@IdRes int viewId, PendingIntent pendingIntent) {
4260         setOnClickResponse(viewId, RemoteResponse.fromPendingIntent(pendingIntent));
4261     }
4262 
4263     /**
4264      * Equivalent of calling
4265      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
4266      * to launch the provided {@link RemoteResponse}.
4267      *
4268      * @param viewId The id of the view that will trigger the {@link RemoteResponse} when clicked
4269      * @param response The {@link RemoteResponse} to send when user clicks
4270      */
4271     public void setOnClickResponse(@IdRes int viewId, @NonNull RemoteResponse response) {
4272         addAction(new SetOnClickResponse(viewId, response));
4273     }
4274 
4275     /**
4276      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
4277      * costly to set PendingIntents on the individual items, and is hence not recommended. Instead
4278      * this method should be used to set a single PendingIntent template on the collection, and
4279      * individual items can differentiate their on-click behavior using
4280      * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
4281      *
4282      * @param viewId The id of the collection who's children will use this PendingIntent template
4283      *          when clicked
4284      * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
4285      *          by a child of viewId and executed when that child is clicked
4286      */
4287     public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) {
4288         addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
4289     }
4290 
4291     /**
4292      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
4293      * costly to set PendingIntents on the individual items, and is hence not recommended. Instead
4294      * a single PendingIntent template can be set on the collection, see {@link
4295      * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
4296      * action of a given item can be distinguished by setting a fillInIntent on that item. The
4297      * fillInIntent is then combined with the PendingIntent template in order to determine the final
4298      * intent which will be executed when the item is clicked. This works as follows: any fields
4299      * which are left blank in the PendingIntent template, but are provided by the fillInIntent
4300      * will be overwritten, and the resulting PendingIntent will be used. The rest
4301      * of the PendingIntent template will then be filled in with the associated fields that are
4302      * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
4303      *
4304      * @param viewId The id of the view on which to set the fillInIntent
4305      * @param fillInIntent The intent which will be combined with the parent's PendingIntent
4306      *        in order to determine the on-click behavior of the view specified by viewId
4307      */
4308     public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) {
4309         setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent));
4310     }
4311 
4312     /**
4313      * Equivalent to calling
4314      * {@link android.widget.CompoundButton#setOnCheckedChangeListener(
4315      * android.widget.CompoundButton.OnCheckedChangeListener)}
4316      * to launch the provided {@link RemoteResponse}.
4317      *
4318      * The intent will be filled with the current checked state of the view at the key
4319      * {@link #EXTRA_CHECKED}.
4320      *
4321      * The {@link RemoteResponse} will not be launched in response to check changes arising from
4322      * {@link #setCompoundButtonChecked(int, boolean)} or {@link #setRadioGroupChecked(int, int)}
4323      * usages.
4324      *
4325      * The {@link RemoteResponse} must be created using
4326      * {@link RemoteResponse#fromFillInIntent(Intent)} in conjunction with
4327      * {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)} for items inside
4328      * collections (eg. {@link ListView}, {@link StackView} etc.).
4329      *
4330      * Otherwise, create the {@link RemoteResponse} using
4331      * {@link RemoteResponse#fromPendingIntent(PendingIntent)}.
4332      *
4333      * @param viewId The id of the view that will trigger the {@link PendingIntent} when checked
4334      *               state changes.
4335      * @param response The {@link RemoteResponse} to send when the checked state changes.
4336      */
4337     public void setOnCheckedChangeResponse(
4338             @IdRes int viewId,
4339             @NonNull RemoteResponse response) {
4340         addAction(
4341                 new SetOnCheckedChangeResponse(
4342                         viewId,
4343                         response.setInteractionType(
4344                                 RemoteResponse.INTERACTION_TYPE_CHECKED_CHANGE)));
4345     }
4346 
4347     /**
4348      * @hide
4349      * Equivalent to calling
4350      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
4351      * on the {@link Drawable} of a given view.
4352      * <p>
4353      *
4354      * @param viewId The id of the view that contains the target
4355      *            {@link Drawable}
4356      * @param targetBackground If true, apply these parameters to the
4357      *            {@link Drawable} returned by
4358      *            {@link android.view.View#getBackground()}. Otherwise, assume
4359      *            the target view is an {@link ImageView} and apply them to
4360      *            {@link ImageView#getDrawable()}.
4361      * @param colorFilter Specify a color for a
4362      *            {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
4363      *            {@code mode} is {@code null}.
4364      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
4365      *            unchanged.
4366      */
4367     public void setDrawableTint(@IdRes int viewId, boolean targetBackground,
4368             @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) {
4369         addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode));
4370     }
4371 
4372     /**
4373      * @hide
4374      * Equivalent to calling
4375      * {@link RippleDrawable#setColor(ColorStateList)} on the {@link Drawable} of a given view,
4376      * assuming it's a {@link RippleDrawable}.
4377      * <p>
4378      *
4379      * @param viewId The id of the view that contains the target
4380      *            {@link RippleDrawable}
4381      * @param colorStateList Specify a color for a
4382      *            {@link ColorStateList} for this drawable.
4383      */
4384     public void setRippleDrawableColor(@IdRes int viewId, ColorStateList colorStateList) {
4385         addAction(new SetRippleDrawableColor(viewId, colorStateList));
4386     }
4387 
4388     /**
4389      * @hide
4390      * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}.
4391      *
4392      * @param viewId The id of the view whose tint should change
4393      * @param tint the tint to apply, may be {@code null} to clear tint
4394      */
4395     public void setProgressTintList(@IdRes int viewId, ColorStateList tint) {
4396         addAction(new ReflectionAction(viewId, "setProgressTintList",
4397                 BaseReflectionAction.COLOR_STATE_LIST, tint));
4398     }
4399 
4400     /**
4401      * @hide
4402      * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}.
4403      *
4404      * @param viewId The id of the view whose tint should change
4405      * @param tint the tint to apply, may be {@code null} to clear tint
4406      */
4407     public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) {
4408         addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
4409                 BaseReflectionAction.COLOR_STATE_LIST, tint));
4410     }
4411 
4412     /**
4413      * @hide
4414      * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}.
4415      *
4416      * @param viewId The id of the view whose tint should change
4417      * @param tint the tint to apply, may be {@code null} to clear tint
4418      */
4419     public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) {
4420         addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
4421                 BaseReflectionAction.COLOR_STATE_LIST, tint));
4422     }
4423 
4424     /**
4425      * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
4426      *
4427      * @param viewId The id of the view whose text color should change
4428      * @param color Sets the text color for all the states (normal, selected,
4429      *            focused) to be this color.
4430      */
4431     public void setTextColor(@IdRes int viewId, @ColorInt int color) {
4432         setInt(viewId, "setTextColor", color);
4433     }
4434 
4435     /**
4436      * @hide
4437      * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}.
4438      *
4439      * @param viewId The id of the view whose text color should change
4440      * @param colors the text colors to set
4441      */
4442     public void setTextColor(@IdRes int viewId, ColorStateList colors) {
4443         addAction(new ReflectionAction(viewId, "setTextColor",
4444                 BaseReflectionAction.COLOR_STATE_LIST, colors));
4445     }
4446 
4447     /**
4448      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
4449      *
4450      * @param appWidgetId The id of the app widget which contains the specified view. (This
4451      *      parameter is ignored in this deprecated method)
4452      * @param viewId The id of the {@link AdapterView}
4453      * @param intent The intent of the service which will be
4454      *            providing data to the RemoteViewsAdapter
4455      * @deprecated This method has been deprecated. See
4456      *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
4457      */
4458     @Deprecated
4459     public void setRemoteAdapter(int appWidgetId, @IdRes int viewId, Intent intent) {
4460         setRemoteAdapter(viewId, intent);
4461     }
4462 
4463     /**
4464      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
4465      * Can only be used for App Widgets.
4466      *
4467      * @param viewId The id of the {@link AdapterView}
4468      * @param intent The intent of the service which will be
4469      *            providing data to the RemoteViewsAdapter
4470      */
4471     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
4472         addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
4473     }
4474 
4475     /**
4476      * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
4477      * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
4478      * This is a simpler but less flexible approach to populating collection widgets. Its use is
4479      * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
4480      * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
4481      * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
4482      * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
4483      *
4484      * This API is supported in the compatibility library for previous API levels, see
4485      * RemoteViewsCompat.
4486      *
4487      * @param viewId The id of the {@link AdapterView}
4488      * @param list The list of RemoteViews which will populate the view specified by viewId.
4489      * @param viewTypeCount The maximum number of unique layout id's used to construct the list of
4490      *      RemoteViews. This count cannot change during the life-cycle of a given widget, so this
4491      *      parameter should account for the maximum possible number of types that may appear in the
4492      *      See {@link Adapter#getViewTypeCount()}.
4493      *
4494      * @hide
4495      * @deprecated this appears to have no users outside of UnsupportedAppUsage?
4496      */
4497     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
4498     @Deprecated
4499     public void setRemoteAdapter(@IdRes int viewId, ArrayList<RemoteViews> list,
4500             int viewTypeCount) {
4501         addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
4502     }
4503 
4504     /**
4505      * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
4506      * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
4507      * This is a simpler but less flexible approach to populating collection widgets. Its use is
4508      * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
4509      * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
4510      * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
4511      * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
4512      *
4513      * This API is supported in the compatibility library for previous API levels, see
4514      * RemoteViewsCompat.
4515      *
4516      * @param viewId The id of the {@link AdapterView}.
4517      * @param items The items to display in the {@link AdapterView}.
4518      */
4519     public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) {
4520         addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items));
4521     }
4522 
4523     /**
4524      * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}.
4525      *
4526      * @param viewId The id of the view to change
4527      * @param position Scroll to this adapter position
4528      */
4529     public void setScrollPosition(@IdRes int viewId, int position) {
4530         setInt(viewId, "smoothScrollToPosition", position);
4531     }
4532 
4533     /**
4534      * Equivalent to calling {@link ListView#smoothScrollByOffset(int)}.
4535      *
4536      * @param viewId The id of the view to change
4537      * @param offset Scroll by this adapter position offset
4538      */
4539     public void setRelativeScrollPosition(@IdRes int viewId, int offset) {
4540         setInt(viewId, "smoothScrollByOffset", offset);
4541     }
4542 
4543     /**
4544      * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}.
4545      *
4546      * @param viewId The id of the view to change
4547      * @param left the left padding in pixels
4548      * @param top the top padding in pixels
4549      * @param right the right padding in pixels
4550      * @param bottom the bottom padding in pixels
4551      */
4552     public void setViewPadding(@IdRes int viewId,
4553             @Px int left, @Px int top, @Px int right, @Px int bottom) {
4554         addAction(new ViewPaddingAction(viewId, left, top, right, bottom));
4555     }
4556 
4557     /**
4558      * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}.
4559      * Only works if the {@link View#getLayoutParams()} supports margins.
4560      *
4561      * @param viewId The id of the view to change
4562      * @param type The margin being set e.g. {@link #MARGIN_END}
4563      * @param dimen a dimension resource to apply to the margin, or 0 to clear the margin.
4564      */
4565     public void setViewLayoutMarginDimen(@IdRes int viewId, @MarginType int type,
4566             @DimenRes int dimen) {
4567         addAction(new LayoutParamAction(viewId, type, dimen, VALUE_TYPE_RESOURCE));
4568     }
4569 
4570     /**
4571      * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}.
4572      * Only works if the {@link View#getLayoutParams()} supports margins.
4573      *
4574      * @param viewId The id of the view to change
4575      * @param type The margin being set e.g. {@link #MARGIN_END}
4576      * @param attr a dimension attribute to apply to the margin, or 0 to clear the margin.
4577      */
4578     public void setViewLayoutMarginAttr(@IdRes int viewId, @MarginType int type,
4579             @AttrRes int attr) {
4580         addAction(new LayoutParamAction(viewId, type, attr, VALUE_TYPE_ATTRIBUTE));
4581     }
4582 
4583     /**
4584      * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}.
4585      * Only works if the {@link View#getLayoutParams()} supports margins.
4586      *
4587      * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
4588      * Setting margins in pixels will behave poorly when the RemoteViews object is used on a
4589      * display with a different density.
4590      *
4591      * @param viewId The id of the view to change
4592      * @param type The margin being set e.g. {@link #MARGIN_END}
4593      * @param value a value for the margin the given units.
4594      * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
4595      */
4596     public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value,
4597             @ComplexDimensionUnit int units) {
4598         addAction(new LayoutParamAction(viewId, type, value, units));
4599     }
4600 
4601     /**
4602      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} except that you may
4603      * provide the value in any dimension units.
4604      *
4605      * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0,
4606      * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}.
4607      * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a
4608      * display with a different density.
4609      *
4610      * @param width Width of the view in the given units
4611      * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
4612      */
4613     public void setViewLayoutWidth(@IdRes int viewId, float width,
4614             @ComplexDimensionUnit int units) {
4615         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, width, units));
4616     }
4617 
4618     /**
4619      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with
4620      * the result of {@link Resources#getDimensionPixelSize(int)}.
4621      *
4622      * @param widthDimen the dimension resource for the view's width
4623      */
4624     public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) {
4625         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen,
4626                 VALUE_TYPE_RESOURCE));
4627     }
4628 
4629     /**
4630      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with
4631      * the value of the given attribute in the current theme.
4632      *
4633      * @param widthAttr the dimension attribute for the view's width
4634      */
4635     public void setViewLayoutWidthAttr(@IdRes int viewId, @AttrRes int widthAttr) {
4636         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthAttr,
4637                 VALUE_TYPE_ATTRIBUTE));
4638     }
4639 
4640     /**
4641      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} except that you may
4642      * provide the value in any dimension units.
4643      *
4644      * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0,
4645      * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}.
4646      * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a
4647      * display with a different density.
4648      *
4649      * @param height height of the view in the given units
4650      * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP}
4651      */
4652     public void setViewLayoutHeight(@IdRes int viewId, float height,
4653             @ComplexDimensionUnit int units) {
4654         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, height, units));
4655     }
4656 
4657     /**
4658      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with
4659      * the result of {@link Resources#getDimensionPixelSize(int)}.
4660      *
4661      * @param heightDimen a dimen resource to read the height from.
4662      */
4663     public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) {
4664         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen,
4665                 VALUE_TYPE_RESOURCE));
4666     }
4667 
4668     /**
4669      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with
4670      * the value of the given attribute in the current theme.
4671      *
4672      * @param heightAttr a dimen attribute to read the height from.
4673      */
4674     public void setViewLayoutHeightAttr(@IdRes int viewId, @AttrRes int heightAttr) {
4675         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightAttr,
4676                 VALUE_TYPE_ATTRIBUTE));
4677     }
4678 
4679     /**
4680      * Sets an OutlineProvider on the view whose corner radius is a dimension calculated using
4681      * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}.
4682      *
4683      * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0.
4684      * Setting margins in pixels will behave poorly when the RemoteViews object is used on a
4685      * display with a different density.
4686      */
4687     public void setViewOutlinePreferredRadius(
4688             @IdRes int viewId, float radius, @ComplexDimensionUnit int units) {
4689         addAction(new SetViewOutlinePreferredRadiusAction(viewId, radius, units));
4690     }
4691 
4692     /**
4693      * Sets an OutlineProvider on the view whose corner radius is a dimension resource with
4694      * {@code resId}.
4695      */
4696     public void setViewOutlinePreferredRadiusDimen(@IdRes int viewId, @DimenRes int resId) {
4697         addAction(new SetViewOutlinePreferredRadiusAction(viewId, resId, VALUE_TYPE_RESOURCE));
4698     }
4699 
4700     /**
4701      * Sets an OutlineProvider on the view whose corner radius is a dimension attribute with
4702      * {@code attrId}.
4703      */
4704     public void setViewOutlinePreferredRadiusAttr(@IdRes int viewId, @AttrRes int attrId) {
4705         addAction(new SetViewOutlinePreferredRadiusAction(viewId, attrId, VALUE_TYPE_ATTRIBUTE));
4706     }
4707 
4708     /**
4709      * Call a method taking one boolean on a view in the layout for this RemoteViews.
4710      *
4711      * @param viewId The id of the view on which to call the method.
4712      * @param methodName The name of the method to call.
4713      * @param value The value to pass to the method.
4714      */
4715     public void setBoolean(@IdRes int viewId, String methodName, boolean value) {
4716         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BOOLEAN, value));
4717     }
4718 
4719     /**
4720      * Call a method taking one byte on a view in the layout for this RemoteViews.
4721      *
4722      * @param viewId The id of the view on which to call the method.
4723      * @param methodName The name of the method to call.
4724      * @param value The value to pass to the method.
4725      */
4726     public void setByte(@IdRes int viewId, String methodName, byte value) {
4727         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BYTE, value));
4728     }
4729 
4730     /**
4731      * Call a method taking one short on a view in the layout for this RemoteViews.
4732      *
4733      * @param viewId The id of the view on which to call the method.
4734      * @param methodName The name of the method to call.
4735      * @param value The value to pass to the method.
4736      */
4737     public void setShort(@IdRes int viewId, String methodName, short value) {
4738         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.SHORT, value));
4739     }
4740 
4741     /**
4742      * Call a method taking one int on a view in the layout for this RemoteViews.
4743      *
4744      * @param viewId The id of the view on which to call the method.
4745      * @param methodName The name of the method to call.
4746      * @param value The value to pass to the method.
4747      */
4748     public void setInt(@IdRes int viewId, String methodName, int value) {
4749         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INT, value));
4750     }
4751 
4752     /**
4753      * Call a method taking one int, a size in pixels, on a view in the layout for this
4754      * RemoteViews.
4755      *
4756      * The dimension will be resolved from the resources at the time the {@link RemoteViews} is
4757      * (re-)applied.
4758      *
4759      * Undefined resources will result in an exception, except 0 which will resolve to 0.
4760      *
4761      * @param viewId The id of the view on which to call the method.
4762      * @param methodName The name of the method to call.
4763      * @param dimenResource The resource to resolve and pass as argument to the method.
4764      */
4765     public void setIntDimen(@IdRes int viewId, @NonNull String methodName,
4766             @DimenRes int dimenResource) {
4767         addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT,
4768                 ResourceReflectionAction.DIMEN_RESOURCE, dimenResource));
4769     }
4770 
4771     /**
4772      * Call a method taking one int, a size in pixels, on a view in the layout for this
4773      * RemoteViews.
4774      *
4775      * The dimension will be resolved from the specified dimension at the time of inflation.
4776      *
4777      * @param viewId The id of the view on which to call the method.
4778      * @param methodName The name of the method to call.
4779      * @param value The value of the dimension.
4780      * @param unit The unit in which the value is specified.
4781      */
4782     public void setIntDimen(@IdRes int viewId, @NonNull String methodName,
4783             float value, @ComplexDimensionUnit int unit) {
4784         addAction(new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.INT,
4785                 value, unit));
4786     }
4787 
4788     /**
4789      * Call a method taking one int, a size in pixels, on a view in the layout for this
4790      * RemoteViews.
4791      *
4792      * The dimension will be resolved from the theme attribute at the time the
4793      * {@link RemoteViews} is (re-)applied.
4794      *
4795      * Unresolvable attributes will result in an exception, except 0 which will resolve to 0.
4796      *
4797      * @param viewId The id of the view on which to call the method.
4798      * @param methodName The name of the method to call.
4799      * @param dimenAttr The attribute to resolve and pass as argument to the method.
4800      */
4801     public void setIntDimenAttr(@IdRes int viewId, @NonNull String methodName,
4802             @AttrRes int dimenAttr) {
4803         addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.INT,
4804                 ResourceReflectionAction.DIMEN_RESOURCE, dimenAttr));
4805     }
4806 
4807     /**
4808      * Call a method taking one int, a color, on a view in the layout for this RemoteViews.
4809      *
4810      * The Color will be resolved from the resources at the time the {@link RemoteViews} is (re-)
4811      * applied.
4812      *
4813      * Undefined resources will result in an exception, except 0 which will resolve to 0.
4814      *
4815      * @param viewId The id of the view on which to call the method.
4816      * @param methodName The name of the method to call.
4817      * @param colorResource The resource to resolve and pass as argument to the method.
4818      */
4819     public void setColor(@IdRes int viewId, @NonNull String methodName,
4820             @ColorRes int colorResource) {
4821         addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT,
4822                 ResourceReflectionAction.COLOR_RESOURCE, colorResource));
4823     }
4824 
4825     /**
4826      * Call a method taking one int, a color, on a view in the layout for this RemoteViews.
4827      *
4828      * The Color will be resolved from the theme attribute at the time the {@link RemoteViews} is
4829      * (re-)applied.
4830      *
4831      * Unresolvable attributes will result in an exception, except 0 which will resolve to 0.
4832      *
4833      * @param viewId The id of the view on which to call the method.
4834      * @param methodName The name of the method to call.
4835      * @param colorAttribute The theme attribute to resolve and pass as argument to the method.
4836      */
4837     public void setColorAttr(@IdRes int viewId, @NonNull String methodName,
4838             @AttrRes int colorAttribute) {
4839         addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.INT,
4840                 AttributeReflectionAction.COLOR_RESOURCE, colorAttribute));
4841     }
4842 
4843     /**
4844      * Call a method taking one int, a color, on a view in the layout for this RemoteViews.
4845      *
4846      * @param viewId The id of the view on which to call the method.
4847      * @param methodName The name of the method to call.
4848      * @param notNight The value to pass to the method when the view's configuration is set to
4849      *                 {@link Configuration#UI_MODE_NIGHT_NO}
4850      * @param night The value to pass to the method when the view's configuration is set to
4851      *                 {@link Configuration#UI_MODE_NIGHT_YES}
4852      */
4853     public void setColorInt(
4854             @IdRes int viewId,
4855             @NonNull String methodName,
4856             @ColorInt int notNight,
4857             @ColorInt int night) {
4858         addAction(
4859                 new NightModeReflectionAction(
4860                         viewId,
4861                         methodName,
4862                         BaseReflectionAction.INT,
4863                         notNight,
4864                         night));
4865     }
4866 
4867 
4868     /**
4869      * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
4870      *
4871      * @param viewId The id of the view on which to call the method.
4872      * @param methodName The name of the method to call.
4873      * @param value The value to pass to the method.
4874      */
4875     public void setColorStateList(@IdRes int viewId, @NonNull String methodName,
4876             @Nullable ColorStateList value) {
4877         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.COLOR_STATE_LIST,
4878                 value));
4879     }
4880 
4881     /**
4882      * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
4883      *
4884      * @param viewId The id of the view on which to call the method.
4885      * @param methodName The name of the method to call.
4886      * @param notNight The value to pass to the method when the view's configuration is set to
4887      *                 {@link Configuration#UI_MODE_NIGHT_NO}
4888      * @param night The value to pass to the method when the view's configuration is set to
4889      *                 {@link Configuration#UI_MODE_NIGHT_YES}
4890      */
4891     public void setColorStateList(
4892             @IdRes int viewId,
4893             @NonNull String methodName,
4894             @Nullable ColorStateList notNight,
4895             @Nullable ColorStateList night) {
4896         addAction(
4897                 new NightModeReflectionAction(
4898                         viewId,
4899                         methodName,
4900                         BaseReflectionAction.COLOR_STATE_LIST,
4901                         notNight,
4902                         night));
4903     }
4904 
4905     /**
4906      * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
4907      *
4908      * The ColorStateList will be resolved from the resources at the time the {@link RemoteViews} is
4909      * (re-)applied.
4910      *
4911      * Undefined resources will result in an exception, except 0 which will resolve to null.
4912      *
4913      * @param viewId The id of the view on which to call the method.
4914      * @param methodName The name of the method to call.
4915      * @param colorResource The resource to resolve and pass as argument to the method.
4916      */
4917     public void setColorStateList(@IdRes int viewId, @NonNull String methodName,
4918             @ColorRes int colorResource) {
4919         addAction(new ResourceReflectionAction(viewId, methodName,
4920                 BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE,
4921                 colorResource));
4922     }
4923 
4924     /**
4925      * Call a method taking one ColorStateList on a view in the layout for this RemoteViews.
4926      *
4927      * The ColorStateList will be resolved from the theme attribute at the time the
4928      * {@link RemoteViews} is (re-)applied.
4929      *
4930      * Unresolvable attributes will result in an exception, except 0 which will resolve to null.
4931      *
4932      * @param viewId The id of the view on which to call the method.
4933      * @param methodName The name of the method to call.
4934      * @param colorAttr The theme attribute to resolve and pass as argument to the method.
4935      */
4936     public void setColorStateListAttr(@IdRes int viewId, @NonNull String methodName,
4937             @AttrRes int colorAttr) {
4938         addAction(new AttributeReflectionAction(viewId, methodName,
4939                 BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE,
4940                 colorAttr));
4941     }
4942 
4943     /**
4944      * Call a method taking one long on a view in the layout for this RemoteViews.
4945      *
4946      * @param viewId The id of the view on which to call the method.
4947      * @param methodName The name of the method to call.
4948      * @param value The value to pass to the method.
4949      */
4950     public void setLong(@IdRes int viewId, String methodName, long value) {
4951         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.LONG, value));
4952     }
4953 
4954     /**
4955      * Call a method taking one float on a view in the layout for this RemoteViews.
4956      *
4957      * @param viewId The id of the view on which to call the method.
4958      * @param methodName The name of the method to call.
4959      * @param value The value to pass to the method.
4960      */
4961     public void setFloat(@IdRes int viewId, String methodName, float value) {
4962         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, value));
4963     }
4964 
4965     /**
4966      * Call a method taking one float, a size in pixels, on a view in the layout for this
4967      * RemoteViews.
4968      *
4969      * The dimension will be resolved from the resources at the time the {@link RemoteViews} is
4970      * (re-)applied.
4971      *
4972      * Undefined resources will result in an exception, except 0 which will resolve to 0f.
4973      *
4974      * @param viewId The id of the view on which to call the method.
4975      * @param methodName The name of the method to call.
4976      * @param dimenResource The resource to resolve and pass as argument to the method.
4977      */
4978     public void setFloatDimen(@IdRes int viewId, @NonNull String methodName,
4979             @DimenRes int dimenResource) {
4980         addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT,
4981                 ResourceReflectionAction.DIMEN_RESOURCE, dimenResource));
4982     }
4983 
4984     /**
4985      * Call a method taking one float, a size in pixels, on a view in the layout for this
4986      * RemoteViews.
4987      *
4988      * The dimension will be resolved from the resources at the time the {@link RemoteViews} is
4989      * (re-)applied.
4990      *
4991      * @param viewId The id of the view on which to call the method.
4992      * @param methodName The name of the method to call.
4993      * @param value The value of the dimension.
4994      * @param unit The unit in which the value is specified.
4995      */
4996     public void setFloatDimen(@IdRes int viewId, @NonNull String methodName,
4997             float value, @ComplexDimensionUnit int unit) {
4998         addAction(
4999                 new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.FLOAT,
5000                         value, unit));
5001     }
5002 
5003     /**
5004      * Call a method taking one float, a size in pixels, on a view in the layout for this
5005      * RemoteViews.
5006      *
5007      * The dimension will be resolved from the theme attribute at the time the {@link RemoteViews}
5008      * is (re-)applied.
5009      *
5010      * Unresolvable attributes will result in an exception, except 0 which will resolve to 0f.
5011      *
5012      * @param viewId The id of the view on which to call the method.
5013      * @param methodName The name of the method to call.
5014      * @param dimenAttr The attribute to resolve and pass as argument to the method.
5015      */
5016     public void setFloatDimenAttr(@IdRes int viewId, @NonNull String methodName,
5017             @AttrRes int dimenAttr) {
5018         addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT,
5019                 ResourceReflectionAction.DIMEN_RESOURCE, dimenAttr));
5020     }
5021 
5022     /**
5023      * Call a method taking one double on a view in the layout for this RemoteViews.
5024      *
5025      * @param viewId The id of the view on which to call the method.
5026      * @param methodName The name of the method to call.
5027      * @param value The value to pass to the method.
5028      */
5029     public void setDouble(@IdRes int viewId, String methodName, double value) {
5030         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.DOUBLE, value));
5031     }
5032 
5033     /**
5034      * Call a method taking one char on a view in the layout for this RemoteViews.
5035      *
5036      * @param viewId The id of the view on which to call the method.
5037      * @param methodName The name of the method to call.
5038      * @param value The value to pass to the method.
5039      */
5040     public void setChar(@IdRes int viewId, String methodName, char value) {
5041         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR, value));
5042     }
5043 
5044     /**
5045      * Call a method taking one String on a view in the layout for this RemoteViews.
5046      *
5047      * @param viewId The id of the view on which to call the method.
5048      * @param methodName The name of the method to call.
5049      * @param value The value to pass to the method.
5050      */
5051     public void setString(@IdRes int viewId, String methodName, String value) {
5052         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.STRING, value));
5053     }
5054 
5055     /**
5056      * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
5057      *
5058      * @param viewId The id of the view on which to call the method.
5059      * @param methodName The name of the method to call.
5060      * @param value The value to pass to the method.
5061      */
5062     public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) {
5063         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE,
5064                 value));
5065     }
5066 
5067     /**
5068      * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
5069      *
5070      * The CharSequence will be resolved from the resources at the time the {@link RemoteViews} is
5071      * (re-)applied.
5072      *
5073      * Undefined resources will result in an exception, except 0 which will resolve to null.
5074      *
5075      * @param viewId The id of the view on which to call the method.
5076      * @param methodName The name of the method to call.
5077      * @param stringResource The resource to resolve and pass as argument to the method.
5078      */
5079     public void setCharSequence(@IdRes int viewId, @NonNull String methodName,
5080             @StringRes int stringResource) {
5081         addAction(
5082                 new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE,
5083                         ResourceReflectionAction.STRING_RESOURCE, stringResource));
5084     }
5085 
5086     /**
5087      * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
5088      *
5089      * The CharSequence will be resolved from the theme attribute at the time the
5090      * {@link RemoteViews} is (re-)applied.
5091      *
5092      * Unresolvable attributes will result in an exception, except 0 which will resolve to null.
5093      *
5094      * @param viewId The id of the view on which to call the method.
5095      * @param methodName The name of the method to call.
5096      * @param stringAttribute The attribute to resolve and pass as argument to the method.
5097      */
5098     public void setCharSequenceAttr(@IdRes int viewId, @NonNull String methodName,
5099             @AttrRes int stringAttribute) {
5100         addAction(
5101                 new AttributeReflectionAction(viewId, methodName,
5102                         BaseReflectionAction.CHAR_SEQUENCE,
5103                         AttributeReflectionAction.STRING_RESOURCE, stringAttribute));
5104     }
5105 
5106     /**
5107      * Call a method taking one Uri on a view in the layout for this RemoteViews.
5108      *
5109      * @param viewId The id of the view on which to call the method.
5110      * @param methodName The name of the method to call.
5111      * @param value The value to pass to the method.
5112      */
5113     public void setUri(@IdRes int viewId, String methodName, Uri value) {
5114         if (value != null) {
5115             // Resolve any filesystem path before sending remotely
5116             value = value.getCanonicalUri();
5117             if (StrictMode.vmFileUriExposureEnabled()) {
5118                 value.checkFileUriExposed("RemoteViews.setUri()");
5119             }
5120         }
5121         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.URI, value));
5122     }
5123 
5124     /**
5125      * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
5126      * @more
5127      * <p class="note">The bitmap will be flattened into the parcel if this object is
5128      * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
5129      *
5130      * @param viewId The id of the view on which to call the method.
5131      * @param methodName The name of the method to call.
5132      * @param value The value to pass to the method.
5133      */
5134     public void setBitmap(@IdRes int viewId, String methodName, Bitmap value) {
5135         addAction(new BitmapReflectionAction(viewId, methodName, value));
5136     }
5137 
5138     /**
5139      * Call a method taking one BlendMode on a view in the layout for this RemoteViews.
5140      *
5141      * @param viewId The id of the view on which to call the method.
5142      * @param methodName The name of the method to call.
5143      * @param value The value to pass to the method.
5144      */
5145     public void setBlendMode(@IdRes int viewId, @NonNull String methodName,
5146             @Nullable BlendMode value) {
5147         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BLEND_MODE, value));
5148     }
5149 
5150     /**
5151      * Call a method taking one Bundle on a view in the layout for this RemoteViews.
5152      *
5153      * @param viewId The id of the view on which to call the method.
5154      * @param methodName The name of the method to call.
5155      * @param value The value to pass to the method.
5156      */
5157     public void setBundle(@IdRes int viewId, String methodName, Bundle value) {
5158         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BUNDLE, value));
5159     }
5160 
5161     /**
5162      * Call a method taking one Intent on a view in the layout for this RemoteViews.
5163      *
5164      * @param viewId The id of the view on which to call the method.
5165      * @param methodName The name of the method to call.
5166      * @param value The {@link android.content.Intent} to pass the method.
5167      */
5168     public void setIntent(@IdRes int viewId, String methodName, Intent value) {
5169         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INTENT, value));
5170     }
5171 
5172     /**
5173      * Call a method taking one Icon on a view in the layout for this RemoteViews.
5174      *
5175      * @param viewId The id of the view on which to call the method.
5176      * @param methodName The name of the method to call.
5177      * @param value The {@link android.graphics.drawable.Icon} to pass the method.
5178      */
5179     public void setIcon(@IdRes int viewId, String methodName, Icon value) {
5180         addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.ICON, value));
5181     }
5182 
5183     /**
5184      * Call a method taking one Icon on a view in the layout for this RemoteViews.
5185      *
5186      * @param viewId The id of the view on which to call the method.
5187      * @param methodName The name of the method to call.
5188      * @param notNight The value to pass to the method when the view's configuration is set to
5189      *                 {@link Configuration#UI_MODE_NIGHT_NO}
5190      * @param night The value to pass to the method when the view's configuration is set to
5191      *                 {@link Configuration#UI_MODE_NIGHT_YES}
5192      */
5193     public void setIcon(
5194             @IdRes int viewId,
5195             @NonNull String methodName,
5196             @Nullable Icon notNight,
5197             @Nullable Icon night) {
5198         addAction(
5199                 new NightModeReflectionAction(
5200                         viewId,
5201                         methodName,
5202                         BaseReflectionAction.ICON,
5203                         notNight,
5204                         night));
5205     }
5206 
5207     /**
5208      * Equivalent to calling View.setContentDescription(CharSequence).
5209      *
5210      * @param viewId The id of the view whose content description should change.
5211      * @param contentDescription The new content description for the view.
5212      */
5213     public void setContentDescription(@IdRes int viewId, CharSequence contentDescription) {
5214         setCharSequence(viewId, "setContentDescription", contentDescription);
5215     }
5216 
5217     /**
5218      * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}.
5219      *
5220      * @param viewId The id of the view whose before view in accessibility traversal to set.
5221      * @param nextId The id of the next in the accessibility traversal.
5222      **/
5223     public void setAccessibilityTraversalBefore(@IdRes int viewId, @IdRes int nextId) {
5224         setInt(viewId, "setAccessibilityTraversalBefore", nextId);
5225     }
5226 
5227     /**
5228      * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}.
5229      *
5230      * @param viewId The id of the view whose after view in accessibility traversal to set.
5231      * @param nextId The id of the next in the accessibility traversal.
5232      **/
5233     public void setAccessibilityTraversalAfter(@IdRes int viewId, @IdRes int nextId) {
5234         setInt(viewId, "setAccessibilityTraversalAfter", nextId);
5235     }
5236 
5237     /**
5238      * Equivalent to calling {@link View#setLabelFor(int)}.
5239      *
5240      * @param viewId The id of the view whose property to set.
5241      * @param labeledId The id of a view for which this view serves as a label.
5242      */
5243     public void setLabelFor(@IdRes int viewId, @IdRes int labeledId) {
5244         setInt(viewId, "setLabelFor", labeledId);
5245     }
5246 
5247     /**
5248      * Equivalent to calling {@link android.widget.CompoundButton#setChecked(boolean)}.
5249      *
5250      * @param viewId The id of the view whose property to set.
5251      * @param checked true to check the button, false to uncheck it.
5252      */
5253     public void setCompoundButtonChecked(@IdRes int viewId, boolean checked) {
5254         addAction(new SetCompoundButtonCheckedAction(viewId, checked));
5255     }
5256 
5257     /**
5258      * Equivalent to calling {@link android.widget.RadioGroup#check(int)}.
5259      *
5260      * @param viewId The id of the view whose property to set.
5261      * @param checkedId The unique id of the radio button to select in the group.
5262      */
5263     public void setRadioGroupChecked(@IdRes int viewId, @IdRes int checkedId) {
5264         addAction(new SetRadioGroupCheckedAction(viewId, checkedId));
5265     }
5266 
5267     /**
5268      * Provides an alternate layout ID, which can be used to inflate this view. This layout will be
5269      * used by the host when the widgets displayed on a light-background where foreground elements
5270      * and text can safely draw using a dark color without any additional background protection.
5271      */
5272     public void setLightBackgroundLayoutId(@LayoutRes int layoutId) {
5273         mLightBackgroundLayoutId = layoutId;
5274     }
5275 
5276     /**
5277      * If this view supports dark text versions, creates a copy representing that version,
5278      * otherwise returns itself.
5279      * @hide
5280      */
5281     public RemoteViews getDarkTextViews() {
5282         if (hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)) {
5283             return this;
5284         }
5285 
5286         try {
5287             addFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT);
5288             return new RemoteViews(this);
5289         } finally {
5290             mApplyFlags &= ~FLAG_USE_LIGHT_BACKGROUND_LAYOUT;
5291         }
5292     }
5293 
5294     private RemoteViews getRemoteViewsToApply(Context context) {
5295         if (hasLandscapeAndPortraitLayouts()) {
5296             int orientation = context.getResources().getConfiguration().orientation;
5297             if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
5298                 return mLandscape;
5299             }
5300             return mPortrait;
5301         }
5302         if (hasSizedRemoteViews()) {
5303             return findSmallestRemoteView();
5304         }
5305         return this;
5306     }
5307 
5308     /**
5309      * Returns the square distance between two points.
5310      *
5311      * This is particularly useful when we only care about the ordering of the distances.
5312      */
5313     private static float squareDistance(SizeF p1, SizeF p2) {
5314         float dx = p1.getWidth() - p2.getWidth();
5315         float dy = p1.getHeight() - p2.getHeight();
5316         return dx * dx + dy * dy;
5317     }
5318 
5319     /**
5320      * Returns whether the layout fits in the space available to the widget.
5321      *
5322      * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions
5323      * are smaller than the ones of the widget, adding some padding to account for rounding errors.
5324      */
5325     private static boolean fitsIn(SizeF sizeLayout, @Nullable SizeF sizeWidget) {
5326         return sizeWidget != null && (Math.ceil(sizeWidget.getWidth()) + 1 > sizeLayout.getWidth())
5327                 && (Math.ceil(sizeWidget.getHeight()) + 1 > sizeLayout.getHeight());
5328     }
5329 
5330     private RemoteViews findBestFitLayout(@NonNull SizeF widgetSize) {
5331         // Find the better remote view
5332         RemoteViews bestFit = null;
5333         float bestSqDist = Float.MAX_VALUE;
5334         for (RemoteViews layout : mSizedRemoteViews) {
5335             SizeF layoutSize = layout.getIdealSize();
5336             if (layoutSize == null) {
5337                 throw new IllegalStateException("Expected RemoteViews to have ideal size");
5338             }
5339 
5340             if (fitsIn(layoutSize, widgetSize)) {
5341                 if (bestFit == null) {
5342                     bestFit = layout;
5343                     bestSqDist = squareDistance(layoutSize, widgetSize);
5344                 } else {
5345                     float newSqDist = squareDistance(layoutSize, widgetSize);
5346                     if (newSqDist < bestSqDist) {
5347                         bestFit = layout;
5348                         bestSqDist = newSqDist;
5349                     }
5350                 }
5351             }
5352         }
5353         if (bestFit == null) {
5354             Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize);
5355             return findSmallestRemoteView();
5356         }
5357         return bestFit;
5358     }
5359 
5360     /**
5361      * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the
5362      * size of the widget.
5363      *
5364      * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is
5365      * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the
5366      * diagonal the most similar to the widget. If no layout fits or the size of the widget is
5367      * not specified, the one with the smallest area will be chosen.
5368      *
5369      * @hide
5370      */
5371     public RemoteViews getRemoteViewsToApply(@NonNull Context context,
5372             @Nullable SizeF widgetSize) {
5373         if (!hasSizedRemoteViews() || widgetSize == null) {
5374             // If there isn't multiple remote views, fall back on the previous methods.
5375             return getRemoteViewsToApply(context);
5376         }
5377         return findBestFitLayout(widgetSize);
5378     }
5379 
5380     /**
5381      * Checks whether the change of size will lead to using a different {@link RemoteViews}.
5382      *
5383      * @hide
5384      */
5385     @Nullable
5386     public RemoteViews getRemoteViewsToApplyIfDifferent(@Nullable SizeF oldSize,
5387             @NonNull SizeF newSize) {
5388         if (!hasSizedRemoteViews()) {
5389             return null;
5390         }
5391         RemoteViews oldBestFit = oldSize == null ? findSmallestRemoteView() : findBestFitLayout(
5392                 oldSize);
5393         RemoteViews newBestFit = findBestFitLayout(newSize);
5394         if (oldBestFit != newBestFit) {
5395             return newBestFit;
5396         }
5397         return null;
5398     }
5399 
5400 
5401     /**
5402      * Inflates the view hierarchy represented by this object and applies
5403      * all of the actions.
5404      *
5405      * <p><strong>Caller beware: this may throw</strong>
5406      *
5407      * @param context Default context to use
5408      * @param parent Parent that the resulting view hierarchy will be attached to. This method
5409      * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
5410      * @return The inflated view hierarchy
5411      */
5412     public View apply(Context context, ViewGroup parent) {
5413         return apply(context, parent, null);
5414     }
5415 
5416     /** @hide */
5417     public View apply(Context context, ViewGroup parent, InteractionHandler handler) {
5418         return apply(context, parent, handler, null);
5419     }
5420 
5421     /** @hide */
5422     public View apply(@NonNull Context context, @NonNull ViewGroup parent,
5423             @Nullable InteractionHandler handler, @Nullable SizeF size) {
5424         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
5425 
5426         View result = inflateView(context, rvToApply, parent);
5427         rvToApply.performApply(result, parent, handler, null);
5428         return result;
5429     }
5430 
5431     /** @hide */
5432     public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
5433             @Nullable InteractionHandler handler, @StyleRes int applyThemeResId) {
5434         return applyWithTheme(context, parent, handler, applyThemeResId, null);
5435     }
5436 
5437     /** @hide */
5438     public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent,
5439             @Nullable InteractionHandler handler, @StyleRes int applyThemeResId,
5440             @Nullable SizeF size) {
5441         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
5442 
5443         View result = inflateView(context, rvToApply, parent, applyThemeResId, null);
5444         rvToApply.performApply(result, parent, handler, null);
5445         return result;
5446     }
5447 
5448     /** @hide */
5449     public View apply(Context context, ViewGroup parent, InteractionHandler handler,
5450             @Nullable SizeF size, @Nullable ColorResources colorResources) {
5451         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
5452 
5453         View result = inflateView(context, rvToApply, parent, 0, colorResources);
5454         rvToApply.performApply(result, parent, handler, colorResources);
5455         return result;
5456     }
5457 
5458     private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
5459         return inflateView(context, rv, parent, 0, null);
5460     }
5461 
5462     private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
5463             @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
5464         // RemoteViews may be built by an application installed in another
5465         // user. So build a context that loads resources from that user but
5466         // still returns the current users userId so settings like data / time formats
5467         // are loaded without requiring cross user persmissions.
5468         final Context contextForResources = getContextForResources(context);
5469         if (colorResources != null) {
5470             colorResources.apply(contextForResources);
5471         }
5472         Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources);
5473 
5474         // If mApplyThemeResId is not given, Theme.DeviceDefault will be used.
5475         if (applyThemeResId != 0) {
5476             inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId);
5477         }
5478         LayoutInflater inflater = LayoutInflater.from(context);
5479 
5480         // Clone inflater so we load resources from correct context and
5481         // we don't add a filter to the static version returned by getSystemService.
5482         inflater = inflater.cloneInContext(inflationContext);
5483         inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this);
5484         View v = inflater.inflate(rv.getLayoutId(), parent, false);
5485         if (mViewId != View.NO_ID) {
5486             v.setId(mViewId);
5487             v.setTagInternal(R.id.remote_views_override_id, mViewId);
5488         }
5489         v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
5490         return v;
5491     }
5492 
5493     /**
5494      * A static filter is much lighter than RemoteViews itself. It's optimized here only for
5495      * RemoteVies class. Subclasses should always override this and return true if not overriding
5496      * {@link this#onLoadClass(Class)}.
5497      *
5498      * @hide
5499      */
5500     protected boolean shouldUseStaticFilter() {
5501         return this.getClass().equals(RemoteViews.class);
5502     }
5503 
5504     /**
5505      * Implement this interface to receive a callback when
5506      * {@link #applyAsync} or {@link #reapplyAsync} is finished.
5507      * @hide
5508      */
5509     public interface OnViewAppliedListener {
5510         /**
5511          * Callback when the RemoteView has finished inflating,
5512          * but no actions have been applied yet.
5513          */
5514         default void onViewInflated(View v) {};
5515 
5516         void onViewApplied(View v);
5517 
5518         void onError(Exception e);
5519     }
5520 
5521     /**
5522      * Applies the views asynchronously, moving as much of the task on the background
5523      * thread as possible.
5524      *
5525      * @see #apply(Context, ViewGroup)
5526      * @param context Default context to use
5527      * @param parent Parent that the resulting view hierarchy will be attached to. This method
5528      * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
5529      * @param listener the callback to run when all actions have been applied. May be null.
5530      * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used.
5531      * @return CancellationSignal
5532      * @hide
5533      */
5534     public CancellationSignal applyAsync(
5535             Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) {
5536         return applyAsync(context, parent, executor, listener, null /* handler */);
5537     }
5538 
5539 
5540     /** @hide */
5541     public CancellationSignal applyAsync(Context context, ViewGroup parent,
5542             Executor executor, OnViewAppliedListener listener, InteractionHandler handler) {
5543         return applyAsync(context, parent, executor, listener, handler, null /* size */);
5544     }
5545 
5546     /** @hide */
5547     public CancellationSignal applyAsync(Context context, ViewGroup parent,
5548             Executor executor, OnViewAppliedListener listener, InteractionHandler handler,
5549             SizeF size) {
5550         return applyAsync(context, parent, executor, listener, handler, size,
5551                 null /* themeColors */);
5552     }
5553 
5554     /** @hide */
5555     public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor,
5556             OnViewAppliedListener listener, InteractionHandler handler, SizeF size,
5557             ColorResources colorResources) {
5558         return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener,
5559                 handler, colorResources, null /* result */,
5560                 true /* topLevel */).startTaskOnExecutor(executor);
5561     }
5562 
5563     private AsyncApplyTask getInternalAsyncApplyTask(Context context, ViewGroup parent,
5564             OnViewAppliedListener listener, InteractionHandler handler, SizeF size,
5565             ColorResources colorResources, View result) {
5566         return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener,
5567                 handler, colorResources, result, false /* topLevel */);
5568     }
5569 
5570     private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
5571             implements CancellationSignal.OnCancelListener {
5572         final CancellationSignal mCancelSignal = new CancellationSignal();
5573         final RemoteViews mRV;
5574         final ViewGroup mParent;
5575         final Context mContext;
5576         final OnViewAppliedListener mListener;
5577         final InteractionHandler mHandler;
5578         final ColorResources mColorResources;
5579         /**
5580          * Whether the remote view is the top-level one (i.e. not within an action).
5581          *
5582          * This is only used if the result is specified (i.e. the view is being recycled).
5583          */
5584         final boolean mTopLevel;
5585 
5586         private View mResult;
5587         private ViewTree mTree;
5588         private Action[] mActions;
5589         private Exception mError;
5590 
5591         private AsyncApplyTask(
5592                 RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener,
5593                 InteractionHandler handler, ColorResources colorResources,
5594                 View result, boolean topLevel) {
5595             mRV = rv;
5596             mParent = parent;
5597             mContext = context;
5598             mListener = listener;
5599             mColorResources = colorResources;
5600             mHandler = handler;
5601             mTopLevel = topLevel;
5602 
5603             mResult = result;
5604         }
5605 
5606         @Nullable
5607         @Override
5608         protected ViewTree doInBackground(Void... params) {
5609             try {
5610                 if (mResult == null) {
5611                     mResult = inflateView(mContext, mRV, mParent, 0, mColorResources);
5612                 }
5613 
5614                 mTree = new ViewTree(mResult);
5615                 if (mRV.mActions != null) {
5616                     int count = mRV.mActions.size();
5617                     mActions = new Action[count];
5618                     for (int i = 0; i < count && !isCancelled(); i++) {
5619                         // TODO: check if isCancelled in nested views.
5620                         mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler,
5621                                 mColorResources);
5622                     }
5623                 } else {
5624                     mActions = null;
5625                 }
5626                 return mTree;
5627             } catch (Exception e) {
5628                 mError = e;
5629                 return null;
5630             }
5631         }
5632 
5633         @Override
5634         protected void onPostExecute(ViewTree viewTree) {
5635             mCancelSignal.setOnCancelListener(null);
5636             if (mError == null) {
5637                 if (mListener != null) {
5638                     mListener.onViewInflated(viewTree.mRoot);
5639                 }
5640 
5641                 try {
5642                     if (mActions != null) {
5643                         InteractionHandler handler = mHandler == null
5644                                 ? DEFAULT_INTERACTION_HANDLER : mHandler;
5645                         for (Action a : mActions) {
5646                             a.apply(viewTree.mRoot, mParent, handler, mColorResources);
5647                         }
5648                     }
5649                     // If the parent of the view is has is a root, resolve the recycling.
5650                     if (mTopLevel && mResult instanceof ViewGroup) {
5651                         finalizeViewRecycling((ViewGroup) mResult);
5652                     }
5653                 } catch (Exception e) {
5654                     mError = e;
5655                 }
5656             }
5657 
5658             if (mListener != null) {
5659                 if (mError != null) {
5660                     mListener.onError(mError);
5661                 } else {
5662                     mListener.onViewApplied(viewTree.mRoot);
5663                 }
5664             } else if (mError != null) {
5665                 if (mError instanceof ActionException) {
5666                     throw (ActionException) mError;
5667                 } else {
5668                     throw new ActionException(mError);
5669                 }
5670             }
5671         }
5672 
5673         @Override
5674         public void onCancel() {
5675             cancel(true);
5676         }
5677 
5678         private CancellationSignal startTaskOnExecutor(Executor executor) {
5679             mCancelSignal.setOnCancelListener(this);
5680             executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
5681             return mCancelSignal;
5682         }
5683     }
5684 
5685     /**
5686      * Applies all of the actions to the provided view.
5687      *
5688      * <p><strong>Caller beware: this may throw</strong>
5689      *
5690      * @param v The view to apply the actions to.  This should be the result of
5691      * the {@link #apply(Context,ViewGroup)} call.
5692      */
5693     public void reapply(Context context, View v) {
5694         reapply(context, v, null /* handler */);
5695     }
5696 
5697     /** @hide */
5698     public void reapply(Context context, View v, InteractionHandler handler) {
5699         reapply(context, v, handler, null /* size */, null /* colorResources */);
5700     }
5701 
5702     /** @hide */
5703     public void reapply(Context context, View v, InteractionHandler handler, SizeF size,
5704             ColorResources colorResources) {
5705         reapply(context, v, handler, size, colorResources, true);
5706     }
5707 
5708     /** @hide */
5709     public boolean canRecycleView(@Nullable View v) {
5710         if (v == null) {
5711             return false;
5712         }
5713         Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame);
5714         if (previousLayoutId == null) {
5715             return false;
5716         }
5717         Integer overrideIdTag = (Integer) v.getTag(R.id.remote_views_override_id);
5718         int overrideId = overrideIdTag == null ? View.NO_ID : overrideIdTag;
5719         // If mViewId is View.NO_ID, we only recycle if overrideId is also View.NO_ID.
5720         // Otherwise, it might be that, on a previous iteration, the view's ID was set to
5721         // something else, and it should now be reset to the ID defined in the XML layout file,
5722         // whatever it is.
5723         return previousLayoutId == getLayoutId() && mViewId == overrideId;
5724     }
5725 
5726     // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls
5727     // should set it to false.
5728     private void reapply(Context context, View v, InteractionHandler handler, SizeF size,
5729             ColorResources colorResources, boolean topLevel) {
5730 
5731         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
5732 
5733         // In the case that a view has this RemoteViews applied in one orientation or size, is
5734         // persisted across change, and has the RemoteViews re-applied in a different situation
5735         // (orientation or size), we throw an exception, since the layouts may be completely
5736         // unrelated.
5737         if (hasMultipleLayouts()) {
5738             if (!rvToApply.canRecycleView(v)) {
5739                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
5740                         " that does not share the same root layout id.");
5741             }
5742         }
5743 
5744         rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources);
5745 
5746         // If the parent of the view is has is a root, resolve the recycling.
5747         if (topLevel && v instanceof ViewGroup) {
5748             finalizeViewRecycling((ViewGroup) v);
5749         }
5750     }
5751 
5752     /**
5753      * Applies all the actions to the provided view, moving as much of the task on the background
5754      * thread as possible.
5755      *
5756      * @see #reapply(Context, View)
5757      * @param context Default context to use
5758      * @param v The view to apply the actions to.  This should be the result of
5759      * the {@link #apply(Context,ViewGroup)} call.
5760      * @param listener the callback to run when all actions have been applied. May be null.
5761      * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used
5762      * @return CancellationSignal
5763      * @hide
5764      */
5765     public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
5766             OnViewAppliedListener listener) {
5767         return reapplyAsync(context, v, executor, listener, null);
5768     }
5769 
5770     /** @hide */
5771     public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
5772             OnViewAppliedListener listener, InteractionHandler handler) {
5773         return reapplyAsync(context, v, executor, listener, handler, null, null);
5774     }
5775 
5776     /** @hide */
5777     public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
5778             OnViewAppliedListener listener, InteractionHandler handler, SizeF size,
5779             ColorResources colorResources) {
5780         RemoteViews rvToApply = getRemoteViewsToApply(context, size);
5781 
5782         // In the case that a view has this RemoteViews applied in one orientation, is persisted
5783         // across orientation change, and has the RemoteViews re-applied in the new orientation,
5784         // we throw an exception, since the layouts may be completely unrelated.
5785         if (hasMultipleLayouts()) {
5786             if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
5787                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
5788                         " that does not share the same root layout id.");
5789             }
5790         }
5791 
5792         return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
5793                 context, listener, handler, colorResources, v, true /* topLevel */)
5794                 .startTaskOnExecutor(executor);
5795     }
5796 
5797     private void performApply(View v, ViewGroup parent, InteractionHandler handler,
5798             ColorResources colorResources) {
5799         if (mActions != null) {
5800             handler = handler == null ? DEFAULT_INTERACTION_HANDLER : handler;
5801             final int count = mActions.size();
5802             for (int i = 0; i < count; i++) {
5803                 Action a = mActions.get(i);
5804                 a.apply(v, parent, handler, colorResources);
5805             }
5806         }
5807     }
5808 
5809     /**
5810      * Returns true if the RemoteViews contains potentially costly operations and should be
5811      * applied asynchronously.
5812      *
5813      * @hide
5814      */
5815     public boolean prefersAsyncApply() {
5816         if (mActions != null) {
5817             final int count = mActions.size();
5818             for (int i = 0; i < count; i++) {
5819                 if (mActions.get(i).prefersAsyncApply()) {
5820                     return true;
5821                 }
5822             }
5823         }
5824         return false;
5825     }
5826 
5827     /** @hide */
5828     public void updateAppInfo(@NonNull ApplicationInfo info) {
5829         if (mApplication != null && mApplication.sourceDir.equals(info.sourceDir)) {
5830             // Overlay paths are generated against a particular version of an application.
5831             // The overlays paths of a newly upgraded application are incompatible with the
5832             // old version of the application.
5833             mApplication = info;
5834         }
5835         if (hasSizedRemoteViews()) {
5836             for (RemoteViews layout : mSizedRemoteViews) {
5837                 layout.updateAppInfo(info);
5838             }
5839         }
5840         if (hasLandscapeAndPortraitLayouts()) {
5841             mLandscape.updateAppInfo(info);
5842             mPortrait.updateAppInfo(info);
5843         }
5844     }
5845 
5846     private Context getContextForResources(Context context) {
5847         if (mApplication != null) {
5848             if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
5849                     && context.getPackageName().equals(mApplication.packageName)) {
5850                 return context;
5851             }
5852             try {
5853                 return context.createApplicationContext(mApplication,
5854                         Context.CONTEXT_RESTRICTED);
5855             } catch (NameNotFoundException e) {
5856                 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found");
5857             }
5858         }
5859 
5860         return context;
5861     }
5862 
5863     /**
5864      * Object allowing the modification of a context to overload the system's dynamic colors.
5865      *
5866      * Only colors from {@link android.R.color#system_accent1_0} to
5867      * {@link android.R.color#system_neutral2_1000} can be overloaded.
5868      * @hide
5869      */
5870     public static final class ColorResources {
5871         // Set of valid colors resources.
5872         private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0;
5873         private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000;
5874         // Size, in bytes, of an entry in the array of colors in an ARSC file.
5875         private static final int ARSC_ENTRY_SIZE = 16;
5876 
5877         private ResourcesLoader mLoader;
5878 
5879         private ColorResources(ResourcesLoader loader) {
5880             mLoader = loader;
5881         }
5882 
5883         /**
5884          * Apply the color resources to the given context.
5885          *
5886          * No resource resolution must have be done on the context given to that method.
5887          */
5888         public void apply(Context context) {
5889             context.getResources().addLoaders(mLoader);
5890         }
5891 
5892         private static ByteArrayOutputStream readFileContent(InputStream input) throws IOException {
5893             ByteArrayOutputStream content = new ByteArrayOutputStream(2048);
5894             byte[] buffer = new byte[4096];
5895             while (input.available() > 0) {
5896                 int read = input.read(buffer);
5897                 content.write(buffer, 0, read);
5898             }
5899             return content;
5900         }
5901 
5902         /**
5903          * Creates the compiled resources content from the asset stored in the APK.
5904          *
5905          * The asset is a compiled resource with the correct resources name and correct ids, only
5906          * the values are incorrect. The last value is at the very end of the file. The resources
5907          * are in an array, the array's entries are 16 bytes each. We use this to work out the
5908          * location of all the positions of the various resources.
5909          */
5910         @Nullable
5911         private static byte[] createCompiledResourcesContent(Context context,
5912                 SparseIntArray colorResources) throws IOException {
5913             byte[] content;
5914             try (InputStream input = context.getResources().openRawResource(
5915                     com.android.internal.R.raw.remote_views_color_resources)) {
5916                 ByteArrayOutputStream rawContent = readFileContent(input);
5917                 content = rawContent.toByteArray();
5918             }
5919             int valuesOffset =
5920                     content.length - (LAST_RESOURCE_COLOR_ID & 0xffff) * ARSC_ENTRY_SIZE - 4;
5921             if (valuesOffset < 0) {
5922                 Log.e(LOG_TAG, "ARSC file for theme colors is invalid.");
5923                 return null;
5924             }
5925             for (int colorRes = FIRST_RESOURCE_COLOR_ID; colorRes <= LAST_RESOURCE_COLOR_ID;
5926                     colorRes++) {
5927                 // The last 2 bytes are the index in the color array.
5928                 int index = colorRes & 0xffff;
5929                 int offset = valuesOffset + index * ARSC_ENTRY_SIZE;
5930                 int value = colorResources.get(colorRes, context.getColor(colorRes));
5931                 // Write the 32 bit integer in little endian
5932                 for (int b = 0; b < 4; b++) {
5933                     content[offset + b] = (byte) (value & 0xff);
5934                     value >>= 8;
5935                 }
5936             }
5937             return content;
5938         }
5939 
5940         /**
5941          *  Adds a resource loader for theme colors to the given context.
5942          *
5943          * @param context Context of the view hosting the widget.
5944          * @param colorMapping Mapping of resources to color values.
5945          *
5946          * @hide
5947          */
5948         @Nullable
5949         public static ColorResources create(Context context, SparseIntArray colorMapping) {
5950             try {
5951                 byte[] contentBytes = createCompiledResourcesContent(context, colorMapping);
5952                 if (contentBytes == null) {
5953                     return null;
5954                 }
5955                 FileDescriptor arscFile = null;
5956                 try {
5957                     arscFile = Os.memfd_create("remote_views_theme_colors.arsc", 0 /* flags */);
5958                     // Note: This must not be closed through the OutputStream.
5959                     try (OutputStream pipeWriter = new FileOutputStream(arscFile)) {
5960                         pipeWriter.write(contentBytes);
5961 
5962                         try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) {
5963                             ResourcesLoader colorsLoader = new ResourcesLoader();
5964                             colorsLoader.addProvider(ResourcesProvider
5965                                     .loadFromTable(pfd, null /* assetsProvider */));
5966                             return new ColorResources(colorsLoader);
5967                         }
5968                     }
5969                 } finally {
5970                     if (arscFile != null) {
5971                         Os.close(arscFile);
5972                     }
5973                 }
5974             } catch (Exception ex) {
5975                 Log.e(LOG_TAG, "Failed to setup the context for theme colors", ex);
5976             }
5977             return null;
5978         }
5979     }
5980 
5981     /**
5982      * Returns the number of actions in this RemoteViews. Can be used as a sequence number.
5983      *
5984      * @hide
5985      */
5986     public int getSequenceNumber() {
5987         return (mActions == null) ? 0 : mActions.size();
5988     }
5989 
5990     /**
5991      * Used to restrict the views which can be inflated
5992      *
5993      * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
5994      * @deprecated Used by system to enforce safe inflation of {@link RemoteViews}. Apps should not
5995      * override this method. Changing of this method will NOT affect the process where RemoteViews
5996      * is rendered.
5997      */
5998     @Deprecated
5999     public boolean onLoadClass(Class clazz) {
6000         return clazz.isAnnotationPresent(RemoteView.class);
6001     }
6002 
6003     public int describeContents() {
6004         return 0;
6005     }
6006 
6007     public void writeToParcel(Parcel dest, int flags) {
6008         if (!hasMultipleLayouts()) {
6009             dest.writeInt(MODE_NORMAL);
6010             // We only write the bitmap cache if we are the root RemoteViews, as this cache
6011             // is shared by all children.
6012             if (mIsRoot) {
6013                 mBitmapCache.writeBitmapsToParcel(dest, flags);
6014             }
6015             if (!mIsRoot && (flags & PARCELABLE_ELIDE_DUPLICATES) != 0) {
6016                 dest.writeInt(0);
6017             } else {
6018                 dest.writeInt(1);
6019                 mApplication.writeToParcel(dest, flags);
6020             }
6021             if (mIsRoot || mIdealSize == null) {
6022                 dest.writeInt(0);
6023             } else {
6024                 dest.writeInt(1);
6025                 mIdealSize.writeToParcel(dest, flags);
6026             }
6027             dest.writeInt(mLayoutId);
6028             dest.writeInt(mViewId);
6029             dest.writeInt(mLightBackgroundLayoutId);
6030             writeActionsToParcel(dest);
6031         } else if (hasSizedRemoteViews()) {
6032             dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS);
6033             if (mIsRoot) {
6034                 mBitmapCache.writeBitmapsToParcel(dest, flags);
6035             }
6036             int childFlags = flags;
6037             dest.writeInt(mSizedRemoteViews.size());
6038             for (RemoteViews view : mSizedRemoteViews) {
6039                 view.writeToParcel(dest, childFlags);
6040                 childFlags |= PARCELABLE_ELIDE_DUPLICATES;
6041             }
6042         } else {
6043             dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
6044             // We only write the bitmap cache if we are the root RemoteViews, as this cache
6045             // is shared by all children.
6046             if (mIsRoot) {
6047                 mBitmapCache.writeBitmapsToParcel(dest, flags);
6048             }
6049             mLandscape.writeToParcel(dest, flags);
6050             // Both RemoteViews already share the same package and user
6051             mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES);
6052         }
6053         dest.writeInt(mApplyFlags);
6054         dest.writeLong(mProviderInstanceId);
6055     }
6056 
6057     private void writeActionsToParcel(Parcel parcel) {
6058         int count;
6059         if (mActions != null) {
6060             count = mActions.size();
6061         } else {
6062             count = 0;
6063         }
6064         parcel.writeInt(count);
6065         for (int i = 0; i < count; i++) {
6066             Action a = mActions.get(i);
6067             parcel.writeInt(a.getActionTag());
6068             a.writeToParcel(parcel, a.hasSameAppInfo(mApplication)
6069                     ? PARCELABLE_ELIDE_DUPLICATES : 0);
6070         }
6071     }
6072 
6073     @Nullable
6074     private static ApplicationInfo getApplicationInfo(@Nullable String packageName, int userId) {
6075         if (packageName == null) {
6076             return null;
6077         }
6078 
6079         // Get the application for the passed in package and user.
6080         Application application = ActivityThread.currentApplication();
6081         if (application == null) {
6082             throw new IllegalStateException("Cannot create remote views out of an aplication.");
6083         }
6084 
6085         ApplicationInfo applicationInfo = application.getApplicationInfo();
6086         if (UserHandle.getUserId(applicationInfo.uid) != userId
6087                 || !applicationInfo.packageName.equals(packageName)) {
6088             try {
6089                 Context context = application.getBaseContext().createPackageContextAsUser(
6090                         packageName, 0, new UserHandle(userId));
6091                 applicationInfo = context.getApplicationInfo();
6092             } catch (NameNotFoundException nnfe) {
6093                 throw new IllegalArgumentException("No such package " + packageName);
6094             }
6095         }
6096 
6097         return applicationInfo;
6098     }
6099 
6100     /**
6101      * Returns true if the {@link #mApplication} is same as the provided info.
6102      *
6103      * @hide
6104      */
6105     public boolean hasSameAppInfo(ApplicationInfo info) {
6106         return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid;
6107     }
6108 
6109     /**
6110      * Parcelable.Creator that instantiates RemoteViews objects
6111      */
6112     public static final @android.annotation.NonNull Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
6113         public RemoteViews createFromParcel(Parcel parcel) {
6114             return new RemoteViews(parcel);
6115         }
6116 
6117         public RemoteViews[] newArray(int size) {
6118             return new RemoteViews[size];
6119         }
6120     };
6121 
6122     /**
6123      * A representation of the view hierarchy. Only views which have a valid ID are added
6124      * and can be searched.
6125      */
6126     private static class ViewTree {
6127         private static final int INSERT_AT_END_INDEX = -1;
6128         private View mRoot;
6129         private ArrayList<ViewTree> mChildren;
6130 
6131         private ViewTree(View root) {
6132             mRoot = root;
6133         }
6134 
6135         public void createTree() {
6136             if (mChildren != null) {
6137                 return;
6138             }
6139 
6140             mChildren = new ArrayList<>();
6141             if (mRoot instanceof ViewGroup) {
6142                 ViewGroup vg = (ViewGroup) mRoot;
6143                 int count = vg.getChildCount();
6144                 for (int i = 0; i < count; i++) {
6145                     addViewChild(vg.getChildAt(i));
6146                 }
6147             }
6148         }
6149 
6150         @Nullable
6151         public ViewTree findViewTreeById(@IdRes int id) {
6152             if (mRoot.getId() == id) {
6153                 return this;
6154             }
6155             if (mChildren == null) {
6156                 return null;
6157             }
6158             for (ViewTree tree : mChildren) {
6159                 ViewTree result = tree.findViewTreeById(id);
6160                 if (result != null) {
6161                     return result;
6162                 }
6163             }
6164             return null;
6165         }
6166 
6167         @Nullable
6168         public ViewTree findViewTreeParentOf(ViewTree child) {
6169             if (mChildren == null) {
6170                 return null;
6171             }
6172             for (ViewTree tree : mChildren) {
6173                 if (tree == child) {
6174                     return this;
6175                 }
6176                 ViewTree result = tree.findViewTreeParentOf(child);
6177                 if (result != null) {
6178                     return result;
6179                 }
6180             }
6181             return null;
6182         }
6183 
6184         public void replaceView(View v) {
6185             mRoot = v;
6186             mChildren = null;
6187             createTree();
6188         }
6189 
6190         @Nullable
6191         public <T extends View> T findViewById(@IdRes int id) {
6192             if (mChildren == null) {
6193                 return mRoot.findViewById(id);
6194             }
6195             ViewTree tree = findViewTreeById(id);
6196             return tree == null ? null : (T) tree.mRoot;
6197         }
6198 
6199         public void addChild(ViewTree child) {
6200             addChild(child, INSERT_AT_END_INDEX);
6201         }
6202 
6203         /**
6204          * Adds the given {@link ViewTree} as a child at the given index.
6205          *
6206          * @param index The position at which to add the child or -1 to add last.
6207          */
6208         public void addChild(ViewTree child, int index) {
6209             if (mChildren == null) {
6210                 mChildren = new ArrayList<>();
6211             }
6212             child.createTree();
6213 
6214             if (index == INSERT_AT_END_INDEX) {
6215                 mChildren.add(child);
6216                 return;
6217             }
6218 
6219             mChildren.add(index, child);
6220         }
6221 
6222         public void removeChildren(int start, int count) {
6223             if (mChildren != null) {
6224                 for (int i = 0; i < count; i++) {
6225                     mChildren.remove(start);
6226                 }
6227             }
6228         }
6229 
6230         private void addViewChild(View v) {
6231             // ViewTree only contains Views which can be found using findViewById.
6232             // If isRootNamespace is true, this view is skipped.
6233             // @see ViewGroup#findViewTraversal(int)
6234             if (v.isRootNamespace()) {
6235                 return;
6236             }
6237             final ViewTree target;
6238 
6239             // If the view has a valid id, i.e., if can be found using findViewById, add it to the
6240             // tree, otherwise skip this view and add its children instead.
6241             if (v.getId() != 0) {
6242                 ViewTree tree = new ViewTree(v);
6243                 mChildren.add(tree);
6244                 target = tree;
6245             } else {
6246                 target = this;
6247             }
6248 
6249             if (v instanceof ViewGroup) {
6250                 if (target.mChildren == null) {
6251                     target.mChildren = new ArrayList<>();
6252                     ViewGroup vg = (ViewGroup) v;
6253                     int count = vg.getChildCount();
6254                     for (int i = 0; i < count; i++) {
6255                         target.addViewChild(vg.getChildAt(i));
6256                     }
6257                 }
6258             }
6259         }
6260 
6261         /** Find the first child for which the condition is true and return its index. */
6262         public int findChildIndex(Predicate<View> condition) {
6263             return findChildIndex(0, condition);
6264         }
6265 
6266         /**
6267          * Find the first child, starting at {@code startIndex}, for which the condition is true and
6268          * return its index.
6269          */
6270         public int findChildIndex(int startIndex, Predicate<View> condition) {
6271             if (mChildren == null) {
6272                 return -1;
6273             }
6274 
6275             for (int i = startIndex; i < mChildren.size(); i++) {
6276                 if (condition.test(mChildren.get(i).mRoot)) {
6277                     return i;
6278                 }
6279             }
6280             return -1;
6281         }
6282     }
6283 
6284     /**
6285      * Class representing a response to an action performed on any element of a RemoteViews.
6286      */
6287     public static class RemoteResponse {
6288 
6289         /** @hide **/
6290         @IntDef(prefix = "INTERACTION_TYPE_", value = {
6291                 INTERACTION_TYPE_CLICK,
6292                 INTERACTION_TYPE_CHECKED_CHANGE,
6293         })
6294         @Retention(RetentionPolicy.SOURCE)
6295         @interface InteractionType {}
6296         /** @hide */
6297         public static final int INTERACTION_TYPE_CLICK = 0;
6298         /** @hide */
6299         public static final int INTERACTION_TYPE_CHECKED_CHANGE = 1;
6300 
6301         private PendingIntent mPendingIntent;
6302         private Intent mFillIntent;
6303 
6304         private int mInteractionType = INTERACTION_TYPE_CLICK;
6305         private IntArray mViewIds;
6306         private ArrayList<String> mElementNames;
6307 
6308         /**
6309          * Creates a response which sends a pending intent as part of the response. The source
6310          * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the
6311          * target view in screen space.
6312          * Note that any activity options associated with the mPendingIntent may get overridden
6313          * before starting the intent.
6314          *
6315          * @param pendingIntent The {@link PendingIntent} to send as part of the response
6316          */
6317         @NonNull
6318         public static RemoteResponse fromPendingIntent(@NonNull PendingIntent pendingIntent) {
6319             RemoteResponse response = new RemoteResponse();
6320             response.mPendingIntent = pendingIntent;
6321             return response;
6322         }
6323 
6324         /**
6325          * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is
6326          * very costly to set PendingIntents on the individual items, and is hence not recommended.
6327          * Instead a single PendingIntent template can be set on the collection, see {@link
6328          * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
6329          * action of a given item can be distinguished by setting a fillInIntent on that item. The
6330          * fillInIntent is then combined with the PendingIntent template in order to determine the
6331          * final intent which will be executed when the item is clicked. This works as follows: any
6332          * fields which are left blank in the PendingIntent template, but are provided by the
6333          * fillInIntent will be overwritten, and the resulting PendingIntent will be used. The rest
6334          * of the PendingIntent template will then be filled in with the associated fields that are
6335          * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
6336          * Creates a response which sends a pending intent as part of the response. The source
6337          * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the
6338          * target view in screen space.
6339          * Note that any activity options associated with the mPendingIntent may get overridden
6340          * before starting the intent.
6341          *
6342          * @param fillIntent The intent which will be combined with the parent's PendingIntent in
6343          *                   order to determine the behavior of the response
6344          * @see RemoteViews#setPendingIntentTemplate(int, PendingIntent)
6345          * @see RemoteViews#setOnClickFillInIntent(int, Intent)
6346          */
6347         @NonNull
6348         public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) {
6349             RemoteResponse response = new RemoteResponse();
6350             response.mFillIntent = fillIntent;
6351             return response;
6352         }
6353 
6354         /**
6355          * Adds a shared element to be transferred as part of the transition between Activities
6356          * using cross-Activity scene animations. The position of the first element will be used as
6357          * the epicenter for the exit Transition. The position of the associated shared element in
6358          * the launched Activity will be the epicenter of its entering Transition.
6359          *
6360          * @param viewId            The id of the view to be shared as part of the transition
6361          * @param sharedElementName The shared element name for this view
6362          * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[])
6363          */
6364         @NonNull
6365         public RemoteResponse addSharedElement(@IdRes int viewId,
6366                 @NonNull String sharedElementName) {
6367             if (mViewIds == null) {
6368                 mViewIds = new IntArray();
6369                 mElementNames = new ArrayList<>();
6370             }
6371             mViewIds.add(viewId);
6372             mElementNames.add(sharedElementName);
6373             return this;
6374         }
6375 
6376         /**
6377          * Sets the interaction type for which this RemoteResponse responds.
6378          *
6379          * @param type the type of interaction for which this is a response, such as clicking or
6380          *             checked state changing
6381          *
6382          * @hide
6383          */
6384         @NonNull
6385         public RemoteResponse setInteractionType(@InteractionType int type) {
6386             mInteractionType = type;
6387             return this;
6388         }
6389 
6390         private void writeToParcel(Parcel dest, int flags) {
6391             PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest);
6392             if (mPendingIntent == null) {
6393                 // Only write the intent if pending intent is null
6394                 dest.writeTypedObject(mFillIntent, flags);
6395             }
6396             dest.writeInt(mInteractionType);
6397             dest.writeIntArray(mViewIds == null ? null : mViewIds.toArray());
6398             dest.writeStringList(mElementNames);
6399         }
6400 
6401         private void readFromParcel(Parcel parcel) {
6402             mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
6403             if (mPendingIntent == null) {
6404                 mFillIntent = parcel.readTypedObject(Intent.CREATOR);
6405             }
6406             mInteractionType = parcel.readInt();
6407             int[] viewIds = parcel.createIntArray();
6408             mViewIds = viewIds == null ? null : IntArray.wrap(viewIds);
6409             mElementNames = parcel.createStringArrayList();
6410         }
6411 
6412         private void handleViewInteraction(
6413                 View v,
6414                 InteractionHandler handler) {
6415             final PendingIntent pi;
6416             if (mPendingIntent != null) {
6417                 pi = mPendingIntent;
6418             } else if (mFillIntent != null) {
6419                 AdapterView<?> ancestor = getAdapterViewAncestor(v);
6420                 if (ancestor == null) {
6421                     Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent");
6422                     return;
6423                 }
6424 
6425                 // Ensure that a template pending intent has been set on the ancestor
6426                 if (!(ancestor.getTag() instanceof PendingIntent)) {
6427                     Log.e(LOG_TAG, "Attempting setOnClickFillInIntent or "
6428                             + "setOnCheckedChangeFillInIntent without calling "
6429                             + "setPendingIntentTemplate on parent.");
6430                     return;
6431                 }
6432 
6433                 pi = (PendingIntent) ancestor.getTag();
6434             } else {
6435                 Log.e(LOG_TAG, "Response has neither pendingIntent nor fillInIntent");
6436                 return;
6437             }
6438 
6439             handler.onInteraction(v, pi, this);
6440         }
6441 
6442         /**
6443          * Returns the closest ancestor of the view that is an AdapterView or null if none could be
6444          * found.
6445          */
6446         @Nullable
6447         private static AdapterView<?> getAdapterViewAncestor(@Nullable View view) {
6448             if (view == null) return null;
6449 
6450             View parent = (View) view.getParent();
6451             // Break the for loop on the first encounter of:
6452             //    1) an AdapterView,
6453             //    2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or
6454             //    3) a null parent.
6455             // 2) and 3) are unexpected and catch the case where a child is not
6456             // correctly parented in an AdapterView.
6457             while (parent != null && !(parent instanceof AdapterView<?>)
6458                     && !((parent instanceof AppWidgetHostView)
6459                     && !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) {
6460                 parent = (View) parent.getParent();
6461             }
6462 
6463             return parent instanceof AdapterView<?> ? (AdapterView<?>) parent : null;
6464         }
6465 
6466         /** @hide */
6467         public Pair<Intent, ActivityOptions> getLaunchOptions(View view) {
6468             Intent intent = mPendingIntent != null ? new Intent() : new Intent(mFillIntent);
6469             intent.setSourceBounds(getSourceBounds(view));
6470 
6471             if (view instanceof CompoundButton
6472                     && mInteractionType == INTERACTION_TYPE_CHECKED_CHANGE) {
6473                 intent.putExtra(EXTRA_CHECKED, ((CompoundButton) view).isChecked());
6474             }
6475 
6476             ActivityOptions opts = null;
6477 
6478             Context context = view.getContext();
6479             if (context.getResources().getBoolean(
6480                     com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) {
6481                 TypedArray windowStyle = context.getTheme().obtainStyledAttributes(
6482                         com.android.internal.R.styleable.Window);
6483                 int windowAnimations = windowStyle.getResourceId(
6484                         com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
6485                 TypedArray windowAnimationStyle = context.obtainStyledAttributes(
6486                         windowAnimations, com.android.internal.R.styleable.WindowAnimation);
6487                 int enterAnimationId = windowAnimationStyle.getResourceId(com.android.internal.R
6488                         .styleable.WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0);
6489                 windowStyle.recycle();
6490                 windowAnimationStyle.recycle();
6491 
6492                 if (enterAnimationId != 0) {
6493                     opts = ActivityOptions.makeCustomAnimation(context,
6494                             enterAnimationId, 0);
6495                     opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
6496                 }
6497             }
6498 
6499             if (opts == null && mViewIds != null && mElementNames != null) {
6500                 View parent = (View) view.getParent();
6501                 while (parent != null && !(parent instanceof AppWidgetHostView)) {
6502                     parent = (View) parent.getParent();
6503                 }
6504                 if (parent instanceof AppWidgetHostView) {
6505                     opts = ((AppWidgetHostView) parent).createSharedElementActivityOptions(
6506                             mViewIds.toArray(),
6507                             mElementNames.toArray(new String[mElementNames.size()]), intent);
6508                 }
6509             }
6510 
6511             if (opts == null) {
6512                 opts = ActivityOptions.makeBasic();
6513                 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
6514             }
6515             return Pair.create(intent, opts);
6516         }
6517     }
6518 
6519     /** @hide */
6520     public static boolean startPendingIntent(View view, PendingIntent pendingIntent,
6521             Pair<Intent, ActivityOptions> options) {
6522         try {
6523             // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
6524             Context context = view.getContext();
6525             // The NEW_TASK flags are applied through the activity options and not as a part of
6526             // the call to startIntentSender() to ensure that they are consistently applied to
6527             // both mutable and immutable PendingIntents.
6528             context.startIntentSender(
6529                     pendingIntent.getIntentSender(), options.first,
6530                     0, 0, 0, options.second.toBundle());
6531         } catch (IntentSender.SendIntentException e) {
6532             Log.e(LOG_TAG, "Cannot send pending intent: ", e);
6533             return false;
6534         } catch (Exception e) {
6535             Log.e(LOG_TAG, "Cannot send pending intent due to unknown exception: ", e);
6536             return false;
6537         }
6538         return true;
6539     }
6540 
6541     /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */
6542     public static final class RemoteCollectionItems implements Parcelable {
6543         private final long[] mIds;
6544         private final RemoteViews[] mViews;
6545         private final boolean mHasStableIds;
6546         private final int mViewTypeCount;
6547 
6548         RemoteCollectionItems(
6549                 long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) {
6550             mIds = ids;
6551             mViews = views;
6552             mHasStableIds = hasStableIds;
6553             mViewTypeCount = viewTypeCount;
6554             if (ids.length != views.length) {
6555                 throw new IllegalArgumentException(
6556                         "RemoteCollectionItems has different number of ids and views");
6557             }
6558             if (viewTypeCount < 1) {
6559                 throw new IllegalArgumentException("View type count must be >= 1");
6560             }
6561             int layoutIdCount = (int) Arrays.stream(views)
6562                     .mapToInt(RemoteViews::getLayoutId)
6563                     .distinct()
6564                     .count();
6565             if (layoutIdCount > viewTypeCount) {
6566                 throw new IllegalArgumentException(
6567                         "View type count is set to " + viewTypeCount + ", but the collection "
6568                                 + "contains " + layoutIdCount + " different layout ids");
6569             }
6570         }
6571 
6572         RemoteCollectionItems(Parcel in) {
6573             int length = in.readInt();
6574             mIds = new long[length];
6575             in.readLongArray(mIds);
6576             mViews = new RemoteViews[length];
6577             in.readTypedArray(mViews, RemoteViews.CREATOR);
6578             mHasStableIds = in.readBoolean();
6579             mViewTypeCount = in.readInt();
6580         }
6581 
6582         @Override
6583         public int describeContents() {
6584             return 0;
6585         }
6586 
6587         @Override
6588         public void writeToParcel(@NonNull Parcel dest, int flags) {
6589             dest.writeInt(mIds.length);
6590             dest.writeLongArray(mIds);
6591             dest.writeTypedArray(mViews, flags);
6592             dest.writeBoolean(mHasStableIds);
6593             dest.writeInt(mViewTypeCount);
6594         }
6595 
6596         /**
6597          * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id
6598          * should be considered meaningful across collection updates.
6599          *
6600          * @return Id for the position.
6601          */
6602         public long getItemId(int position) {
6603             return mIds[position];
6604         }
6605 
6606         /**
6607          * Returns the {@link RemoteViews} to display at {@code position}.
6608          *
6609          * @return RemoteViews for the position.
6610          */
6611         @NonNull
6612         public RemoteViews getItemView(int position) {
6613             return mViews[position];
6614         }
6615 
6616         /**
6617          * Returns the number of elements in the collection.
6618          *
6619          * @return Count of items.
6620          */
6621         public int getItemCount() {
6622             return mIds.length;
6623         }
6624 
6625         /**
6626          * Returns the view type count for the collection when used in an adapter
6627          *
6628          * @return Count of view types for the collection when used in an adapter.
6629          * @see android.widget.Adapter#getViewTypeCount()
6630          */
6631         public int getViewTypeCount() {
6632             return mViewTypeCount;
6633         }
6634 
6635         /**
6636          * Indicates whether the item ids are stable across changes to the underlying data.
6637          *
6638          * @return True if the same id always refers to the same object.
6639          * @see android.widget.Adapter#hasStableIds()
6640          */
6641         public boolean hasStableIds() {
6642             return mHasStableIds;
6643         }
6644 
6645         @NonNull
6646         public static final Creator<RemoteCollectionItems> CREATOR =
6647                 new Creator<RemoteCollectionItems>() {
6648             @NonNull
6649             @Override
6650             public RemoteCollectionItems createFromParcel(@NonNull Parcel source) {
6651                 return new RemoteCollectionItems(source);
6652             }
6653 
6654             @NonNull
6655             @Override
6656             public RemoteCollectionItems[] newArray(int size) {
6657                 return new RemoteCollectionItems[size];
6658             }
6659         };
6660 
6661         /** Builder class for {@link RemoteCollectionItems} objects.*/
6662         public static final class Builder {
6663             private final LongArray mIds = new LongArray();
6664             private final List<RemoteViews> mViews = new ArrayList<>();
6665             private boolean mHasStableIds;
6666             private int mViewTypeCount;
6667 
6668             /**
6669              * Adds a {@link RemoteViews} to the collection.
6670              *
6671              * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to
6672              *           indicate that ids are stable across changes to the collection.
6673              * @param view RemoteViews to display for the row.
6674              */
6675             @NonNull
6676             // Covered by getItemId, getItemView, getItemCount.
6677             @SuppressLint("MissingGetterMatchingBuilder")
6678             public Builder addItem(long id, @NonNull RemoteViews view) {
6679                 if (view == null) throw new NullPointerException();
6680                 if (view.hasMultipleLayouts()) {
6681                     throw new IllegalArgumentException(
6682                             "RemoteViews used in a RemoteCollectionItems cannot specify separate "
6683                                     + "layouts for orientations or sizes.");
6684                 }
6685                 mIds.add(id);
6686                 mViews.add(view);
6687                 return this;
6688             }
6689 
6690             /**
6691              * Sets whether the item ids are stable across changes to the underlying data.
6692              *
6693              * @see android.widget.Adapter#hasStableIds()
6694              */
6695             @NonNull
6696             public Builder setHasStableIds(boolean hasStableIds) {
6697                 mHasStableIds = hasStableIds;
6698                 return this;
6699             }
6700 
6701             /**
6702              * Sets the view type count for the collection when used in an adapter. This can be set
6703              * to the maximum number of different layout ids that will be used by RemoteViews in
6704              * this collection.
6705              *
6706              * If this value is not set, then a value will be inferred from the provided items. As
6707              * a result, the adapter may need to be recreated when the list is updated with
6708              * previously unseen RemoteViews layouts for new items.
6709              *
6710              * @see android.widget.Adapter#getViewTypeCount()
6711              */
6712             @NonNull
6713             public Builder setViewTypeCount(int viewTypeCount) {
6714                 mViewTypeCount = viewTypeCount;
6715                 return this;
6716             }
6717 
6718             /** Creates the {@link RemoteCollectionItems} defined by this builder. */
6719             @NonNull
6720             public RemoteCollectionItems build() {
6721                 if (mViewTypeCount < 1) {
6722                     // If a view type count wasn't specified, set it to be the number of distinct
6723                     // layout ids used in the items.
6724                     mViewTypeCount = (int) mViews.stream()
6725                             .mapToInt(RemoteViews::getLayoutId)
6726                             .distinct()
6727                             .count();
6728                 }
6729                 return new RemoteCollectionItems(
6730                         mIds.toArray(),
6731                         mViews.toArray(new RemoteViews[0]),
6732                         mHasStableIds,
6733                         Math.max(mViewTypeCount, 1));
6734             }
6735         }
6736     }
6737 
6738     /**
6739      * Get the ID of the top-level view of the XML layout, if set using
6740      * {@link RemoteViews#RemoteViews(String, int, int)}.
6741      */
6742     public @IdRes int getViewId() {
6743         return mViewId;
6744     }
6745 
6746     /**
6747      * Set the provider instance ID.
6748      *
6749      * This should only be used by {@link com.android.server.appwidget.AppWidgetService}.
6750      * @hide
6751      */
6752     public void setProviderInstanceId(long id) {
6753         mProviderInstanceId = id;
6754     }
6755 
6756     /**
6757      * Get the provider instance id.
6758      *
6759      * This should uniquely identifies {@link RemoteViews} coming from a given App Widget
6760      * Provider. This changes each time the App Widget provider update the {@link RemoteViews} of
6761      * its widget. Returns -1 if the {@link RemoteViews} doesn't come from an App Widget provider.
6762      * @hide
6763      */
6764     public long getProviderInstanceId() {
6765         return mProviderInstanceId;
6766     }
6767 
6768     /**
6769      * Identify the child of this {@link RemoteViews}, or 0 if this is not a child.
6770      *
6771      * The returned value is always a small integer, currently between 0 and 17.
6772      */
6773     private int getChildId(@NonNull RemoteViews child) {
6774         if (child == this) {
6775             return 0;
6776         }
6777         if (hasSizedRemoteViews()) {
6778             for (int i = 0; i < mSizedRemoteViews.size(); i++) {
6779                 if (mSizedRemoteViews.get(i) == child) {
6780                     return i + 1;
6781                 }
6782             }
6783         }
6784         if (hasLandscapeAndPortraitLayouts()) {
6785             if (mLandscape == child) {
6786                 return 1;
6787             } else if (mPortrait == child) {
6788                 return 2;
6789             }
6790         }
6791         // This is not a child of this RemoteViews.
6792         return 0;
6793     }
6794 
6795     /**
6796      * Identify uniquely this RemoteViews, or returns -1 if not possible.
6797      *
6798      * @param parent If the {@link RemoteViews} is not a root {@link RemoteViews}, this should be
6799      *              the parent that contains it.
6800      *
6801      * @hide
6802      */
6803     public long computeUniqueId(@Nullable RemoteViews parent) {
6804         if (mIsRoot) {
6805             long viewId = getProviderInstanceId();
6806             if (viewId != -1) {
6807                 viewId <<= 8;
6808             }
6809             return viewId;
6810         }
6811         if (parent == null) {
6812             return -1;
6813         }
6814         long viewId = parent.getProviderInstanceId();
6815         if (viewId == -1) {
6816             return -1;
6817         }
6818         int childId = parent.getChildId(this);
6819         if (childId == -1) {
6820             return -1;
6821         }
6822         viewId <<= 8;
6823         viewId |= childId;
6824         return viewId;
6825     }
6826 }
6827