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.StrictMode;
     38 import android.os.UserHandle;
     39 import android.text.TextUtils;
     40 import android.util.Log;
     41 import android.view.LayoutInflater;
     42 import android.view.LayoutInflater.Filter;
     43 import android.view.RemotableViewMethod;
     44 import android.view.View;
     45 import android.view.View.OnClickListener;
     46 import android.view.ViewGroup;
     47 import android.widget.AdapterView.OnItemClickListener;
     48 
     49 import java.lang.annotation.ElementType;
     50 import java.lang.annotation.Retention;
     51 import java.lang.annotation.RetentionPolicy;
     52 import java.lang.annotation.Target;
     53 import java.lang.reflect.Method;
     54 import java.util.ArrayList;
     55 import java.util.HashMap;
     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(LOG_TAG, "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(LOG_TAG, "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(LOG_TAG, "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(LOG_TAG, "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 SetRemoteViewsAdapterList extends Action {
    491         public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
    492             this.viewId = id;
    493             this.list = list;
    494             this.viewTypeCount = viewTypeCount;
    495         }
    496 
    497         public SetRemoteViewsAdapterList(Parcel parcel) {
    498             viewId = parcel.readInt();
    499             viewTypeCount = parcel.readInt();
    500             int count = parcel.readInt();
    501             list = new ArrayList<RemoteViews>();
    502 
    503             for (int i = 0; i < count; i++) {
    504                 RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
    505                 list.add(rv);
    506             }
    507         }
    508 
    509         public void writeToParcel(Parcel dest, int flags) {
    510             dest.writeInt(TAG);
    511             dest.writeInt(viewId);
    512             dest.writeInt(viewTypeCount);
    513 
    514             if (list == null || list.size() == 0) {
    515                 dest.writeInt(0);
    516             } else {
    517                 int count = list.size();
    518                 dest.writeInt(count);
    519                 for (int i = 0; i < count; i++) {
    520                     RemoteViews rv = list.get(i);
    521                     rv.writeToParcel(dest, flags);
    522                 }
    523             }
    524         }
    525 
    526         @Override
    527         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    528             final View target = root.findViewById(viewId);
    529             if (target == null) return;
    530 
    531             // Ensure that we are applying to an AppWidget root
    532             if (!(rootParent instanceof AppWidgetHostView)) {
    533                 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
    534                         "AppWidgets (root id: " + viewId + ")");
    535                 return;
    536             }
    537             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
    538             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
    539                 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
    540                         "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
    541                 return;
    542             }
    543 
    544             if (target instanceof AbsListView) {
    545                 AbsListView v = (AbsListView) target;
    546                 Adapter a = v.getAdapter();
    547                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
    548                     ((RemoteViewsListAdapter) a).setViewsList(list);
    549                 } else {
    550                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
    551                 }
    552             } else if (target instanceof AdapterViewAnimator) {
    553                 AdapterViewAnimator v = (AdapterViewAnimator) target;
    554                 Adapter a = v.getAdapter();
    555                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
    556                     ((RemoteViewsListAdapter) a).setViewsList(list);
    557                 } else {
    558                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
    559                 }
    560             }
    561         }
    562 
    563         public String getActionName() {
    564             return "SetRemoteViewsAdapterList";
    565         }
    566 
    567         int viewTypeCount;
    568         ArrayList<RemoteViews> list;
    569         public final static int TAG = 15;
    570     }
    571 
    572     private class SetRemoteViewsAdapterIntent extends Action {
    573         public SetRemoteViewsAdapterIntent(int id, Intent intent) {
    574             this.viewId = id;
    575             this.intent = intent;
    576         }
    577 
    578         public SetRemoteViewsAdapterIntent(Parcel parcel) {
    579             viewId = parcel.readInt();
    580             intent = Intent.CREATOR.createFromParcel(parcel);
    581         }
    582 
    583         public void writeToParcel(Parcel dest, int flags) {
    584             dest.writeInt(TAG);
    585             dest.writeInt(viewId);
    586             intent.writeToParcel(dest, flags);
    587         }
    588 
    589         @Override
    590         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    591             final View target = root.findViewById(viewId);
    592             if (target == null) return;
    593 
    594             // Ensure that we are applying to an AppWidget root
    595             if (!(rootParent instanceof AppWidgetHostView)) {
    596                 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
    597                         "AppWidgets (root id: " + viewId + ")");
    598                 return;
    599             }
    600             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
    601             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
    602                 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
    603                         "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
    604                 return;
    605             }
    606 
    607             // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
    608             // RemoteViewsService
    609             AppWidgetHostView host = (AppWidgetHostView) rootParent;
    610             intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
    611             if (target instanceof AbsListView) {
    612                 AbsListView v = (AbsListView) target;
    613                 v.setRemoteViewsAdapter(intent);
    614                 v.setRemoteViewsOnClickHandler(handler);
    615             } else if (target instanceof AdapterViewAnimator) {
    616                 AdapterViewAnimator v = (AdapterViewAnimator) target;
    617                 v.setRemoteViewsAdapter(intent);
    618                 v.setRemoteViewsOnClickHandler(handler);
    619             }
    620         }
    621 
    622         public String getActionName() {
    623             return "SetRemoteViewsAdapterIntent";
    624         }
    625 
    626         Intent intent;
    627 
    628         public final static int TAG = 10;
    629     }
    630 
    631     /**
    632      * Equivalent to calling
    633      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
    634      * to launch the provided {@link PendingIntent}.
    635      */
    636     private class SetOnClickPendingIntent extends Action {
    637         public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
    638             this.viewId = id;
    639             this.pendingIntent = pendingIntent;
    640         }
    641 
    642         public SetOnClickPendingIntent(Parcel parcel) {
    643             viewId = parcel.readInt();
    644 
    645             // We check a flag to determine if the parcel contains a PendingIntent.
    646             if (parcel.readInt() != 0) {
    647                 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
    648             }
    649         }
    650 
    651         public void writeToParcel(Parcel dest, int flags) {
    652             dest.writeInt(TAG);
    653             dest.writeInt(viewId);
    654 
    655             // We use a flag to indicate whether the parcel contains a valid object.
    656             dest.writeInt(pendingIntent != null ? 1 : 0);
    657             if (pendingIntent != null) {
    658                 pendingIntent.writeToParcel(dest, 0 /* no flags */);
    659             }
    660         }
    661 
    662         @Override
    663         public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
    664             final View target = root.findViewById(viewId);
    665             if (target == null) return;
    666 
    667             // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
    668             // sense, do they mean to set a PendingIntent template for the AdapterView's children?
    669             if (mIsWidgetCollectionChild) {
    670                 Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
    671                         "(id: " + viewId + ")");
    672                 ApplicationInfo appInfo = root.getContext().getApplicationInfo();
    673 
    674                 // We let this slide for HC and ICS so as to not break compatibility. It should have
    675                 // been disabled from the outset, but was left open by accident.
    676                 if (appInfo != null &&
    677                         appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
    678                     return;
    679                 }
    680             }
    681 
    682             if (target != null) {
    683                 // If the pendingIntent is null, we clear the onClickListener
    684                 OnClickListener listener = null;
    685                 if (pendingIntent != null) {
    686                     listener = new OnClickListener() {
    687                         public void onClick(View v) {
    688                             // Find target view location in screen coordinates and
    689                             // fill into PendingIntent before sending.
    690                             final float appScale = v.getContext().getResources()
    691                                     .getCompatibilityInfo().applicationScale;
    692                             final int[] pos = new int[2];
    693                             v.getLocationOnScreen(pos);
    694 
    695                             final Rect rect = new Rect();
    696                             rect.left = (int) (pos[0] * appScale + 0.5f);
    697                             rect.top = (int) (pos[1] * appScale + 0.5f);
    698                             rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
    699                             rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
    700 
    701                             final Intent intent = new Intent();
    702                             intent.setSourceBounds(rect);
    703                             handler.onClickHandler(v, pendingIntent, intent);
    704                         }
    705                     };
    706                 }
    707                 target.setOnClickListener(listener);
    708             }
    709         }
    710 
    711         public String getActionName() {
    712             return "SetOnClickPendingIntent";
    713         }
    714 
    715         PendingIntent pendingIntent;
    716 
    717         public final static int TAG = 1;
    718     }
    719 
    720     /**
    721      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
    722      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
    723      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
    724      * <p>
    725      * These operations will be performed on the {@link Drawable} returned by the
    726      * target {@link View#getBackground()} by default.  If targetBackground is false,
    727      * we assume the target is an {@link ImageView} and try applying the operations
    728      * to {@link ImageView#getDrawable()}.
    729      * <p>
    730      * You can omit specific calls by marking their values with null or -1.
    731      */
    732     private class SetDrawableParameters extends Action {
    733         public SetDrawableParameters(int id, boolean targetBackground, int alpha,
    734                 int colorFilter, PorterDuff.Mode mode, int level) {
    735             this.viewId = id;
    736             this.targetBackground = targetBackground;
    737             this.alpha = alpha;
    738             this.colorFilter = colorFilter;
    739             this.filterMode = mode;
    740             this.level = level;
    741         }
    742 
    743         public SetDrawableParameters(Parcel parcel) {
    744             viewId = parcel.readInt();
    745             targetBackground = parcel.readInt() != 0;
    746             alpha = parcel.readInt();
    747             colorFilter = parcel.readInt();
    748             boolean hasMode = parcel.readInt() != 0;
    749             if (hasMode) {
    750                 filterMode = PorterDuff.Mode.valueOf(parcel.readString());
    751             } else {
    752                 filterMode = null;
    753             }
    754             level = parcel.readInt();
    755         }
    756 
    757         public void writeToParcel(Parcel dest, int flags) {
    758             dest.writeInt(TAG);
    759             dest.writeInt(viewId);
    760             dest.writeInt(targetBackground ? 1 : 0);
    761             dest.writeInt(alpha);
    762             dest.writeInt(colorFilter);
    763             if (filterMode != null) {
    764                 dest.writeInt(1);
    765                 dest.writeString(filterMode.toString());
    766             } else {
    767                 dest.writeInt(0);
    768             }
    769             dest.writeInt(level);
    770         }
    771 
    772         @Override
    773         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    774             final View target = root.findViewById(viewId);
    775             if (target == null) return;
    776 
    777             // Pick the correct drawable to modify for this view
    778             Drawable targetDrawable = null;
    779             if (targetBackground) {
    780                 targetDrawable = target.getBackground();
    781             } else if (target instanceof ImageView) {
    782                 ImageView imageView = (ImageView) target;
    783                 targetDrawable = imageView.getDrawable();
    784             }
    785 
    786             if (targetDrawable != null) {
    787                 // Perform modifications only if values are set correctly
    788                 if (alpha != -1) {
    789                     targetDrawable.setAlpha(alpha);
    790                 }
    791                 if (colorFilter != -1 && filterMode != null) {
    792                     targetDrawable.setColorFilter(colorFilter, filterMode);
    793                 }
    794                 if (level != -1) {
    795                     targetDrawable.setLevel(level);
    796                 }
    797             }
    798         }
    799 
    800         public String getActionName() {
    801             return "SetDrawableParameters";
    802         }
    803 
    804         boolean targetBackground;
    805         int alpha;
    806         int colorFilter;
    807         PorterDuff.Mode filterMode;
    808         int level;
    809 
    810         public final static int TAG = 3;
    811     }
    812 
    813     private class ReflectionActionWithoutParams extends Action {
    814         String methodName;
    815 
    816         public final static int TAG = 5;
    817 
    818         ReflectionActionWithoutParams(int viewId, String methodName) {
    819             this.viewId = viewId;
    820             this.methodName = methodName;
    821         }
    822 
    823         ReflectionActionWithoutParams(Parcel in) {
    824             this.viewId = in.readInt();
    825             this.methodName = in.readString();
    826         }
    827 
    828         public void writeToParcel(Parcel out, int flags) {
    829             out.writeInt(TAG);
    830             out.writeInt(this.viewId);
    831             out.writeString(this.methodName);
    832         }
    833 
    834         @Override
    835         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    836             final View view = root.findViewById(viewId);
    837             if (view == null) return;
    838 
    839             Class klass = view.getClass();
    840             Method method;
    841             try {
    842                 method = klass.getMethod(this.methodName);
    843             } catch (NoSuchMethodException ex) {
    844                 throw new ActionException("view: " + klass.getName() + " doesn't have method: "
    845                         + this.methodName + "()");
    846             }
    847 
    848             if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
    849                 throw new ActionException("view: " + klass.getName()
    850                         + " can't use method with RemoteViews: "
    851                         + this.methodName + "()");
    852             }
    853 
    854             try {
    855                 //noinspection ConstantIfStatement
    856                 if (false) {
    857                     Log.d(LOG_TAG, "view: " + klass.getName() + " calling method: "
    858                         + this.methodName + "()");
    859                 }
    860                 method.invoke(view);
    861             } catch (Exception ex) {
    862                 throw new ActionException(ex);
    863             }
    864         }
    865 
    866         public int mergeBehavior() {
    867             // we don't need to build up showNext or showPrevious calls
    868             if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
    869                 return MERGE_IGNORE;
    870             } else {
    871                 return MERGE_REPLACE;
    872             }
    873         }
    874 
    875         public String getActionName() {
    876             return "ReflectionActionWithoutParams";
    877         }
    878     }
    879 
    880     private static class BitmapCache {
    881         ArrayList<Bitmap> mBitmaps;
    882 
    883         public BitmapCache() {
    884             mBitmaps = new ArrayList<Bitmap>();
    885         }
    886 
    887         public BitmapCache(Parcel source) {
    888             int count = source.readInt();
    889             mBitmaps = new ArrayList<Bitmap>();
    890             for (int i = 0; i < count; i++) {
    891                 Bitmap b = Bitmap.CREATOR.createFromParcel(source);
    892                 mBitmaps.add(b);
    893             }
    894         }
    895 
    896         public int getBitmapId(Bitmap b) {
    897             if (b == null) {
    898                 return -1;
    899             } else {
    900                 if (mBitmaps.contains(b)) {
    901                     return mBitmaps.indexOf(b);
    902                 } else {
    903                     mBitmaps.add(b);
    904                     return (mBitmaps.size() - 1);
    905                 }
    906             }
    907         }
    908 
    909         public Bitmap getBitmapForId(int id) {
    910             if (id == -1 || id >= mBitmaps.size()) {
    911                 return null;
    912             } else {
    913                 return mBitmaps.get(id);
    914             }
    915         }
    916 
    917         public void writeBitmapsToParcel(Parcel dest, int flags) {
    918             int count = mBitmaps.size();
    919             dest.writeInt(count);
    920             for (int i = 0; i < count; i++) {
    921                 mBitmaps.get(i).writeToParcel(dest, flags);
    922             }
    923         }
    924 
    925         public void assimilate(BitmapCache bitmapCache) {
    926             ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps;
    927             int count = bitmapsToBeAdded.size();
    928             for (int i = 0; i < count; i++) {
    929                 Bitmap b = bitmapsToBeAdded.get(i);
    930                 if (!mBitmaps.contains(b)) {
    931                     mBitmaps.add(b);
    932                 }
    933             }
    934         }
    935 
    936         public void addBitmapMemory(MemoryUsageCounter memoryCounter) {
    937             for (int i = 0; i < mBitmaps.size(); i++) {
    938                 memoryCounter.addBitmapMemory(mBitmaps.get(i));
    939             }
    940         }
    941     }
    942 
    943     private class BitmapReflectionAction extends Action {
    944         int bitmapId;
    945         Bitmap bitmap;
    946         String methodName;
    947 
    948         BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {
    949             this.bitmap = bitmap;
    950             this.viewId = viewId;
    951             this.methodName = methodName;
    952             bitmapId = mBitmapCache.getBitmapId(bitmap);
    953         }
    954 
    955         BitmapReflectionAction(Parcel in) {
    956             viewId = in.readInt();
    957             methodName = in.readString();
    958             bitmapId = in.readInt();
    959             bitmap = mBitmapCache.getBitmapForId(bitmapId);
    960         }
    961 
    962         @Override
    963         public void writeToParcel(Parcel dest, int flags) {
    964             dest.writeInt(TAG);
    965             dest.writeInt(viewId);
    966             dest.writeString(methodName);
    967             dest.writeInt(bitmapId);
    968         }
    969 
    970         @Override
    971         public void apply(View root, ViewGroup rootParent,
    972                 OnClickHandler handler) throws ActionException {
    973             ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
    974                     bitmap);
    975             ra.apply(root, rootParent, handler);
    976         }
    977 
    978         @Override
    979         public void setBitmapCache(BitmapCache bitmapCache) {
    980             bitmapId = bitmapCache.getBitmapId(bitmap);
    981         }
    982 
    983         public String getActionName() {
    984             return "BitmapReflectionAction";
    985         }
    986 
    987         public final static int TAG = 12;
    988     }
    989 
    990     /**
    991      * Base class for the reflection actions.
    992      */
    993     private class ReflectionAction extends Action {
    994         static final int TAG = 2;
    995 
    996         static final int BOOLEAN = 1;
    997         static final int BYTE = 2;
    998         static final int SHORT = 3;
    999         static final int INT = 4;
   1000         static final int LONG = 5;
   1001         static final int FLOAT = 6;
   1002         static final int DOUBLE = 7;
   1003         static final int CHAR = 8;
   1004         static final int STRING = 9;
   1005         static final int CHAR_SEQUENCE = 10;
   1006         static final int URI = 11;
   1007         // BITMAP actions are never stored in the list of actions. They are only used locally
   1008         // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache.
   1009         static final int BITMAP = 12;
   1010         static final int BUNDLE = 13;
   1011         static final int INTENT = 14;
   1012 
   1013         String methodName;
   1014         int type;
   1015         Object value;
   1016 
   1017         ReflectionAction(int viewId, String methodName, int type, Object value) {
   1018             this.viewId = viewId;
   1019             this.methodName = methodName;
   1020             this.type = type;
   1021             this.value = value;
   1022         }
   1023 
   1024         ReflectionAction(Parcel in) {
   1025             this.viewId = in.readInt();
   1026             this.methodName = in.readString();
   1027             this.type = in.readInt();
   1028             //noinspection ConstantIfStatement
   1029             if (false) {
   1030                 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
   1031                         + " methodName=" + this.methodName + " type=" + this.type);
   1032             }
   1033 
   1034             // For some values that may have been null, we first check a flag to see if they were
   1035             // written to the parcel.
   1036             switch (this.type) {
   1037                 case BOOLEAN:
   1038                     this.value = in.readInt() != 0;
   1039                     break;
   1040                 case BYTE:
   1041                     this.value = in.readByte();
   1042                     break;
   1043                 case SHORT:
   1044                     this.value = (short)in.readInt();
   1045                     break;
   1046                 case INT:
   1047                     this.value = in.readInt();
   1048                     break;
   1049                 case LONG:
   1050                     this.value = in.readLong();
   1051                     break;
   1052                 case FLOAT:
   1053                     this.value = in.readFloat();
   1054                     break;
   1055                 case DOUBLE:
   1056                     this.value = in.readDouble();
   1057                     break;
   1058                 case CHAR:
   1059                     this.value = (char)in.readInt();
   1060                     break;
   1061                 case STRING:
   1062                     this.value = in.readString();
   1063                     break;
   1064                 case CHAR_SEQUENCE:
   1065                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   1066                     break;
   1067                 case URI:
   1068                     if (in.readInt() != 0) {
   1069                         this.value = Uri.CREATOR.createFromParcel(in);
   1070                     }
   1071                     break;
   1072                 case BITMAP:
   1073                     if (in.readInt() != 0) {
   1074                         this.value = Bitmap.CREATOR.createFromParcel(in);
   1075                     }
   1076                     break;
   1077                 case BUNDLE:
   1078                     this.value = in.readBundle();
   1079                     break;
   1080                 case INTENT:
   1081                     if (in.readInt() != 0) {
   1082                         this.value = Intent.CREATOR.createFromParcel(in);
   1083                     }
   1084                     break;
   1085                 default:
   1086                     break;
   1087             }
   1088         }
   1089 
   1090         public void writeToParcel(Parcel out, int flags) {
   1091             out.writeInt(TAG);
   1092             out.writeInt(this.viewId);
   1093             out.writeString(this.methodName);
   1094             out.writeInt(this.type);
   1095             //noinspection ConstantIfStatement
   1096             if (false) {
   1097                 Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
   1098                         + " methodName=" + this.methodName + " type=" + this.type);
   1099             }
   1100 
   1101             // For some values which are null, we record an integer flag to indicate whether
   1102             // we have written a valid value to the parcel.
   1103             switch (this.type) {
   1104                 case BOOLEAN:
   1105                     out.writeInt((Boolean) this.value ? 1 : 0);
   1106                     break;
   1107                 case BYTE:
   1108                     out.writeByte((Byte) this.value);
   1109                     break;
   1110                 case SHORT:
   1111                     out.writeInt((Short) this.value);
   1112                     break;
   1113                 case INT:
   1114                     out.writeInt((Integer) this.value);
   1115                     break;
   1116                 case LONG:
   1117                     out.writeLong((Long) this.value);
   1118                     break;
   1119                 case FLOAT:
   1120                     out.writeFloat((Float) this.value);
   1121                     break;
   1122                 case DOUBLE:
   1123                     out.writeDouble((Double) this.value);
   1124                     break;
   1125                 case CHAR:
   1126                     out.writeInt((int)((Character)this.value).charValue());
   1127                     break;
   1128                 case STRING:
   1129                     out.writeString((String)this.value);
   1130                     break;
   1131                 case CHAR_SEQUENCE:
   1132                     TextUtils.writeToParcel((CharSequence)this.value, out, flags);
   1133                     break;
   1134                 case URI:
   1135                     out.writeInt(this.value != null ? 1 : 0);
   1136                     if (this.value != null) {
   1137                         ((Uri)this.value).writeToParcel(out, flags);
   1138                     }
   1139                     break;
   1140                 case BITMAP:
   1141                     out.writeInt(this.value != null ? 1 : 0);
   1142                     if (this.value != null) {
   1143                         ((Bitmap)this.value).writeToParcel(out, flags);
   1144                     }
   1145                     break;
   1146                 case BUNDLE:
   1147                     out.writeBundle((Bundle) this.value);
   1148                     break;
   1149                 case INTENT:
   1150                     out.writeInt(this.value != null ? 1 : 0);
   1151                     if (this.value != null) {
   1152                         ((Intent)this.value).writeToParcel(out, flags);
   1153                     }
   1154                     break;
   1155                 default:
   1156                     break;
   1157             }
   1158         }
   1159 
   1160         private Class getParameterType() {
   1161             switch (this.type) {
   1162                 case BOOLEAN:
   1163                     return boolean.class;
   1164                 case BYTE:
   1165                     return byte.class;
   1166                 case SHORT:
   1167                     return short.class;
   1168                 case INT:
   1169                     return int.class;
   1170                 case LONG:
   1171                     return long.class;
   1172                 case FLOAT:
   1173                     return float.class;
   1174                 case DOUBLE:
   1175                     return double.class;
   1176                 case CHAR:
   1177                     return char.class;
   1178                 case STRING:
   1179                     return String.class;
   1180                 case CHAR_SEQUENCE:
   1181                     return CharSequence.class;
   1182                 case URI:
   1183                     return Uri.class;
   1184                 case BITMAP:
   1185                     return Bitmap.class;
   1186                 case BUNDLE:
   1187                     return Bundle.class;
   1188                 case INTENT:
   1189                     return Intent.class;
   1190                 default:
   1191                     return null;
   1192             }
   1193         }
   1194 
   1195         @Override
   1196         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1197             final View view = root.findViewById(viewId);
   1198             if (view == null) return;
   1199 
   1200             Class param = getParameterType();
   1201             if (param == null) {
   1202                 throw new ActionException("bad type: " + this.type);
   1203             }
   1204 
   1205             Class klass = view.getClass();
   1206             Method method;
   1207             try {
   1208                 method = klass.getMethod(this.methodName, getParameterType());
   1209             }
   1210             catch (NoSuchMethodException ex) {
   1211                 throw new ActionException("view: " + klass.getName() + " doesn't have method: "
   1212                         + this.methodName + "(" + param.getName() + ")");
   1213             }
   1214 
   1215             if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
   1216                 throw new ActionException("view: " + klass.getName()
   1217                         + " can't use method with RemoteViews: "
   1218                         + this.methodName + "(" + param.getName() + ")");
   1219             }
   1220 
   1221             try {
   1222                 //noinspection ConstantIfStatement
   1223                 if (false) {
   1224                     Log.d(LOG_TAG, "view: " + klass.getName() + " calling method: "
   1225                         + this.methodName + "(" + param.getName() + ") with "
   1226                         + (this.value == null ? "null" : this.value.getClass().getName()));
   1227                 }
   1228                 method.invoke(view, this.value);
   1229             }
   1230             catch (Exception ex) {
   1231                 throw new ActionException(ex);
   1232             }
   1233         }
   1234 
   1235         public int mergeBehavior() {
   1236             // smoothScrollBy is cumulative, everything else overwites.
   1237             if (methodName.equals("smoothScrollBy")) {
   1238                 return MERGE_APPEND;
   1239             } else {
   1240                 return MERGE_REPLACE;
   1241             }
   1242         }
   1243 
   1244         public String getActionName() {
   1245             // Each type of reflection action corresponds to a setter, so each should be seen as
   1246             // unique from the standpoint of merging.
   1247             return "ReflectionAction" + this.methodName + this.type;
   1248         }
   1249     }
   1250 
   1251     private void configureRemoteViewsAsChild(RemoteViews rv) {
   1252         mBitmapCache.assimilate(rv.mBitmapCache);
   1253         rv.setBitmapCache(mBitmapCache);
   1254         rv.setNotRoot();
   1255     }
   1256 
   1257     void setNotRoot() {
   1258         mIsRoot = false;
   1259     }
   1260 
   1261     /**
   1262      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
   1263      * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
   1264      * when null. This allows users to build "nested" {@link RemoteViews}.
   1265      */
   1266     private class ViewGroupAction extends Action {
   1267         public ViewGroupAction(int viewId, RemoteViews nestedViews) {
   1268             this.viewId = viewId;
   1269             this.nestedViews = nestedViews;
   1270             if (nestedViews != null) {
   1271                 configureRemoteViewsAsChild(nestedViews);
   1272             }
   1273         }
   1274 
   1275         public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) {
   1276             viewId = parcel.readInt();
   1277             boolean nestedViewsNull = parcel.readInt() == 0;
   1278             if (!nestedViewsNull) {
   1279                 nestedViews = new RemoteViews(parcel, bitmapCache);
   1280             } else {
   1281                 nestedViews = null;
   1282             }
   1283         }
   1284 
   1285         public void writeToParcel(Parcel dest, int flags) {
   1286             dest.writeInt(TAG);
   1287             dest.writeInt(viewId);
   1288             if (nestedViews != null) {
   1289                 dest.writeInt(1);
   1290                 nestedViews.writeToParcel(dest, flags);
   1291             } else {
   1292                 // signifies null
   1293                 dest.writeInt(0);
   1294             }
   1295         }
   1296 
   1297         @Override
   1298         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1299             final Context context = root.getContext();
   1300             final ViewGroup target = (ViewGroup) root.findViewById(viewId);
   1301             if (target == null) return;
   1302             if (nestedViews != null) {
   1303                 // Inflate nested views and add as children
   1304                 target.addView(nestedViews.apply(context, target, handler));
   1305             } else {
   1306                 // Clear all children when nested views omitted
   1307                 target.removeAllViews();
   1308             }
   1309         }
   1310 
   1311         @Override
   1312         public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
   1313             if (nestedViews != null) {
   1314                 counter.increment(nestedViews.estimateMemoryUsage());
   1315             }
   1316         }
   1317 
   1318         @Override
   1319         public void setBitmapCache(BitmapCache bitmapCache) {
   1320             if (nestedViews != null) {
   1321                 nestedViews.setBitmapCache(bitmapCache);
   1322             }
   1323         }
   1324 
   1325         public String getActionName() {
   1326             return "ViewGroupAction" + this.nestedViews == null ? "Remove" : "Add";
   1327         }
   1328 
   1329         public int mergeBehavior() {
   1330             return MERGE_APPEND;
   1331         }
   1332 
   1333         RemoteViews nestedViews;
   1334 
   1335         public final static int TAG = 4;
   1336     }
   1337 
   1338     /**
   1339      * Helper action to set compound drawables on a TextView. Supports relative
   1340      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
   1341      */
   1342     private class TextViewDrawableAction extends Action {
   1343         public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) {
   1344             this.viewId = viewId;
   1345             this.isRelative = isRelative;
   1346             this.d1 = d1;
   1347             this.d2 = d2;
   1348             this.d3 = d3;
   1349             this.d4 = d4;
   1350         }
   1351 
   1352         public TextViewDrawableAction(Parcel parcel) {
   1353             viewId = parcel.readInt();
   1354             isRelative = (parcel.readInt() != 0);
   1355             d1 = parcel.readInt();
   1356             d2 = parcel.readInt();
   1357             d3 = parcel.readInt();
   1358             d4 = parcel.readInt();
   1359         }
   1360 
   1361         public void writeToParcel(Parcel dest, int flags) {
   1362             dest.writeInt(TAG);
   1363             dest.writeInt(viewId);
   1364             dest.writeInt(isRelative ? 1 : 0);
   1365             dest.writeInt(d1);
   1366             dest.writeInt(d2);
   1367             dest.writeInt(d3);
   1368             dest.writeInt(d4);
   1369         }
   1370 
   1371         @Override
   1372         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1373             final Context context = root.getContext();
   1374             final TextView target = (TextView) root.findViewById(viewId);
   1375             if (target == null) return;
   1376             if (isRelative) {
   1377                 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
   1378             } else {
   1379                 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
   1380             }
   1381         }
   1382 
   1383         public String getActionName() {
   1384             return "TextViewDrawableAction";
   1385         }
   1386 
   1387         boolean isRelative = false;
   1388         int d1, d2, d3, d4;
   1389 
   1390         public final static int TAG = 11;
   1391     }
   1392 
   1393     /**
   1394      * Helper action to set text size on a TextView in any supported units.
   1395      */
   1396     private class TextViewSizeAction extends Action {
   1397         public TextViewSizeAction(int viewId, int units, float size) {
   1398             this.viewId = viewId;
   1399             this.units = units;
   1400             this.size = size;
   1401         }
   1402 
   1403         public TextViewSizeAction(Parcel parcel) {
   1404             viewId = parcel.readInt();
   1405             units = parcel.readInt();
   1406             size  = parcel.readFloat();
   1407         }
   1408 
   1409         public void writeToParcel(Parcel dest, int flags) {
   1410             dest.writeInt(TAG);
   1411             dest.writeInt(viewId);
   1412             dest.writeInt(units);
   1413             dest.writeFloat(size);
   1414         }
   1415 
   1416         @Override
   1417         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1418             final Context context = root.getContext();
   1419             final TextView target = (TextView) root.findViewById(viewId);
   1420             if (target == null) return;
   1421             target.setTextSize(units, size);
   1422         }
   1423 
   1424         public String getActionName() {
   1425             return "TextViewSizeAction";
   1426         }
   1427 
   1428         int units;
   1429         float size;
   1430 
   1431         public final static int TAG = 13;
   1432     }
   1433 
   1434     /**
   1435      * Helper action to set padding on a View.
   1436      */
   1437     private class ViewPaddingAction extends Action {
   1438         public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) {
   1439             this.viewId = viewId;
   1440             this.left = left;
   1441             this.top = top;
   1442             this.right = right;
   1443             this.bottom = bottom;
   1444         }
   1445 
   1446         public ViewPaddingAction(Parcel parcel) {
   1447             viewId = parcel.readInt();
   1448             left = parcel.readInt();
   1449             top = parcel.readInt();
   1450             right = parcel.readInt();
   1451             bottom = parcel.readInt();
   1452         }
   1453 
   1454         public void writeToParcel(Parcel dest, int flags) {
   1455             dest.writeInt(TAG);
   1456             dest.writeInt(viewId);
   1457             dest.writeInt(left);
   1458             dest.writeInt(top);
   1459             dest.writeInt(right);
   1460             dest.writeInt(bottom);
   1461         }
   1462 
   1463         @Override
   1464         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1465             final Context context = root.getContext();
   1466             final View target = root.findViewById(viewId);
   1467             if (target == null) return;
   1468             target.setPadding(left, top, right, bottom);
   1469         }
   1470 
   1471         public String getActionName() {
   1472             return "ViewPaddingAction";
   1473         }
   1474 
   1475         int left, top, right, bottom;
   1476 
   1477         public final static int TAG = 14;
   1478     }
   1479 
   1480     /**
   1481      * Simple class used to keep track of memory usage in a RemoteViews.
   1482      *
   1483      */
   1484     private class MemoryUsageCounter {
   1485         public void clear() {
   1486             mMemoryUsage = 0;
   1487         }
   1488 
   1489         public void increment(int numBytes) {
   1490             mMemoryUsage += numBytes;
   1491         }
   1492 
   1493         public int getMemoryUsage() {
   1494             return mMemoryUsage;
   1495         }
   1496 
   1497         public void addBitmapMemory(Bitmap b) {
   1498             final Bitmap.Config c = b.getConfig();
   1499             // If we don't know, be pessimistic and assume 4
   1500             int bpp = 4;
   1501             if (c != null) {
   1502                 switch (c) {
   1503                 case ALPHA_8:
   1504                     bpp = 1;
   1505                     break;
   1506                 case RGB_565:
   1507                 case ARGB_4444:
   1508                     bpp = 2;
   1509                     break;
   1510                 case ARGB_8888:
   1511                     bpp = 4;
   1512                     break;
   1513                 }
   1514             }
   1515             increment(b.getWidth() * b.getHeight() * bpp);
   1516         }
   1517 
   1518         int mMemoryUsage;
   1519     }
   1520 
   1521     /**
   1522      * Create a new RemoteViews object that will display the views contained
   1523      * in the specified layout file.
   1524      *
   1525      * @param packageName Name of the package that contains the layout resource
   1526      * @param layoutId The id of the layout resource
   1527      */
   1528     public RemoteViews(String packageName, int layoutId) {
   1529         mPackage = packageName;
   1530         mLayoutId = layoutId;
   1531         mBitmapCache = new BitmapCache();
   1532 
   1533         // setup the memory usage statistics
   1534         mMemoryUsageCounter = new MemoryUsageCounter();
   1535         recalculateMemoryUsage();
   1536     }
   1537 
   1538     /** {@hide} */
   1539     public void setUser(UserHandle user) {
   1540         mUser = user;
   1541     }
   1542 
   1543     private boolean hasLandscapeAndPortraitLayouts() {
   1544         return (mLandscape != null) && (mPortrait != null);
   1545     }
   1546 
   1547     /**
   1548      * Create a new RemoteViews object that will inflate as the specified
   1549      * landspace or portrait RemoteViews, depending on the current configuration.
   1550      *
   1551      * @param landscape The RemoteViews to inflate in landscape configuration
   1552      * @param portrait The RemoteViews to inflate in portrait configuration
   1553      */
   1554     public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
   1555         if (landscape == null || portrait == null) {
   1556             throw new RuntimeException("Both RemoteViews must be non-null");
   1557         }
   1558         if (landscape.getPackage().compareTo(portrait.getPackage()) != 0) {
   1559             throw new RuntimeException("Both RemoteViews must share the same package");
   1560         }
   1561         mPackage = portrait.getPackage();
   1562         mLayoutId = portrait.getLayoutId();
   1563 
   1564         mLandscape = landscape;
   1565         mPortrait = portrait;
   1566 
   1567         // setup the memory usage statistics
   1568         mMemoryUsageCounter = new MemoryUsageCounter();
   1569 
   1570         mBitmapCache = new BitmapCache();
   1571         configureRemoteViewsAsChild(landscape);
   1572         configureRemoteViewsAsChild(portrait);
   1573 
   1574         recalculateMemoryUsage();
   1575     }
   1576 
   1577     /**
   1578      * Reads a RemoteViews object from a parcel.
   1579      *
   1580      * @param parcel
   1581      */
   1582     public RemoteViews(Parcel parcel) {
   1583         this(parcel, null);
   1584     }
   1585 
   1586     private RemoteViews(Parcel parcel, BitmapCache bitmapCache) {
   1587         int mode = parcel.readInt();
   1588 
   1589         // We only store a bitmap cache in the root of the RemoteViews.
   1590         if (bitmapCache == null) {
   1591             mBitmapCache = new BitmapCache(parcel);
   1592         } else {
   1593             setBitmapCache(bitmapCache);
   1594             setNotRoot();
   1595         }
   1596 
   1597         if (mode == MODE_NORMAL) {
   1598             mPackage = parcel.readString();
   1599             mLayoutId = parcel.readInt();
   1600             mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false;
   1601 
   1602             int count = parcel.readInt();
   1603             if (count > 0) {
   1604                 mActions = new ArrayList<Action>(count);
   1605                 for (int i=0; i<count; i++) {
   1606                     int tag = parcel.readInt();
   1607                     switch (tag) {
   1608                     case SetOnClickPendingIntent.TAG:
   1609                         mActions.add(new SetOnClickPendingIntent(parcel));
   1610                         break;
   1611                     case SetDrawableParameters.TAG:
   1612                         mActions.add(new SetDrawableParameters(parcel));
   1613                         break;
   1614                     case ReflectionAction.TAG:
   1615                         mActions.add(new ReflectionAction(parcel));
   1616                         break;
   1617                     case ViewGroupAction.TAG:
   1618                         mActions.add(new ViewGroupAction(parcel, mBitmapCache));
   1619                         break;
   1620                     case ReflectionActionWithoutParams.TAG:
   1621                         mActions.add(new ReflectionActionWithoutParams(parcel));
   1622                         break;
   1623                     case SetEmptyView.TAG:
   1624                         mActions.add(new SetEmptyView(parcel));
   1625                         break;
   1626                     case SetPendingIntentTemplate.TAG:
   1627                         mActions.add(new SetPendingIntentTemplate(parcel));
   1628                         break;
   1629                     case SetOnClickFillInIntent.TAG:
   1630                         mActions.add(new SetOnClickFillInIntent(parcel));
   1631                         break;
   1632                     case SetRemoteViewsAdapterIntent.TAG:
   1633                         mActions.add(new SetRemoteViewsAdapterIntent(parcel));
   1634                         break;
   1635                     case TextViewDrawableAction.TAG:
   1636                         mActions.add(new TextViewDrawableAction(parcel));
   1637                         break;
   1638                     case TextViewSizeAction.TAG:
   1639                         mActions.add(new TextViewSizeAction(parcel));
   1640                         break;
   1641                     case ViewPaddingAction.TAG:
   1642                         mActions.add(new ViewPaddingAction(parcel));
   1643                         break;
   1644                     case BitmapReflectionAction.TAG:
   1645                         mActions.add(new BitmapReflectionAction(parcel));
   1646                         break;
   1647                     case SetRemoteViewsAdapterList.TAG:
   1648                         mActions.add(new SetRemoteViewsAdapterList(parcel));
   1649                         break;
   1650                     default:
   1651                         throw new ActionException("Tag " + tag + " not found");
   1652                     }
   1653                 }
   1654             }
   1655         } else {
   1656             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
   1657             mLandscape = new RemoteViews(parcel, mBitmapCache);
   1658             mPortrait = new RemoteViews(parcel, mBitmapCache);
   1659             mPackage = mPortrait.getPackage();
   1660             mLayoutId = mPortrait.getLayoutId();
   1661         }
   1662 
   1663         // setup the memory usage statistics
   1664         mMemoryUsageCounter = new MemoryUsageCounter();
   1665         recalculateMemoryUsage();
   1666     }
   1667 
   1668 
   1669     public RemoteViews clone() {
   1670         Parcel p = Parcel.obtain();
   1671         writeToParcel(p, 0);
   1672         p.setDataPosition(0);
   1673         return new RemoteViews(p);
   1674     }
   1675 
   1676     public String getPackage() {
   1677         return mPackage;
   1678     }
   1679 
   1680     /**
   1681      * Reutrns the layout id of the root layout associated with this RemoteViews. In the case
   1682      * that the RemoteViews has both a landscape and portrait root, this will return the layout
   1683      * id associated with the portrait layout.
   1684      *
   1685      * @return the layout id.
   1686      */
   1687     public int getLayoutId() {
   1688         return mLayoutId;
   1689     }
   1690 
   1691     /*
   1692      * This flag indicates whether this RemoteViews object is being created from a
   1693      * RemoteViewsService for use as a child of a widget collection. This flag is used
   1694      * to determine whether or not certain features are available, in particular,
   1695      * setting on click extras and setting on click pending intents. The former is enabled,
   1696      * and the latter disabled when this flag is true.
   1697      */
   1698     void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
   1699         mIsWidgetCollectionChild = isWidgetCollectionChild;
   1700     }
   1701 
   1702     /**
   1703      * Updates the memory usage statistics.
   1704      */
   1705     private void recalculateMemoryUsage() {
   1706         mMemoryUsageCounter.clear();
   1707 
   1708         if (!hasLandscapeAndPortraitLayouts()) {
   1709             // Accumulate the memory usage for each action
   1710             if (mActions != null) {
   1711                 final int count = mActions.size();
   1712                 for (int i= 0; i < count; ++i) {
   1713                     mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
   1714                 }
   1715             }
   1716             if (mIsRoot) {
   1717                 mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
   1718             }
   1719         } else {
   1720             mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage());
   1721             mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage());
   1722             mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
   1723         }
   1724     }
   1725 
   1726     /**
   1727      * Recursively sets BitmapCache in the hierarchy and update the bitmap ids.
   1728      */
   1729     private void setBitmapCache(BitmapCache bitmapCache) {
   1730         mBitmapCache = bitmapCache;
   1731         if (!hasLandscapeAndPortraitLayouts()) {
   1732             if (mActions != null) {
   1733                 final int count = mActions.size();
   1734                 for (int i= 0; i < count; ++i) {
   1735                     mActions.get(i).setBitmapCache(bitmapCache);
   1736                 }
   1737             }
   1738         } else {
   1739             mLandscape.setBitmapCache(bitmapCache);
   1740             mPortrait.setBitmapCache(bitmapCache);
   1741         }
   1742     }
   1743 
   1744     /**
   1745      * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
   1746      */
   1747     /** @hide */
   1748     public int estimateMemoryUsage() {
   1749         return mMemoryUsageCounter.getMemoryUsage();
   1750     }
   1751 
   1752     /**
   1753      * Add an action to be executed on the remote side when apply is called.
   1754      *
   1755      * @param a The action to add
   1756      */
   1757     private void addAction(Action a) {
   1758         if (hasLandscapeAndPortraitLayouts()) {
   1759             throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
   1760                     " layouts cannot be modified. Instead, fully configure the landscape and" +
   1761                     " portrait layouts individually before constructing the combined layout.");
   1762         }
   1763         if (mActions == null) {
   1764             mActions = new ArrayList<Action>();
   1765         }
   1766         mActions.add(a);
   1767 
   1768         // update the memory usage stats
   1769         a.updateMemoryUsageEstimate(mMemoryUsageCounter);
   1770     }
   1771 
   1772     /**
   1773      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
   1774      * given {@link RemoteViews}. This allows users to build "nested"
   1775      * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
   1776      * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
   1777      * children.
   1778      *
   1779      * @param viewId The id of the parent {@link ViewGroup} to add child into.
   1780      * @param nestedView {@link RemoteViews} that describes the child.
   1781      */
   1782     public void addView(int viewId, RemoteViews nestedView) {
   1783         addAction(new ViewGroupAction(viewId, nestedView));
   1784     }
   1785 
   1786     /**
   1787      * Equivalent to calling {@link ViewGroup#removeAllViews()}.
   1788      *
   1789      * @param viewId The id of the parent {@link ViewGroup} to remove all
   1790      *            children from.
   1791      */
   1792     public void removeAllViews(int viewId) {
   1793         addAction(new ViewGroupAction(viewId, null));
   1794     }
   1795 
   1796     /**
   1797      * Equivalent to calling {@link AdapterViewAnimator#showNext()}
   1798      *
   1799      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
   1800      */
   1801     public void showNext(int viewId) {
   1802         addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
   1803     }
   1804 
   1805     /**
   1806      * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
   1807      *
   1808      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
   1809      */
   1810     public void showPrevious(int viewId) {
   1811         addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
   1812     }
   1813 
   1814     /**
   1815      * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)}
   1816      *
   1817      * @param viewId The id of the view on which to call
   1818      *               {@link AdapterViewAnimator#setDisplayedChild(int)}
   1819      */
   1820     public void setDisplayedChild(int viewId, int childIndex) {
   1821         setInt(viewId, "setDisplayedChild", childIndex);
   1822     }
   1823 
   1824     /**
   1825      * Equivalent to calling View.setVisibility
   1826      *
   1827      * @param viewId The id of the view whose visibility should change
   1828      * @param visibility The new visibility for the view
   1829      */
   1830     public void setViewVisibility(int viewId, int visibility) {
   1831         setInt(viewId, "setVisibility", visibility);
   1832     }
   1833 
   1834     /**
   1835      * Equivalent to calling TextView.setText
   1836      *
   1837      * @param viewId The id of the view whose text should change
   1838      * @param text The new text for the view
   1839      */
   1840     public void setTextViewText(int viewId, CharSequence text) {
   1841         setCharSequence(viewId, "setText", text);
   1842     }
   1843 
   1844     /**
   1845      * Equivalent to calling {@link TextView#setTextSize(int, float)}
   1846      *
   1847      * @param viewId The id of the view whose text size should change
   1848      * @param units The units of size (e.g. COMPLEX_UNIT_SP)
   1849      * @param size The size of the text
   1850      */
   1851     public void setTextViewTextSize(int viewId, int units, float size) {
   1852         addAction(new TextViewSizeAction(viewId, units, size));
   1853     }
   1854 
   1855     /**
   1856      * Equivalent to calling
   1857      * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
   1858      *
   1859      * @param viewId The id of the view whose text should change
   1860      * @param left The id of a drawable to place to the left of the text, or 0
   1861      * @param top The id of a drawable to place above the text, or 0
   1862      * @param right The id of a drawable to place to the right of the text, or 0
   1863      * @param bottom The id of a drawable to place below the text, or 0
   1864      */
   1865     public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
   1866         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
   1867     }
   1868 
   1869     /**
   1870      * Equivalent to calling {@link
   1871      * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}.
   1872      *
   1873      * @param viewId The id of the view whose text should change
   1874      * @param start The id of a drawable to place before the text (relative to the
   1875      * layout direction), or 0
   1876      * @param top The id of a drawable to place above the text, or 0
   1877      * @param end The id of a drawable to place after the text, or 0
   1878      * @param bottom The id of a drawable to place below the text, or 0
   1879      */
   1880     public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) {
   1881         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
   1882     }
   1883 
   1884     /**
   1885      * Equivalent to calling ImageView.setImageResource
   1886      *
   1887      * @param viewId The id of the view whose drawable should change
   1888      * @param srcId The new resource id for the drawable
   1889      */
   1890     public void setImageViewResource(int viewId, int srcId) {
   1891         setInt(viewId, "setImageResource", srcId);
   1892     }
   1893 
   1894     /**
   1895      * Equivalent to calling ImageView.setImageURI
   1896      *
   1897      * @param viewId The id of the view whose drawable should change
   1898      * @param uri The Uri for the image
   1899      */
   1900     public void setImageViewUri(int viewId, Uri uri) {
   1901         setUri(viewId, "setImageURI", uri);
   1902     }
   1903 
   1904     /**
   1905      * Equivalent to calling ImageView.setImageBitmap
   1906      *
   1907      * @param viewId The id of the view whose bitmap should change
   1908      * @param bitmap The new Bitmap for the drawable
   1909      */
   1910     public void setImageViewBitmap(int viewId, Bitmap bitmap) {
   1911         setBitmap(viewId, "setImageBitmap", bitmap);
   1912     }
   1913 
   1914     /**
   1915      * Equivalent to calling AdapterView.setEmptyView
   1916      *
   1917      * @param viewId The id of the view on which to set the empty view
   1918      * @param emptyViewId The view id of the empty view
   1919      */
   1920     public void setEmptyView(int viewId, int emptyViewId) {
   1921         addAction(new SetEmptyView(viewId, emptyViewId));
   1922     }
   1923 
   1924     /**
   1925      * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
   1926      * {@link Chronometer#setFormat Chronometer.setFormat},
   1927      * and {@link Chronometer#start Chronometer.start()} or
   1928      * {@link Chronometer#stop Chronometer.stop()}.
   1929      *
   1930      * @param viewId The id of the {@link Chronometer} to change
   1931      * @param base The time at which the timer would have read 0:00.  This
   1932      *             time should be based off of
   1933      *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
   1934      * @param format The Chronometer format string, or null to
   1935      *               simply display the timer value.
   1936      * @param started True if you want the clock to be started, false if not.
   1937      */
   1938     public void setChronometer(int viewId, long base, String format, boolean started) {
   1939         setLong(viewId, "setBase", base);
   1940         setString(viewId, "setFormat", format);
   1941         setBoolean(viewId, "setStarted", started);
   1942     }
   1943 
   1944     /**
   1945      * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
   1946      * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
   1947      * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
   1948      *
   1949      * If indeterminate is true, then the values for max and progress are ignored.
   1950      *
   1951      * @param viewId The id of the {@link ProgressBar} to change
   1952      * @param max The 100% value for the progress bar
   1953      * @param progress The current value of the progress bar.
   1954      * @param indeterminate True if the progress bar is indeterminate,
   1955      *                false if not.
   1956      */
   1957     public void setProgressBar(int viewId, int max, int progress,
   1958             boolean indeterminate) {
   1959         setBoolean(viewId, "setIndeterminate", indeterminate);
   1960         if (!indeterminate) {
   1961             setInt(viewId, "setMax", max);
   1962             setInt(viewId, "setProgress", progress);
   1963         }
   1964     }
   1965 
   1966     /**
   1967      * Equivalent to calling
   1968      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
   1969      * to launch the provided {@link PendingIntent}.
   1970      *
   1971      * When setting the on-click action of items within collections (eg. {@link ListView},
   1972      * {@link StackView} etc.), this method will not work. Instead, use {@link
   1973      * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
   1974      * RemoteViews#setOnClickFillInIntent(int, Intent).
   1975      *
   1976      * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
   1977      * @param pendingIntent The {@link PendingIntent} to send when user clicks
   1978      */
   1979     public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
   1980         addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
   1981     }
   1982 
   1983     /**
   1984      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
   1985      * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
   1986      * this method should be used to set a single PendingIntent template on the collection, and
   1987      * individual items can differentiate their on-click behavior using
   1988      * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
   1989      *
   1990      * @param viewId The id of the collection who's children will use this PendingIntent template
   1991      *          when clicked
   1992      * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
   1993      *          by a child of viewId and executed when that child is clicked
   1994      */
   1995     public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
   1996         addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
   1997     }
   1998 
   1999     /**
   2000      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
   2001      * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
   2002      * a single PendingIntent template can be set on the collection, see {@link
   2003      * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
   2004      * action of a given item can be distinguished by setting a fillInIntent on that item. The
   2005      * fillInIntent is then combined with the PendingIntent template in order to determine the final
   2006      * intent which will be executed when the item is clicked. This works as follows: any fields
   2007      * which are left blank in the PendingIntent template, but are provided by the fillInIntent
   2008      * will be overwritten, and the resulting PendingIntent will be used.
   2009      *
   2010      *
   2011      * of the PendingIntent template will then be filled in with the associated fields that are
   2012      * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
   2013      *
   2014      * @param viewId The id of the view on which to set the fillInIntent
   2015      * @param fillInIntent The intent which will be combined with the parent's PendingIntent
   2016      *        in order to determine the on-click behavior of the view specified by viewId
   2017      */
   2018     public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
   2019         addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
   2020     }
   2021 
   2022     /**
   2023      * @hide
   2024      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
   2025      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
   2026      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
   2027      * view.
   2028      * <p>
   2029      * You can omit specific calls by marking their values with null or -1.
   2030      *
   2031      * @param viewId The id of the view that contains the target
   2032      *            {@link Drawable}
   2033      * @param targetBackground If true, apply these parameters to the
   2034      *            {@link Drawable} returned by
   2035      *            {@link android.view.View#getBackground()}. Otherwise, assume
   2036      *            the target view is an {@link ImageView} and apply them to
   2037      *            {@link ImageView#getDrawable()}.
   2038      * @param alpha Specify an alpha value for the drawable, or -1 to leave
   2039      *            unchanged.
   2040      * @param colorFilter Specify a color for a
   2041      *            {@link android.graphics.ColorFilter} for this drawable, or -1
   2042      *            to leave unchanged.
   2043      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
   2044      *            unchanged.
   2045      * @param level Specify the level for the drawable, or -1 to leave
   2046      *            unchanged.
   2047      */
   2048     public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
   2049             int colorFilter, PorterDuff.Mode mode, int level) {
   2050         addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
   2051                 colorFilter, mode, level));
   2052     }
   2053 
   2054     /**
   2055      * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
   2056      *
   2057      * @param viewId The id of the view whose text color should change
   2058      * @param color Sets the text color for all the states (normal, selected,
   2059      *            focused) to be this color.
   2060      */
   2061     public void setTextColor(int viewId, int color) {
   2062         setInt(viewId, "setTextColor", color);
   2063     }
   2064 
   2065     /**
   2066      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
   2067      *
   2068      * @param appWidgetId The id of the app widget which contains the specified view. (This
   2069      *      parameter is ignored in this deprecated method)
   2070      * @param viewId The id of the {@link AdapterView}
   2071      * @param intent The intent of the service which will be
   2072      *            providing data to the RemoteViewsAdapter
   2073      * @deprecated This method has been deprecated. See
   2074      *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
   2075      */
   2076     @Deprecated
   2077     public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
   2078         setRemoteAdapter(viewId, intent);
   2079     }
   2080 
   2081     /**
   2082      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
   2083      * Can only be used for App Widgets.
   2084      *
   2085      * @param viewId The id of the {@link AdapterView}
   2086      * @param intent The intent of the service which will be
   2087      *            providing data to the RemoteViewsAdapter
   2088      */
   2089     public void setRemoteAdapter(int viewId, Intent intent) {
   2090         addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
   2091     }
   2092 
   2093     /**
   2094      * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
   2095      * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
   2096      * This is a simpler but less flexible approach to populating collection widgets. Its use is
   2097      * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
   2098      * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
   2099      * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
   2100      * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
   2101      *
   2102      * This API is supported in the compatibility library for previous API levels, see
   2103      * RemoteViewsCompat.
   2104      *
   2105      * @param viewId The id of the {@link AdapterView}
   2106      * @param list The list of RemoteViews which will populate the view specified by viewId.
   2107      * @param viewTypeCount The maximum number of unique layout id's used to construct the list of
   2108      *      RemoteViews. This count cannot change during the life-cycle of a given widget, so this
   2109      *      parameter should account for the maximum possible number of types that may appear in the
   2110      *      See {@link Adapter#getViewTypeCount()}.
   2111      *
   2112      * @hide
   2113      */
   2114     public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
   2115         addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
   2116     }
   2117 
   2118     /**
   2119      * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
   2120      *
   2121      * @param viewId The id of the view to change
   2122      * @param position Scroll to this adapter position
   2123      */
   2124     public void setScrollPosition(int viewId, int position) {
   2125         setInt(viewId, "smoothScrollToPosition", position);
   2126     }
   2127 
   2128     /**
   2129      * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
   2130      *
   2131      * @param viewId The id of the view to change
   2132      * @param offset Scroll by this adapter position offset
   2133      */
   2134     public void setRelativeScrollPosition(int viewId, int offset) {
   2135         setInt(viewId, "smoothScrollByOffset", offset);
   2136     }
   2137 
   2138     /**
   2139      * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}.
   2140      *
   2141      * @param viewId The id of the view to change
   2142      * @param left the left padding in pixels
   2143      * @param top the top padding in pixels
   2144      * @param right the right padding in pixels
   2145      * @param bottom the bottom padding in pixels
   2146      */
   2147     public void setViewPadding(int viewId, int left, int top, int right, int bottom) {
   2148         addAction(new ViewPaddingAction(viewId, left, top, right, bottom));
   2149     }
   2150 
   2151     /**
   2152      * Call a method taking one boolean 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 setBoolean(int viewId, String methodName, boolean value) {
   2159         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
   2160     }
   2161 
   2162     /**
   2163      * Call a method taking one byte on a view in the layout for this RemoteViews.
   2164      *
   2165      * @param viewId The id of the view on which to call the method.
   2166      * @param methodName The name of the method to call.
   2167      * @param value The value to pass to the method.
   2168      */
   2169     public void setByte(int viewId, String methodName, byte value) {
   2170         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
   2171     }
   2172 
   2173     /**
   2174      * Call a method taking one short on a view in the layout for this RemoteViews.
   2175      *
   2176      * @param viewId The id of the view on which to call the method.
   2177      * @param methodName The name of the method to call.
   2178      * @param value The value to pass to the method.
   2179      */
   2180     public void setShort(int viewId, String methodName, short value) {
   2181         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
   2182     }
   2183 
   2184     /**
   2185      * Call a method taking one int on a view in the layout for this RemoteViews.
   2186      *
   2187      * @param viewId The id of the view on which to call the method.
   2188      * @param methodName The name of the method to call.
   2189      * @param value The value to pass to the method.
   2190      */
   2191     public void setInt(int viewId, String methodName, int value) {
   2192         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
   2193     }
   2194 
   2195     /**
   2196      * Call a method taking one long on a view in the layout for this RemoteViews.
   2197      *
   2198      * @param viewId The id of the view on which to call the method.
   2199      * @param methodName The name of the method to call.
   2200      * @param value The value to pass to the method.
   2201      */
   2202     public void setLong(int viewId, String methodName, long value) {
   2203         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
   2204     }
   2205 
   2206     /**
   2207      * Call a method taking one float on a view in the layout for this RemoteViews.
   2208      *
   2209      * @param viewId The id of the view on which to call the method.
   2210      * @param methodName The name of the method to call.
   2211      * @param value The value to pass to the method.
   2212      */
   2213     public void setFloat(int viewId, String methodName, float value) {
   2214         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
   2215     }
   2216 
   2217     /**
   2218      * Call a method taking one double on a view in the layout for this RemoteViews.
   2219      *
   2220      * @param viewId The id of the view on which to call the method.
   2221      * @param methodName The name of the method to call.
   2222      * @param value The value to pass to the method.
   2223      */
   2224     public void setDouble(int viewId, String methodName, double value) {
   2225         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
   2226     }
   2227 
   2228     /**
   2229      * Call a method taking one char on a view in the layout for this RemoteViews.
   2230      *
   2231      * @param viewId The id of the view on which to call the method.
   2232      * @param methodName The name of the method to call.
   2233      * @param value The value to pass to the method.
   2234      */
   2235     public void setChar(int viewId, String methodName, char value) {
   2236         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
   2237     }
   2238 
   2239     /**
   2240      * Call a method taking one String on a view in the layout for this RemoteViews.
   2241      *
   2242      * @param viewId The id of the view on which to call the method.
   2243      * @param methodName The name of the method to call.
   2244      * @param value The value to pass to the method.
   2245      */
   2246     public void setString(int viewId, String methodName, String value) {
   2247         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
   2248     }
   2249 
   2250     /**
   2251      * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
   2252      *
   2253      * @param viewId The id of the view on which to call the method.
   2254      * @param methodName The name of the method to call.
   2255      * @param value The value to pass to the method.
   2256      */
   2257     public void setCharSequence(int viewId, String methodName, CharSequence value) {
   2258         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
   2259     }
   2260 
   2261     /**
   2262      * Call a method taking one Uri on a view in the layout for this RemoteViews.
   2263      *
   2264      * @param viewId The id of the view on which to call the method.
   2265      * @param methodName The name of the method to call.
   2266      * @param value The value to pass to the method.
   2267      */
   2268     public void setUri(int viewId, String methodName, Uri value) {
   2269         if (value != null) {
   2270             // Resolve any filesystem path before sending remotely
   2271             value = value.getCanonicalUri();
   2272             if (StrictMode.vmFileUriExposureEnabled()) {
   2273                 value.checkFileUriExposed("RemoteViews.setUri()");
   2274             }
   2275         }
   2276         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
   2277     }
   2278 
   2279     /**
   2280      * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
   2281      * @more
   2282      * <p class="note">The bitmap will be flattened into the parcel if this object is
   2283      * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
   2284      *
   2285      * @param viewId The id of the view on which to call the method.
   2286      * @param methodName The name of the method to call.
   2287      * @param value The value to pass to the method.
   2288      */
   2289     public void setBitmap(int viewId, String methodName, Bitmap value) {
   2290         addAction(new BitmapReflectionAction(viewId, methodName, value));
   2291     }
   2292 
   2293     /**
   2294      * Call a method taking one Bundle on a view in the layout for this RemoteViews.
   2295      *
   2296      * @param viewId The id of the view on which to call the method.
   2297      * @param methodName The name of the method to call.
   2298      * @param value The value to pass to the method.
   2299      */
   2300     public void setBundle(int viewId, String methodName, Bundle value) {
   2301         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
   2302     }
   2303 
   2304     /**
   2305      * Call a method taking one Intent on a view in the layout for this RemoteViews.
   2306      *
   2307      * @param viewId The id of the view on which to call the method.
   2308      * @param methodName The name of the method to call.
   2309      * @param value The {@link android.content.Intent} to pass the method.
   2310      */
   2311     public void setIntent(int viewId, String methodName, Intent value) {
   2312         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
   2313     }
   2314 
   2315     /**
   2316      * Equivalent to calling View.setContentDescription(CharSequence).
   2317      *
   2318      * @param viewId The id of the view whose content description should change.
   2319      * @param contentDescription The new content description for the view.
   2320      */
   2321     public void setContentDescription(int viewId, CharSequence contentDescription) {
   2322         setCharSequence(viewId, "setContentDescription", contentDescription);
   2323     }
   2324 
   2325     /**
   2326      * Equivalent to calling View.setLabelFor(int).
   2327      *
   2328      * @param viewId The id of the view whose property to set.
   2329      * @param labeledId The id of a view for which this view serves as a label.
   2330      */
   2331     public void setLabelFor(int viewId, int labeledId) {
   2332         setInt(viewId, "setLabelFor", labeledId);
   2333     }
   2334 
   2335     private RemoteViews getRemoteViewsToApply(Context context) {
   2336         if (hasLandscapeAndPortraitLayouts()) {
   2337             int orientation = context.getResources().getConfiguration().orientation;
   2338             if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
   2339                 return mLandscape;
   2340             } else {
   2341                 return mPortrait;
   2342             }
   2343         }
   2344         return this;
   2345     }
   2346 
   2347     /**
   2348      * Inflates the view hierarchy represented by this object and applies
   2349      * all of the actions.
   2350      *
   2351      * <p><strong>Caller beware: this may throw</strong>
   2352      *
   2353      * @param context Default context to use
   2354      * @param parent Parent that the resulting view hierarchy will be attached to. This method
   2355      * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
   2356      * @return The inflated view hierarchy
   2357      */
   2358     public View apply(Context context, ViewGroup parent) {
   2359         return apply(context, parent, null);
   2360     }
   2361 
   2362     /** @hide */
   2363     public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
   2364         RemoteViews rvToApply = getRemoteViewsToApply(context);
   2365 
   2366         View result;
   2367 
   2368         Context c = prepareContext(context);
   2369 
   2370         LayoutInflater inflater = (LayoutInflater)
   2371                 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   2372 
   2373         inflater = inflater.cloneInContext(c);
   2374         inflater.setFilter(this);
   2375 
   2376         result = inflater.inflate(rvToApply.getLayoutId(), parent, false);
   2377 
   2378         rvToApply.performApply(result, parent, handler);
   2379 
   2380         return result;
   2381     }
   2382 
   2383     /**
   2384      * Applies all of the actions to the provided view.
   2385      *
   2386      * <p><strong>Caller beware: this may throw</strong>
   2387      *
   2388      * @param v The view to apply the actions to.  This should be the result of
   2389      * the {@link #apply(Context,ViewGroup)} call.
   2390      */
   2391     public void reapply(Context context, View v) {
   2392         reapply(context, v, null);
   2393     }
   2394 
   2395     /** @hide */
   2396     public void reapply(Context context, View v, OnClickHandler handler) {
   2397         RemoteViews rvToApply = getRemoteViewsToApply(context);
   2398 
   2399         // In the case that a view has this RemoteViews applied in one orientation, is persisted
   2400         // across orientation change, and has the RemoteViews re-applied in the new orientation,
   2401         // we throw an exception, since the layouts may be completely unrelated.
   2402         if (hasLandscapeAndPortraitLayouts()) {
   2403             if (v.getId() != rvToApply.getLayoutId()) {
   2404                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
   2405                         " that does not share the same root layout id.");
   2406             }
   2407         }
   2408 
   2409         prepareContext(context);
   2410         rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
   2411     }
   2412 
   2413     private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
   2414         if (mActions != null) {
   2415             handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
   2416             final int count = mActions.size();
   2417             for (int i = 0; i < count; i++) {
   2418                 Action a = mActions.get(i);
   2419                 a.apply(v, parent, handler);
   2420             }
   2421         }
   2422     }
   2423 
   2424     private Context prepareContext(Context context) {
   2425         Context c;
   2426         String packageName = mPackage;
   2427 
   2428         if (packageName != null) {
   2429             try {
   2430                 c = context.createPackageContextAsUser(
   2431                         packageName, Context.CONTEXT_RESTRICTED, mUser);
   2432             } catch (NameNotFoundException e) {
   2433                 Log.e(LOG_TAG, "Package name " + packageName + " not found");
   2434                 c = context;
   2435             }
   2436         } else {
   2437             c = context;
   2438         }
   2439 
   2440         return c;
   2441     }
   2442 
   2443     /* (non-Javadoc)
   2444      * Used to restrict the views which can be inflated
   2445      *
   2446      * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
   2447      */
   2448     public boolean onLoadClass(Class clazz) {
   2449         return clazz.isAnnotationPresent(RemoteView.class);
   2450     }
   2451 
   2452     public int describeContents() {
   2453         return 0;
   2454     }
   2455 
   2456     public void writeToParcel(Parcel dest, int flags) {
   2457         if (!hasLandscapeAndPortraitLayouts()) {
   2458             dest.writeInt(MODE_NORMAL);
   2459             // We only write the bitmap cache if we are the root RemoteViews, as this cache
   2460             // is shared by all children.
   2461             if (mIsRoot) {
   2462                 mBitmapCache.writeBitmapsToParcel(dest, flags);
   2463             }
   2464             dest.writeString(mPackage);
   2465             dest.writeInt(mLayoutId);
   2466             dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
   2467             int count;
   2468             if (mActions != null) {
   2469                 count = mActions.size();
   2470             } else {
   2471                 count = 0;
   2472             }
   2473             dest.writeInt(count);
   2474             for (int i=0; i<count; i++) {
   2475                 Action a = mActions.get(i);
   2476                 a.writeToParcel(dest, 0);
   2477             }
   2478         } else {
   2479             dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
   2480             // We only write the bitmap cache if we are the root RemoteViews, as this cache
   2481             // is shared by all children.
   2482             if (mIsRoot) {
   2483                 mBitmapCache.writeBitmapsToParcel(dest, flags);
   2484             }
   2485             mLandscape.writeToParcel(dest, flags);
   2486             mPortrait.writeToParcel(dest, flags);
   2487         }
   2488     }
   2489 
   2490     /**
   2491      * Parcelable.Creator that instantiates RemoteViews objects
   2492      */
   2493     public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
   2494         public RemoteViews createFromParcel(Parcel parcel) {
   2495             return new RemoteViews(parcel);
   2496         }
   2497 
   2498         public RemoteViews[] newArray(int size) {
   2499             return new RemoteViews[size];
   2500         }
   2501     };
   2502 }
   2503