Home | History | Annotate | Download | only in widget
      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 {
    146         public ActionException(Exception ex) {
    147             super(ex);
    148         }
    149         public ActionException(String message) {
    150             super(message);
    151         }
    152     }
    153 
    154     /** @hide */
    155     public static class OnClickHandler {
    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 {
    187         public abstract void apply(View root, ViewGroup rootParent,
    188                 OnClickHandler handler) throws ActionException;
    189 
    190         public int describeContents() {
    191             return 0;
    192         }
    193 
    194         /**
    195          * Overridden by each class to report on it's own memory usage
    196          */
    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 
    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 
    214         SetEmptyView(int viewId, int emptyViewId) {
    215             this.viewId = viewId;
    216             this.emptyViewId = emptyViewId;
    217         }
    218 
    219         SetEmptyView(Parcel in) {
    220             this.viewId = in.readInt();
    221             this.emptyViewId = in.readInt();
    222         }
    223 
    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
    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 {
    245         public SetOnClickFillInIntent(int id, Intent fillInIntent) {
    246             this.viewId = id;
    247             this.fillInIntent = fillInIntent;
    248         }
    249 
    250         public SetOnClickFillInIntent(Parcel parcel) {
    251             viewId = parcel.readInt();
    252             fillInIntent = Intent.CREATOR.createFromParcel(parcel);
    253         }
    254 
    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
    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 {
    326         public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
    327             this.viewId = id;
    328             this.pendingIntentTemplate = pendingIntentTemplate;
    329         }
    330 
    331         public SetPendingIntentTemplate(Parcel parcel) {
    332             viewId = parcel.readInt();
    333             pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
    334         }
    335 
    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
    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 {
    409         public SetRemoteViewsAdapterIntent(int id, Intent intent) {
    410             this.viewId = id;
    411             this.intent = intent;
    412         }
    413 
    414         public SetRemoteViewsAdapterIntent(Parcel parcel) {
    415             viewId = parcel.readInt();
    416             intent = Intent.CREATOR.createFromParcel(parcel);
    417         }
    418 
    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
    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 {
    468         public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
    469             this.viewId = id;
    470             this.pendingIntent = pendingIntent;
    471         }
    472 
    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 
    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
    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 {
    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 
    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 
    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
    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 
    644         ReflectionActionWithoutParams(int viewId, String methodName) {
    645             this.viewId = viewId;
    646             this.methodName = methodName;
    647         }
    648 
    649         ReflectionActionWithoutParams(Parcel in) {
    650             this.viewId = in.readInt();
    651             this.methodName = in.readString();
    652         }
    653 
    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
    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 
    696         public BitmapCache() {
    697             mBitmaps = new ArrayList<Bitmap>();
    698         }
    699 
    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 
    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 
    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 
    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 
    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 
    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 
    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 
    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
    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
    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
    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 
    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 
    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 
    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 
    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
   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
   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 
   1060     private void configureRemoteViewsAsChild(RemoteViews rv) {
   1061         mBitmapCache.assimilate(rv.mBitmapCache);
   1062         rv.setBitmapCache(mBitmapCache);
   1063         rv.setNotRoot();
   1064     }
   1065 
   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 {
   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 
   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 
   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
   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
   1121         public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
   1122             if (nestedViews != null) {
   1123                 counter.increment(nestedViews.estimateMemoryUsage());
   1124             }
   1125         }
   1126 
   1127         @Override
   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 {
   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 
   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 
   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
   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 {
   1196         public TextViewSizeAction(int viewId, int units, float size) {
   1197             this.viewId = viewId;
   1198             this.units = units;
   1199             this.size = size;
   1200         }
   1201 
   1202         public TextViewSizeAction(Parcel parcel) {
   1203             viewId = parcel.readInt();
   1204             units = parcel.readInt();
   1205             size  = parcel.readFloat();
   1206         }
   1207 
   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
   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 {
   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 
   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 
   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
   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 {
   1278         public void clear() {
   1279             mMemoryUsage = 0;
   1280         }
   1281 
   1282         public void increment(int numBytes) {
   1283             mMemoryUsage += numBytes;
   1284         }
   1285 
   1286         public int getMemoryUsage() {
   1287             return mMemoryUsage;
   1288         }
   1289 
   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      */
   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 
   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      */
   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      */
   1370     public RemoteViews(Parcel parcel) {
   1371         this(parcel, null);
   1372     }
   1373 
   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
   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 
   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      */
   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      */
   1494     void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
   1495         mIsWidgetCollectionChild = isWidgetCollectionChild;
   1496     }
   1497 
   1498     /**
   1499      * Updates the memory usage statistics.
   1500      */
   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      */
   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 */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   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      */
   2082     public void setContentDescription(int viewId, CharSequence contentDescription) {
   2083         setCharSequence(viewId, "setContentDescription", contentDescription);
   2084     }
   2085 
   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      */
   2109     public View apply(Context context, ViewGroup parent) {
   2110         return apply(context, parent, DEFAULT_ON_CLICK_HANDLER);
   2111     }
   2112 
   2113     /** @hide */
   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      */
   2142     public void reapply(Context context, View v) {
   2143         reapply(context, v, DEFAULT_ON_CLICK_HANDLER);
   2144     }
   2145 
   2146     /** @hide */
   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 
   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 
   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      */
   2197     public boolean onLoadClass(Class clazz) {
   2198         return clazz.isAnnotationPresent(RemoteView.class);
   2199     }
   2200 
   2201     public int describeContents() {
   2202         return 0;
   2203     }
   2204 
   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