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