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