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.annotation.ColorInt;
     20 import android.annotation.DimenRes;
     21 import android.app.ActivityManager.StackId;
     22 import android.app.ActivityOptions;
     23 import android.app.ActivityThread;
     24 import android.app.Application;
     25 import android.app.PendingIntent;
     26 import android.app.RemoteInput;
     27 import android.appwidget.AppWidgetHostView;
     28 import android.content.Context;
     29 import android.content.ContextWrapper;
     30 import android.content.Intent;
     31 import android.content.IntentSender;
     32 import android.content.pm.ApplicationInfo;
     33 import android.content.pm.PackageManager.NameNotFoundException;
     34 import android.content.res.ColorStateList;
     35 import android.content.res.Configuration;
     36 import android.content.res.Resources;
     37 import android.content.res.TypedArray;
     38 import android.graphics.Bitmap;
     39 import android.graphics.PorterDuff;
     40 import android.graphics.Rect;
     41 import android.graphics.drawable.Drawable;
     42 import android.graphics.drawable.Icon;
     43 import android.net.Uri;
     44 import android.os.AsyncTask;
     45 import android.os.Build;
     46 import android.os.Bundle;
     47 import android.os.CancellationSignal;
     48 import android.os.Parcel;
     49 import android.os.Parcelable;
     50 import android.os.StrictMode;
     51 import android.os.UserHandle;
     52 import android.text.TextUtils;
     53 import android.util.ArrayMap;
     54 import android.util.Log;
     55 import android.view.LayoutInflater;
     56 import android.view.LayoutInflater.Filter;
     57 import android.view.RemotableViewMethod;
     58 import android.view.View;
     59 import android.view.View.OnClickListener;
     60 import android.view.ViewGroup;
     61 import android.widget.AdapterView.OnItemClickListener;
     62 import libcore.util.Objects;
     63 
     64 import com.android.internal.R;
     65 import com.android.internal.util.Preconditions;
     66 
     67 import java.lang.annotation.ElementType;
     68 import java.lang.annotation.Retention;
     69 import java.lang.annotation.RetentionPolicy;
     70 import java.lang.annotation.Target;
     71 import java.lang.reflect.Method;
     72 import java.util.ArrayList;
     73 import java.util.HashMap;
     74 import java.util.concurrent.Executor;
     75 
     76 /**
     77  * A class that describes a view hierarchy that can be displayed in
     78  * another process. The hierarchy is inflated from a layout resource
     79  * file, and this class provides some basic operations for modifying
     80  * the content of the inflated hierarchy.
     81  */
     82 public class RemoteViews implements Parcelable, Filter {
     83 
     84     private static final String LOG_TAG = "RemoteViews";
     85 
     86     /**
     87      * The intent extra that contains the appWidgetId.
     88      * @hide
     89      */
     90     static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId";
     91 
     92     /**
     93      * Application that hosts the remote views.
     94      *
     95      * @hide
     96      */
     97     private ApplicationInfo mApplication;
     98 
     99     /**
    100      * The resource ID of the layout file. (Added to the parcel)
    101      */
    102     private final int mLayoutId;
    103 
    104     /**
    105      * An array of actions to perform on the view tree once it has been
    106      * inflated
    107      */
    108     private ArrayList<Action> mActions;
    109 
    110     /**
    111      * A class to keep track of memory usage by this RemoteViews
    112      */
    113     private MemoryUsageCounter mMemoryUsageCounter;
    114 
    115     /**
    116      * Maps bitmaps to unique indicies to avoid Bitmap duplication.
    117      */
    118     private BitmapCache mBitmapCache;
    119 
    120     /**
    121      * Indicates whether or not this RemoteViews object is contained as a child of any other
    122      * RemoteViews.
    123      */
    124     private boolean mIsRoot = true;
    125 
    126     /**
    127      * Constants to whether or not this RemoteViews is composed of a landscape and portrait
    128      * RemoteViews.
    129      */
    130     private static final int MODE_NORMAL = 0;
    131     private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1;
    132 
    133     /**
    134      * Used in conjunction with the special constructor
    135      * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait
    136      * RemoteViews.
    137      */
    138     private RemoteViews mLandscape = null;
    139     private RemoteViews mPortrait = null;
    140 
    141     /**
    142      * This flag indicates whether this RemoteViews object is being created from a
    143      * RemoteViewsService for use as a child of a widget collection. This flag is used
    144      * to determine whether or not certain features are available, in particular,
    145      * setting on click extras and setting on click pending intents. The former is enabled,
    146      * and the latter disabled when this flag is true.
    147      */
    148     private boolean mIsWidgetCollectionChild = false;
    149 
    150     private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler();
    151 
    152     private static final Object[] sMethodsLock = new Object[0];
    153     private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods =
    154             new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>();
    155     private static final ArrayMap<Method, Method> sAsyncMethods = new ArrayMap<>();
    156 
    157     private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() {
    158         @Override
    159         protected Object[] initialValue() {
    160             return new Object[1];
    161         }
    162     };
    163 
    164     /**
    165      * @hide
    166      */
    167     public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) {
    168         mActions.add(new SetRemoteInputsAction(viewId, remoteInputs));
    169     }
    170 
    171     /**
    172      * Handle with care!
    173      */
    174     static class MutablePair<F, S> {
    175         F first;
    176         S second;
    177 
    178         MutablePair(F first, S second) {
    179             this.first = first;
    180             this.second = second;
    181         }
    182 
    183         @Override
    184         public boolean equals(Object o) {
    185             if (!(o instanceof MutablePair)) {
    186                 return false;
    187             }
    188             MutablePair<?, ?> p = (MutablePair<?, ?>) o;
    189             return Objects.equal(p.first, first) && Objects.equal(p.second, second);
    190         }
    191 
    192         @Override
    193         public int hashCode() {
    194             return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
    195         }
    196     }
    197 
    198     /**
    199      * This pair is used to perform lookups in sMethods without causing allocations.
    200      */
    201     private final MutablePair<String, Class<?>> mPair =
    202             new MutablePair<String, Class<?>>(null, null);
    203 
    204     /**
    205      * This annotation indicates that a subclass of View is alllowed to be used
    206      * with the {@link RemoteViews} mechanism.
    207      */
    208     @Target({ ElementType.TYPE })
    209     @Retention(RetentionPolicy.RUNTIME)
    210     public @interface RemoteView {
    211     }
    212 
    213     /**
    214      * Exception to send when something goes wrong executing an action
    215      *
    216      */
    217     public static class ActionException extends RuntimeException {
    218         public ActionException(Exception ex) {
    219             super(ex);
    220         }
    221         public ActionException(String message) {
    222             super(message);
    223         }
    224     }
    225 
    226     /** @hide */
    227     public static class OnClickHandler {
    228 
    229         private int mEnterAnimationId;
    230 
    231         public boolean onClickHandler(View view, PendingIntent pendingIntent,
    232                 Intent fillInIntent) {
    233             return onClickHandler(view, pendingIntent, fillInIntent, StackId.INVALID_STACK_ID);
    234         }
    235 
    236         public boolean onClickHandler(View view, PendingIntent pendingIntent,
    237                 Intent fillInIntent, int launchStackId) {
    238             try {
    239                 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
    240                 Context context = view.getContext();
    241                 ActivityOptions opts;
    242                 if (mEnterAnimationId != 0) {
    243                     opts = ActivityOptions.makeCustomAnimation(context, mEnterAnimationId, 0);
    244                 } else {
    245                     opts = ActivityOptions.makeScaleUpAnimation(view,
    246                             0, 0,
    247                             view.getMeasuredWidth(), view.getMeasuredHeight());
    248                 }
    249 
    250                 if (launchStackId != StackId.INVALID_STACK_ID) {
    251                     opts.setLaunchStackId(launchStackId);
    252                 }
    253                 context.startIntentSender(
    254                         pendingIntent.getIntentSender(), fillInIntent,
    255                         Intent.FLAG_ACTIVITY_NEW_TASK,
    256                         Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle());
    257             } catch (IntentSender.SendIntentException e) {
    258                 android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
    259                 return false;
    260             } catch (Exception e) {
    261                 android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " +
    262                         "unknown exception: ", e);
    263                 return false;
    264             }
    265             return true;
    266         }
    267 
    268         public void setEnterAnimationId(int enterAnimationId) {
    269             mEnterAnimationId = enterAnimationId;
    270         }
    271     }
    272 
    273     /**
    274      * Base class for all actions that can be performed on an
    275      * inflated view.
    276      *
    277      *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
    278      */
    279     private abstract static class Action implements Parcelable {
    280         public abstract void apply(View root, ViewGroup rootParent,
    281                 OnClickHandler handler) throws ActionException;
    282 
    283         public static final int MERGE_REPLACE = 0;
    284         public static final int MERGE_APPEND = 1;
    285         public static final int MERGE_IGNORE = 2;
    286 
    287         public int describeContents() {
    288             return 0;
    289         }
    290 
    291         /**
    292          * Overridden by each class to report on it's own memory usage
    293          */
    294         public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
    295             // We currently only calculate Bitmap memory usage, so by default,
    296             // don't do anything here
    297         }
    298 
    299         public void setBitmapCache(BitmapCache bitmapCache) {
    300             // Do nothing
    301         }
    302 
    303         public int mergeBehavior() {
    304             return MERGE_REPLACE;
    305         }
    306 
    307         public abstract String getActionName();
    308 
    309         public String getUniqueKey() {
    310             return (getActionName() + viewId);
    311         }
    312 
    313         /**
    314          * This is called on the background thread. It should perform any non-ui computations
    315          * and return the final action which will run on the UI thread.
    316          * Override this if some of the tasks can be performed async.
    317          */
    318         public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
    319             return this;
    320         }
    321 
    322         int viewId;
    323     }
    324 
    325     /**
    326      * Action class used during async inflation of RemoteViews. Subclasses are not parcelable.
    327      */
    328     private static abstract class RuntimeAction extends Action {
    329         @Override
    330         public final String getActionName() {
    331             return "RuntimeAction";
    332         }
    333 
    334         @Override
    335         public final void writeToParcel(Parcel dest, int flags) {
    336             throw new UnsupportedOperationException();
    337         }
    338     }
    339 
    340     // Constant used during async execution. It is not parcelable.
    341     private static final Action ACTION_NOOP = new RuntimeAction() {
    342         @Override
    343         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { }
    344     };
    345 
    346     /**
    347      * Merges the passed RemoteViews actions with this RemoteViews actions according to
    348      * action-specific merge rules.
    349      *
    350      * @param newRv
    351      *
    352      * @hide
    353      */
    354     public void mergeRemoteViews(RemoteViews newRv) {
    355         if (newRv == null) return;
    356         // We first copy the new RemoteViews, as the process of merging modifies the way the actions
    357         // reference the bitmap cache. We don't want to modify the object as it may need to
    358         // be merged and applied multiple times.
    359         RemoteViews copy = newRv.clone();
    360 
    361         HashMap<String, Action> map = new HashMap<String, Action>();
    362         if (mActions == null) {
    363             mActions = new ArrayList<Action>();
    364         }
    365 
    366         int count = mActions.size();
    367         for (int i = 0; i < count; i++) {
    368             Action a = mActions.get(i);
    369             map.put(a.getUniqueKey(), a);
    370         }
    371 
    372         ArrayList<Action> newActions = copy.mActions;
    373         if (newActions == null) return;
    374         count = newActions.size();
    375         for (int i = 0; i < count; i++) {
    376             Action a = newActions.get(i);
    377             String key = newActions.get(i).getUniqueKey();
    378             int mergeBehavior = newActions.get(i).mergeBehavior();
    379             if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) {
    380                 mActions.remove(map.get(key));
    381                 map.remove(key);
    382             }
    383 
    384             // If the merge behavior is ignore, we don't bother keeping the extra action
    385             if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) {
    386                 mActions.add(a);
    387             }
    388         }
    389 
    390         // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache
    391         mBitmapCache = new BitmapCache();
    392         setBitmapCache(mBitmapCache);
    393     }
    394 
    395     private class SetEmptyView extends Action {
    396         int viewId;
    397         int emptyViewId;
    398 
    399         public final static int TAG = 6;
    400 
    401         SetEmptyView(int viewId, int emptyViewId) {
    402             this.viewId = viewId;
    403             this.emptyViewId = emptyViewId;
    404         }
    405 
    406         SetEmptyView(Parcel in) {
    407             this.viewId = in.readInt();
    408             this.emptyViewId = in.readInt();
    409         }
    410 
    411         public void writeToParcel(Parcel out, int flags) {
    412             out.writeInt(TAG);
    413             out.writeInt(this.viewId);
    414             out.writeInt(this.emptyViewId);
    415         }
    416 
    417         @Override
    418         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    419             final View view = root.findViewById(viewId);
    420             if (!(view instanceof AdapterView<?>)) return;
    421 
    422             AdapterView<?> adapterView = (AdapterView<?>) view;
    423 
    424             final View emptyView = root.findViewById(emptyViewId);
    425             if (emptyView == null) return;
    426 
    427             adapterView.setEmptyView(emptyView);
    428         }
    429 
    430         public String getActionName() {
    431             return "SetEmptyView";
    432         }
    433     }
    434 
    435     private class SetOnClickFillInIntent extends Action {
    436         public SetOnClickFillInIntent(int id, Intent fillInIntent) {
    437             this.viewId = id;
    438             this.fillInIntent = fillInIntent;
    439         }
    440 
    441         public SetOnClickFillInIntent(Parcel parcel) {
    442             viewId = parcel.readInt();
    443             fillInIntent = Intent.CREATOR.createFromParcel(parcel);
    444         }
    445 
    446         public void writeToParcel(Parcel dest, int flags) {
    447             dest.writeInt(TAG);
    448             dest.writeInt(viewId);
    449             fillInIntent.writeToParcel(dest, 0 /* no flags */);
    450         }
    451 
    452         @Override
    453         public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
    454             final View target = root.findViewById(viewId);
    455             if (target == null) return;
    456 
    457             if (!mIsWidgetCollectionChild) {
    458                 Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " +
    459                         "only from RemoteViewsFactory (ie. on collection items).");
    460                 return;
    461             }
    462             if (target == root) {
    463                 target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent);
    464             } else if (fillInIntent != null) {
    465                 OnClickListener listener = new OnClickListener() {
    466                     public void onClick(View v) {
    467                         // Insure that this view is a child of an AdapterView
    468                         View parent = (View) v.getParent();
    469                         // Break the for loop on the first encounter of:
    470                         //    1) an AdapterView,
    471                         //    2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or
    472                         //    3) a null parent.
    473                         // 2) and 3) are unexpected and catch the case where a child is not
    474                         // correctly parented in an AdapterView.
    475                         while (parent != null && !(parent instanceof AdapterView<?>)
    476                                 && !((parent instanceof AppWidgetHostView) &&
    477                                     !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) {
    478                             parent = (View) parent.getParent();
    479                         }
    480 
    481                         if (!(parent instanceof AdapterView<?>)) {
    482                             // Somehow they've managed to get this far without having
    483                             // and AdapterView as a parent.
    484                             Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent");
    485                             return;
    486                         }
    487 
    488                         // Insure that a template pending intent has been set on an ancestor
    489                         if (!(parent.getTag() instanceof PendingIntent)) {
    490                             Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" +
    491                                     " calling setPendingIntentTemplate on parent.");
    492                             return;
    493                         }
    494 
    495                         PendingIntent pendingIntent = (PendingIntent) parent.getTag();
    496 
    497                         final Rect rect = getSourceBounds(v);
    498 
    499                         fillInIntent.setSourceBounds(rect);
    500                         handler.onClickHandler(v, pendingIntent, fillInIntent);
    501                     }
    502 
    503                 };
    504                 target.setOnClickListener(listener);
    505             }
    506         }
    507 
    508         public String getActionName() {
    509             return "SetOnClickFillInIntent";
    510         }
    511 
    512         Intent fillInIntent;
    513 
    514         public final static int TAG = 9;
    515     }
    516 
    517     private class SetPendingIntentTemplate extends Action {
    518         public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) {
    519             this.viewId = id;
    520             this.pendingIntentTemplate = pendingIntentTemplate;
    521         }
    522 
    523         public SetPendingIntentTemplate(Parcel parcel) {
    524             viewId = parcel.readInt();
    525             pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
    526         }
    527 
    528         public void writeToParcel(Parcel dest, int flags) {
    529             dest.writeInt(TAG);
    530             dest.writeInt(viewId);
    531             pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */);
    532         }
    533 
    534         @Override
    535         public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
    536             final View target = root.findViewById(viewId);
    537             if (target == null) return;
    538 
    539             // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense
    540             if (target instanceof AdapterView<?>) {
    541                 AdapterView<?> av = (AdapterView<?>) target;
    542                 // The PendingIntent template is stored in the view's tag.
    543                 OnItemClickListener listener = new OnItemClickListener() {
    544                     public void onItemClick(AdapterView<?> parent, View view,
    545                             int position, long id) {
    546                         // The view should be a frame layout
    547                         if (view instanceof ViewGroup) {
    548                             ViewGroup vg = (ViewGroup) view;
    549 
    550                             // AdapterViews contain their children in a frame
    551                             // so we need to go one layer deeper here.
    552                             if (parent instanceof AdapterViewAnimator) {
    553                                 vg = (ViewGroup) vg.getChildAt(0);
    554                             }
    555                             if (vg == null) return;
    556 
    557                             Intent fillInIntent = null;
    558                             int childCount = vg.getChildCount();
    559                             for (int i = 0; i < childCount; i++) {
    560                                 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent);
    561                                 if (tag instanceof Intent) {
    562                                     fillInIntent = (Intent) tag;
    563                                     break;
    564                                 }
    565                             }
    566                             if (fillInIntent == null) return;
    567 
    568                             final Rect rect = getSourceBounds(view);
    569 
    570                             final Intent intent = new Intent();
    571                             intent.setSourceBounds(rect);
    572                             handler.onClickHandler(view, pendingIntentTemplate, fillInIntent);
    573                         }
    574                     }
    575                 };
    576                 av.setOnItemClickListener(listener);
    577                 av.setTag(pendingIntentTemplate);
    578             } else {
    579                 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" +
    580                         "an AdapterView (id: " + viewId + ")");
    581                 return;
    582             }
    583         }
    584 
    585         public String getActionName() {
    586             return "SetPendingIntentTemplate";
    587         }
    588 
    589         PendingIntent pendingIntentTemplate;
    590 
    591         public final static int TAG = 8;
    592     }
    593 
    594     private class SetRemoteViewsAdapterList extends Action {
    595         public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) {
    596             this.viewId = id;
    597             this.list = list;
    598             this.viewTypeCount = viewTypeCount;
    599         }
    600 
    601         public SetRemoteViewsAdapterList(Parcel parcel) {
    602             viewId = parcel.readInt();
    603             viewTypeCount = parcel.readInt();
    604             int count = parcel.readInt();
    605             list = new ArrayList<RemoteViews>();
    606 
    607             for (int i = 0; i < count; i++) {
    608                 RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel);
    609                 list.add(rv);
    610             }
    611         }
    612 
    613         public void writeToParcel(Parcel dest, int flags) {
    614             dest.writeInt(TAG);
    615             dest.writeInt(viewId);
    616             dest.writeInt(viewTypeCount);
    617 
    618             if (list == null || list.size() == 0) {
    619                 dest.writeInt(0);
    620             } else {
    621                 int count = list.size();
    622                 dest.writeInt(count);
    623                 for (int i = 0; i < count; i++) {
    624                     RemoteViews rv = list.get(i);
    625                     rv.writeToParcel(dest, flags);
    626                 }
    627             }
    628         }
    629 
    630         @Override
    631         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    632             final View target = root.findViewById(viewId);
    633             if (target == null) return;
    634 
    635             // Ensure that we are applying to an AppWidget root
    636             if (!(rootParent instanceof AppWidgetHostView)) {
    637                 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
    638                         "AppWidgets (root id: " + viewId + ")");
    639                 return;
    640             }
    641             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
    642             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
    643                 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
    644                         "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
    645                 return;
    646             }
    647 
    648             if (target instanceof AbsListView) {
    649                 AbsListView v = (AbsListView) target;
    650                 Adapter a = v.getAdapter();
    651                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
    652                     ((RemoteViewsListAdapter) a).setViewsList(list);
    653                 } else {
    654                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
    655                 }
    656             } else if (target instanceof AdapterViewAnimator) {
    657                 AdapterViewAnimator v = (AdapterViewAnimator) target;
    658                 Adapter a = v.getAdapter();
    659                 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) {
    660                     ((RemoteViewsListAdapter) a).setViewsList(list);
    661                 } else {
    662                     v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount));
    663                 }
    664             }
    665         }
    666 
    667         public String getActionName() {
    668             return "SetRemoteViewsAdapterList";
    669         }
    670 
    671         int viewTypeCount;
    672         ArrayList<RemoteViews> list;
    673         public final static int TAG = 15;
    674     }
    675 
    676     private class SetRemoteViewsAdapterIntent extends Action {
    677         public SetRemoteViewsAdapterIntent(int id, Intent intent) {
    678             this.viewId = id;
    679             this.intent = intent;
    680         }
    681 
    682         public SetRemoteViewsAdapterIntent(Parcel parcel) {
    683             viewId = parcel.readInt();
    684             intent = Intent.CREATOR.createFromParcel(parcel);
    685         }
    686 
    687         public void writeToParcel(Parcel dest, int flags) {
    688             dest.writeInt(TAG);
    689             dest.writeInt(viewId);
    690             intent.writeToParcel(dest, flags);
    691         }
    692 
    693         @Override
    694         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    695             final View target = root.findViewById(viewId);
    696             if (target == null) return;
    697 
    698             // Ensure that we are applying to an AppWidget root
    699             if (!(rootParent instanceof AppWidgetHostView)) {
    700                 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " +
    701                         "AppWidgets (root id: " + viewId + ")");
    702                 return;
    703             }
    704             // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it
    705             if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) {
    706                 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " +
    707                         "an AbsListView or AdapterViewAnimator (id: " + viewId + ")");
    708                 return;
    709             }
    710 
    711             // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent
    712             // RemoteViewsService
    713             AppWidgetHostView host = (AppWidgetHostView) rootParent;
    714             intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId());
    715             if (target instanceof AbsListView) {
    716                 AbsListView v = (AbsListView) target;
    717                 v.setRemoteViewsAdapter(intent);
    718                 v.setRemoteViewsOnClickHandler(handler);
    719             } else if (target instanceof AdapterViewAnimator) {
    720                 AdapterViewAnimator v = (AdapterViewAnimator) target;
    721                 v.setRemoteViewsAdapter(intent);
    722                 v.setRemoteViewsOnClickHandler(handler);
    723             }
    724         }
    725 
    726         public String getActionName() {
    727             return "SetRemoteViewsAdapterIntent";
    728         }
    729 
    730         Intent intent;
    731 
    732         public final static int TAG = 10;
    733     }
    734 
    735     /**
    736      * Equivalent to calling
    737      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
    738      * to launch the provided {@link PendingIntent}.
    739      */
    740     private class SetOnClickPendingIntent extends Action {
    741         public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
    742             this.viewId = id;
    743             this.pendingIntent = pendingIntent;
    744         }
    745 
    746         public SetOnClickPendingIntent(Parcel parcel) {
    747             viewId = parcel.readInt();
    748 
    749             // We check a flag to determine if the parcel contains a PendingIntent.
    750             if (parcel.readInt() != 0) {
    751                 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
    752             }
    753         }
    754 
    755         public void writeToParcel(Parcel dest, int flags) {
    756             dest.writeInt(TAG);
    757             dest.writeInt(viewId);
    758 
    759             // We use a flag to indicate whether the parcel contains a valid object.
    760             dest.writeInt(pendingIntent != null ? 1 : 0);
    761             if (pendingIntent != null) {
    762                 pendingIntent.writeToParcel(dest, 0 /* no flags */);
    763             }
    764         }
    765 
    766         @Override
    767         public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) {
    768             final View target = root.findViewById(viewId);
    769             if (target == null) return;
    770 
    771             // If the view is an AdapterView, setting a PendingIntent on click doesn't make much
    772             // sense, do they mean to set a PendingIntent template for the AdapterView's children?
    773             if (mIsWidgetCollectionChild) {
    774                 Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " +
    775                         "(id: " + viewId + ")");
    776                 ApplicationInfo appInfo = root.getContext().getApplicationInfo();
    777 
    778                 // We let this slide for HC and ICS so as to not break compatibility. It should have
    779                 // been disabled from the outset, but was left open by accident.
    780                 if (appInfo != null &&
    781                         appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) {
    782                     return;
    783                 }
    784             }
    785 
    786             // If the pendingIntent is null, we clear the onClickListener
    787             OnClickListener listener = null;
    788             if (pendingIntent != null) {
    789                 listener = new OnClickListener() {
    790                     public void onClick(View v) {
    791                         // Find target view location in screen coordinates and
    792                         // fill into PendingIntent before sending.
    793                         final Rect rect = getSourceBounds(v);
    794 
    795                         final Intent intent = new Intent();
    796                         intent.setSourceBounds(rect);
    797                         handler.onClickHandler(v, pendingIntent, intent);
    798                     }
    799                 };
    800             }
    801             target.setOnClickListener(listener);
    802         }
    803 
    804         public String getActionName() {
    805             return "SetOnClickPendingIntent";
    806         }
    807 
    808         PendingIntent pendingIntent;
    809 
    810         public final static int TAG = 1;
    811     }
    812 
    813     private static Rect getSourceBounds(View v) {
    814         final float appScale = v.getContext().getResources()
    815                 .getCompatibilityInfo().applicationScale;
    816         final int[] pos = new int[2];
    817         v.getLocationOnScreen(pos);
    818 
    819         final Rect rect = new Rect();
    820         rect.left = (int) (pos[0] * appScale + 0.5f);
    821         rect.top = (int) (pos[1] * appScale + 0.5f);
    822         rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
    823         rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
    824         return rect;
    825     }
    826 
    827     private Method getMethod(View view, String methodName, Class<?> paramType) {
    828         Method method;
    829         Class<? extends View> klass = view.getClass();
    830 
    831         synchronized (sMethodsLock) {
    832             ArrayMap<MutablePair<String, Class<?>>, Method> methods = sMethods.get(klass);
    833             if (methods == null) {
    834                 methods = new ArrayMap<MutablePair<String, Class<?>>, Method>();
    835                 sMethods.put(klass, methods);
    836             }
    837 
    838             mPair.first = methodName;
    839             mPair.second = paramType;
    840 
    841             method = methods.get(mPair);
    842             if (method == null) {
    843                 try {
    844                     if (paramType == null) {
    845                         method = klass.getMethod(methodName);
    846                     } else {
    847                         method = klass.getMethod(methodName, paramType);
    848                     }
    849                 } catch (NoSuchMethodException ex) {
    850                     throw new ActionException("view: " + klass.getName() + " doesn't have method: "
    851                             + methodName + getParameters(paramType));
    852                 }
    853 
    854                 if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
    855                     throw new ActionException("view: " + klass.getName()
    856                             + " can't use method with RemoteViews: "
    857                             + methodName + getParameters(paramType));
    858                 }
    859 
    860                 methods.put(new MutablePair<String, Class<?>>(methodName, paramType), method);
    861             }
    862         }
    863 
    864         return method;
    865     }
    866 
    867     /**
    868      * @return the async implementation of the provided method.
    869      */
    870     private Method getAsyncMethod(Method method) {
    871         synchronized (sAsyncMethods) {
    872             int valueIndex = sAsyncMethods.indexOfKey(method);
    873             if (valueIndex >= 0) {
    874                 return sAsyncMethods.valueAt(valueIndex);
    875             }
    876 
    877             RemotableViewMethod annotation = method.getAnnotation(RemotableViewMethod.class);
    878             Method asyncMethod = null;
    879             if (!annotation.asyncImpl().isEmpty()) {
    880                 try {
    881                     asyncMethod = method.getDeclaringClass()
    882                             .getMethod(annotation.asyncImpl(), method.getParameterTypes());
    883                     if (!asyncMethod.getReturnType().equals(Runnable.class)) {
    884                         throw new ActionException("Async implementation for " + method.getName() +
    885                             " does not return a Runnable");
    886                     }
    887                 } catch (NoSuchMethodException ex) {
    888                     throw new ActionException("Async implementation declared but not defined for " +
    889                             method.getName());
    890                 }
    891             }
    892             sAsyncMethods.put(method, asyncMethod);
    893             return asyncMethod;
    894         }
    895     }
    896 
    897     private static String getParameters(Class<?> paramType) {
    898         if (paramType == null) return "()";
    899         return "(" + paramType + ")";
    900     }
    901 
    902     private static Object[] wrapArg(Object value) {
    903         Object[] args = sInvokeArgsTls.get();
    904         args[0] = value;
    905         return args;
    906     }
    907 
    908     /**
    909      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
    910      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
    911      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
    912      * <p>
    913      * These operations will be performed on the {@link Drawable} returned by the
    914      * target {@link View#getBackground()} by default.  If targetBackground is false,
    915      * we assume the target is an {@link ImageView} and try applying the operations
    916      * to {@link ImageView#getDrawable()}.
    917      * <p>
    918      * You can omit specific calls by marking their values with null or -1.
    919      */
    920     private class SetDrawableParameters extends Action {
    921         public SetDrawableParameters(int id, boolean targetBackground, int alpha,
    922                 int colorFilter, PorterDuff.Mode mode, int level) {
    923             this.viewId = id;
    924             this.targetBackground = targetBackground;
    925             this.alpha = alpha;
    926             this.colorFilter = colorFilter;
    927             this.filterMode = mode;
    928             this.level = level;
    929         }
    930 
    931         public SetDrawableParameters(Parcel parcel) {
    932             viewId = parcel.readInt();
    933             targetBackground = parcel.readInt() != 0;
    934             alpha = parcel.readInt();
    935             colorFilter = parcel.readInt();
    936             boolean hasMode = parcel.readInt() != 0;
    937             if (hasMode) {
    938                 filterMode = PorterDuff.Mode.valueOf(parcel.readString());
    939             } else {
    940                 filterMode = null;
    941             }
    942             level = parcel.readInt();
    943         }
    944 
    945         public void writeToParcel(Parcel dest, int flags) {
    946             dest.writeInt(TAG);
    947             dest.writeInt(viewId);
    948             dest.writeInt(targetBackground ? 1 : 0);
    949             dest.writeInt(alpha);
    950             dest.writeInt(colorFilter);
    951             if (filterMode != null) {
    952                 dest.writeInt(1);
    953                 dest.writeString(filterMode.toString());
    954             } else {
    955                 dest.writeInt(0);
    956             }
    957             dest.writeInt(level);
    958         }
    959 
    960         @Override
    961         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
    962             final View target = root.findViewById(viewId);
    963             if (target == null) return;
    964 
    965             // Pick the correct drawable to modify for this view
    966             Drawable targetDrawable = null;
    967             if (targetBackground) {
    968                 targetDrawable = target.getBackground();
    969             } else if (target instanceof ImageView) {
    970                 ImageView imageView = (ImageView) target;
    971                 targetDrawable = imageView.getDrawable();
    972             }
    973 
    974             if (targetDrawable != null) {
    975                 // Perform modifications only if values are set correctly
    976                 if (alpha != -1) {
    977                     targetDrawable.mutate().setAlpha(alpha);
    978                 }
    979                 if (filterMode != null) {
    980                     targetDrawable.mutate().setColorFilter(colorFilter, filterMode);
    981                 }
    982                 if (level != -1) {
    983                     targetDrawable.mutate().setLevel(level);
    984                 }
    985             }
    986         }
    987 
    988         public String getActionName() {
    989             return "SetDrawableParameters";
    990         }
    991 
    992         boolean targetBackground;
    993         int alpha;
    994         int colorFilter;
    995         PorterDuff.Mode filterMode;
    996         int level;
    997 
    998         public final static int TAG = 3;
    999     }
   1000 
   1001     private final class ReflectionActionWithoutParams extends Action {
   1002         final String methodName;
   1003 
   1004         public final static int TAG = 5;
   1005 
   1006         ReflectionActionWithoutParams(int viewId, String methodName) {
   1007             this.viewId = viewId;
   1008             this.methodName = methodName;
   1009         }
   1010 
   1011         ReflectionActionWithoutParams(Parcel in) {
   1012             this.viewId = in.readInt();
   1013             this.methodName = in.readString();
   1014         }
   1015 
   1016         public void writeToParcel(Parcel out, int flags) {
   1017             out.writeInt(TAG);
   1018             out.writeInt(this.viewId);
   1019             out.writeString(this.methodName);
   1020         }
   1021 
   1022         @Override
   1023         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1024             final View view = root.findViewById(viewId);
   1025             if (view == null) return;
   1026 
   1027             try {
   1028                 getMethod(view, this.methodName, null).invoke(view);
   1029             } catch (ActionException e) {
   1030                 throw e;
   1031             } catch (Exception ex) {
   1032                 throw new ActionException(ex);
   1033             }
   1034         }
   1035 
   1036         public int mergeBehavior() {
   1037             // we don't need to build up showNext or showPrevious calls
   1038             if (methodName.equals("showNext") || methodName.equals("showPrevious")) {
   1039                 return MERGE_IGNORE;
   1040             } else {
   1041                 return MERGE_REPLACE;
   1042             }
   1043         }
   1044 
   1045         public String getActionName() {
   1046             return "ReflectionActionWithoutParams";
   1047         }
   1048     }
   1049 
   1050     private static class BitmapCache {
   1051         ArrayList<Bitmap> mBitmaps;
   1052 
   1053         public BitmapCache() {
   1054             mBitmaps = new ArrayList<Bitmap>();
   1055         }
   1056 
   1057         public BitmapCache(Parcel source) {
   1058             int count = source.readInt();
   1059             mBitmaps = new ArrayList<Bitmap>();
   1060             for (int i = 0; i < count; i++) {
   1061                 Bitmap b = Bitmap.CREATOR.createFromParcel(source);
   1062                 mBitmaps.add(b);
   1063             }
   1064         }
   1065 
   1066         public int getBitmapId(Bitmap b) {
   1067             if (b == null) {
   1068                 return -1;
   1069             } else {
   1070                 if (mBitmaps.contains(b)) {
   1071                     return mBitmaps.indexOf(b);
   1072                 } else {
   1073                     mBitmaps.add(b);
   1074                     return (mBitmaps.size() - 1);
   1075                 }
   1076             }
   1077         }
   1078 
   1079         public Bitmap getBitmapForId(int id) {
   1080             if (id == -1 || id >= mBitmaps.size()) {
   1081                 return null;
   1082             } else {
   1083                 return mBitmaps.get(id);
   1084             }
   1085         }
   1086 
   1087         public void writeBitmapsToParcel(Parcel dest, int flags) {
   1088             int count = mBitmaps.size();
   1089             dest.writeInt(count);
   1090             for (int i = 0; i < count; i++) {
   1091                 mBitmaps.get(i).writeToParcel(dest, flags);
   1092             }
   1093         }
   1094 
   1095         public void assimilate(BitmapCache bitmapCache) {
   1096             ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps;
   1097             int count = bitmapsToBeAdded.size();
   1098             for (int i = 0; i < count; i++) {
   1099                 Bitmap b = bitmapsToBeAdded.get(i);
   1100                 if (!mBitmaps.contains(b)) {
   1101                     mBitmaps.add(b);
   1102                 }
   1103             }
   1104         }
   1105 
   1106         public void addBitmapMemory(MemoryUsageCounter memoryCounter) {
   1107             for (int i = 0; i < mBitmaps.size(); i++) {
   1108                 memoryCounter.addBitmapMemory(mBitmaps.get(i));
   1109             }
   1110         }
   1111 
   1112         @Override
   1113         protected BitmapCache clone() {
   1114             BitmapCache bitmapCache = new BitmapCache();
   1115             bitmapCache.mBitmaps.addAll(mBitmaps);
   1116             return bitmapCache;
   1117         }
   1118     }
   1119 
   1120     private class BitmapReflectionAction extends Action {
   1121         int bitmapId;
   1122         Bitmap bitmap;
   1123         String methodName;
   1124 
   1125         BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) {
   1126             this.bitmap = bitmap;
   1127             this.viewId = viewId;
   1128             this.methodName = methodName;
   1129             bitmapId = mBitmapCache.getBitmapId(bitmap);
   1130         }
   1131 
   1132         BitmapReflectionAction(Parcel in) {
   1133             viewId = in.readInt();
   1134             methodName = in.readString();
   1135             bitmapId = in.readInt();
   1136             bitmap = mBitmapCache.getBitmapForId(bitmapId);
   1137         }
   1138 
   1139         @Override
   1140         public void writeToParcel(Parcel dest, int flags) {
   1141             dest.writeInt(TAG);
   1142             dest.writeInt(viewId);
   1143             dest.writeString(methodName);
   1144             dest.writeInt(bitmapId);
   1145         }
   1146 
   1147         @Override
   1148         public void apply(View root, ViewGroup rootParent,
   1149                 OnClickHandler handler) throws ActionException {
   1150             ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP,
   1151                     bitmap);
   1152             ra.apply(root, rootParent, handler);
   1153         }
   1154 
   1155         @Override
   1156         public void setBitmapCache(BitmapCache bitmapCache) {
   1157             bitmapId = bitmapCache.getBitmapId(bitmap);
   1158         }
   1159 
   1160         public String getActionName() {
   1161             return "BitmapReflectionAction";
   1162         }
   1163 
   1164         public final static int TAG = 12;
   1165     }
   1166 
   1167     /**
   1168      * Base class for the reflection actions.
   1169      */
   1170     private final class ReflectionAction extends Action {
   1171         static final int TAG = 2;
   1172 
   1173         static final int BOOLEAN = 1;
   1174         static final int BYTE = 2;
   1175         static final int SHORT = 3;
   1176         static final int INT = 4;
   1177         static final int LONG = 5;
   1178         static final int FLOAT = 6;
   1179         static final int DOUBLE = 7;
   1180         static final int CHAR = 8;
   1181         static final int STRING = 9;
   1182         static final int CHAR_SEQUENCE = 10;
   1183         static final int URI = 11;
   1184         // BITMAP actions are never stored in the list of actions. They are only used locally
   1185         // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache.
   1186         static final int BITMAP = 12;
   1187         static final int BUNDLE = 13;
   1188         static final int INTENT = 14;
   1189         static final int COLOR_STATE_LIST = 15;
   1190         static final int ICON = 16;
   1191 
   1192         String methodName;
   1193         int type;
   1194         Object value;
   1195 
   1196         ReflectionAction(int viewId, String methodName, int type, Object value) {
   1197             this.viewId = viewId;
   1198             this.methodName = methodName;
   1199             this.type = type;
   1200             this.value = value;
   1201         }
   1202 
   1203         ReflectionAction(Parcel in) {
   1204             this.viewId = in.readInt();
   1205             this.methodName = in.readString();
   1206             this.type = in.readInt();
   1207             //noinspection ConstantIfStatement
   1208             if (false) {
   1209                 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId)
   1210                         + " methodName=" + this.methodName + " type=" + this.type);
   1211             }
   1212 
   1213             // For some values that may have been null, we first check a flag to see if they were
   1214             // written to the parcel.
   1215             switch (this.type) {
   1216                 case BOOLEAN:
   1217                     this.value = in.readInt() != 0;
   1218                     break;
   1219                 case BYTE:
   1220                     this.value = in.readByte();
   1221                     break;
   1222                 case SHORT:
   1223                     this.value = (short)in.readInt();
   1224                     break;
   1225                 case INT:
   1226                     this.value = in.readInt();
   1227                     break;
   1228                 case LONG:
   1229                     this.value = in.readLong();
   1230                     break;
   1231                 case FLOAT:
   1232                     this.value = in.readFloat();
   1233                     break;
   1234                 case DOUBLE:
   1235                     this.value = in.readDouble();
   1236                     break;
   1237                 case CHAR:
   1238                     this.value = (char)in.readInt();
   1239                     break;
   1240                 case STRING:
   1241                     this.value = in.readString();
   1242                     break;
   1243                 case CHAR_SEQUENCE:
   1244                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
   1245                     break;
   1246                 case URI:
   1247                     if (in.readInt() != 0) {
   1248                         this.value = Uri.CREATOR.createFromParcel(in);
   1249                     }
   1250                     break;
   1251                 case BITMAP:
   1252                     if (in.readInt() != 0) {
   1253                         this.value = Bitmap.CREATOR.createFromParcel(in);
   1254                     }
   1255                     break;
   1256                 case BUNDLE:
   1257                     this.value = in.readBundle();
   1258                     break;
   1259                 case INTENT:
   1260                     if (in.readInt() != 0) {
   1261                         this.value = Intent.CREATOR.createFromParcel(in);
   1262                     }
   1263                     break;
   1264                 case COLOR_STATE_LIST:
   1265                     if (in.readInt() != 0) {
   1266                         this.value = ColorStateList.CREATOR.createFromParcel(in);
   1267                     }
   1268                     break;
   1269                 case ICON:
   1270                     if (in.readInt() != 0) {
   1271                         this.value = Icon.CREATOR.createFromParcel(in);
   1272                     }
   1273                 default:
   1274                     break;
   1275             }
   1276         }
   1277 
   1278         public void writeToParcel(Parcel out, int flags) {
   1279             out.writeInt(TAG);
   1280             out.writeInt(this.viewId);
   1281             out.writeString(this.methodName);
   1282             out.writeInt(this.type);
   1283             //noinspection ConstantIfStatement
   1284             if (false) {
   1285                 Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId)
   1286                         + " methodName=" + this.methodName + " type=" + this.type);
   1287             }
   1288 
   1289             // For some values which are null, we record an integer flag to indicate whether
   1290             // we have written a valid value to the parcel.
   1291             switch (this.type) {
   1292                 case BOOLEAN:
   1293                     out.writeInt((Boolean) this.value ? 1 : 0);
   1294                     break;
   1295                 case BYTE:
   1296                     out.writeByte((Byte) this.value);
   1297                     break;
   1298                 case SHORT:
   1299                     out.writeInt((Short) this.value);
   1300                     break;
   1301                 case INT:
   1302                     out.writeInt((Integer) this.value);
   1303                     break;
   1304                 case LONG:
   1305                     out.writeLong((Long) this.value);
   1306                     break;
   1307                 case FLOAT:
   1308                     out.writeFloat((Float) this.value);
   1309                     break;
   1310                 case DOUBLE:
   1311                     out.writeDouble((Double) this.value);
   1312                     break;
   1313                 case CHAR:
   1314                     out.writeInt((int)((Character)this.value).charValue());
   1315                     break;
   1316                 case STRING:
   1317                     out.writeString((String)this.value);
   1318                     break;
   1319                 case CHAR_SEQUENCE:
   1320                     TextUtils.writeToParcel((CharSequence)this.value, out, flags);
   1321                     break;
   1322                 case URI:
   1323                     out.writeInt(this.value != null ? 1 : 0);
   1324                     if (this.value != null) {
   1325                         ((Uri)this.value).writeToParcel(out, flags);
   1326                     }
   1327                     break;
   1328                 case BITMAP:
   1329                     out.writeInt(this.value != null ? 1 : 0);
   1330                     if (this.value != null) {
   1331                         ((Bitmap)this.value).writeToParcel(out, flags);
   1332                     }
   1333                     break;
   1334                 case BUNDLE:
   1335                     out.writeBundle((Bundle) this.value);
   1336                     break;
   1337                 case INTENT:
   1338                     out.writeInt(this.value != null ? 1 : 0);
   1339                     if (this.value != null) {
   1340                         ((Intent)this.value).writeToParcel(out, flags);
   1341                     }
   1342                     break;
   1343                 case COLOR_STATE_LIST:
   1344                     out.writeInt(this.value != null ? 1 : 0);
   1345                     if (this.value != null) {
   1346                         ((ColorStateList)this.value).writeToParcel(out, flags);
   1347                     }
   1348                     break;
   1349                 case ICON:
   1350                     out.writeInt(this.value != null ? 1 : 0);
   1351                     if (this.value != null) {
   1352                         ((Icon)this.value).writeToParcel(out, flags);
   1353                     }
   1354                     break;
   1355                 default:
   1356                     break;
   1357             }
   1358         }
   1359 
   1360         private Class<?> getParameterType() {
   1361             switch (this.type) {
   1362                 case BOOLEAN:
   1363                     return boolean.class;
   1364                 case BYTE:
   1365                     return byte.class;
   1366                 case SHORT:
   1367                     return short.class;
   1368                 case INT:
   1369                     return int.class;
   1370                 case LONG:
   1371                     return long.class;
   1372                 case FLOAT:
   1373                     return float.class;
   1374                 case DOUBLE:
   1375                     return double.class;
   1376                 case CHAR:
   1377                     return char.class;
   1378                 case STRING:
   1379                     return String.class;
   1380                 case CHAR_SEQUENCE:
   1381                     return CharSequence.class;
   1382                 case URI:
   1383                     return Uri.class;
   1384                 case BITMAP:
   1385                     return Bitmap.class;
   1386                 case BUNDLE:
   1387                     return Bundle.class;
   1388                 case INTENT:
   1389                     return Intent.class;
   1390                 case COLOR_STATE_LIST:
   1391                     return ColorStateList.class;
   1392                 case ICON:
   1393                     return Icon.class;
   1394                 default:
   1395                     return null;
   1396             }
   1397         }
   1398 
   1399         @Override
   1400         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1401             final View view = root.findViewById(viewId);
   1402             if (view == null) return;
   1403 
   1404             Class<?> param = getParameterType();
   1405             if (param == null) {
   1406                 throw new ActionException("bad type: " + this.type);
   1407             }
   1408 
   1409             try {
   1410                 getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value));
   1411             } catch (ActionException e) {
   1412                 throw e;
   1413             } catch (Exception ex) {
   1414                 throw new ActionException(ex);
   1415             }
   1416         }
   1417 
   1418         @Override
   1419         public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
   1420             final View view = root.findViewById(viewId);
   1421             if (view == null) return ACTION_NOOP;
   1422 
   1423             Class<?> param = getParameterType();
   1424             if (param == null) {
   1425                 throw new ActionException("bad type: " + this.type);
   1426             }
   1427 
   1428             try {
   1429                 Method method = getMethod(view, this.methodName, param);
   1430                 Method asyncMethod = getAsyncMethod(method);
   1431 
   1432                 if (asyncMethod != null) {
   1433                     Runnable endAction = (Runnable) asyncMethod.invoke(view, wrapArg(this.value));
   1434                     if (endAction == null) {
   1435                         return ACTION_NOOP;
   1436                     } else {
   1437                         return new RunnableAction(endAction);
   1438                     }
   1439                 }
   1440             } catch (ActionException e) {
   1441                 throw e;
   1442             } catch (Exception ex) {
   1443                 throw new ActionException(ex);
   1444             }
   1445 
   1446             return this;
   1447         }
   1448 
   1449         public int mergeBehavior() {
   1450             // smoothScrollBy is cumulative, everything else overwites.
   1451             if (methodName.equals("smoothScrollBy")) {
   1452                 return MERGE_APPEND;
   1453             } else {
   1454                 return MERGE_REPLACE;
   1455             }
   1456         }
   1457 
   1458         public String getActionName() {
   1459             // Each type of reflection action corresponds to a setter, so each should be seen as
   1460             // unique from the standpoint of merging.
   1461             return "ReflectionAction" + this.methodName + this.type;
   1462         }
   1463     }
   1464 
   1465     /**
   1466      * This is only used for async execution of actions and it not parcelable.
   1467      */
   1468     private static final class RunnableAction extends RuntimeAction {
   1469         private final Runnable mRunnable;
   1470 
   1471         RunnableAction(Runnable r) {
   1472             mRunnable = r;
   1473         }
   1474 
   1475         @Override
   1476         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1477             mRunnable.run();
   1478         }
   1479     }
   1480 
   1481     private void configureRemoteViewsAsChild(RemoteViews rv) {
   1482         mBitmapCache.assimilate(rv.mBitmapCache);
   1483         rv.setBitmapCache(mBitmapCache);
   1484         rv.setNotRoot();
   1485     }
   1486 
   1487     void setNotRoot() {
   1488         mIsRoot = false;
   1489     }
   1490 
   1491     /**
   1492      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
   1493      * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
   1494      * when null. This allows users to build "nested" {@link RemoteViews}.
   1495      */
   1496     private class ViewGroupAction extends Action {
   1497         public ViewGroupAction(int viewId, RemoteViews nestedViews) {
   1498             this.viewId = viewId;
   1499             this.nestedViews = nestedViews;
   1500             if (nestedViews != null) {
   1501                 configureRemoteViewsAsChild(nestedViews);
   1502             }
   1503         }
   1504 
   1505         public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) {
   1506             viewId = parcel.readInt();
   1507             boolean nestedViewsNull = parcel.readInt() == 0;
   1508             if (!nestedViewsNull) {
   1509                 nestedViews = new RemoteViews(parcel, bitmapCache);
   1510             } else {
   1511                 nestedViews = null;
   1512             }
   1513         }
   1514 
   1515         public void writeToParcel(Parcel dest, int flags) {
   1516             dest.writeInt(TAG);
   1517             dest.writeInt(viewId);
   1518             if (nestedViews != null) {
   1519                 dest.writeInt(1);
   1520                 nestedViews.writeToParcel(dest, flags);
   1521             } else {
   1522                 // signifies null
   1523                 dest.writeInt(0);
   1524             }
   1525         }
   1526 
   1527         @Override
   1528         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1529             final Context context = root.getContext();
   1530             final ViewGroup target = (ViewGroup) root.findViewById(viewId);
   1531             if (target == null) return;
   1532             if (nestedViews != null) {
   1533                 // Inflate nested views and add as children
   1534                 target.addView(nestedViews.apply(context, target, handler));
   1535             } else {
   1536                 // Clear all children when nested views omitted
   1537                 target.removeAllViews();
   1538             }
   1539         }
   1540 
   1541         @Override
   1542         public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
   1543             // In the async implementation, update the view tree so that subsequent calls to
   1544             // findViewById return the currect view.
   1545             root.createTree();
   1546             ViewTree target = root.findViewTreeById(viewId);
   1547             if ((target == null) || !(target.mRoot instanceof ViewGroup)) {
   1548                 return ACTION_NOOP;
   1549             }
   1550             if (nestedViews == null) {
   1551                 // Clear all children when nested views omitted
   1552                 target.mChildren = null;
   1553                 return this;
   1554             } else {
   1555                 // Inflate nested views and perform all the async tasks for the child remoteView.
   1556                 final Context context = root.mRoot.getContext();
   1557                 final AsyncApplyTask task = nestedViews.getAsyncApplyTask(
   1558                         context, (ViewGroup) target.mRoot, null, handler);
   1559                 final ViewTree tree = task.doInBackground();
   1560 
   1561                 // Update the global view tree, so that next call to findViewTreeById
   1562                 // goes through the subtree as well.
   1563                 target.addChild(tree);
   1564 
   1565                 return new RuntimeAction() {
   1566 
   1567                     @Override
   1568                     public void apply(View root, ViewGroup rootParent, OnClickHandler handler) throws ActionException {
   1569                         // This view will exist as we have already made sure
   1570                         final ViewGroup target = (ViewGroup) root.findViewById(viewId);
   1571                         task.onPostExecute(tree);
   1572                         target.addView(task.mResult);
   1573                     }
   1574                 };
   1575             }
   1576         }
   1577 
   1578         @Override
   1579         public void updateMemoryUsageEstimate(MemoryUsageCounter counter) {
   1580             if (nestedViews != null) {
   1581                 counter.increment(nestedViews.estimateMemoryUsage());
   1582             }
   1583         }
   1584 
   1585         @Override
   1586         public void setBitmapCache(BitmapCache bitmapCache) {
   1587             if (nestedViews != null) {
   1588                 nestedViews.setBitmapCache(bitmapCache);
   1589             }
   1590         }
   1591 
   1592         public String getActionName() {
   1593             return "ViewGroupAction" + (nestedViews == null ? "Remove" : "Add");
   1594         }
   1595 
   1596         public int mergeBehavior() {
   1597             return MERGE_APPEND;
   1598         }
   1599 
   1600         RemoteViews nestedViews;
   1601 
   1602         public final static int TAG = 4;
   1603     }
   1604 
   1605     /**
   1606      * Helper action to set compound drawables on a TextView. Supports relative
   1607      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
   1608      */
   1609     private class TextViewDrawableAction extends Action {
   1610         public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) {
   1611             this.viewId = viewId;
   1612             this.isRelative = isRelative;
   1613             this.useIcons = false;
   1614             this.d1 = d1;
   1615             this.d2 = d2;
   1616             this.d3 = d3;
   1617             this.d4 = d4;
   1618         }
   1619 
   1620         public TextViewDrawableAction(int viewId, boolean isRelative,
   1621                 Icon i1, Icon i2, Icon i3, Icon i4) {
   1622             this.viewId = viewId;
   1623             this.isRelative = isRelative;
   1624             this.useIcons = true;
   1625             this.i1 = i1;
   1626             this.i2 = i2;
   1627             this.i3 = i3;
   1628             this.i4 = i4;
   1629         }
   1630 
   1631         public TextViewDrawableAction(Parcel parcel) {
   1632             viewId = parcel.readInt();
   1633             isRelative = (parcel.readInt() != 0);
   1634             useIcons = (parcel.readInt() != 0);
   1635             if (useIcons) {
   1636                 if (parcel.readInt() != 0) {
   1637                     i1 = Icon.CREATOR.createFromParcel(parcel);
   1638                 }
   1639                 if (parcel.readInt() != 0) {
   1640                     i2 = Icon.CREATOR.createFromParcel(parcel);
   1641                 }
   1642                 if (parcel.readInt() != 0) {
   1643                     i3 = Icon.CREATOR.createFromParcel(parcel);
   1644                 }
   1645                 if (parcel.readInt() != 0) {
   1646                     i4 = Icon.CREATOR.createFromParcel(parcel);
   1647                 }
   1648             } else {
   1649                 d1 = parcel.readInt();
   1650                 d2 = parcel.readInt();
   1651                 d3 = parcel.readInt();
   1652                 d4 = parcel.readInt();
   1653             }
   1654         }
   1655 
   1656         public void writeToParcel(Parcel dest, int flags) {
   1657             dest.writeInt(TAG);
   1658             dest.writeInt(viewId);
   1659             dest.writeInt(isRelative ? 1 : 0);
   1660             dest.writeInt(useIcons ? 1 : 0);
   1661             if (useIcons) {
   1662                 if (i1 != null) {
   1663                     dest.writeInt(1);
   1664                     i1.writeToParcel(dest, 0);
   1665                 } else {
   1666                     dest.writeInt(0);
   1667                 }
   1668                 if (i2 != null) {
   1669                     dest.writeInt(1);
   1670                     i2.writeToParcel(dest, 0);
   1671                 } else {
   1672                     dest.writeInt(0);
   1673                 }
   1674                 if (i3 != null) {
   1675                     dest.writeInt(1);
   1676                     i3.writeToParcel(dest, 0);
   1677                 } else {
   1678                     dest.writeInt(0);
   1679                 }
   1680                 if (i4 != null) {
   1681                     dest.writeInt(1);
   1682                     i4.writeToParcel(dest, 0);
   1683                 } else {
   1684                     dest.writeInt(0);
   1685                 }
   1686             } else {
   1687                 dest.writeInt(d1);
   1688                 dest.writeInt(d2);
   1689                 dest.writeInt(d3);
   1690                 dest.writeInt(d4);
   1691             }
   1692         }
   1693 
   1694         @Override
   1695         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1696             final TextView target = (TextView) root.findViewById(viewId);
   1697             if (target == null) return;
   1698             if (drawablesLoaded) {
   1699                 if (isRelative) {
   1700                     target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
   1701                 } else {
   1702                     target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
   1703                 }
   1704             } else if (useIcons) {
   1705                 final Context ctx = target.getContext();
   1706                 final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx);
   1707                 final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx);
   1708                 final Drawable id3 = i3 == null ? null : i3.loadDrawable(ctx);
   1709                 final Drawable id4 = i4 == null ? null : i4.loadDrawable(ctx);
   1710                 if (isRelative) {
   1711                     target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4);
   1712                 } else {
   1713                     target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4);
   1714                 }
   1715             } else {
   1716                 if (isRelative) {
   1717                     target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4);
   1718                 } else {
   1719                     target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4);
   1720                 }
   1721             }
   1722         }
   1723 
   1724         @Override
   1725         public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) {
   1726             final TextView target = (TextView) root.findViewById(viewId);
   1727             if (target == null) return ACTION_NOOP;
   1728 
   1729             TextViewDrawableAction copy = useIcons ?
   1730                     new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) :
   1731                     new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4);
   1732 
   1733             // Load the drawables on the background thread.
   1734             copy.drawablesLoaded = true;
   1735             final Context ctx = target.getContext();
   1736 
   1737             if (useIcons) {
   1738                 copy.id1 = i1 == null ? null : i1.loadDrawable(ctx);
   1739                 copy.id2 = i2 == null ? null : i2.loadDrawable(ctx);
   1740                 copy.id3 = i3 == null ? null : i3.loadDrawable(ctx);
   1741                 copy.id4 = i4 == null ? null : i4.loadDrawable(ctx);
   1742             } else {
   1743                 copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1);
   1744                 copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2);
   1745                 copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3);
   1746                 copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4);
   1747             }
   1748             return copy;
   1749         }
   1750 
   1751         public String getActionName() {
   1752             return "TextViewDrawableAction";
   1753         }
   1754 
   1755         boolean isRelative = false;
   1756         boolean useIcons = false;
   1757         int d1, d2, d3, d4;
   1758         Icon i1, i2, i3, i4;
   1759 
   1760         boolean drawablesLoaded = false;
   1761         Drawable id1, id2, id3, id4;
   1762 
   1763         public final static int TAG = 11;
   1764     }
   1765 
   1766     /**
   1767      * Helper action to set text size on a TextView in any supported units.
   1768      */
   1769     private class TextViewSizeAction extends Action {
   1770         public TextViewSizeAction(int viewId, int units, float size) {
   1771             this.viewId = viewId;
   1772             this.units = units;
   1773             this.size = size;
   1774         }
   1775 
   1776         public TextViewSizeAction(Parcel parcel) {
   1777             viewId = parcel.readInt();
   1778             units = parcel.readInt();
   1779             size  = parcel.readFloat();
   1780         }
   1781 
   1782         public void writeToParcel(Parcel dest, int flags) {
   1783             dest.writeInt(TAG);
   1784             dest.writeInt(viewId);
   1785             dest.writeInt(units);
   1786             dest.writeFloat(size);
   1787         }
   1788 
   1789         @Override
   1790         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1791             final TextView target = (TextView) root.findViewById(viewId);
   1792             if (target == null) return;
   1793             target.setTextSize(units, size);
   1794         }
   1795 
   1796         public String getActionName() {
   1797             return "TextViewSizeAction";
   1798         }
   1799 
   1800         int units;
   1801         float size;
   1802 
   1803         public final static int TAG = 13;
   1804     }
   1805 
   1806     /**
   1807      * Helper action to set padding on a View.
   1808      */
   1809     private class ViewPaddingAction extends Action {
   1810         public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) {
   1811             this.viewId = viewId;
   1812             this.left = left;
   1813             this.top = top;
   1814             this.right = right;
   1815             this.bottom = bottom;
   1816         }
   1817 
   1818         public ViewPaddingAction(Parcel parcel) {
   1819             viewId = parcel.readInt();
   1820             left = parcel.readInt();
   1821             top = parcel.readInt();
   1822             right = parcel.readInt();
   1823             bottom = parcel.readInt();
   1824         }
   1825 
   1826         public void writeToParcel(Parcel dest, int flags) {
   1827             dest.writeInt(TAG);
   1828             dest.writeInt(viewId);
   1829             dest.writeInt(left);
   1830             dest.writeInt(top);
   1831             dest.writeInt(right);
   1832             dest.writeInt(bottom);
   1833         }
   1834 
   1835         @Override
   1836         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1837             final View target = root.findViewById(viewId);
   1838             if (target == null) return;
   1839             target.setPadding(left, top, right, bottom);
   1840         }
   1841 
   1842         public String getActionName() {
   1843             return "ViewPaddingAction";
   1844         }
   1845 
   1846         int left, top, right, bottom;
   1847 
   1848         public final static int TAG = 14;
   1849     }
   1850 
   1851     /**
   1852      * Helper action to set layout params on a View.
   1853      */
   1854     private static class LayoutParamAction extends Action {
   1855 
   1856         /** Set marginEnd */
   1857         public static final int LAYOUT_MARGIN_END_DIMEN = 1;
   1858         /** Set width */
   1859         public static final int LAYOUT_WIDTH = 2;
   1860         public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3;
   1861 
   1862         /**
   1863          * @param viewId ID of the view alter
   1864          * @param property which layout parameter to alter
   1865          * @param value new value of the layout parameter
   1866          */
   1867         public LayoutParamAction(int viewId, int property, int value) {
   1868             this.viewId = viewId;
   1869             this.property = property;
   1870             this.value = value;
   1871         }
   1872 
   1873         public LayoutParamAction(Parcel parcel) {
   1874             viewId = parcel.readInt();
   1875             property = parcel.readInt();
   1876             value = parcel.readInt();
   1877         }
   1878 
   1879         public void writeToParcel(Parcel dest, int flags) {
   1880             dest.writeInt(TAG);
   1881             dest.writeInt(viewId);
   1882             dest.writeInt(property);
   1883             dest.writeInt(value);
   1884         }
   1885 
   1886         @Override
   1887         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1888             final View target = root.findViewById(viewId);
   1889             if (target == null) {
   1890                 return;
   1891             }
   1892             ViewGroup.LayoutParams layoutParams = target.getLayoutParams();
   1893             if (layoutParams == null) {
   1894                 return;
   1895             }
   1896             switch (property) {
   1897                 case LAYOUT_MARGIN_END_DIMEN:
   1898                     if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
   1899                         int resolved = resolveDimenPixelOffset(target, value);
   1900                         ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(resolved);
   1901                         target.setLayoutParams(layoutParams);
   1902                     }
   1903                     break;
   1904                 case LAYOUT_MARGIN_BOTTOM_DIMEN:
   1905                     if (layoutParams instanceof ViewGroup.MarginLayoutParams) {
   1906                         int resolved = resolveDimenPixelOffset(target, value);
   1907                         ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved;
   1908                         target.setLayoutParams(layoutParams);
   1909                     }
   1910                     break;
   1911                 case LAYOUT_WIDTH:
   1912                     layoutParams.width = value;
   1913                     target.setLayoutParams(layoutParams);
   1914                     break;
   1915                 default:
   1916                     throw new IllegalArgumentException("Unknown property " + property);
   1917             }
   1918         }
   1919 
   1920         private static int resolveDimenPixelOffset(View target, int value) {
   1921             if (value == 0) {
   1922                 return 0;
   1923             }
   1924             return target.getContext().getResources().getDimensionPixelOffset(value);
   1925         }
   1926 
   1927         public String getActionName() {
   1928             return "LayoutParamAction" + property + ".";
   1929         }
   1930 
   1931         int property;
   1932         int value;
   1933 
   1934         public final static int TAG = 19;
   1935     }
   1936 
   1937     /**
   1938      * Helper action to set a color filter on a compound drawable on a TextView. Supports relative
   1939      * (s/t/e/b) or cardinal (l/t/r/b) arrangement.
   1940      */
   1941     private class TextViewDrawableColorFilterAction extends Action {
   1942         public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index,
   1943                 int color, PorterDuff.Mode mode) {
   1944             this.viewId = viewId;
   1945             this.isRelative = isRelative;
   1946             this.index = index;
   1947             this.color = color;
   1948             this.mode = mode;
   1949         }
   1950 
   1951         public TextViewDrawableColorFilterAction(Parcel parcel) {
   1952             viewId = parcel.readInt();
   1953             isRelative = (parcel.readInt() != 0);
   1954             index = parcel.readInt();
   1955             color = parcel.readInt();
   1956             mode = readPorterDuffMode(parcel);
   1957         }
   1958 
   1959         private PorterDuff.Mode readPorterDuffMode(Parcel parcel) {
   1960             int mode = parcel.readInt();
   1961             if (mode >= 0 && mode < PorterDuff.Mode.values().length) {
   1962                 return PorterDuff.Mode.values()[mode];
   1963             } else {
   1964                 return PorterDuff.Mode.CLEAR;
   1965             }
   1966         }
   1967 
   1968         public void writeToParcel(Parcel dest, int flags) {
   1969             dest.writeInt(TAG);
   1970             dest.writeInt(viewId);
   1971             dest.writeInt(isRelative ? 1 : 0);
   1972             dest.writeInt(index);
   1973             dest.writeInt(color);
   1974             dest.writeInt(mode.ordinal());
   1975         }
   1976 
   1977         @Override
   1978         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   1979             final TextView target = (TextView) root.findViewById(viewId);
   1980             if (target == null) return;
   1981             Drawable[] drawables = isRelative
   1982                     ? target.getCompoundDrawablesRelative()
   1983                     : target.getCompoundDrawables();
   1984             if (index < 0 || index >= 4) {
   1985                 throw new IllegalStateException("index must be in range [0, 3].");
   1986             }
   1987             Drawable d = drawables[index];
   1988             if (d != null) {
   1989                 d.mutate();
   1990                 d.setColorFilter(color, mode);
   1991             }
   1992         }
   1993 
   1994         public String getActionName() {
   1995             return "TextViewDrawableColorFilterAction";
   1996         }
   1997 
   1998         final boolean isRelative;
   1999         final int index;
   2000         final int color;
   2001         final PorterDuff.Mode mode;
   2002 
   2003         public final static int TAG = 17;
   2004     }
   2005 
   2006     /**
   2007      * Helper action to add a view tag with RemoteInputs.
   2008      */
   2009     private class SetRemoteInputsAction extends Action {
   2010 
   2011         public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) {
   2012             this.viewId = viewId;
   2013             this.remoteInputs = remoteInputs;
   2014         }
   2015 
   2016         public SetRemoteInputsAction(Parcel parcel) {
   2017             viewId = parcel.readInt();
   2018             remoteInputs = parcel.createTypedArray(RemoteInput.CREATOR);
   2019         }
   2020 
   2021         public void writeToParcel(Parcel dest, int flags) {
   2022             dest.writeInt(TAG);
   2023             dest.writeInt(viewId);
   2024             dest.writeTypedArray(remoteInputs, flags);
   2025         }
   2026 
   2027         @Override
   2028         public void apply(View root, ViewGroup rootParent, OnClickHandler handler) {
   2029             final TextView target = (TextView) root.findViewById(viewId);
   2030             if (target == null) return;
   2031 
   2032             target.setTagInternal(R.id.remote_input_tag, remoteInputs);
   2033         }
   2034 
   2035         public String getActionName() {
   2036             return "SetRemoteInputsAction";
   2037         }
   2038 
   2039         final Parcelable[] remoteInputs;
   2040         public final static int TAG = 18;
   2041     }
   2042 
   2043     /**
   2044      * Simple class used to keep track of memory usage in a RemoteViews.
   2045      *
   2046      */
   2047     private class MemoryUsageCounter {
   2048         public void clear() {
   2049             mMemoryUsage = 0;
   2050         }
   2051 
   2052         public void increment(int numBytes) {
   2053             mMemoryUsage += numBytes;
   2054         }
   2055 
   2056         public int getMemoryUsage() {
   2057             return mMemoryUsage;
   2058         }
   2059 
   2060         @SuppressWarnings("deprecation")
   2061         public void addBitmapMemory(Bitmap b) {
   2062             final Bitmap.Config c = b.getConfig();
   2063             // If we don't know, be pessimistic and assume 4
   2064             int bpp = 4;
   2065             if (c != null) {
   2066                 switch (c) {
   2067                     case ALPHA_8:
   2068                         bpp = 1;
   2069                         break;
   2070                     case RGB_565:
   2071                     case ARGB_4444:
   2072                         bpp = 2;
   2073                         break;
   2074                     case ARGB_8888:
   2075                         bpp = 4;
   2076                         break;
   2077                 }
   2078             }
   2079             increment(b.getWidth() * b.getHeight() * bpp);
   2080         }
   2081 
   2082         int mMemoryUsage;
   2083     }
   2084 
   2085     /**
   2086      * Create a new RemoteViews object that will display the views contained
   2087      * in the specified layout file.
   2088      *
   2089      * @param packageName Name of the package that contains the layout resource
   2090      * @param layoutId The id of the layout resource
   2091      */
   2092     public RemoteViews(String packageName, int layoutId) {
   2093         this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId);
   2094     }
   2095 
   2096     /**
   2097      * Create a new RemoteViews object that will display the views contained
   2098      * in the specified layout file.
   2099      *
   2100      * @param packageName Name of the package that contains the layout resource.
   2101      * @param userId The user under which the package is running.
   2102      * @param layoutId The id of the layout resource.
   2103      *
   2104      * @hide
   2105      */
   2106     public RemoteViews(String packageName, int userId, int layoutId) {
   2107         this(getApplicationInfo(packageName, userId), layoutId);
   2108     }
   2109 
   2110     /**
   2111      * Create a new RemoteViews object that will display the views contained
   2112      * in the specified layout file.
   2113      *
   2114      * @param application The application whose content is shown by the views.
   2115      * @param layoutId The id of the layout resource.
   2116      *
   2117      * @hide
   2118      */
   2119     protected RemoteViews(ApplicationInfo application, int layoutId) {
   2120         mApplication = application;
   2121         mLayoutId = layoutId;
   2122         mBitmapCache = new BitmapCache();
   2123         // setup the memory usage statistics
   2124         mMemoryUsageCounter = new MemoryUsageCounter();
   2125         recalculateMemoryUsage();
   2126     }
   2127 
   2128     private boolean hasLandscapeAndPortraitLayouts() {
   2129         return (mLandscape != null) && (mPortrait != null);
   2130     }
   2131 
   2132     /**
   2133      * Create a new RemoteViews object that will inflate as the specified
   2134      * landspace or portrait RemoteViews, depending on the current configuration.
   2135      *
   2136      * @param landscape The RemoteViews to inflate in landscape configuration
   2137      * @param portrait The RemoteViews to inflate in portrait configuration
   2138      */
   2139     public RemoteViews(RemoteViews landscape, RemoteViews portrait) {
   2140         if (landscape == null || portrait == null) {
   2141             throw new RuntimeException("Both RemoteViews must be non-null");
   2142         }
   2143         if (landscape.mApplication.uid != portrait.mApplication.uid
   2144                 || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) {
   2145             throw new RuntimeException("Both RemoteViews must share the same package and user");
   2146         }
   2147         mApplication = portrait.mApplication;
   2148         mLayoutId = portrait.getLayoutId();
   2149 
   2150         mLandscape = landscape;
   2151         mPortrait = portrait;
   2152 
   2153         // setup the memory usage statistics
   2154         mMemoryUsageCounter = new MemoryUsageCounter();
   2155 
   2156         mBitmapCache = new BitmapCache();
   2157         configureRemoteViewsAsChild(landscape);
   2158         configureRemoteViewsAsChild(portrait);
   2159 
   2160         recalculateMemoryUsage();
   2161     }
   2162 
   2163     /**
   2164      * Reads a RemoteViews object from a parcel.
   2165      *
   2166      * @param parcel
   2167      */
   2168     public RemoteViews(Parcel parcel) {
   2169         this(parcel, null);
   2170     }
   2171 
   2172     private RemoteViews(Parcel parcel, BitmapCache bitmapCache) {
   2173         int mode = parcel.readInt();
   2174 
   2175         // We only store a bitmap cache in the root of the RemoteViews.
   2176         if (bitmapCache == null) {
   2177             mBitmapCache = new BitmapCache(parcel);
   2178         } else {
   2179             setBitmapCache(bitmapCache);
   2180             setNotRoot();
   2181         }
   2182 
   2183         if (mode == MODE_NORMAL) {
   2184             mApplication = parcel.readParcelable(null);
   2185             mLayoutId = parcel.readInt();
   2186             mIsWidgetCollectionChild = parcel.readInt() == 1;
   2187 
   2188             int count = parcel.readInt();
   2189             if (count > 0) {
   2190                 mActions = new ArrayList<Action>(count);
   2191                 for (int i=0; i<count; i++) {
   2192                     int tag = parcel.readInt();
   2193                     switch (tag) {
   2194                         case SetOnClickPendingIntent.TAG:
   2195                             mActions.add(new SetOnClickPendingIntent(parcel));
   2196                             break;
   2197                         case SetDrawableParameters.TAG:
   2198                             mActions.add(new SetDrawableParameters(parcel));
   2199                             break;
   2200                         case ReflectionAction.TAG:
   2201                             mActions.add(new ReflectionAction(parcel));
   2202                             break;
   2203                         case ViewGroupAction.TAG:
   2204                             mActions.add(new ViewGroupAction(parcel, mBitmapCache));
   2205                             break;
   2206                         case ReflectionActionWithoutParams.TAG:
   2207                             mActions.add(new ReflectionActionWithoutParams(parcel));
   2208                             break;
   2209                         case SetEmptyView.TAG:
   2210                             mActions.add(new SetEmptyView(parcel));
   2211                             break;
   2212                         case SetPendingIntentTemplate.TAG:
   2213                             mActions.add(new SetPendingIntentTemplate(parcel));
   2214                             break;
   2215                         case SetOnClickFillInIntent.TAG:
   2216                             mActions.add(new SetOnClickFillInIntent(parcel));
   2217                             break;
   2218                         case SetRemoteViewsAdapterIntent.TAG:
   2219                             mActions.add(new SetRemoteViewsAdapterIntent(parcel));
   2220                             break;
   2221                         case TextViewDrawableAction.TAG:
   2222                             mActions.add(new TextViewDrawableAction(parcel));
   2223                             break;
   2224                         case TextViewSizeAction.TAG:
   2225                             mActions.add(new TextViewSizeAction(parcel));
   2226                             break;
   2227                         case ViewPaddingAction.TAG:
   2228                             mActions.add(new ViewPaddingAction(parcel));
   2229                             break;
   2230                         case BitmapReflectionAction.TAG:
   2231                             mActions.add(new BitmapReflectionAction(parcel));
   2232                             break;
   2233                         case SetRemoteViewsAdapterList.TAG:
   2234                             mActions.add(new SetRemoteViewsAdapterList(parcel));
   2235                             break;
   2236                         case TextViewDrawableColorFilterAction.TAG:
   2237                             mActions.add(new TextViewDrawableColorFilterAction(parcel));
   2238                             break;
   2239                         case SetRemoteInputsAction.TAG:
   2240                             mActions.add(new SetRemoteInputsAction(parcel));
   2241                             break;
   2242                         case LayoutParamAction.TAG:
   2243                             mActions.add(new LayoutParamAction(parcel));
   2244                             break;
   2245                         default:
   2246                             throw new ActionException("Tag " + tag + " not found");
   2247                     }
   2248                 }
   2249             }
   2250         } else {
   2251             // MODE_HAS_LANDSCAPE_AND_PORTRAIT
   2252             mLandscape = new RemoteViews(parcel, mBitmapCache);
   2253             mPortrait = new RemoteViews(parcel, mBitmapCache);
   2254             mApplication = mPortrait.mApplication;
   2255             mLayoutId = mPortrait.getLayoutId();
   2256         }
   2257 
   2258         // setup the memory usage statistics
   2259         mMemoryUsageCounter = new MemoryUsageCounter();
   2260         recalculateMemoryUsage();
   2261     }
   2262 
   2263 
   2264     public RemoteViews clone() {
   2265         Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. "
   2266                 + "May only clone the root of a RemoteView hierarchy.");
   2267 
   2268         Parcel p = Parcel.obtain();
   2269 
   2270         // Do not parcel the Bitmap cache - doing so creates an expensive copy of all bitmaps.
   2271         // Instead pretend we're not owning the cache while parceling.
   2272         mIsRoot = false;
   2273         writeToParcel(p, 0);
   2274         p.setDataPosition(0);
   2275         mIsRoot = true;
   2276 
   2277         RemoteViews rv = new RemoteViews(p, mBitmapCache.clone());
   2278         rv.mIsRoot = true;
   2279 
   2280         p.recycle();
   2281         return rv;
   2282     }
   2283 
   2284     public String getPackage() {
   2285         return (mApplication != null) ? mApplication.packageName : null;
   2286     }
   2287 
   2288     /**
   2289      * Returns the layout id of the root layout associated with this RemoteViews. In the case
   2290      * that the RemoteViews has both a landscape and portrait root, this will return the layout
   2291      * id associated with the portrait layout.
   2292      *
   2293      * @return the layout id.
   2294      */
   2295     public int getLayoutId() {
   2296         return mLayoutId;
   2297     }
   2298 
   2299     /*
   2300      * This flag indicates whether this RemoteViews object is being created from a
   2301      * RemoteViewsService for use as a child of a widget collection. This flag is used
   2302      * to determine whether or not certain features are available, in particular,
   2303      * setting on click extras and setting on click pending intents. The former is enabled,
   2304      * and the latter disabled when this flag is true.
   2305      */
   2306     void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) {
   2307         mIsWidgetCollectionChild = isWidgetCollectionChild;
   2308     }
   2309 
   2310     /**
   2311      * Updates the memory usage statistics.
   2312      */
   2313     private void recalculateMemoryUsage() {
   2314         mMemoryUsageCounter.clear();
   2315 
   2316         if (!hasLandscapeAndPortraitLayouts()) {
   2317             // Accumulate the memory usage for each action
   2318             if (mActions != null) {
   2319                 final int count = mActions.size();
   2320                 for (int i= 0; i < count; ++i) {
   2321                     mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter);
   2322                 }
   2323             }
   2324             if (mIsRoot) {
   2325                 mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
   2326             }
   2327         } else {
   2328             mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage());
   2329             mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage());
   2330             mBitmapCache.addBitmapMemory(mMemoryUsageCounter);
   2331         }
   2332     }
   2333 
   2334     /**
   2335      * Recursively sets BitmapCache in the hierarchy and update the bitmap ids.
   2336      */
   2337     private void setBitmapCache(BitmapCache bitmapCache) {
   2338         mBitmapCache = bitmapCache;
   2339         if (!hasLandscapeAndPortraitLayouts()) {
   2340             if (mActions != null) {
   2341                 final int count = mActions.size();
   2342                 for (int i= 0; i < count; ++i) {
   2343                     mActions.get(i).setBitmapCache(bitmapCache);
   2344                 }
   2345             }
   2346         } else {
   2347             mLandscape.setBitmapCache(bitmapCache);
   2348             mPortrait.setBitmapCache(bitmapCache);
   2349         }
   2350     }
   2351 
   2352     /**
   2353      * Returns an estimate of the bitmap heap memory usage for this RemoteViews.
   2354      */
   2355     /** @hide */
   2356     public int estimateMemoryUsage() {
   2357         return mMemoryUsageCounter.getMemoryUsage();
   2358     }
   2359 
   2360     /**
   2361      * Add an action to be executed on the remote side when apply is called.
   2362      *
   2363      * @param a The action to add
   2364      */
   2365     private void addAction(Action a) {
   2366         if (hasLandscapeAndPortraitLayouts()) {
   2367             throw new RuntimeException("RemoteViews specifying separate landscape and portrait" +
   2368                     " layouts cannot be modified. Instead, fully configure the landscape and" +
   2369                     " portrait layouts individually before constructing the combined layout.");
   2370         }
   2371         if (mActions == null) {
   2372             mActions = new ArrayList<Action>();
   2373         }
   2374         mActions.add(a);
   2375 
   2376         // update the memory usage stats
   2377         a.updateMemoryUsageEstimate(mMemoryUsageCounter);
   2378     }
   2379 
   2380     /**
   2381      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
   2382      * given {@link RemoteViews}. This allows users to build "nested"
   2383      * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
   2384      * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
   2385      * children.
   2386      *
   2387      * @param viewId The id of the parent {@link ViewGroup} to add child into.
   2388      * @param nestedView {@link RemoteViews} that describes the child.
   2389      */
   2390     public void addView(int viewId, RemoteViews nestedView) {
   2391         addAction(new ViewGroupAction(viewId, nestedView));
   2392     }
   2393 
   2394     /**
   2395      * Equivalent to calling {@link ViewGroup#removeAllViews()}.
   2396      *
   2397      * @param viewId The id of the parent {@link ViewGroup} to remove all
   2398      *            children from.
   2399      */
   2400     public void removeAllViews(int viewId) {
   2401         addAction(new ViewGroupAction(viewId, null));
   2402     }
   2403 
   2404     /**
   2405      * Equivalent to calling {@link AdapterViewAnimator#showNext()}
   2406      *
   2407      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()}
   2408      */
   2409     public void showNext(int viewId) {
   2410         addAction(new ReflectionActionWithoutParams(viewId, "showNext"));
   2411     }
   2412 
   2413     /**
   2414      * Equivalent to calling {@link AdapterViewAnimator#showPrevious()}
   2415      *
   2416      * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()}
   2417      */
   2418     public void showPrevious(int viewId) {
   2419         addAction(new ReflectionActionWithoutParams(viewId, "showPrevious"));
   2420     }
   2421 
   2422     /**
   2423      * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)}
   2424      *
   2425      * @param viewId The id of the view on which to call
   2426      *               {@link AdapterViewAnimator#setDisplayedChild(int)}
   2427      */
   2428     public void setDisplayedChild(int viewId, int childIndex) {
   2429         setInt(viewId, "setDisplayedChild", childIndex);
   2430     }
   2431 
   2432     /**
   2433      * Equivalent to calling View.setVisibility
   2434      *
   2435      * @param viewId The id of the view whose visibility should change
   2436      * @param visibility The new visibility for the view
   2437      */
   2438     public void setViewVisibility(int viewId, int visibility) {
   2439         setInt(viewId, "setVisibility", visibility);
   2440     }
   2441 
   2442     /**
   2443      * Equivalent to calling TextView.setText
   2444      *
   2445      * @param viewId The id of the view whose text should change
   2446      * @param text The new text for the view
   2447      */
   2448     public void setTextViewText(int viewId, CharSequence text) {
   2449         setCharSequence(viewId, "setText", text);
   2450     }
   2451 
   2452     /**
   2453      * Equivalent to calling {@link TextView#setTextSize(int, float)}
   2454      *
   2455      * @param viewId The id of the view whose text size should change
   2456      * @param units The units of size (e.g. COMPLEX_UNIT_SP)
   2457      * @param size The size of the text
   2458      */
   2459     public void setTextViewTextSize(int viewId, int units, float size) {
   2460         addAction(new TextViewSizeAction(viewId, units, size));
   2461     }
   2462 
   2463     /**
   2464      * Equivalent to calling
   2465      * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}.
   2466      *
   2467      * @param viewId The id of the view whose text should change
   2468      * @param left The id of a drawable to place to the left of the text, or 0
   2469      * @param top The id of a drawable to place above the text, or 0
   2470      * @param right The id of a drawable to place to the right of the text, or 0
   2471      * @param bottom The id of a drawable to place below the text, or 0
   2472      */
   2473     public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) {
   2474         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
   2475     }
   2476 
   2477     /**
   2478      * Equivalent to calling {@link
   2479      * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}.
   2480      *
   2481      * @param viewId The id of the view whose text should change
   2482      * @param start The id of a drawable to place before the text (relative to the
   2483      * layout direction), or 0
   2484      * @param top The id of a drawable to place above the text, or 0
   2485      * @param end The id of a drawable to place after the text, or 0
   2486      * @param bottom The id of a drawable to place below the text, or 0
   2487      */
   2488     public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) {
   2489         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
   2490     }
   2491 
   2492     /**
   2493      * Equivalent to applying a color filter on one of the drawables in
   2494      * {@link android.widget.TextView#getCompoundDrawablesRelative()}.
   2495      *
   2496      * @param viewId The id of the view whose text should change.
   2497      * @param index  The index of the drawable in the array of
   2498      *               {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color
   2499      *               filter on. Must be in [0, 3].
   2500      * @param color  The color of the color filter. See
   2501      *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
   2502      * @param mode   The mode of the color filter. See
   2503      *               {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}.
   2504      * @hide
   2505      */
   2506     public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId,
   2507             int index, int color, PorterDuff.Mode mode) {
   2508         if (index < 0 || index >= 4) {
   2509             throw new IllegalArgumentException("index must be in range [0, 3].");
   2510         }
   2511         addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode));
   2512     }
   2513 
   2514     /**
   2515      * Equivalent to calling {@link
   2516      * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
   2517      * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
   2518      *
   2519      * @param viewId The id of the view whose text should change
   2520      * @param left an Icon to place to the left of the text, or 0
   2521      * @param top an Icon to place above the text, or 0
   2522      * @param right an Icon to place to the right of the text, or 0
   2523      * @param bottom an Icon to place below the text, or 0
   2524      *
   2525      * @hide
   2526      */
   2527     public void setTextViewCompoundDrawables(int viewId, Icon left, Icon top, Icon right, Icon bottom) {
   2528         addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom));
   2529     }
   2530 
   2531     /**
   2532      * Equivalent to calling {@link
   2533      * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)}
   2534      * using the drawables yielded by {@link Icon#loadDrawable(Context)}.
   2535      *
   2536      * @param viewId The id of the view whose text should change
   2537      * @param start an Icon to place before the text (relative to the
   2538      * layout direction), or 0
   2539      * @param top an Icon to place above the text, or 0
   2540      * @param end an Icon to place after the text, or 0
   2541      * @param bottom an Icon to place below the text, or 0
   2542      *
   2543      * @hide
   2544      */
   2545     public void setTextViewCompoundDrawablesRelative(int viewId, Icon start, Icon top, Icon end, Icon bottom) {
   2546         addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom));
   2547     }
   2548 
   2549     /**
   2550      * Equivalent to calling ImageView.setImageResource
   2551      *
   2552      * @param viewId The id of the view whose drawable should change
   2553      * @param srcId The new resource id for the drawable
   2554      */
   2555     public void setImageViewResource(int viewId, int srcId) {
   2556         setInt(viewId, "setImageResource", srcId);
   2557     }
   2558 
   2559     /**
   2560      * Equivalent to calling ImageView.setImageURI
   2561      *
   2562      * @param viewId The id of the view whose drawable should change
   2563      * @param uri The Uri for the image
   2564      */
   2565     public void setImageViewUri(int viewId, Uri uri) {
   2566         setUri(viewId, "setImageURI", uri);
   2567     }
   2568 
   2569     /**
   2570      * Equivalent to calling ImageView.setImageBitmap
   2571      *
   2572      * @param viewId The id of the view whose bitmap should change
   2573      * @param bitmap The new Bitmap for the drawable
   2574      */
   2575     public void setImageViewBitmap(int viewId, Bitmap bitmap) {
   2576         setBitmap(viewId, "setImageBitmap", bitmap);
   2577     }
   2578 
   2579     /**
   2580      * Equivalent to calling ImageView.setImageIcon
   2581      *
   2582      * @param viewId The id of the view whose bitmap should change
   2583      * @param icon The new Icon for the ImageView
   2584      */
   2585     public void setImageViewIcon(int viewId, Icon icon) {
   2586         setIcon(viewId, "setImageIcon", icon);
   2587     }
   2588 
   2589     /**
   2590      * Equivalent to calling AdapterView.setEmptyView
   2591      *
   2592      * @param viewId The id of the view on which to set the empty view
   2593      * @param emptyViewId The view id of the empty view
   2594      */
   2595     public void setEmptyView(int viewId, int emptyViewId) {
   2596         addAction(new SetEmptyView(viewId, emptyViewId));
   2597     }
   2598 
   2599     /**
   2600      * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
   2601      * {@link Chronometer#setFormat Chronometer.setFormat},
   2602      * and {@link Chronometer#start Chronometer.start()} or
   2603      * {@link Chronometer#stop Chronometer.stop()}.
   2604      *
   2605      * @param viewId The id of the {@link Chronometer} to change
   2606      * @param base The time at which the timer would have read 0:00.  This
   2607      *             time should be based off of
   2608      *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
   2609      * @param format The Chronometer format string, or null to
   2610      *               simply display the timer value.
   2611      * @param started True if you want the clock to be started, false if not.
   2612      *
   2613      * @see #setChronometerCountDown(int, boolean)
   2614      */
   2615     public void setChronometer(int viewId, long base, String format, boolean started) {
   2616         setLong(viewId, "setBase", base);
   2617         setString(viewId, "setFormat", format);
   2618         setBoolean(viewId, "setStarted", started);
   2619     }
   2620 
   2621     /**
   2622      * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on
   2623      * the chronometer with the given viewId.
   2624      *
   2625      * @param viewId The id of the {@link Chronometer} to change
   2626      * @param isCountDown True if you want the chronometer to count down to base instead of
   2627      *                    counting up.
   2628      */
   2629     public void setChronometerCountDown(int viewId, boolean isCountDown) {
   2630         setBoolean(viewId, "setCountDown", isCountDown);
   2631     }
   2632 
   2633     /**
   2634      * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
   2635      * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
   2636      * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
   2637      *
   2638      * If indeterminate is true, then the values for max and progress are ignored.
   2639      *
   2640      * @param viewId The id of the {@link ProgressBar} to change
   2641      * @param max The 100% value for the progress bar
   2642      * @param progress The current value of the progress bar.
   2643      * @param indeterminate True if the progress bar is indeterminate,
   2644      *                false if not.
   2645      */
   2646     public void setProgressBar(int viewId, int max, int progress,
   2647             boolean indeterminate) {
   2648         setBoolean(viewId, "setIndeterminate", indeterminate);
   2649         if (!indeterminate) {
   2650             setInt(viewId, "setMax", max);
   2651             setInt(viewId, "setProgress", progress);
   2652         }
   2653     }
   2654 
   2655     /**
   2656      * Equivalent to calling
   2657      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
   2658      * to launch the provided {@link PendingIntent}.
   2659      *
   2660      * When setting the on-click action of items within collections (eg. {@link ListView},
   2661      * {@link StackView} etc.), this method will not work. Instead, use {@link
   2662      * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with
   2663      * RemoteViews#setOnClickFillInIntent(int, Intent).
   2664      *
   2665      * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
   2666      * @param pendingIntent The {@link PendingIntent} to send when user clicks
   2667      */
   2668     public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
   2669         addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
   2670     }
   2671 
   2672     /**
   2673      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
   2674      * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
   2675      * this method should be used to set a single PendingIntent template on the collection, and
   2676      * individual items can differentiate their on-click behavior using
   2677      * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}.
   2678      *
   2679      * @param viewId The id of the collection who's children will use this PendingIntent template
   2680      *          when clicked
   2681      * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified
   2682      *          by a child of viewId and executed when that child is clicked
   2683      */
   2684     public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) {
   2685         addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate));
   2686     }
   2687 
   2688     /**
   2689      * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very
   2690      * costly to set PendingIntents on the individual items, and is hence not permitted. Instead
   2691      * a single PendingIntent template can be set on the collection, see {@link
   2692      * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click
   2693      * action of a given item can be distinguished by setting a fillInIntent on that item. The
   2694      * fillInIntent is then combined with the PendingIntent template in order to determine the final
   2695      * intent which will be executed when the item is clicked. This works as follows: any fields
   2696      * which are left blank in the PendingIntent template, but are provided by the fillInIntent
   2697      * will be overwritten, and the resulting PendingIntent will be used.
   2698      *
   2699      *
   2700      * of the PendingIntent template will then be filled in with the associated fields that are
   2701      * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details.
   2702      *
   2703      * @param viewId The id of the view on which to set the fillInIntent
   2704      * @param fillInIntent The intent which will be combined with the parent's PendingIntent
   2705      *        in order to determine the on-click behavior of the view specified by viewId
   2706      */
   2707     public void setOnClickFillInIntent(int viewId, Intent fillInIntent) {
   2708         addAction(new SetOnClickFillInIntent(viewId, fillInIntent));
   2709     }
   2710 
   2711     /**
   2712      * @hide
   2713      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
   2714      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
   2715      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
   2716      * view.
   2717      * <p>
   2718      * You can omit specific calls by marking their values with null or -1.
   2719      *
   2720      * @param viewId The id of the view that contains the target
   2721      *            {@link Drawable}
   2722      * @param targetBackground If true, apply these parameters to the
   2723      *            {@link Drawable} returned by
   2724      *            {@link android.view.View#getBackground()}. Otherwise, assume
   2725      *            the target view is an {@link ImageView} and apply them to
   2726      *            {@link ImageView#getDrawable()}.
   2727      * @param alpha Specify an alpha value for the drawable, or -1 to leave
   2728      *            unchanged.
   2729      * @param colorFilter Specify a color for a
   2730      *            {@link android.graphics.ColorFilter} for this drawable. This will be ignored if
   2731      *            {@code mode} is {@code null}.
   2732      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
   2733      *            unchanged.
   2734      * @param level Specify the level for the drawable, or -1 to leave
   2735      *            unchanged.
   2736      */
   2737     public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
   2738             int colorFilter, PorterDuff.Mode mode, int level) {
   2739         addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
   2740                 colorFilter, mode, level));
   2741     }
   2742 
   2743     /**
   2744      * @hide
   2745      * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}.
   2746      *
   2747      * @param viewId The id of the view whose tint should change
   2748      * @param tint the tint to apply, may be {@code null} to clear tint
   2749      */
   2750     public void setProgressTintList(int viewId, ColorStateList tint) {
   2751         addAction(new ReflectionAction(viewId, "setProgressTintList",
   2752                 ReflectionAction.COLOR_STATE_LIST, tint));
   2753     }
   2754 
   2755     /**
   2756      * @hide
   2757      * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}.
   2758      *
   2759      * @param viewId The id of the view whose tint should change
   2760      * @param tint the tint to apply, may be {@code null} to clear tint
   2761      */
   2762     public void setProgressBackgroundTintList(int viewId, ColorStateList tint) {
   2763         addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList",
   2764                 ReflectionAction.COLOR_STATE_LIST, tint));
   2765     }
   2766 
   2767     /**
   2768      * @hide
   2769      * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}.
   2770      *
   2771      * @param viewId The id of the view whose tint should change
   2772      * @param tint the tint to apply, may be {@code null} to clear tint
   2773      */
   2774     public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) {
   2775         addAction(new ReflectionAction(viewId, "setIndeterminateTintList",
   2776                 ReflectionAction.COLOR_STATE_LIST, tint));
   2777     }
   2778 
   2779     /**
   2780      * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
   2781      *
   2782      * @param viewId The id of the view whose text color should change
   2783      * @param color Sets the text color for all the states (normal, selected,
   2784      *            focused) to be this color.
   2785      */
   2786     public void setTextColor(int viewId, @ColorInt int color) {
   2787         setInt(viewId, "setTextColor", color);
   2788     }
   2789 
   2790     /**
   2791      * @hide
   2792      * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}.
   2793      *
   2794      * @param viewId The id of the view whose text color should change
   2795      * @param colors the text colors to set
   2796      */
   2797     public void setTextColor(int viewId, @ColorInt ColorStateList colors) {
   2798         addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST,
   2799                 colors));
   2800     }
   2801 
   2802     /**
   2803      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
   2804      *
   2805      * @param appWidgetId The id of the app widget which contains the specified view. (This
   2806      *      parameter is ignored in this deprecated method)
   2807      * @param viewId The id of the {@link AdapterView}
   2808      * @param intent The intent of the service which will be
   2809      *            providing data to the RemoteViewsAdapter
   2810      * @deprecated This method has been deprecated. See
   2811      *      {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)}
   2812      */
   2813     @Deprecated
   2814     public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) {
   2815         setRemoteAdapter(viewId, intent);
   2816     }
   2817 
   2818     /**
   2819      * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}.
   2820      * Can only be used for App Widgets.
   2821      *
   2822      * @param viewId The id of the {@link AdapterView}
   2823      * @param intent The intent of the service which will be
   2824      *            providing data to the RemoteViewsAdapter
   2825      */
   2826     public void setRemoteAdapter(int viewId, Intent intent) {
   2827         addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
   2828     }
   2829 
   2830     /**
   2831      * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView,
   2832      * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}.
   2833      * This is a simpler but less flexible approach to populating collection widgets. Its use is
   2834      * encouraged for most scenarios, as long as the total memory within the list of RemoteViews
   2835      * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link
   2836      * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still
   2837      * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}.
   2838      *
   2839      * This API is supported in the compatibility library for previous API levels, see
   2840      * RemoteViewsCompat.
   2841      *
   2842      * @param viewId The id of the {@link AdapterView}
   2843      * @param list The list of RemoteViews which will populate the view specified by viewId.
   2844      * @param viewTypeCount The maximum number of unique layout id's used to construct the list of
   2845      *      RemoteViews. This count cannot change during the life-cycle of a given widget, so this
   2846      *      parameter should account for the maximum possible number of types that may appear in the
   2847      *      See {@link Adapter#getViewTypeCount()}.
   2848      *
   2849      * @hide
   2850      */
   2851     public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) {
   2852         addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount));
   2853     }
   2854 
   2855     /**
   2856      * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
   2857      *
   2858      * @param viewId The id of the view to change
   2859      * @param position Scroll to this adapter position
   2860      */
   2861     public void setScrollPosition(int viewId, int position) {
   2862         setInt(viewId, "smoothScrollToPosition", position);
   2863     }
   2864 
   2865     /**
   2866      * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}.
   2867      *
   2868      * @param viewId The id of the view to change
   2869      * @param offset Scroll by this adapter position offset
   2870      */
   2871     public void setRelativeScrollPosition(int viewId, int offset) {
   2872         setInt(viewId, "smoothScrollByOffset", offset);
   2873     }
   2874 
   2875     /**
   2876      * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}.
   2877      *
   2878      * @param viewId The id of the view to change
   2879      * @param left the left padding in pixels
   2880      * @param top the top padding in pixels
   2881      * @param right the right padding in pixels
   2882      * @param bottom the bottom padding in pixels
   2883      */
   2884     public void setViewPadding(int viewId, int left, int top, int right, int bottom) {
   2885         addAction(new ViewPaddingAction(viewId, left, top, right, bottom));
   2886     }
   2887 
   2888     /**
   2889      * @hide
   2890      * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}.
   2891      * Only works if the {@link View#getLayoutParams()} supports margins.
   2892      * Hidden for now since we don't want to support this for all different layout margins yet.
   2893      *
   2894      * @param viewId The id of the view to change
   2895      * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin.
   2896      */
   2897     public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) {
   2898         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN,
   2899                 endMarginDimen));
   2900     }
   2901 
   2902     /**
   2903      * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}.
   2904      *
   2905      * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin.
   2906      * @hide
   2907      */
   2908     public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) {
   2909         addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN,
   2910                 bottomMarginDimen));
   2911     }
   2912 
   2913     /**
   2914      * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}.
   2915      *
   2916      * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed
   2917      *                    because they behave poorly when the density changes.
   2918      * @hide
   2919      */
   2920     public void setViewLayoutWidth(int viewId, int layoutWidth) {
   2921         if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT
   2922                 && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) {
   2923             throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT");
   2924         }
   2925         mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth));
   2926     }
   2927 
   2928     /**
   2929      * Call a method taking one boolean on a view in the layout for this RemoteViews.
   2930      *
   2931      * @param viewId The id of the view on which to call the method.
   2932      * @param methodName The name of the method to call.
   2933      * @param value The value to pass to the method.
   2934      */
   2935     public void setBoolean(int viewId, String methodName, boolean value) {
   2936         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
   2937     }
   2938 
   2939     /**
   2940      * Call a method taking one byte on a view in the layout for this RemoteViews.
   2941      *
   2942      * @param viewId The id of the view on which to call the method.
   2943      * @param methodName The name of the method to call.
   2944      * @param value The value to pass to the method.
   2945      */
   2946     public void setByte(int viewId, String methodName, byte value) {
   2947         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
   2948     }
   2949 
   2950     /**
   2951      * Call a method taking one short on a view in the layout for this RemoteViews.
   2952      *
   2953      * @param viewId The id of the view on which to call the method.
   2954      * @param methodName The name of the method to call.
   2955      * @param value The value to pass to the method.
   2956      */
   2957     public void setShort(int viewId, String methodName, short value) {
   2958         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
   2959     }
   2960 
   2961     /**
   2962      * Call a method taking one int on a view in the layout for this RemoteViews.
   2963      *
   2964      * @param viewId The id of the view on which to call the method.
   2965      * @param methodName The name of the method to call.
   2966      * @param value The value to pass to the method.
   2967      */
   2968     public void setInt(int viewId, String methodName, int value) {
   2969         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
   2970     }
   2971 
   2972     /**
   2973      * Call a method taking one long on a view in the layout for this RemoteViews.
   2974      *
   2975      * @param viewId The id of the view on which to call the method.
   2976      * @param methodName The name of the method to call.
   2977      * @param value The value to pass to the method.
   2978      */
   2979     public void setLong(int viewId, String methodName, long value) {
   2980         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
   2981     }
   2982 
   2983     /**
   2984      * Call a method taking one float on a view in the layout for this RemoteViews.
   2985      *
   2986      * @param viewId The id of the view on which to call the method.
   2987      * @param methodName The name of the method to call.
   2988      * @param value The value to pass to the method.
   2989      */
   2990     public void setFloat(int viewId, String methodName, float value) {
   2991         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
   2992     }
   2993 
   2994     /**
   2995      * Call a method taking one double on a view in the layout for this RemoteViews.
   2996      *
   2997      * @param viewId The id of the view on which to call the method.
   2998      * @param methodName The name of the method to call.
   2999      * @param value The value to pass to the method.
   3000      */
   3001     public void setDouble(int viewId, String methodName, double value) {
   3002         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
   3003     }
   3004 
   3005     /**
   3006      * Call a method taking one char on a view in the layout for this RemoteViews.
   3007      *
   3008      * @param viewId The id of the view on which to call the method.
   3009      * @param methodName The name of the method to call.
   3010      * @param value The value to pass to the method.
   3011      */
   3012     public void setChar(int viewId, String methodName, char value) {
   3013         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
   3014     }
   3015 
   3016     /**
   3017      * Call a method taking one String on a view in the layout for this RemoteViews.
   3018      *
   3019      * @param viewId The id of the view on which to call the method.
   3020      * @param methodName The name of the method to call.
   3021      * @param value The value to pass to the method.
   3022      */
   3023     public void setString(int viewId, String methodName, String value) {
   3024         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
   3025     }
   3026 
   3027     /**
   3028      * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
   3029      *
   3030      * @param viewId The id of the view on which to call the method.
   3031      * @param methodName The name of the method to call.
   3032      * @param value The value to pass to the method.
   3033      */
   3034     public void setCharSequence(int viewId, String methodName, CharSequence value) {
   3035         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
   3036     }
   3037 
   3038     /**
   3039      * Call a method taking one Uri on a view in the layout for this RemoteViews.
   3040      *
   3041      * @param viewId The id of the view on which to call the method.
   3042      * @param methodName The name of the method to call.
   3043      * @param value The value to pass to the method.
   3044      */
   3045     public void setUri(int viewId, String methodName, Uri value) {
   3046         if (value != null) {
   3047             // Resolve any filesystem path before sending remotely
   3048             value = value.getCanonicalUri();
   3049             if (StrictMode.vmFileUriExposureEnabled()) {
   3050                 value.checkFileUriExposed("RemoteViews.setUri()");
   3051             }
   3052         }
   3053         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
   3054     }
   3055 
   3056     /**
   3057      * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
   3058      * @more
   3059      * <p class="note">The bitmap will be flattened into the parcel if this object is
   3060      * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
   3061      *
   3062      * @param viewId The id of the view on which to call the method.
   3063      * @param methodName The name of the method to call.
   3064      * @param value The value to pass to the method.
   3065      */
   3066     public void setBitmap(int viewId, String methodName, Bitmap value) {
   3067         addAction(new BitmapReflectionAction(viewId, methodName, value));
   3068     }
   3069 
   3070     /**
   3071      * Call a method taking one Bundle on a view in the layout for this RemoteViews.
   3072      *
   3073      * @param viewId The id of the view on which to call the method.
   3074      * @param methodName The name of the method to call.
   3075      * @param value The value to pass to the method.
   3076      */
   3077     public void setBundle(int viewId, String methodName, Bundle value) {
   3078         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
   3079     }
   3080 
   3081     /**
   3082      * Call a method taking one Intent on a view in the layout for this RemoteViews.
   3083      *
   3084      * @param viewId The id of the view on which to call the method.
   3085      * @param methodName The name of the method to call.
   3086      * @param value The {@link android.content.Intent} to pass the method.
   3087      */
   3088     public void setIntent(int viewId, String methodName, Intent value) {
   3089         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value));
   3090     }
   3091 
   3092     /**
   3093      * Call a method taking one Icon on a view in the layout for this RemoteViews.
   3094      *
   3095      * @param viewId The id of the view on which to call the method.
   3096      * @param methodName The name of the method to call.
   3097      * @param value The {@link android.graphics.drawable.Icon} to pass the method.
   3098      */
   3099     public void setIcon(int viewId, String methodName, Icon value) {
   3100         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value));
   3101     }
   3102 
   3103     /**
   3104      * Equivalent to calling View.setContentDescription(CharSequence).
   3105      *
   3106      * @param viewId The id of the view whose content description should change.
   3107      * @param contentDescription The new content description for the view.
   3108      */
   3109     public void setContentDescription(int viewId, CharSequence contentDescription) {
   3110         setCharSequence(viewId, "setContentDescription", contentDescription);
   3111     }
   3112 
   3113     /**
   3114      * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}.
   3115      *
   3116      * @param viewId The id of the view whose before view in accessibility traversal to set.
   3117      * @param nextId The id of the next in the accessibility traversal.
   3118      **/
   3119     public void setAccessibilityTraversalBefore(int viewId, int nextId) {
   3120         setInt(viewId, "setAccessibilityTraversalBefore", nextId);
   3121     }
   3122 
   3123     /**
   3124      * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}.
   3125      *
   3126      * @param viewId The id of the view whose after view in accessibility traversal to set.
   3127      * @param nextId The id of the next in the accessibility traversal.
   3128      **/
   3129     public void setAccessibilityTraversalAfter(int viewId, int nextId) {
   3130         setInt(viewId, "setAccessibilityTraversalAfter", nextId);
   3131     }
   3132 
   3133     /**
   3134      * Equivalent to calling View.setLabelFor(int).
   3135      *
   3136      * @param viewId The id of the view whose property to set.
   3137      * @param labeledId The id of a view for which this view serves as a label.
   3138      */
   3139     public void setLabelFor(int viewId, int labeledId) {
   3140         setInt(viewId, "setLabelFor", labeledId);
   3141     }
   3142 
   3143     private RemoteViews getRemoteViewsToApply(Context context) {
   3144         if (hasLandscapeAndPortraitLayouts()) {
   3145             int orientation = context.getResources().getConfiguration().orientation;
   3146             if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
   3147                 return mLandscape;
   3148             } else {
   3149                 return mPortrait;
   3150             }
   3151         }
   3152         return this;
   3153     }
   3154 
   3155     /**
   3156      * Inflates the view hierarchy represented by this object and applies
   3157      * all of the actions.
   3158      *
   3159      * <p><strong>Caller beware: this may throw</strong>
   3160      *
   3161      * @param context Default context to use
   3162      * @param parent Parent that the resulting view hierarchy will be attached to. This method
   3163      * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
   3164      * @return The inflated view hierarchy
   3165      */
   3166     public View apply(Context context, ViewGroup parent) {
   3167         return apply(context, parent, null);
   3168     }
   3169 
   3170     /** @hide */
   3171     public View apply(Context context, ViewGroup parent, OnClickHandler handler) {
   3172         RemoteViews rvToApply = getRemoteViewsToApply(context);
   3173 
   3174         View result = inflateView(context, rvToApply, parent);
   3175         loadTransitionOverride(context, handler);
   3176 
   3177         rvToApply.performApply(result, parent, handler);
   3178 
   3179         return result;
   3180     }
   3181 
   3182     private View inflateView(Context context, RemoteViews rv, ViewGroup parent) {
   3183         // RemoteViews may be built by an application installed in another
   3184         // user. So build a context that loads resources from that user but
   3185         // still returns the current users userId so settings like data / time formats
   3186         // are loaded without requiring cross user persmissions.
   3187         final Context contextForResources = getContextForResources(context);
   3188         Context inflationContext = new ContextWrapper(context) {
   3189             @Override
   3190             public Resources getResources() {
   3191                 return contextForResources.getResources();
   3192             }
   3193             @Override
   3194             public Resources.Theme getTheme() {
   3195                 return contextForResources.getTheme();
   3196             }
   3197             @Override
   3198             public String getPackageName() {
   3199                 return contextForResources.getPackageName();
   3200             }
   3201         };
   3202 
   3203         LayoutInflater inflater = (LayoutInflater)
   3204                 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
   3205 
   3206         // Clone inflater so we load resources from correct context and
   3207         // we don't add a filter to the static version returned by getSystemService.
   3208         inflater = inflater.cloneInContext(inflationContext);
   3209         inflater.setFilter(this);
   3210         View v = inflater.inflate(rv.getLayoutId(), parent, false);
   3211         v.setTagInternal(R.id.widget_frame, rv.getLayoutId());
   3212         return v;
   3213     }
   3214 
   3215     private static void loadTransitionOverride(Context context,
   3216             RemoteViews.OnClickHandler handler) {
   3217         if (handler != null && context.getResources().getBoolean(
   3218                 com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) {
   3219             TypedArray windowStyle = context.getTheme().obtainStyledAttributes(
   3220                     com.android.internal.R.styleable.Window);
   3221             int windowAnimations = windowStyle.getResourceId(
   3222                     com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
   3223             TypedArray windowAnimationStyle = context.obtainStyledAttributes(
   3224                     windowAnimations, com.android.internal.R.styleable.WindowAnimation);
   3225             handler.setEnterAnimationId(windowAnimationStyle.getResourceId(
   3226                     com.android.internal.R.styleable.
   3227                             WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0));
   3228             windowStyle.recycle();
   3229             windowAnimationStyle.recycle();
   3230         }
   3231     }
   3232 
   3233     /**
   3234      * Implement this interface to receive a callback when
   3235      * {@link #applyAsync} or {@link #reapplyAsync} is finished.
   3236      * @hide
   3237      */
   3238     public interface OnViewAppliedListener {
   3239         void onViewApplied(View v);
   3240 
   3241         void onError(Exception e);
   3242     }
   3243 
   3244     /**
   3245      * Applies the views asynchronously, moving as much of the task on the background
   3246      * thread as possible.
   3247      *
   3248      * @see {@link #apply(Context, ViewGroup)}
   3249      * @param context Default context to use
   3250      * @param parent Parent that the resulting view hierarchy will be attached to. This method
   3251      * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
   3252      * @param listener the callback to run when all actions have been applied. May be null.
   3253      * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used.
   3254      * @return CancellationSignal
   3255      * @hide
   3256      */
   3257     public CancellationSignal applyAsync(
   3258             Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) {
   3259         return applyAsync(context, parent, executor, listener, null);
   3260     }
   3261 
   3262     private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) {
   3263         CancellationSignal cancelSignal = new CancellationSignal();
   3264         cancelSignal.setOnCancelListener(task);
   3265 
   3266         task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor);
   3267         return cancelSignal;
   3268     }
   3269 
   3270     /** @hide */
   3271     public CancellationSignal applyAsync(Context context, ViewGroup parent,
   3272             Executor executor, OnViewAppliedListener listener, OnClickHandler handler) {
   3273         return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor);
   3274     }
   3275 
   3276     private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent,
   3277             OnViewAppliedListener listener, OnClickHandler handler) {
   3278         return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener,
   3279                 handler, null);
   3280     }
   3281 
   3282     private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree>
   3283             implements CancellationSignal.OnCancelListener {
   3284         final RemoteViews mRV;
   3285         final ViewGroup mParent;
   3286         final Context mContext;
   3287         final OnViewAppliedListener mListener;
   3288         final OnClickHandler mHandler;
   3289 
   3290         private View mResult;
   3291         private ViewTree mTree;
   3292         private Action[] mActions;
   3293         private Exception mError;
   3294 
   3295         private AsyncApplyTask(
   3296                 RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener,
   3297                 OnClickHandler handler, View result) {
   3298             mRV = rv;
   3299             mParent = parent;
   3300             mContext = context;
   3301             mListener = listener;
   3302             mHandler = handler;
   3303 
   3304             mResult = result;
   3305             loadTransitionOverride(context, handler);
   3306         }
   3307 
   3308         @Override
   3309         protected ViewTree doInBackground(Void... params) {
   3310             try {
   3311                 if (mResult == null) {
   3312                     mResult = inflateView(mContext, mRV, mParent);
   3313                 }
   3314 
   3315                 mTree = new ViewTree(mResult);
   3316                 if (mRV.mActions != null) {
   3317                     int count = mRV.mActions.size();
   3318                     mActions = new Action[count];
   3319                     for (int i = 0; i < count && !isCancelled(); i++) {
   3320                         // TODO: check if isCanclled in nested views.
   3321                         mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler);
   3322                     }
   3323                 } else {
   3324                     mActions = null;
   3325                 }
   3326                 return mTree;
   3327             } catch (Exception e) {
   3328                 mError = e;
   3329                 return null;
   3330             }
   3331         }
   3332 
   3333         @Override
   3334         protected void onPostExecute(ViewTree viewTree) {
   3335             if (mError == null) {
   3336                 try {
   3337                     if (mActions != null) {
   3338                         OnClickHandler handler = mHandler == null
   3339                                 ? DEFAULT_ON_CLICK_HANDLER : mHandler;
   3340                         for (Action a : mActions) {
   3341                             a.apply(viewTree.mRoot, mParent, handler);
   3342                         }
   3343                     }
   3344                 } catch (Exception e) {
   3345                     mError = e;
   3346                 }
   3347             }
   3348 
   3349             if (mListener != null) {
   3350                 if (mError != null) {
   3351                     mListener.onError(mError);
   3352                 } else {
   3353                     mListener.onViewApplied(viewTree.mRoot);
   3354                 }
   3355             } else if (mError != null) {
   3356                 if (mError instanceof ActionException) {
   3357                     throw (ActionException) mError;
   3358                 } else {
   3359                     throw new ActionException(mError);
   3360                 }
   3361             }
   3362         }
   3363 
   3364         @Override
   3365         public void onCancel() {
   3366             cancel(true);
   3367         }
   3368     }
   3369 
   3370     /**
   3371      * Applies all of the actions to the provided view.
   3372      *
   3373      * <p><strong>Caller beware: this may throw</strong>
   3374      *
   3375      * @param v The view to apply the actions to.  This should be the result of
   3376      * the {@link #apply(Context,ViewGroup)} call.
   3377      */
   3378     public void reapply(Context context, View v) {
   3379         reapply(context, v, null);
   3380     }
   3381 
   3382     /** @hide */
   3383     public void reapply(Context context, View v, OnClickHandler handler) {
   3384         RemoteViews rvToApply = getRemoteViewsToApply(context);
   3385 
   3386         // In the case that a view has this RemoteViews applied in one orientation, is persisted
   3387         // across orientation change, and has the RemoteViews re-applied in the new orientation,
   3388         // we throw an exception, since the layouts may be completely unrelated.
   3389         if (hasLandscapeAndPortraitLayouts()) {
   3390             if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
   3391                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
   3392                         " that does not share the same root layout id.");
   3393             }
   3394         }
   3395 
   3396         rvToApply.performApply(v, (ViewGroup) v.getParent(), handler);
   3397     }
   3398 
   3399     /**
   3400      * Applies all the actions to the provided view, moving as much of the task on the background
   3401      * thread as possible.
   3402      *
   3403      * @see {@link #reapply(Context, View)}
   3404      * @param context Default context to use
   3405      * @param v The view to apply the actions to.  This should be the result of
   3406      * the {@link #apply(Context,ViewGroup)} call.
   3407      * @param listener the callback to run when all actions have been applied. May be null.
   3408      * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used
   3409      * @return CancellationSignal
   3410      * @hide
   3411      */
   3412     public CancellationSignal reapplyAsync(
   3413             Context context, View v, Executor executor, OnViewAppliedListener listener) {
   3414         return reapplyAsync(context, v, executor, listener, null);
   3415     }
   3416 
   3417     /** @hide */
   3418     public CancellationSignal reapplyAsync(Context context, View v, Executor executor,
   3419             OnViewAppliedListener listener, OnClickHandler handler) {
   3420         RemoteViews rvToApply = getRemoteViewsToApply(context);
   3421 
   3422         // In the case that a view has this RemoteViews applied in one orientation, is persisted
   3423         // across orientation change, and has the RemoteViews re-applied in the new orientation,
   3424         // we throw an exception, since the layouts may be completely unrelated.
   3425         if (hasLandscapeAndPortraitLayouts()) {
   3426             if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) {
   3427                 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" +
   3428                         " that does not share the same root layout id.");
   3429             }
   3430         }
   3431 
   3432         return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(),
   3433                 context, listener, handler, v), executor);
   3434     }
   3435 
   3436     private void performApply(View v, ViewGroup parent, OnClickHandler handler) {
   3437         if (mActions != null) {
   3438             handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler;
   3439             final int count = mActions.size();
   3440             for (int i = 0; i < count; i++) {
   3441                 Action a = mActions.get(i);
   3442                 a.apply(v, parent, handler);
   3443             }
   3444         }
   3445     }
   3446 
   3447     private Context getContextForResources(Context context) {
   3448         if (mApplication != null) {
   3449             if (context.getUserId() == UserHandle.getUserId(mApplication.uid)
   3450                     && context.getPackageName().equals(mApplication.packageName)) {
   3451                 return context;
   3452             }
   3453             try {
   3454                 return context.createApplicationContext(mApplication,
   3455                         Context.CONTEXT_RESTRICTED);
   3456             } catch (NameNotFoundException e) {
   3457                 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found");
   3458             }
   3459         }
   3460 
   3461         return context;
   3462     }
   3463 
   3464     /**
   3465      * Returns the number of actions in this RemoteViews. Can be used as a sequence number.
   3466      *
   3467      * @hide
   3468      */
   3469     public int getSequenceNumber() {
   3470         return (mActions == null) ? 0 : mActions.size();
   3471     }
   3472 
   3473     /* (non-Javadoc)
   3474      * Used to restrict the views which can be inflated
   3475      *
   3476      * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
   3477      */
   3478     public boolean onLoadClass(Class clazz) {
   3479         return clazz.isAnnotationPresent(RemoteView.class);
   3480     }
   3481 
   3482     public int describeContents() {
   3483         return 0;
   3484     }
   3485 
   3486     public void writeToParcel(Parcel dest, int flags) {
   3487         if (!hasLandscapeAndPortraitLayouts()) {
   3488             dest.writeInt(MODE_NORMAL);
   3489             // We only write the bitmap cache if we are the root RemoteViews, as this cache
   3490             // is shared by all children.
   3491             if (mIsRoot) {
   3492                 mBitmapCache.writeBitmapsToParcel(dest, flags);
   3493             }
   3494             dest.writeParcelable(mApplication, flags);
   3495             dest.writeInt(mLayoutId);
   3496             dest.writeInt(mIsWidgetCollectionChild ? 1 : 0);
   3497             int count;
   3498             if (mActions != null) {
   3499                 count = mActions.size();
   3500             } else {
   3501                 count = 0;
   3502             }
   3503             dest.writeInt(count);
   3504             for (int i=0; i<count; i++) {
   3505                 Action a = mActions.get(i);
   3506                 a.writeToParcel(dest, 0);
   3507             }
   3508         } else {
   3509             dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT);
   3510             // We only write the bitmap cache if we are the root RemoteViews, as this cache
   3511             // is shared by all children.
   3512             if (mIsRoot) {
   3513                 mBitmapCache.writeBitmapsToParcel(dest, flags);
   3514             }
   3515             mLandscape.writeToParcel(dest, flags);
   3516             mPortrait.writeToParcel(dest, flags);
   3517         }
   3518     }
   3519 
   3520     private static ApplicationInfo getApplicationInfo(String packageName, int userId) {
   3521         if (packageName == null) {
   3522             return null;
   3523         }
   3524 
   3525         // Get the application for the passed in package and user.
   3526         Application application = ActivityThread.currentApplication();
   3527         if (application == null) {
   3528             throw new IllegalStateException("Cannot create remote views out of an aplication.");
   3529         }
   3530 
   3531         ApplicationInfo applicationInfo = application.getApplicationInfo();
   3532         if (UserHandle.getUserId(applicationInfo.uid) != userId
   3533                 || !applicationInfo.packageName.equals(packageName)) {
   3534             try {
   3535                 Context context = application.getBaseContext().createPackageContextAsUser(
   3536                         packageName, 0, new UserHandle(userId));
   3537                 applicationInfo = context.getApplicationInfo();
   3538             } catch (NameNotFoundException nnfe) {
   3539                 throw new IllegalArgumentException("No such package " + packageName);
   3540             }
   3541         }
   3542 
   3543         return applicationInfo;
   3544     }
   3545 
   3546     /**
   3547      * Parcelable.Creator that instantiates RemoteViews objects
   3548      */
   3549     public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
   3550         public RemoteViews createFromParcel(Parcel parcel) {
   3551             return new RemoteViews(parcel);
   3552         }
   3553 
   3554         public RemoteViews[] newArray(int size) {
   3555             return new RemoteViews[size];
   3556         }
   3557     };
   3558 
   3559     /**
   3560      * A representation of the view hierarchy. Only views which have a valid ID are added
   3561      * and can be searched.
   3562      */
   3563     private static class ViewTree {
   3564         private final View mRoot;
   3565 
   3566         private ArrayList<ViewTree> mChildren;
   3567 
   3568         private ViewTree(View root) {
   3569             mRoot = root;
   3570         }
   3571 
   3572         public void createTree() {
   3573             if (mChildren != null) {
   3574                 return;
   3575             }
   3576 
   3577             mChildren = new ArrayList<>();
   3578             if (mRoot instanceof ViewGroup && mRoot.isRootNamespace()) {
   3579                 ViewGroup vg = (ViewGroup) mRoot;
   3580                 int count = vg.getChildCount();
   3581                 for (int i = 0; i < count; i++) {
   3582                     addViewChild(vg.getChildAt(i));
   3583                 }
   3584             }
   3585         }
   3586 
   3587         public ViewTree findViewTreeById(int id) {
   3588             if (mRoot.getId() == id) {
   3589                 return this;
   3590             }
   3591             if (mChildren == null) {
   3592                 return null;
   3593             }
   3594             for (ViewTree tree : mChildren) {
   3595                 ViewTree result = tree.findViewTreeById(id);
   3596                 if (result != null) {
   3597                     return result;
   3598                 }
   3599             }
   3600             return null;
   3601         }
   3602 
   3603         public View findViewById(int id) {
   3604             if (mChildren == null) {
   3605                 return mRoot.findViewById(id);
   3606             }
   3607             ViewTree tree = findViewTreeById(id);
   3608             return tree == null ? null : tree.mRoot;
   3609         }
   3610 
   3611         public void addChild(ViewTree child) {
   3612             if (mChildren == null) {
   3613                 mChildren = new ArrayList<>();
   3614             }
   3615             child.createTree();
   3616             mChildren.add(child);
   3617         }
   3618 
   3619         private void addViewChild(View v) {
   3620             final ViewTree target;
   3621 
   3622             // If the view has a valid id, i.e., if can be found using findViewById, add it to the
   3623             // tree, otherwise skip this view and add its children instead.
   3624             if (v.getId() != 0) {
   3625                 ViewTree tree = new ViewTree(v);
   3626                 mChildren.add(tree);
   3627                 target = tree;
   3628             } else {
   3629                 target = this;
   3630             }
   3631 
   3632             if (v instanceof ViewGroup && v.isRootNamespace()) {
   3633                 if (target.mChildren == null) {
   3634                     target.mChildren = new ArrayList<>();
   3635                     ViewGroup vg = (ViewGroup) v;
   3636                     int count = vg.getChildCount();
   3637                     for (int i = 0; i < count; i++) {
   3638                         target.addViewChild(vg.getChildAt(i));
   3639                     }
   3640                 }
   3641             }
   3642         }
   3643     }
   3644 }
   3645