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