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