• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2007 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.widget;
18 
19 import android.app.ActivityOptions;
20 import android.app.PendingIntent;
21 import android.appwidget.AppWidgetHostView;
22 import android.content.Context;
23 import android.content.Intent;
24 import android.content.IntentSender;
25 import android.content.pm.ApplicationInfo;
26 import android.content.pm.PackageManager.NameNotFoundException;
27 import android.content.res.Configuration;
28 import android.graphics.Bitmap;
29 import android.graphics.PorterDuff;
30 import android.graphics.Rect;
31 import android.graphics.drawable.Drawable;
32 import android.net.Uri;
33 import android.os.Build;
34 import android.os.Bundle;
35 import android.os.Parcel;
36 import android.os.Parcelable;
37 import android.os.StrictMode;
38 import android.os.UserHandle;
39 import android.text.TextUtils;
40 import android.util.ArrayMap;
41 import android.util.Log;
42 import android.view.LayoutInflater;
43 import android.view.LayoutInflater.Filter;
44 import android.view.RemotableViewMethod;
45 import android.view.View;
46 import android.view.View.OnClickListener;
47 import android.view.ViewGroup;
48 import android.widget.AdapterView.OnItemClickListener;
49 import libcore.util.Objects;
50 
51 import java.lang.annotation.ElementType;
52 import java.lang.annotation.Retention;
53 import java.lang.annotation.RetentionPolicy;
54 import java.lang.annotation.Target;
55 import java.lang.reflect.Method;
56 import java.util.ArrayList;
57 import java.util.HashMap;
58 
59 /**
60  * A class that describes a view hierarchy that can be displayed in
61  * another process. The hierarchy is inflated from a layout resource
62  * file, and this class provides some basic operations for modifying
63  * the content of the inflated hierarchy.
64  */
65 public class RemoteViews implements Parcelable, Filter {
66 
67     private static final String LOG_TAG = "RemoteViews";
68 
69     /**
70      * The intent extra that contains the appWidgetId.
71      * @hide
72      */
73     static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
74 
75     /**
76      * User that these views should be applied as. Requires
77      * {@link android.Manifest.permission#INTERACT_ACROSS_USERS_FULL} when
78      * crossing user boundaries.
79      */
80     private UserHandle mUser = android.os.Process.myUserHandle();
81 
82     /**
83      * The package name of the package containing the layout
84      * resource. (Added to the parcel)
85      */
86     private final String mPackage;
87 
88     /**
89      * The resource ID of the layout file. (Added to the parcel)
90      */
91     private final int mLayoutId;
92 
93     /**
94      * An array of actions to perform on the view tree once it has been
95      * inflated
96      */
97     private ArrayList<Action> mActions;
98 
99     /**
100      * A class to keep track of memory usage by this RemoteViews
101      */
102     private MemoryUsageCounter mMemoryUsageCounter;
103 
104     /**
105      * Maps bitmaps to unique indicies to avoid Bitmap duplication.
106      */
107     private BitmapCache mBitmapCache;
108 
109     /**
110      * Indicates whether or not this RemoteViews object is contained as a child of any other
111      * RemoteViews.
112      */
113     private boolean mIsRoot = true;
114 
115     /**
116      * Constants to whether or not this RemoteViews is composed of a landscape and portrait
117      * RemoteViews.
118      */
119     private static final int MODE_NORMAL = 0;
120     private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
121 
122     /**
123      * Used in conjunction with the special constructor
124      * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait
125      * RemoteViews.
126      */
127     private RemoteViews mLandscape = null;
128     private RemoteViews mPortrait = null;
129 
130     /**
131      * This flag indicates whether this RemoteViews object is being created from a
132      * RemoteViewsService for use as a child of a widget collection. This flag is used
133      * to determine whether or not certain features are available, in particular,
134      * setting on click extras and setting on click pending intents. The former is enabled,
135      * and the latter disabled when this flag is true.
136      */
137     private boolean mIsWidgetCollectionChild = false;
138 
139     private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler();
140 
141     private static final Object[] sMethodsLock = new Object[0];
142     private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods =
143             new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>();
144     private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() {
145         @Override
146         protected Object[] initialValue() {
147             return new Object[1];
148         }
149     };
150 
151     /**
152      * Handle with care!
153      */
154     static class MutablePair<F, S> {
155         F first;
156         S second;
157 
MutablePair(F first, S second)158         MutablePair(F first, S second) {
159             this.first = first;
160             this.second = second;
161         }
162 
163         @Override
equals(Object o)164         public boolean equals(Object o) {
165             if (!(o instanceof MutablePair)) {
166                 return false;
167             }
168             MutablePair<?, ?> p = (MutablePair<?, ?>) o;
169             return Objects.equal(p.first, first) && Objects.equal(p.second, second);
170         }
171 
172         @Override
hashCode()173         public int hashCode() {
174             return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
175         }
176     }
177 
178     /**
179      * This pair is used to perform lookups in sMethods without causing allocations.
180      */
181     private final MutablePair<String, Class<?>> mPair =
182             new MutablePair<String, Class<?>>(null, null);
183 
184     /**
185      * This annotation indicates that a subclass of View is alllowed to be used
186      * with the {@link RemoteViews} mechanism.
187      */
188     @Target({ ElementType.TYPE })
189     @Retention(RetentionPolicy.RUNTIME)
190     public @interface RemoteView {
191     }
192 
193     /**
194      * Exception to send when something goes wrong executing an action
195      *
196      */
197     public static class ActionException extends RuntimeException {
ActionException(Exception ex)198         public ActionException(Exception ex) {
199             super(ex);
200         }
ActionException(String message)201         public ActionException(String message) {
202             super(message);
203         }
204     }
205 
206     /** @hide */
207     public static class OnClickHandler {
onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent)208         public boolean onClickHandler(View view, PendingIntent pendingIntent,
209                 Intent fillInIntent) {
210             try {
211                 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
212                 Context context = view.getContext();
213                 ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view,
214                         0, 0,
215                         view.getMeasuredWidth(), view.getMeasuredHeight());
216                 context.startIntentSender(
217                         pendingIntent.getIntentSender(), fillInIntent,
218                         Intent.FLAG_ACTIVITY_NEW_TASK,
219                         Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle());
220             } catch (IntentSender.SendIntentException e) {
221                 android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
222                 return false;
223             } catch (Exception e) {
224                 android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " +
225                         "unknown exception: ", e);
226                 return false;
227             }
228             return true;
229         }
230     }
231 
232     /**
233      * Base class for all actions that can be performed on an
234      * inflated view.
235      *
236      *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
237      */
238     private abstract static class Action implements Parcelable {
apply(View root, ViewGroup rootParent, OnClickHandler handler)239         public abstract void apply(View root, ViewGroup rootParent,
240                 OnClickHandler handler) throws ActionException;
241 
242         public static final int MERGE_REPLACE = 0;
243         public static final int MERGE_APPEND = 1;
244         public static final int MERGE_IGNORE = 2;
245 
describeContents()246         public int describeContents() {
247             return 0;
248         }
249 
250         /**
251          * Overridden by each class to report on it's own memory usage
252          */
updateMemoryUsageEstimate(MemoryUsageCounter counter)253         public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
254             // We currently only calculate Bitmap memory usage, so by default,
255             // don't do anything here
256         }
257 
setBitmapCache(BitmapCache bitmapCache)258         public void setBitmapCache(BitmapCache bitmapCache) {
259             // Do nothing
260         }
261 
mergeBehavior()262         public int mergeBehavior() {
263             return MERGE_REPLACE;
264         }
265 
getActionName()266         public abstract String getActionName();
267 
getUniqueKey()268         public String getUniqueKey() {
269             return (getActionName() + viewId);
270         }
271 
272         int viewId;
273     }
274 
275     /**
276      * Merges the passed RemoteViews actions with this RemoteViews actions according to
277      * action-specific merge rules.
278      *
279      * @param newRv
280      *
281      * @hide
282      */
mergeRemoteViews(RemoteViews newRv)283     public void mergeRemoteViews(RemoteViews newRv) {
284         if (newRv == null) return;
285         // We first copy the new RemoteViews, as the process of merging modifies the way the actions
286         // reference the bitmap cache. We don't want to modify the object as it may need to
287         // be merged and applied multiple times.
288         RemoteViews copy = newRv.clone();
289 
290         HashMap<String, Action> map = new HashMap<String, Action>();
291         if (mActions == null) {
292             mActions = new ArrayList<Action>();
293         }
294 
295         int count = mActions.size();
296         for (int i = 0; i < count; i++) {
297             Action a = mActions.get(i);
298             map.put(a.getUniqueKey(), a);
299         }
300 
301         ArrayList<Action> newActions = copy.mActions;
302         if (newActions == null) return;
303         count = newActions.size();
304         for (int i = 0; i < count; i++) {
305             Action a = newActions.get(i);
306             String key = newActions.get(i).getUniqueKey();
307             int mergeBehavior = newActions.get(i).mergeBehavior();
308             if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) {
309                 mActions.remove(map.get(key));
310                 map.remove(key);
311             }
312 
313             // If the merge behavior is ignore, we don't bother keeping the extra action
314             if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) {
315                 mActions.add(a);
316             }
317         }
318 
319         // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache
320         mBitmapCache = new BitmapCache();
321         setBitmapCache(mBitmapCache);
322     }
323 
324     private class SetEmptyView extends Action {
325         int viewId;
326         int emptyViewId;
327 
328         public final static int TAG = 6;
329 
SetEmptyView(int viewId, int emptyViewId)330         SetEmptyView(int viewId, int emptyViewId) {
331             this.viewId = viewId;
332             this.emptyViewId = emptyViewId;
333         }
334 
SetEmptyView(Parcel in)335         SetEmptyView(Parcel in) {
336             this.viewId = in.readInt();
337             this.emptyViewId = in.readInt();
338         }
339 
writeToParcel(Parcel out, int flags)340         public void writeToParcel(Parcel out, int flags) {
341             out.writeInt(TAG);
342             out.writeInt(this.viewId);
343             out.writeInt(this.emptyViewId);
344         }
345 
346         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)347         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
348             final View view = root.findViewById(viewId);
349             if (!(view instanceof AdapterView<?>)) return;
350 
351             AdapterView<?> adapterView = (AdapterView<?>) view;
352 
353             final View emptyView = root.findViewById(emptyViewId);
354             if (emptyView == null) return;
355 
356             adapterView.setEmptyView(emptyView);
357         }
358 
getActionName()359         public String getActionName() {
360             return "SetEmptyView";
361         }
362     }
363 
364     private class SetOnClickFillInIntent extends Action {
SetOnClickFillInIntent(int id, Intent fillInIntent)365         public SetOnClickFillInIntent(int id, Intent fillInIntent) {
366             this.viewId = id;
367             this.fillInIntent = fillInIntent;
368         }
369 
SetOnClickFillInIntent(Parcel parcel)370         public SetOnClickFillInIntent(Parcel parcel) {
371             viewId = parcel.readInt();
372             fillInIntent = Intent.CREATOR.createFromParcel(parcel);
373         }
374 
writeToParcel(Parcel dest, int flags)375         public void writeToParcel(Parcel dest, int flags) {
376             dest.writeInt(TAG);
377             dest.writeInt(viewId);
378             fillInIntent.writeToParcel(dest, 0 /* no flags */);
379         }
380 
381         @Override
apply(View root, ViewGroup rootParent, final OnClickHandler handler)382         public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
383             final View target = root.findViewById(viewId);
384             if (target == null) return;
385 
386             if (!mIsWidgetCollectionChild) {
387                 Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " +
388                         "only from RemoteViewsFactory (ie. on collection items).");
389                 return;
390             }
391             if (target == root) {
392                 target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent);
393             } else if (fillInIntent != null) {
394                 OnClickListener listener = new OnClickListener() {
395                     public void onClick(View v) {
396                         // Insure that this view is a child of an AdapterView
397                         View parent = (View) v.getParent();
398                         while (parent != null && !(parent instanceof AdapterView<?>)
399                                 && !(parent instanceof AppWidgetHostView)) {
400                             parent = (View) parent.getParent();
401                         }
402 
403                         if (parent instanceof AppWidgetHostView || parent == null) {
404                             // Somehow they've managed to get this far without having
405                             // and AdapterView as a parent.
406                             Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent");
407                             return;
408                         }
409 
410                         // Insure that a template pending intent has been set on an ancestor
411                         if (!(parent.getTag() instanceof PendingIntent)) {
412                             Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" +
413                                     " calling setPendingIntentTemplate on parent.");
414                             return;
415                         }
416 
417                         PendingIntent pendingIntent = (PendingIntent) parent.getTag();
418 
419                         final Rect rect = getSourceBounds(v);
420 
421                         fillInIntent.setSourceBounds(rect);
422                         handler.onClickHandler(v, pendingIntent, fillInIntent);
423                     }
424 
425                 };
426                 target.setOnClickListener(listener);
427             }
428         }
429 
getActionName()430         public String getActionName() {
431             return "SetOnClickFillInIntent";
432         }
433 
434         Intent fillInIntent;
435 
436         public final static int TAG = 9;
437     }
438 
439     private class SetPendingIntentTemplate extends Action {
SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate)440         public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
441             this.viewId = id;
442             this.pendingIntentTemplate = pendingIntentTemplate;
443         }
444 
SetPendingIntentTemplate(Parcel parcel)445         public SetPendingIntentTemplate(Parcel parcel) {
446             viewId = parcel.readInt();
447             pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
448         }
449 
writeToParcel(Parcel dest, int flags)450         public void writeToParcel(Parcel dest, int flags) {
451             dest.writeInt(TAG);
452             dest.writeInt(viewId);
453             pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
454         }
455 
456         @Override
apply(View root, ViewGroup rootParent, final OnClickHandler handler)457         public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
458             final View target = root.findViewById(viewId);
459             if (target == null) return;
460 
461             // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense
462             if (target instanceof AdapterView<?>) {
463                 AdapterView<?> av = (AdapterView<?>) target;
464                 // The PendingIntent template is stored in the view's tag.
465                 OnItemClickListener listener = new OnItemClickListener() {
466                     public void onItemClick(AdapterView<?> parent, View view,
467                             int position, long id) {
468                         // The view should be a frame layout
469                         if (view instanceof ViewGroup) {
470                             ViewGroup vg = (ViewGroup) view;
471 
472                             // AdapterViews contain their children in a frame
473                             // so we need to go one layer deeper here.
474                             if (parent instanceof AdapterViewAnimator) {
475                                 vg = (ViewGroup) vg.getChildAt(0);
476                             }
477                             if (vg == null) return;
478 
479                             Intent fillInIntent = null;
480                             int childCount = vg.getChildCount();
481                             for (int i = 0; i < childCount; i++) {
482                                 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent);
483                                 if (tag instanceof Intent) {
484                                     fillInIntent = (Intent) tag;
485                                     break;
486                                 }
487                             }
488                             if (fillInIntent == null) return;
489 
490                             final Rect rect = getSourceBounds(view);
491 
492                             final Intent intent = new Intent();
493                             intent.setSourceBounds(rect);
494                             handler.onClickHandler(view, pendingIntentTemplate, fillInIntent);
495                         }
496                     }
497                 };
498                 av.setOnItemClickListener(listener);
499                 av.setTag(pendingIntentTemplate);
500             } else {
501                 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" +
502                         "an AdapterView (id: " + viewId + ")");
503                 return;
504             }
505         }
506 
getActionName()507         public String getActionName() {
508             return "SetPendingIntentTemplate";
509         }
510 
511         PendingIntent pendingIntentTemplate;
512 
513         public final static int TAG = 8;
514     }
515 
516     private class SetRemoteViewsAdapterList extends Action {
SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount)517         public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
518             this.viewId = id;
519             this.list = list;
520             this.viewTypeCount = viewTypeCount;
521         }
522 
SetRemoteViewsAdapterList(Parcel parcel)523         public SetRemoteViewsAdapterList(Parcel parcel) {
524             viewId = parcel.readInt();
525             viewTypeCount = parcel.readInt();
526             int count = parcel.readInt();
527             list = new ArrayList<RemoteViews>();
528 
529             for (int i = 0; i < count; i++) {
530                 RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
531                 list.add(rv);
532             }
533         }
534 
writeToParcel(Parcel dest, int flags)535         public void writeToParcel(Parcel dest, int flags) {
536             dest.writeInt(TAG);
537             dest.writeInt(viewId);
538             dest.writeInt(viewTypeCount);
539 
540             if (list == null || list.size() == 0) {
541                 dest.writeInt(0);
542             } else {
543                 int count = list.size();
544                 dest.writeInt(count);
545                 for (int i = 0; i < count; i++) {
546                     RemoteViews rv = list.get(i);
547                     rv.writeToParcel(dest, flags);
548                 }
549             }
550         }
551 
552         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)553         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
554             final View target = root.findViewById(viewId);
555             if (target == null) return;
556 
557             // Ensure that we are applying to an AppWidget root
558             if (!(rootParent instanceof AppWidgetHostView)) {
559                 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
560                         "AppWidgets (root id: " + viewId + ")");
561                 return;
562             }
563             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
564             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
565                 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
566                         "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
567                 return;
568             }
569 
570             if (target instanceof AbsListView) {
571                 AbsListView v = (AbsListView) target;
572                 Adapter a = v.getAdapter();
573                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
574                     ((RemoteViewsListAdapter) a).setViewsList(list);
575                 } else {
576                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
577                 }
578             } else if (target instanceof AdapterViewAnimator) {
579                 AdapterViewAnimator v = (AdapterViewAnimator) target;
580                 Adapter a = v.getAdapter();
581                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
582                     ((RemoteViewsListAdapter) a).setViewsList(list);
583                 } else {
584                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
585                 }
586             }
587         }
588 
getActionName()589         public String getActionName() {
590             return "SetRemoteViewsAdapterList";
591         }
592 
593         int viewTypeCount;
594         ArrayList<RemoteViews> list;
595         public final static int TAG = 15;
596     }
597 
598     private class SetRemoteViewsAdapterIntent extends Action {
SetRemoteViewsAdapterIntent(int id, Intent intent)599         public SetRemoteViewsAdapterIntent(int id, Intent intent) {
600             this.viewId = id;
601             this.intent = intent;
602         }
603 
SetRemoteViewsAdapterIntent(Parcel parcel)604         public SetRemoteViewsAdapterIntent(Parcel parcel) {
605             viewId = parcel.readInt();
606             intent = Intent.CREATOR.createFromParcel(parcel);
607         }
608 
writeToParcel(Parcel dest, int flags)609         public void writeToParcel(Parcel dest, int flags) {
610             dest.writeInt(TAG);
611             dest.writeInt(viewId);
612             intent.writeToParcel(dest, flags);
613         }
614 
615         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)616         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
617             final View target = root.findViewById(viewId);
618             if (target == null) return;
619 
620             // Ensure that we are applying to an AppWidget root
621             if (!(rootParent instanceof AppWidgetHostView)) {
622                 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
623                         "AppWidgets (root id: " + viewId + ")");
624                 return;
625             }
626             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
627             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
628                 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
629                         "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
630                 return;
631             }
632 
633             // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
634             // RemoteViewsService
635             AppWidgetHostView host = (AppWidgetHostView) rootParent;
636             intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
637             if (target instanceof AbsListView) {
638                 AbsListView v = (AbsListView) target;
639                 v.setRemoteViewsAdapter(intent);
640                 v.setRemoteViewsOnClickHandler(handler);
641             } else if (target instanceof AdapterViewAnimator) {
642                 AdapterViewAnimator v = (AdapterViewAnimator) target;
643                 v.setRemoteViewsAdapter(intent);
644                 v.setRemoteViewsOnClickHandler(handler);
645             }
646         }
647 
getActionName()648         public String getActionName() {
649             return "SetRemoteViewsAdapterIntent";
650         }
651 
652         Intent intent;
653 
654         public final static int TAG = 10;
655     }
656 
657     /**
658      * Equivalent to calling
659      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
660      * to launch the provided {@link PendingIntent}.
661      */
662     private class SetOnClickPendingIntent extends Action {
SetOnClickPendingIntent(int id, PendingIntent pendingIntent)663         public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
664             this.viewId = id;
665             this.pendingIntent = pendingIntent;
666         }
667 
SetOnClickPendingIntent(Parcel parcel)668         public SetOnClickPendingIntent(Parcel parcel) {
669             viewId = parcel.readInt();
670 
671             // We check a flag to determine if the parcel contains a PendingIntent.
672             if (parcel.readInt() != 0) {
673                 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
674             }
675         }
676 
writeToParcel(Parcel dest, int flags)677         public void writeToParcel(Parcel dest, int flags) {
678             dest.writeInt(TAG);
679             dest.writeInt(viewId);
680 
681             // We use a flag to indicate whether the parcel contains a valid object.
682             dest.writeInt(pendingIntent != null ? 1 : 0);
683             if (pendingIntent != null) {
684                 pendingIntent.writeToParcel(dest, 0 /* no flags */);
685             }
686         }
687 
688         @Override
apply(View root, ViewGroup rootParent, final OnClickHandler handler)689         public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
690             final View target = root.findViewById(viewId);
691             if (target == null) return;
692 
693             // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
694             // sense, do they mean to set a PendingIntent template for the AdapterView's children?
695             if (mIsWidgetCollectionChild) {
696                 Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
697                         "(id: " + viewId + ")");
698                 ApplicationInfo appInfo = root.getContext().getApplicationInfo();
699 
700                 // We let this slide for HC and ICS so as to not break compatibility. It should have
701                 // been disabled from the outset, but was left open by accident.
702                 if (appInfo != null &&
703                         appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
704                     return;
705                 }
706             }
707 
708             // If the pendingIntent is null, we clear the onClickListener
709             OnClickListener listener = null;
710             if (pendingIntent != null) {
711                 listener = new OnClickListener() {
712                     public void onClick(View v) {
713                         // Find target view location in screen coordinates and
714                         // fill into PendingIntent before sending.
715                         final Rect rect = getSourceBounds(v);
716 
717                         final Intent intent = new Intent();
718                         intent.setSourceBounds(rect);
719                         handler.onClickHandler(v, pendingIntent, intent);
720                     }
721                 };
722             }
723             target.setOnClickListener(listener);
724         }
725 
getActionName()726         public String getActionName() {
727             return "SetOnClickPendingIntent";
728         }
729 
730         PendingIntent pendingIntent;
731 
732         public final static int TAG = 1;
733     }
734 
getSourceBounds(View v)735     private static Rect getSourceBounds(View v) {
736         final float appScale = v.getContext().getResources()
737                 .getCompatibilityInfo().applicationScale;
738         final int[] pos = new int[2];
739         v.getLocationOnScreen(pos);
740 
741         final Rect rect = new Rect();
742         rect.left = (int) (pos[0] * appScale + 0.5f);
743         rect.top = (int) (pos[1] * appScale + 0.5f);
744         rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
745         rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
746         return rect;
747     }
748 
getMethod(View view, String methodName, Class<?> paramType)749     private Method getMethod(View view, String methodName, Class<?> paramType) {
750         Method method;
751         Class<? extends View> klass = view.getClass();
752 
753         synchronized (sMethodsLock) {
754             ArrayMap<MutablePair<String, Class<?>>, Method> methods = sMethods.get(klass);
755             if (methods == null) {
756                 methods = new ArrayMap<MutablePair<String, Class<?>>, Method>();
757                 sMethods.put(klass, methods);
758             }
759 
760             mPair.first = methodName;
761             mPair.second = paramType;
762 
763             method = methods.get(mPair);
764             if (method == null) {
765                 try {
766                     if (paramType == null) {
767                         method = klass.getMethod(methodName);
768                     } else {
769                         method = klass.getMethod(methodName, paramType);
770                     }
771                 } catch (NoSuchMethodException ex) {
772                     throw new ActionException("view: " + klass.getName() + " doesn't have method: "
773                             + methodName + getParameters(paramType));
774                 }
775 
776                 if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
777                     throw new ActionException("view: " + klass.getName()
778                             + " can't use method with RemoteViews: "
779                             + methodName + getParameters(paramType));
780                 }
781 
782                 methods.put(new MutablePair<String, Class<?>>(methodName, paramType), method);
783             }
784         }
785 
786         return method;
787     }
788 
getParameters(Class<?> paramType)789     private static String getParameters(Class<?> paramType) {
790         if (paramType == null) return "()";
791         return "(" + paramType + ")";
792     }
793 
wrapArg(Object value)794     private static Object[] wrapArg(Object value) {
795         Object[] args = sInvokeArgsTls.get();
796         args[0] = value;
797         return args;
798     }
799 
800     /**
801      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
802      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
803      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
804      * <p>
805      * These operations will be performed on the {@link Drawable} returned by the
806      * target {@link View#getBackground()} by default.  If targetBackground is false,
807      * we assume the target is an {@link ImageView} and try applying the operations
808      * to {@link ImageView#getDrawable()}.
809      * <p>
810      * You can omit specific calls by marking their values with null or -1.
811      */
812     private class SetDrawableParameters extends Action {
SetDrawableParameters(int id, boolean targetBackground, int alpha, int colorFilter, PorterDuff.Mode mode, int level)813         public SetDrawableParameters(int id, boolean targetBackground, int alpha,
814                 int colorFilter, PorterDuff.Mode mode, int level) {
815             this.viewId = id;
816             this.targetBackground = targetBackground;
817             this.alpha = alpha;
818             this.colorFilter = colorFilter;
819             this.filterMode = mode;
820             this.level = level;
821         }
822 
SetDrawableParameters(Parcel parcel)823         public SetDrawableParameters(Parcel parcel) {
824             viewId = parcel.readInt();
825             targetBackground = parcel.readInt() != 0;
826             alpha = parcel.readInt();
827             colorFilter = parcel.readInt();
828             boolean hasMode = parcel.readInt() != 0;
829             if (hasMode) {
830                 filterMode = PorterDuff.Mode.valueOf(parcel.readString());
831             } else {
832                 filterMode = null;
833             }
834             level = parcel.readInt();
835         }
836 
writeToParcel(Parcel dest, int flags)837         public void writeToParcel(Parcel dest, int flags) {
838             dest.writeInt(TAG);
839             dest.writeInt(viewId);
840             dest.writeInt(targetBackground ? 1 : 0);
841             dest.writeInt(alpha);
842             dest.writeInt(colorFilter);
843             if (filterMode != null) {
844                 dest.writeInt(1);
845                 dest.writeString(filterMode.toString());
846             } else {
847                 dest.writeInt(0);
848             }
849             dest.writeInt(level);
850         }
851 
852         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)853         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
854             final View target = root.findViewById(viewId);
855             if (target == null) return;
856 
857             // Pick the correct drawable to modify for this view
858             Drawable targetDrawable = null;
859             if (targetBackground) {
860                 targetDrawable = target.getBackground();
861             } else if (target instanceof ImageView) {
862                 ImageView imageView = (ImageView) target;
863                 targetDrawable = imageView.getDrawable();
864             }
865 
866             if (targetDrawable != null) {
867                 // Perform modifications only if values are set correctly
868                 if (alpha != -1) {
869                     targetDrawable.setAlpha(alpha);
870                 }
871                 if (colorFilter != -1 && filterMode != null) {
872                     targetDrawable.setColorFilter(colorFilter, filterMode);
873                 }
874                 if (level != -1) {
875                     targetDrawable.setLevel(level);
876                 }
877             }
878         }
879 
getActionName()880         public String getActionName() {
881             return "SetDrawableParameters";
882         }
883 
884         boolean targetBackground;
885         int alpha;
886         int colorFilter;
887         PorterDuff.Mode filterMode;
888         int level;
889 
890         public final static int TAG = 3;
891     }
892 
893     private final class ReflectionActionWithoutParams extends Action {
894         final String methodName;
895 
896         public final static int TAG = 5;
897 
ReflectionActionWithoutParams(int viewId, String methodName)898         ReflectionActionWithoutParams(int viewId, String methodName) {
899             this.viewId = viewId;
900             this.methodName = methodName;
901         }
902 
ReflectionActionWithoutParams(Parcel in)903         ReflectionActionWithoutParams(Parcel in) {
904             this.viewId = in.readInt();
905             this.methodName = in.readString();
906         }
907 
writeToParcel(Parcel out, int flags)908         public void writeToParcel(Parcel out, int flags) {
909             out.writeInt(TAG);
910             out.writeInt(this.viewId);
911             out.writeString(this.methodName);
912         }
913 
914         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)915         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
916             final View view = root.findViewById(viewId);
917             if (view == null) return;
918 
919             try {
920                 getMethod(view, this.methodName, null).invoke(view);
921             } catch (ActionException e) {
922                 throw e;
923             } catch (Exception ex) {
924                 throw new ActionException(ex);
925             }
926         }
927 
mergeBehavior()928         public int mergeBehavior() {
929             // we don't need to build up showNext or showPrevious calls
930             if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
931                 return MERGE_IGNORE;
932             } else {
933                 return MERGE_REPLACE;
934             }
935         }
936 
getActionName()937         public String getActionName() {
938             return "ReflectionActionWithoutParams";
939         }
940     }
941 
942     private static class BitmapCache {
943         ArrayList<Bitmap> mBitmaps;
944 
BitmapCache()945         public BitmapCache() {
946             mBitmaps = new ArrayList<Bitmap>();
947         }
948 
BitmapCache(Parcel source)949         public BitmapCache(Parcel source) {
950             int count = source.readInt();
951             mBitmaps = new ArrayList<Bitmap>();
952             for (int i = 0; i < count; i++) {
953                 Bitmap b = Bitmap.CREATOR.createFromParcel(source);
954                 mBitmaps.add(b);
955             }
956         }
957 
getBitmapId(Bitmap b)958         public int getBitmapId(Bitmap b) {
959             if (b == null) {
960                 return -1;
961             } else {
962                 if (mBitmaps.contains(b)) {
963                     return mBitmaps.indexOf(b);
964                 } else {
965                     mBitmaps.add(b);
966                     return (mBitmaps.size() - 1);
967                 }
968             }
969         }
970 
getBitmapForId(int id)971         public Bitmap getBitmapForId(int id) {
972             if (id == -1 || id >= mBitmaps.size()) {
973                 return null;
974             } else {
975                 return mBitmaps.get(id);
976             }
977         }
978 
writeBitmapsToParcel(Parcel dest, int flags)979         public void writeBitmapsToParcel(Parcel dest, int flags) {
980             int count = mBitmaps.size();
981             dest.writeInt(count);
982             for (int i = 0; i < count; i++) {
983                 mBitmaps.get(i).writeToParcel(dest, flags);
984             }
985         }
986 
assimilate(BitmapCache bitmapCache)987         public void assimilate(BitmapCache bitmapCache) {
988             ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps;
989             int count = bitmapsToBeAdded.size();
990             for (int i = 0; i < count; i++) {
991                 Bitmap b = bitmapsToBeAdded.get(i);
992                 if (!mBitmaps.contains(b)) {
993                     mBitmaps.add(b);
994                 }
995             }
996         }
997 
addBitmapMemory(MemoryUsageCounter memoryCounter)998         public void addBitmapMemory(MemoryUsageCounter memoryCounter) {
999             for (int i = 0; i < mBitmaps.size(); i++) {
1000                 memoryCounter.addBitmapMemory(mBitmaps.get(i));
1001             }
1002         }
1003     }
1004 
1005     private class BitmapReflectionAction extends Action {
1006         int bitmapId;
1007         Bitmap bitmap;
1008         String methodName;
1009 
BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap)1010         BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {
1011             this.bitmap = bitmap;
1012             this.viewId = viewId;
1013             this.methodName = methodName;
1014             bitmapId = mBitmapCache.getBitmapId(bitmap);
1015         }
1016 
BitmapReflectionAction(Parcel in)1017         BitmapReflectionAction(Parcel in) {
1018             viewId = in.readInt();
1019             methodName = in.readString();
1020             bitmapId = in.readInt();
1021             bitmap = mBitmapCache.getBitmapForId(bitmapId);
1022         }
1023 
1024         @Override
writeToParcel(Parcel dest, int flags)1025         public void writeToParcel(Parcel dest, int flags) {
1026             dest.writeInt(TAG);
1027             dest.writeInt(viewId);
1028             dest.writeString(methodName);
1029             dest.writeInt(bitmapId);
1030         }
1031 
1032         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)1033         public void apply(View root, ViewGroup rootParent,
1034                 OnClickHandler handler) throws ActionException {
1035             ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
1036                     bitmap);
1037             ra.apply(root, rootParent, handler);
1038         }
1039 
1040         @Override
setBitmapCache(BitmapCache bitmapCache)1041         public void setBitmapCache(BitmapCache bitmapCache) {
1042             bitmapId = bitmapCache.getBitmapId(bitmap);
1043         }
1044 
getActionName()1045         public String getActionName() {
1046             return "BitmapReflectionAction";
1047         }
1048 
1049         public final static int TAG = 12;
1050     }
1051 
1052     /**
1053      * Base class for the reflection actions.
1054      */
1055     private final class ReflectionAction extends Action {
1056         static final int TAG = 2;
1057 
1058         static final int BOOLEAN = 1;
1059         static final int BYTE = 2;
1060         static final int SHORT = 3;
1061         static final int INT = 4;
1062         static final int LONG = 5;
1063         static final int FLOAT = 6;
1064         static final int DOUBLE = 7;
1065         static final int CHAR = 8;
1066         static final int STRING = 9;
1067         static final int CHAR_SEQUENCE = 10;
1068         static final int URI = 11;
1069         // BITMAP actions are never stored in the list of actions. They are only used locally
1070         // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache.
1071         static final int BITMAP = 12;
1072         static final int BUNDLE = 13;
1073         static final int INTENT = 14;
1074 
1075         String methodName;
1076         int type;
1077         Object value;
1078 
ReflectionAction(int viewId, String methodName, int type, Object value)1079         ReflectionAction(int viewId, String methodName, int type, Object value) {
1080             this.viewId = viewId;
1081             this.methodName = methodName;
1082             this.type = type;
1083             this.value = value;
1084         }
1085 
ReflectionAction(Parcel in)1086         ReflectionAction(Parcel in) {
1087             this.viewId = in.readInt();
1088             this.methodName = in.readString();
1089             this.type = in.readInt();
1090             //noinspection ConstantIfStatement
1091             if (false) {
1092                 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
1093                         + " methodName=" + this.methodName + " type=" + this.type);
1094             }
1095 
1096             // For some values that may have been null, we first check a flag to see if they were
1097             // written to the parcel.
1098             switch (this.type) {
1099                 case BOOLEAN:
1100                     this.value = in.readInt() != 0;
1101                     break;
1102                 case BYTE:
1103                     this.value = in.readByte();
1104                     break;
1105                 case SHORT:
1106                     this.value = (short)in.readInt();
1107                     break;
1108                 case INT:
1109                     this.value = in.readInt();
1110                     break;
1111                 case LONG:
1112                     this.value = in.readLong();
1113                     break;
1114                 case FLOAT:
1115                     this.value = in.readFloat();
1116                     break;
1117                 case DOUBLE:
1118                     this.value = in.readDouble();
1119                     break;
1120                 case CHAR:
1121                     this.value = (char)in.readInt();
1122                     break;
1123                 case STRING:
1124                     this.value = in.readString();
1125                     break;
1126                 case CHAR_SEQUENCE:
1127                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
1128                     break;
1129                 case URI:
1130                     if (in.readInt() != 0) {
1131                         this.value = Uri.CREATOR.createFromParcel(in);
1132                     }
1133                     break;
1134                 case BITMAP:
1135                     if (in.readInt() != 0) {
1136                         this.value = Bitmap.CREATOR.createFromParcel(in);
1137                     }
1138                     break;
1139                 case BUNDLE:
1140                     this.value = in.readBundle();
1141                     break;
1142                 case INTENT:
1143                     if (in.readInt() != 0) {
1144                         this.value = Intent.CREATOR.createFromParcel(in);
1145                     }
1146                     break;
1147                 default:
1148                     break;
1149             }
1150         }
1151 
writeToParcel(Parcel out, int flags)1152         public void writeToParcel(Parcel out, int flags) {
1153             out.writeInt(TAG);
1154             out.writeInt(this.viewId);
1155             out.writeString(this.methodName);
1156             out.writeInt(this.type);
1157             //noinspection ConstantIfStatement
1158             if (false) {
1159                 Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
1160                         + " methodName=" + this.methodName + " type=" + this.type);
1161             }
1162 
1163             // For some values which are null, we record an integer flag to indicate whether
1164             // we have written a valid value to the parcel.
1165             switch (this.type) {
1166                 case BOOLEAN:
1167                     out.writeInt((Boolean) this.value ? 1 : 0);
1168                     break;
1169                 case BYTE:
1170                     out.writeByte((Byte) this.value);
1171                     break;
1172                 case SHORT:
1173                     out.writeInt((Short) this.value);
1174                     break;
1175                 case INT:
1176                     out.writeInt((Integer) this.value);
1177                     break;
1178                 case LONG:
1179                     out.writeLong((Long) this.value);
1180                     break;
1181                 case FLOAT:
1182                     out.writeFloat((Float) this.value);
1183                     break;
1184                 case DOUBLE:
1185                     out.writeDouble((Double) this.value);
1186                     break;
1187                 case CHAR:
1188                     out.writeInt((int)((Character)this.value).charValue());
1189                     break;
1190                 case STRING:
1191                     out.writeString((String)this.value);
1192                     break;
1193                 case CHAR_SEQUENCE:
1194                     TextUtils.writeToParcel((CharSequence)this.value, out, flags);
1195                     break;
1196                 case URI:
1197                     out.writeInt(this.value != null ? 1 : 0);
1198                     if (this.value != null) {
1199                         ((Uri)this.value).writeToParcel(out, flags);
1200                     }
1201                     break;
1202                 case BITMAP:
1203                     out.writeInt(this.value != null ? 1 : 0);
1204                     if (this.value != null) {
1205                         ((Bitmap)this.value).writeToParcel(out, flags);
1206                     }
1207                     break;
1208                 case BUNDLE:
1209                     out.writeBundle((Bundle) this.value);
1210                     break;
1211                 case INTENT:
1212                     out.writeInt(this.value != null ? 1 : 0);
1213                     if (this.value != null) {
1214                         ((Intent)this.value).writeToParcel(out, flags);
1215                     }
1216                     break;
1217                 default:
1218                     break;
1219             }
1220         }
1221 
getParameterType()1222         private Class<?> getParameterType() {
1223             switch (this.type) {
1224                 case BOOLEAN:
1225                     return boolean.class;
1226                 case BYTE:
1227                     return byte.class;
1228                 case SHORT:
1229                     return short.class;
1230                 case INT:
1231                     return int.class;
1232                 case LONG:
1233                     return long.class;
1234                 case FLOAT:
1235                     return float.class;
1236                 case DOUBLE:
1237                     return double.class;
1238                 case CHAR:
1239                     return char.class;
1240                 case STRING:
1241                     return String.class;
1242                 case CHAR_SEQUENCE:
1243                     return CharSequence.class;
1244                 case URI:
1245                     return Uri.class;
1246                 case BITMAP:
1247                     return Bitmap.class;
1248                 case BUNDLE:
1249                     return Bundle.class;
1250                 case INTENT:
1251                     return Intent.class;
1252                 default:
1253                     return null;
1254             }
1255         }
1256 
1257         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)1258         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1259             final View view = root.findViewById(viewId);
1260             if (view == null) return;
1261 
1262             Class<?> param = getParameterType();
1263             if (param == null) {
1264                 throw new ActionException("bad type: " + this.type);
1265             }
1266 
1267             try {
1268                 getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
1269             } catch (ActionException e) {
1270                 throw e;
1271             } catch (Exception ex) {
1272                 throw new ActionException(ex);
1273             }
1274         }
1275 
mergeBehavior()1276         public int mergeBehavior() {
1277             // smoothScrollBy is cumulative, everything else overwites.
1278             if (methodName.equals("smoothScrollBy")) {
1279                 return MERGE_APPEND;
1280             } else {
1281                 return MERGE_REPLACE;
1282             }
1283         }
1284 
getActionName()1285         public String getActionName() {
1286             // Each type of reflection action corresponds to a setter, so each should be seen as
1287             // unique from the standpoint of merging.
1288             return "ReflectionAction" + this.methodName + this.type;
1289         }
1290     }
1291 
configureRemoteViewsAsChild(RemoteViews rv)1292     private void configureRemoteViewsAsChild(RemoteViews rv) {
1293         mBitmapCache.assimilate(rv.mBitmapCache);
1294         rv.setBitmapCache(mBitmapCache);
1295         rv.setNotRoot();
1296     }
1297 
setNotRoot()1298     void setNotRoot() {
1299         mIsRoot = false;
1300     }
1301 
1302     /**
1303      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
1304      * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
1305      * when null. This allows users to build "nested" {@link RemoteViews}.
1306      */
1307     private class ViewGroupAction extends Action {
ViewGroupAction(int viewId, RemoteViews nestedViews)1308         public ViewGroupAction(int viewId, RemoteViews nestedViews) {
1309             this.viewId = viewId;
1310             this.nestedViews = nestedViews;
1311             if (nestedViews != null) {
1312                 configureRemoteViewsAsChild(nestedViews);
1313             }
1314         }
1315 
ViewGroupAction(Parcel parcel, BitmapCache bitmapCache)1316         public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) {
1317             viewId = parcel.readInt();
1318             boolean nestedViewsNull = parcel.readInt() == 0;
1319             if (!nestedViewsNull) {
1320                 nestedViews = new RemoteViews(parcel, bitmapCache);
1321             } else {
1322                 nestedViews = null;
1323             }
1324         }
1325 
writeToParcel(Parcel dest, int flags)1326         public void writeToParcel(Parcel dest, int flags) {
1327             dest.writeInt(TAG);
1328             dest.writeInt(viewId);
1329             if (nestedViews != null) {
1330                 dest.writeInt(1);
1331                 nestedViews.writeToParcel(dest, flags);
1332             } else {
1333                 // signifies null
1334                 dest.writeInt(0);
1335             }
1336         }
1337 
1338         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)1339         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1340             final Context context = root.getContext();
1341             final ViewGroup target = (ViewGroup) root.findViewById(viewId);
1342             if (target == null) return;
1343             if (nestedViews != null) {
1344                 // Inflate nested views and add as children
1345                 target.addView(nestedViews.apply(context, target, handler));
1346             } else {
1347                 // Clear all children when nested views omitted
1348                 target.removeAllViews();
1349             }
1350         }
1351 
1352         @Override
updateMemoryUsageEstimate(MemoryUsageCounter counter)1353         public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
1354             if (nestedViews != null) {
1355                 counter.increment(nestedViews.estimateMemoryUsage());
1356             }
1357         }
1358 
1359         @Override
setBitmapCache(BitmapCache bitmapCache)1360         public void setBitmapCache(BitmapCache bitmapCache) {
1361             if (nestedViews != null) {
1362                 nestedViews.setBitmapCache(bitmapCache);
1363             }
1364         }
1365 
getActionName()1366         public String getActionName() {
1367             return "ViewGroupAction" + (nestedViews == null ? "Remove" : "Add");
1368         }
1369 
mergeBehavior()1370         public int mergeBehavior() {
1371             return MERGE_APPEND;
1372         }
1373 
1374         RemoteViews nestedViews;
1375 
1376         public final static int TAG = 4;
1377     }
1378 
1379     /**
1380      * Helper action to set compound drawables on a TextView. Supports relative
1381      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
1382      */
1383     private class TextViewDrawableAction extends Action {
TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4)1384         public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) {
1385             this.viewId = viewId;
1386             this.isRelative = isRelative;
1387             this.d1 = d1;
1388             this.d2 = d2;
1389             this.d3 = d3;
1390             this.d4 = d4;
1391         }
1392 
TextViewDrawableAction(Parcel parcel)1393         public TextViewDrawableAction(Parcel parcel) {
1394             viewId = parcel.readInt();
1395             isRelative = (parcel.readInt() != 0);
1396             d1 = parcel.readInt();
1397             d2 = parcel.readInt();
1398             d3 = parcel.readInt();
1399             d4 = parcel.readInt();
1400         }
1401 
writeToParcel(Parcel dest, int flags)1402         public void writeToParcel(Parcel dest, int flags) {
1403             dest.writeInt(TAG);
1404             dest.writeInt(viewId);
1405             dest.writeInt(isRelative ? 1 : 0);
1406             dest.writeInt(d1);
1407             dest.writeInt(d2);
1408             dest.writeInt(d3);
1409             dest.writeInt(d4);
1410         }
1411 
1412         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)1413         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1414             final TextView target = (TextView) root.findViewById(viewId);
1415             if (target == null) return;
1416             if (isRelative) {
1417                 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
1418             } else {
1419                 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
1420             }
1421         }
1422 
getActionName()1423         public String getActionName() {
1424             return "TextViewDrawableAction";
1425         }
1426 
1427         boolean isRelative = false;
1428         int d1, d2, d3, d4;
1429 
1430         public final static int TAG = 11;
1431     }
1432 
1433     /**
1434      * Helper action to set text size on a TextView in any supported units.
1435      */
1436     private class TextViewSizeAction extends Action {
TextViewSizeAction(int viewId, int units, float size)1437         public TextViewSizeAction(int viewId, int units, float size) {
1438             this.viewId = viewId;
1439             this.units = units;
1440             this.size = size;
1441         }
1442 
TextViewSizeAction(Parcel parcel)1443         public TextViewSizeAction(Parcel parcel) {
1444             viewId = parcel.readInt();
1445             units = parcel.readInt();
1446             size  = parcel.readFloat();
1447         }
1448 
writeToParcel(Parcel dest, int flags)1449         public void writeToParcel(Parcel dest, int flags) {
1450             dest.writeInt(TAG);
1451             dest.writeInt(viewId);
1452             dest.writeInt(units);
1453             dest.writeFloat(size);
1454         }
1455 
1456         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)1457         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1458             final TextView target = (TextView) root.findViewById(viewId);
1459             if (target == null) return;
1460             target.setTextSize(units, size);
1461         }
1462 
getActionName()1463         public String getActionName() {
1464             return "TextViewSizeAction";
1465         }
1466 
1467         int units;
1468         float size;
1469 
1470         public final static int TAG = 13;
1471     }
1472 
1473     /**
1474      * Helper action to set padding on a View.
1475      */
1476     private class ViewPaddingAction extends Action {
ViewPaddingAction(int viewId, int left, int top, int right, int bottom)1477         public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) {
1478             this.viewId = viewId;
1479             this.left = left;
1480             this.top = top;
1481             this.right = right;
1482             this.bottom = bottom;
1483         }
1484 
ViewPaddingAction(Parcel parcel)1485         public ViewPaddingAction(Parcel parcel) {
1486             viewId = parcel.readInt();
1487             left = parcel.readInt();
1488             top = parcel.readInt();
1489             right = parcel.readInt();
1490             bottom = parcel.readInt();
1491         }
1492 
writeToParcel(Parcel dest, int flags)1493         public void writeToParcel(Parcel dest, int flags) {
1494             dest.writeInt(TAG);
1495             dest.writeInt(viewId);
1496             dest.writeInt(left);
1497             dest.writeInt(top);
1498             dest.writeInt(right);
1499             dest.writeInt(bottom);
1500         }
1501 
1502         @Override
apply(View root, ViewGroup rootParent, OnClickHandler handler)1503         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
1504             final View target = root.findViewById(viewId);
1505             if (target == null) return;
1506             target.setPadding(left, top, right, bottom);
1507         }
1508 
getActionName()1509         public String getActionName() {
1510             return "ViewPaddingAction";
1511         }
1512 
1513         int left, top, right, bottom;
1514 
1515         public final static int TAG = 14;
1516     }
1517 
1518     /**
1519      * Simple class used to keep track of memory usage in a RemoteViews.
1520      *
1521      */
1522     private class MemoryUsageCounter {
clear()1523         public void clear() {
1524             mMemoryUsage = 0;
1525         }
1526 
increment(int numBytes)1527         public void increment(int numBytes) {
1528             mMemoryUsage += numBytes;
1529         }
1530 
getMemoryUsage()1531         public int getMemoryUsage() {
1532             return mMemoryUsage;
1533         }
1534 
1535         @SuppressWarnings("deprecation")
addBitmapMemory(Bitmap b)1536         public void addBitmapMemory(Bitmap b) {
1537             final Bitmap.Config c = b.getConfig();
1538             // If we don't know, be pessimistic and assume 4
1539             int bpp = 4;
1540             if (c != null) {
1541                 switch (c) {
1542                 case ALPHA_8:
1543                     bpp = 1;
1544                     break;
1545                 case RGB_565:
1546                 case ARGB_4444:
1547                     bpp = 2;
1548                     break;
1549                 case ARGB_8888:
1550                     bpp = 4;
1551                     break;
1552                 }
1553             }
1554             increment(b.getWidth() * b.getHeight() * bpp);
1555         }
1556 
1557         int mMemoryUsage;
1558     }
1559 
1560     /**
1561      * Create a new RemoteViews object that will display the views contained
1562      * in the specified layout file.
1563      *
1564      * @param packageName Name of the package that contains the layout resource
1565      * @param layoutId The id of the layout resource
1566      */
RemoteViews(String packageName, int layoutId)1567     public RemoteViews(String packageName, int layoutId) {
1568         mPackage = packageName;
1569         mLayoutId = layoutId;
1570         mBitmapCache = new BitmapCache();
1571 
1572         // setup the memory usage statistics
1573         mMemoryUsageCounter = new MemoryUsageCounter();
1574         recalculateMemoryUsage();
1575     }
1576 
1577     /** {@hide} */
setUser(UserHandle user)1578     public void setUser(UserHandle user) {
1579         mUser = user;
1580     }
1581 
hasLandscapeAndPortraitLayouts()1582     private boolean hasLandscapeAndPortraitLayouts() {
1583         return (mLandscape != null) && (mPortrait != null);
1584     }
1585 
1586     /**
1587      * Create a new RemoteViews object that will inflate as the specified
1588      * landspace or portrait RemoteViews, depending on the current configuration.
1589      *
1590      * @param landscape The RemoteViews to inflate in landscape configuration
1591      * @param portrait The RemoteViews to inflate in portrait configuration
1592      */
RemoteViews(RemoteViews landscape, RemoteViews portrait)1593     public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
1594         if (landscape == null || portrait == null) {
1595             throw new RuntimeException("Both RemoteViews must be non-null");
1596         }
1597         if (landscape.getPackage().compareTo(portrait.getPackage()) != 0) {
1598             throw new RuntimeException("Both RemoteViews must share the same package");
1599         }
1600         mPackage = portrait.getPackage();
1601         mLayoutId = portrait.getLayoutId();
1602 
1603         mLandscape = landscape;
1604         mPortrait = portrait;
1605 
1606         // setup the memory usage statistics
1607         mMemoryUsageCounter = new MemoryUsageCounter();
1608 
1609         mBitmapCache = new BitmapCache();
1610         configureRemoteViewsAsChild(landscape);
1611         configureRemoteViewsAsChild(portrait);
1612 
1613         recalculateMemoryUsage();
1614     }
1615 
1616     /**
1617      * Reads a RemoteViews object from a parcel.
1618      *
1619      * @param parcel
1620      */
RemoteViews(Parcel parcel)1621     public RemoteViews(Parcel parcel) {
1622         this(parcel, null);
1623     }
1624 
RemoteViews(Parcel parcel, BitmapCache bitmapCache)1625     private RemoteViews(Parcel parcel, BitmapCache bitmapCache) {
1626         int mode = parcel.readInt();
1627 
1628         // We only store a bitmap cache in the root of the RemoteViews.
1629         if (bitmapCache == null) {
1630             mBitmapCache = new BitmapCache(parcel);
1631         } else {
1632             setBitmapCache(bitmapCache);
1633             setNotRoot();
1634         }
1635 
1636         if (mode == MODE_NORMAL) {
1637             mPackage = parcel.readString();
1638             mLayoutId = parcel.readInt();
1639             mIsWidgetCollectionChild = parcel.readInt() == 1;
1640 
1641             int count = parcel.readInt();
1642             if (count > 0) {
1643                 mActions = new ArrayList<Action>(count);
1644                 for (int i=0; i<count; i++) {
1645                     int tag = parcel.readInt();
1646                     switch (tag) {
1647                     case SetOnClickPendingIntent.TAG:
1648                         mActions.add(new SetOnClickPendingIntent(parcel));
1649                         break;
1650                     case SetDrawableParameters.TAG:
1651                         mActions.add(new SetDrawableParameters(parcel));
1652                         break;
1653                     case ReflectionAction.TAG:
1654                         mActions.add(new ReflectionAction(parcel));
1655                         break;
1656                     case ViewGroupAction.TAG:
1657                         mActions.add(new ViewGroupAction(parcel, mBitmapCache));
1658                         break;
1659                     case ReflectionActionWithoutParams.TAG:
1660                         mActions.add(new ReflectionActionWithoutParams(parcel));
1661                         break;
1662                     case SetEmptyView.TAG:
1663                         mActions.add(new SetEmptyView(parcel));
1664                         break;
1665                     case SetPendingIntentTemplate.TAG:
1666                         mActions.add(new SetPendingIntentTemplate(parcel));
1667                         break;
1668                     case SetOnClickFillInIntent.TAG:
1669                         mActions.add(new SetOnClickFillInIntent(parcel));
1670                         break;
1671                     case SetRemoteViewsAdapterIntent.TAG:
1672                         mActions.add(new SetRemoteViewsAdapterIntent(parcel));
1673                         break;
1674                     case TextViewDrawableAction.TAG:
1675                         mActions.add(new TextViewDrawableAction(parcel));
1676                         break;
1677                     case TextViewSizeAction.TAG:
1678                         mActions.add(new TextViewSizeAction(parcel));
1679                         break;
1680                     case ViewPaddingAction.TAG:
1681                         mActions.add(new ViewPaddingAction(parcel));
1682                         break;
1683                     case BitmapReflectionAction.TAG:
1684                         mActions.add(new BitmapReflectionAction(parcel));
1685                         break;
1686                     case SetRemoteViewsAdapterList.TAG:
1687                         mActions.add(new SetRemoteViewsAdapterList(parcel));
1688                         break;
1689                     default:
1690                         throw new ActionException("Tag " + tag + " not found");
1691                     }
1692                 }
1693             }
1694         } else {
1695             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
1696             mLandscape = new RemoteViews(parcel, mBitmapCache);
1697             mPortrait = new RemoteViews(parcel, mBitmapCache);
1698             mPackage = mPortrait.getPackage();
1699             mLayoutId = mPortrait.getLayoutId();
1700         }
1701 
1702         // setup the memory usage statistics
1703         mMemoryUsageCounter = new MemoryUsageCounter();
1704         recalculateMemoryUsage();
1705     }
1706 
1707 
clone()1708     public RemoteViews clone() {
1709         Parcel p = Parcel.obtain();
1710         writeToParcel(p, 0);
1711         p.setDataPosition(0);
1712         return new RemoteViews(p);
1713     }
1714 
getPackage()1715     public String getPackage() {
1716         return mPackage;
1717     }
1718 
1719     /**
1720      * Reutrns the layout id of the root layout associated with this RemoteViews. In the case
1721      * that the RemoteViews has both a landscape and portrait root, this will return the layout
1722      * id associated with the portrait layout.
1723      *
1724      * @return the layout id.
1725      */
getLayoutId()1726     public int getLayoutId() {
1727         return mLayoutId;
1728     }
1729 
1730     /*
1731      * This flag indicates whether this RemoteViews object is being created from a
1732      * RemoteViewsService for use as a child of a widget collection. This flag is used
1733      * to determine whether or not certain features are available, in particular,
1734      * setting on click extras and setting on click pending intents. The former is enabled,
1735      * and the latter disabled when this flag is true.
1736      */
setIsWidgetCollectionChild(boolean isWidgetCollectionChild)1737     void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
1738         mIsWidgetCollectionChild = isWidgetCollectionChild;
1739     }
1740 
1741     /**
1742      * Updates the memory usage statistics.
1743      */
recalculateMemoryUsage()1744     private void recalculateMemoryUsage() {
1745         mMemoryUsageCounter.clear();
1746 
1747         if (!hasLandscapeAndPortraitLayouts()) {
1748             // Accumulate the memory usage for each action
1749             if (mActions != null) {
1750                 final int count = mActions.size();
1751                 for (int i= 0; i < count; ++i) {
1752                     mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
1753                 }
1754             }
1755             if (mIsRoot) {
1756                 mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
1757             }
1758         } else {
1759             mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage());
1760             mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage());
1761             mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
1762         }
1763     }
1764 
1765     /**
1766      * Recursively sets BitmapCache in the hierarchy and update the bitmap ids.
1767      */
setBitmapCache(BitmapCache bitmapCache)1768     private void setBitmapCache(BitmapCache bitmapCache) {
1769         mBitmapCache = bitmapCache;
1770         if (!hasLandscapeAndPortraitLayouts()) {
1771             if (mActions != null) {
1772                 final int count = mActions.size();
1773                 for (int i= 0; i < count; ++i) {
1774                     mActions.get(i).setBitmapCache(bitmapCache);
1775                 }
1776             }
1777         } else {
1778             mLandscape.setBitmapCache(bitmapCache);
1779             mPortrait.setBitmapCache(bitmapCache);
1780         }
1781     }
1782 
1783     /**
1784      * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
1785      */
1786     /** @hide */
estimateMemoryUsage()1787     public int estimateMemoryUsage() {
1788         return mMemoryUsageCounter.getMemoryUsage();
1789     }
1790 
1791     /**
1792      * Add an action to be executed on the remote side when apply is called.
1793      *
1794      * @param a The action to add
1795      */
addAction(Action a)1796     private void addAction(Action a) {
1797         if (hasLandscapeAndPortraitLayouts()) {
1798             throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
1799                     " layouts cannot be modified. Instead, fully configure the landscape and" +
1800                     " portrait layouts individually before constructing the combined layout.");
1801         }
1802         if (mActions == null) {
1803             mActions = new ArrayList<Action>();
1804         }
1805         mActions.add(a);
1806 
1807         // update the memory usage stats
1808         a.updateMemoryUsageEstimate(mMemoryUsageCounter);
1809     }
1810 
1811     /**
1812      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
1813      * given {@link RemoteViews}. This allows users to build "nested"
1814      * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
1815      * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
1816      * children.
1817      *
1818      * @param viewId The id of the parent {@link ViewGroup} to add child into.
1819      * @param nestedView {@link RemoteViews} that describes the child.
1820      */
addView(int viewId, RemoteViews nestedView)1821     public void addView(int viewId, RemoteViews nestedView) {
1822         addAction(new ViewGroupAction(viewId, nestedView));
1823     }
1824 
1825     /**
1826      * Equivalent to calling {@link ViewGroup#removeAllViews()}.
1827      *
1828      * @param viewId The id of the parent {@link ViewGroup} to remove all
1829      *            children from.
1830      */
removeAllViews(int viewId)1831     public void removeAllViews(int viewId) {
1832         addAction(new ViewGroupAction(viewId, null));
1833     }
1834 
1835     /**
1836      * Equivalent to calling {@link AdapterViewAnimator#showNext()}
1837      *
1838      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
1839      */
showNext(int viewId)1840     public void showNext(int viewId) {
1841         addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
1842     }
1843 
1844     /**
1845      * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
1846      *
1847      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
1848      */
showPrevious(int viewId)1849     public void showPrevious(int viewId) {
1850         addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
1851     }
1852 
1853     /**
1854      * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)}
1855      *
1856      * @param viewId The id of the view on which to call
1857      *               {@link AdapterViewAnimator#setDisplayedChild(int)}
1858      */
setDisplayedChild(int viewId, int childIndex)1859     public void setDisplayedChild(int viewId, int childIndex) {
1860         setInt(viewId, "setDisplayedChild", childIndex);
1861     }
1862 
1863     /**
1864      * Equivalent to calling View.setVisibility
1865      *
1866      * @param viewId The id of the view whose visibility should change
1867      * @param visibility The new visibility for the view
1868      */
setViewVisibility(int viewId, int visibility)1869     public void setViewVisibility(int viewId, int visibility) {
1870         setInt(viewId, "setVisibility", visibility);
1871     }
1872 
1873     /**
1874      * Equivalent to calling TextView.setText
1875      *
1876      * @param viewId The id of the view whose text should change
1877      * @param text The new text for the view
1878      */
setTextViewText(int viewId, CharSequence text)1879     public void setTextViewText(int viewId, CharSequence text) {
1880         setCharSequence(viewId, "setText", text);
1881     }
1882 
1883     /**
1884      * Equivalent to calling {@link TextView#setTextSize(int, float)}
1885      *
1886      * @param viewId The id of the view whose text size should change
1887      * @param units The units of size (e.g. COMPLEX_UNIT_SP)
1888      * @param size The size of the text
1889      */
setTextViewTextSize(int viewId, int units, float size)1890     public void setTextViewTextSize(int viewId, int units, float size) {
1891         addAction(new TextViewSizeAction(viewId, units, size));
1892     }
1893 
1894     /**
1895      * Equivalent to calling
1896      * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
1897      *
1898      * @param viewId The id of the view whose text should change
1899      * @param left The id of a drawable to place to the left of the text, or 0
1900      * @param top The id of a drawable to place above the text, or 0
1901      * @param right The id of a drawable to place to the right of the text, or 0
1902      * @param bottom The id of a drawable to place below the text, or 0
1903      */
setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom)1904     public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
1905         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
1906     }
1907 
1908     /**
1909      * Equivalent to calling {@link
1910      * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}.
1911      *
1912      * @param viewId The id of the view whose text should change
1913      * @param start The id of a drawable to place before the text (relative to the
1914      * layout direction), or 0
1915      * @param top The id of a drawable to place above the text, or 0
1916      * @param end The id of a drawable to place after the text, or 0
1917      * @param bottom The id of a drawable to place below the text, or 0
1918      */
setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom)1919     public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) {
1920         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
1921     }
1922 
1923     /**
1924      * Equivalent to calling ImageView.setImageResource
1925      *
1926      * @param viewId The id of the view whose drawable should change
1927      * @param srcId The new resource id for the drawable
1928      */
setImageViewResource(int viewId, int srcId)1929     public void setImageViewResource(int viewId, int srcId) {
1930         setInt(viewId, "setImageResource", srcId);
1931     }
1932 
1933     /**
1934      * Equivalent to calling ImageView.setImageURI
1935      *
1936      * @param viewId The id of the view whose drawable should change
1937      * @param uri The Uri for the image
1938      */
setImageViewUri(int viewId, Uri uri)1939     public void setImageViewUri(int viewId, Uri uri) {
1940         setUri(viewId, "setImageURI", uri);
1941     }
1942 
1943     /**
1944      * Equivalent to calling ImageView.setImageBitmap
1945      *
1946      * @param viewId The id of the view whose bitmap should change
1947      * @param bitmap The new Bitmap for the drawable
1948      */
setImageViewBitmap(int viewId, Bitmap bitmap)1949     public void setImageViewBitmap(int viewId, Bitmap bitmap) {
1950         setBitmap(viewId, "setImageBitmap", bitmap);
1951     }
1952 
1953     /**
1954      * Equivalent to calling AdapterView.setEmptyView
1955      *
1956      * @param viewId The id of the view on which to set the empty view
1957      * @param emptyViewId The view id of the empty view
1958      */
setEmptyView(int viewId, int emptyViewId)1959     public void setEmptyView(int viewId, int emptyViewId) {
1960         addAction(new SetEmptyView(viewId, emptyViewId));
1961     }
1962 
1963     /**
1964      * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
1965      * {@link Chronometer#setFormat Chronometer.setFormat},
1966      * and {@link Chronometer#start Chronometer.start()} or
1967      * {@link Chronometer#stop Chronometer.stop()}.
1968      *
1969      * @param viewId The id of the {@link Chronometer} to change
1970      * @param base The time at which the timer would have read 0:00.  This
1971      *             time should be based off of
1972      *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
1973      * @param format The Chronometer format string, or null to
1974      *               simply display the timer value.
1975      * @param started True if you want the clock to be started, false if not.
1976      */
setChronometer(int viewId, long base, String format, boolean started)1977     public void setChronometer(int viewId, long base, String format, boolean started) {
1978         setLong(viewId, "setBase", base);
1979         setString(viewId, "setFormat", format);
1980         setBoolean(viewId, "setStarted", started);
1981     }
1982 
1983     /**
1984      * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
1985      * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
1986      * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
1987      *
1988      * If indeterminate is true, then the values for max and progress are ignored.
1989      *
1990      * @param viewId The id of the {@link ProgressBar} to change
1991      * @param max The 100% value for the progress bar
1992      * @param progress The current value of the progress bar.
1993      * @param indeterminate True if the progress bar is indeterminate,
1994      *                false if not.
1995      */
setProgressBar(int viewId, int max, int progress, boolean indeterminate)1996     public void setProgressBar(int viewId, int max, int progress,
1997             boolean indeterminate) {
1998         setBoolean(viewId, "setIndeterminate", indeterminate);
1999         if (!indeterminate) {
2000             setInt(viewId, "setMax", max);
2001             setInt(viewId, "setProgress", progress);
2002         }
2003     }
2004 
2005     /**
2006      * Equivalent to calling
2007      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
2008      * to launch the provided {@link PendingIntent}.
2009      *
2010      * When setting the on-click action of items within collections (eg. {@link ListView},
2011      * {@link StackView} etc.), this method will not work. Instead, use {@link
2012      * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
2013      * RemoteViews#setOnClickFillInIntent(int, Intent).
2014      *
2015      * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
2016      * @param pendingIntent The {@link PendingIntent} to send when user clicks
2017      */
setOnClickPendingIntent(int viewId, PendingIntent pendingIntent)2018     public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
2019         addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
2020     }
2021 
2022     /**
2023      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
2024      * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
2025      * this method should be used to set a single PendingIntent template on the collection, and
2026      * individual items can differentiate their on-click behavior using
2027      * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
2028      *
2029      * @param viewId The id of the collection who's children will use this PendingIntent template
2030      *          when clicked
2031      * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
2032      *          by a child of viewId and executed when that child is clicked
2033      */
setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate)2034     public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
2035         addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
2036     }
2037 
2038     /**
2039      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
2040      * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
2041      * a single PendingIntent template can be set on the collection, see {@link
2042      * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
2043      * action of a given item can be distinguished by setting a fillInIntent on that item. The
2044      * fillInIntent is then combined with the PendingIntent template in order to determine the final
2045      * intent which will be executed when the item is clicked. This works as follows: any fields
2046      * which are left blank in the PendingIntent template, but are provided by the fillInIntent
2047      * will be overwritten, and the resulting PendingIntent will be used.
2048      *
2049      *
2050      * of the PendingIntent template will then be filled in with the associated fields that are
2051      * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
2052      *
2053      * @param viewId The id of the view on which to set the fillInIntent
2054      * @param fillInIntent The intent which will be combined with the parent's PendingIntent
2055      *        in order to determine the on-click behavior of the view specified by viewId
2056      */
setOnClickFillInIntent(int viewId, Intent fillInIntent)2057     public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
2058         addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
2059     }
2060 
2061     /**
2062      * @hide
2063      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
2064      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
2065      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
2066      * view.
2067      * <p>
2068      * You can omit specific calls by marking their values with null or -1.
2069      *
2070      * @param viewId The id of the view that contains the target
2071      *            {@link Drawable}
2072      * @param targetBackground If true, apply these parameters to the
2073      *            {@link Drawable} returned by
2074      *            {@link android.view.View#getBackground()}. Otherwise, assume
2075      *            the target view is an {@link ImageView} and apply them to
2076      *            {@link ImageView#getDrawable()}.
2077      * @param alpha Specify an alpha value for the drawable, or -1 to leave
2078      *            unchanged.
2079      * @param colorFilter Specify a color for a
2080      *            {@link android.graphics.ColorFilter} for this drawable, or -1
2081      *            to leave unchanged.
2082      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
2083      *            unchanged.
2084      * @param level Specify the level for the drawable, or -1 to leave
2085      *            unchanged.
2086      */
setDrawableParameters(int viewId, boolean targetBackground, int alpha, int colorFilter, PorterDuff.Mode mode, int level)2087     public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
2088             int colorFilter, PorterDuff.Mode mode, int level) {
2089         addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
2090                 colorFilter, mode, level));
2091     }
2092 
2093     /**
2094      * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
2095      *
2096      * @param viewId The id of the view whose text color should change
2097      * @param color Sets the text color for all the states (normal, selected,
2098      *            focused) to be this color.
2099      */
setTextColor(int viewId, int color)2100     public void setTextColor(int viewId, int color) {
2101         setInt(viewId, "setTextColor", color);
2102     }
2103 
2104     /**
2105      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
2106      *
2107      * @param appWidgetId The id of the app widget which contains the specified view. (This
2108      *      parameter is ignored in this deprecated method)
2109      * @param viewId The id of the {@link AdapterView}
2110      * @param intent The intent of the service which will be
2111      *            providing data to the RemoteViewsAdapter
2112      * @deprecated This method has been deprecated. See
2113      *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
2114      */
2115     @Deprecated
setRemoteAdapter(int appWidgetId, int viewId, Intent intent)2116     public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
2117         setRemoteAdapter(viewId, intent);
2118     }
2119 
2120     /**
2121      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
2122      * Can only be used for App Widgets.
2123      *
2124      * @param viewId The id of the {@link AdapterView}
2125      * @param intent The intent of the service which will be
2126      *            providing data to the RemoteViewsAdapter
2127      */
setRemoteAdapter(int viewId, Intent intent)2128     public void setRemoteAdapter(int viewId, Intent intent) {
2129         addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
2130     }
2131 
2132     /**
2133      * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
2134      * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
2135      * This is a simpler but less flexible approach to populating collection widgets. Its use is
2136      * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
2137      * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
2138      * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
2139      * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
2140      *
2141      * This API is supported in the compatibility library for previous API levels, see
2142      * RemoteViewsCompat.
2143      *
2144      * @param viewId The id of the {@link AdapterView}
2145      * @param list The list of RemoteViews which will populate the view specified by viewId.
2146      * @param viewTypeCount The maximum number of unique layout id's used to construct the list of
2147      *      RemoteViews. This count cannot change during the life-cycle of a given widget, so this
2148      *      parameter should account for the maximum possible number of types that may appear in the
2149      *      See {@link Adapter#getViewTypeCount()}.
2150      *
2151      * @hide
2152      */
setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount)2153     public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
2154         addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
2155     }
2156 
2157     /**
2158      * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
2159      *
2160      * @param viewId The id of the view to change
2161      * @param position Scroll to this adapter position
2162      */
setScrollPosition(int viewId, int position)2163     public void setScrollPosition(int viewId, int position) {
2164         setInt(viewId, "smoothScrollToPosition", position);
2165     }
2166 
2167     /**
2168      * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
2169      *
2170      * @param viewId The id of the view to change
2171      * @param offset Scroll by this adapter position offset
2172      */
setRelativeScrollPosition(int viewId, int offset)2173     public void setRelativeScrollPosition(int viewId, int offset) {
2174         setInt(viewId, "smoothScrollByOffset", offset);
2175     }
2176 
2177     /**
2178      * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}.
2179      *
2180      * @param viewId The id of the view to change
2181      * @param left the left padding in pixels
2182      * @param top the top padding in pixels
2183      * @param right the right padding in pixels
2184      * @param bottom the bottom padding in pixels
2185      */
setViewPadding(int viewId, int left, int top, int right, int bottom)2186     public void setViewPadding(int viewId, int left, int top, int right, int bottom) {
2187         addAction(new ViewPaddingAction(viewId, left, top, right, bottom));
2188     }
2189 
2190     /**
2191      * Call a method taking one boolean on a view in the layout for this RemoteViews.
2192      *
2193      * @param viewId The id of the view on which to call the method.
2194      * @param methodName The name of the method to call.
2195      * @param value The value to pass to the method.
2196      */
setBoolean(int viewId, String methodName, boolean value)2197     public void setBoolean(int viewId, String methodName, boolean value) {
2198         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
2199     }
2200 
2201     /**
2202      * Call a method taking one byte on a view in the layout for this RemoteViews.
2203      *
2204      * @param viewId The id of the view on which to call the method.
2205      * @param methodName The name of the method to call.
2206      * @param value The value to pass to the method.
2207      */
setByte(int viewId, String methodName, byte value)2208     public void setByte(int viewId, String methodName, byte value) {
2209         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
2210     }
2211 
2212     /**
2213      * Call a method taking one short on a view in the layout for this RemoteViews.
2214      *
2215      * @param viewId The id of the view on which to call the method.
2216      * @param methodName The name of the method to call.
2217      * @param value The value to pass to the method.
2218      */
setShort(int viewId, String methodName, short value)2219     public void setShort(int viewId, String methodName, short value) {
2220         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
2221     }
2222 
2223     /**
2224      * Call a method taking one int on a view in the layout for this RemoteViews.
2225      *
2226      * @param viewId The id of the view on which to call the method.
2227      * @param methodName The name of the method to call.
2228      * @param value The value to pass to the method.
2229      */
setInt(int viewId, String methodName, int value)2230     public void setInt(int viewId, String methodName, int value) {
2231         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
2232     }
2233 
2234     /**
2235      * Call a method taking one long on a view in the layout for this RemoteViews.
2236      *
2237      * @param viewId The id of the view on which to call the method.
2238      * @param methodName The name of the method to call.
2239      * @param value The value to pass to the method.
2240      */
setLong(int viewId, String methodName, long value)2241     public void setLong(int viewId, String methodName, long value) {
2242         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
2243     }
2244 
2245     /**
2246      * Call a method taking one float on a view in the layout for this RemoteViews.
2247      *
2248      * @param viewId The id of the view on which to call the method.
2249      * @param methodName The name of the method to call.
2250      * @param value The value to pass to the method.
2251      */
setFloat(int viewId, String methodName, float value)2252     public void setFloat(int viewId, String methodName, float value) {
2253         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
2254     }
2255 
2256     /**
2257      * Call a method taking one double on a view in the layout for this RemoteViews.
2258      *
2259      * @param viewId The id of the view on which to call the method.
2260      * @param methodName The name of the method to call.
2261      * @param value The value to pass to the method.
2262      */
setDouble(int viewId, String methodName, double value)2263     public void setDouble(int viewId, String methodName, double value) {
2264         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
2265     }
2266 
2267     /**
2268      * Call a method taking one char on a view in the layout for this RemoteViews.
2269      *
2270      * @param viewId The id of the view on which to call the method.
2271      * @param methodName The name of the method to call.
2272      * @param value The value to pass to the method.
2273      */
setChar(int viewId, String methodName, char value)2274     public void setChar(int viewId, String methodName, char value) {
2275         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
2276     }
2277 
2278     /**
2279      * Call a method taking one String on a view in the layout for this RemoteViews.
2280      *
2281      * @param viewId The id of the view on which to call the method.
2282      * @param methodName The name of the method to call.
2283      * @param value The value to pass to the method.
2284      */
setString(int viewId, String methodName, String value)2285     public void setString(int viewId, String methodName, String value) {
2286         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
2287     }
2288 
2289     /**
2290      * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
2291      *
2292      * @param viewId The id of the view on which to call the method.
2293      * @param methodName The name of the method to call.
2294      * @param value The value to pass to the method.
2295      */
setCharSequence(int viewId, String methodName, CharSequence value)2296     public void setCharSequence(int viewId, String methodName, CharSequence value) {
2297         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
2298     }
2299 
2300     /**
2301      * Call a method taking one Uri on a view in the layout for this RemoteViews.
2302      *
2303      * @param viewId The id of the view on which to call the method.
2304      * @param methodName The name of the method to call.
2305      * @param value The value to pass to the method.
2306      */
setUri(int viewId, String methodName, Uri value)2307     public void setUri(int viewId, String methodName, Uri value) {
2308         if (value != null) {
2309             // Resolve any filesystem path before sending remotely
2310             value = value.getCanonicalUri();
2311             if (StrictMode.vmFileUriExposureEnabled()) {
2312                 value.checkFileUriExposed("RemoteViews.setUri()");
2313             }
2314         }
2315         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
2316     }
2317 
2318     /**
2319      * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
2320      * @more
2321      * <p class="note">The bitmap will be flattened into the parcel if this object is
2322      * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
2323      *
2324      * @param viewId The id of the view on which to call the method.
2325      * @param methodName The name of the method to call.
2326      * @param value The value to pass to the method.
2327      */
setBitmap(int viewId, String methodName, Bitmap value)2328     public void setBitmap(int viewId, String methodName, Bitmap value) {
2329         addAction(new BitmapReflectionAction(viewId, methodName, value));
2330     }
2331 
2332     /**
2333      * Call a method taking one Bundle on a view in the layout for this RemoteViews.
2334      *
2335      * @param viewId The id of the view on which to call the method.
2336      * @param methodName The name of the method to call.
2337      * @param value The value to pass to the method.
2338      */
setBundle(int viewId, String methodName, Bundle value)2339     public void setBundle(int viewId, String methodName, Bundle value) {
2340         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
2341     }
2342 
2343     /**
2344      * Call a method taking one Intent on a view in the layout for this RemoteViews.
2345      *
2346      * @param viewId The id of the view on which to call the method.
2347      * @param methodName The name of the method to call.
2348      * @param value The {@link android.content.Intent} to pass the method.
2349      */
setIntent(int viewId, String methodName, Intent value)2350     public void setIntent(int viewId, String methodName, Intent value) {
2351         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
2352     }
2353 
2354     /**
2355      * Equivalent to calling View.setContentDescription(CharSequence).
2356      *
2357      * @param viewId The id of the view whose content description should change.
2358      * @param contentDescription The new content description for the view.
2359      */
setContentDescription(int viewId, CharSequence contentDescription)2360     public void setContentDescription(int viewId, CharSequence contentDescription) {
2361         setCharSequence(viewId, "setContentDescription", contentDescription);
2362     }
2363 
2364     /**
2365      * Equivalent to calling View.setLabelFor(int).
2366      *
2367      * @param viewId The id of the view whose property to set.
2368      * @param labeledId The id of a view for which this view serves as a label.
2369      */
setLabelFor(int viewId, int labeledId)2370     public void setLabelFor(int viewId, int labeledId) {
2371         setInt(viewId, "setLabelFor", labeledId);
2372     }
2373 
getRemoteViewsToApply(Context context)2374     private RemoteViews getRemoteViewsToApply(Context context) {
2375         if (hasLandscapeAndPortraitLayouts()) {
2376             int orientation = context.getResources().getConfiguration().orientation;
2377             if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
2378                 return mLandscape;
2379             } else {
2380                 return mPortrait;
2381             }
2382         }
2383         return this;
2384     }
2385 
2386     /**
2387      * Inflates the view hierarchy represented by this object and applies
2388      * all of the actions.
2389      *
2390      * <p><strong>Caller beware: this may throw</strong>
2391      *
2392      * @param context Default context to use
2393      * @param parent Parent that the resulting view hierarchy will be attached to. This method
2394      * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
2395      * @return The inflated view hierarchy
2396      */
apply(Context context, ViewGroup parent)2397     public View apply(Context context, ViewGroup parent) {
2398         return apply(context, parent, null);
2399     }
2400 
2401     /** @hide */
apply(Context context, ViewGroup parent, OnClickHandler handler)2402     public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
2403         RemoteViews rvToApply = getRemoteViewsToApply(context);
2404 
2405         View result;
2406 
2407         Context c = prepareContext(context);
2408 
2409         LayoutInflater inflater = (LayoutInflater)
2410                 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
2411 
2412         inflater = inflater.cloneInContext(c);
2413         inflater.setFilter(this);
2414 
2415         result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
2416 
2417         rvToApply.performApply(result, parent, handler);
2418 
2419         return result;
2420     }
2421 
2422     /**
2423      * Applies all of the actions to the provided view.
2424      *
2425      * <p><strong>Caller beware: this may throw</strong>
2426      *
2427      * @param v The view to apply the actions to.  This should be the result of
2428      * the {@link #apply(Context,ViewGroup)} call.
2429      */
reapply(Context context, View v)2430     public void reapply(Context context, View v) {
2431         reapply(context, v, null);
2432     }
2433 
2434     /** @hide */
reapply(Context context, View v, OnClickHandler handler)2435     public void reapply(Context context, View v, OnClickHandler handler) {
2436         RemoteViews rvToApply = getRemoteViewsToApply(context);
2437 
2438         // In the case that a view has this RemoteViews applied in one orientation, is persisted
2439         // across orientation change, and has the RemoteViews re-applied in the new orientation,
2440         // we throw an exception, since the layouts may be completely unrelated.
2441         if (hasLandscapeAndPortraitLayouts()) {
2442             if (v.getId() != rvToApply.getLayoutId()) {
2443                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
2444                         " that does not share the same root layout id.");
2445             }
2446         }
2447 
2448         prepareContext(context);
2449         rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
2450     }
2451 
performApply(View v, ViewGroup parent, OnClickHandler handler)2452     private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
2453         if (mActions != null) {
2454             handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
2455             final int count = mActions.size();
2456             for (int i = 0; i < count; i++) {
2457                 Action a = mActions.get(i);
2458                 a.apply(v, parent, handler);
2459             }
2460         }
2461     }
2462 
prepareContext(Context context)2463     private Context prepareContext(Context context) {
2464         Context c;
2465         String packageName = mPackage;
2466 
2467         if (packageName != null) {
2468             try {
2469                 c = context.createPackageContextAsUser(
2470                         packageName, Context.CONTEXT_RESTRICTED, mUser);
2471             } catch (NameNotFoundException e) {
2472                 Log.e(LOG_TAG, "Package name " + packageName + " not found");
2473                 c = context;
2474             }
2475         } else {
2476             c = context;
2477         }
2478 
2479         return c;
2480     }
2481 
2482     /* (non-Javadoc)
2483      * Used to restrict the views which can be inflated
2484      *
2485      * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
2486      */
onLoadClass(Class clazz)2487     public boolean onLoadClass(Class clazz) {
2488         return clazz.isAnnotationPresent(RemoteView.class);
2489     }
2490 
describeContents()2491     public int describeContents() {
2492         return 0;
2493     }
2494 
writeToParcel(Parcel dest, int flags)2495     public void writeToParcel(Parcel dest, int flags) {
2496         if (!hasLandscapeAndPortraitLayouts()) {
2497             dest.writeInt(MODE_NORMAL);
2498             // We only write the bitmap cache if we are the root RemoteViews, as this cache
2499             // is shared by all children.
2500             if (mIsRoot) {
2501                 mBitmapCache.writeBitmapsToParcel(dest, flags);
2502             }
2503             dest.writeString(mPackage);
2504             dest.writeInt(mLayoutId);
2505             dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
2506             int count;
2507             if (mActions != null) {
2508                 count = mActions.size();
2509             } else {
2510                 count = 0;
2511             }
2512             dest.writeInt(count);
2513             for (int i=0; i<count; i++) {
2514                 Action a = mActions.get(i);
2515                 a.writeToParcel(dest, 0);
2516             }
2517         } else {
2518             dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
2519             // We only write the bitmap cache if we are the root RemoteViews, as this cache
2520             // is shared by all children.
2521             if (mIsRoot) {
2522                 mBitmapCache.writeBitmapsToParcel(dest, flags);
2523             }
2524             mLandscape.writeToParcel(dest, flags);
2525             mPortrait.writeToParcel(dest, flags);
2526         }
2527     }
2528 
2529     /**
2530      * Parcelable.Creator that instantiates RemoteViews objects
2531      */
2532     public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
2533         public RemoteViews createFromParcel(Parcel parcel) {
2534             return new RemoteViews(parcel);
2535         }
2536 
2537         public RemoteViews[] newArray(int size) {
2538             return new RemoteViews[size];
2539         }
2540     };
2541 }
2542