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