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