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