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