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