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