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