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