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