Home | History | Annotate | Download | only in widget
      1 /*
      2  * Copyright (C) 2007 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package android.widget;
     18 
     19 import android.app.PendingIntent;
     20 import android.content.Context;
     21 import android.content.Intent;
     22 import android.content.IntentSender;
     23 import android.content.pm.PackageManager.NameNotFoundException;
     24 import android.graphics.Bitmap;
     25 import android.graphics.PorterDuff;
     26 import android.graphics.Rect;
     27 import android.graphics.drawable.Drawable;
     28 import android.net.Uri;
     29 import android.os.Bundle;
     30 import android.os.Parcel;
     31 import android.os.Parcelable;
     32 import android.text.TextUtils;
     33 import android.util.Log;
     34 import android.view.LayoutInflater;
     35 import android.view.RemotableViewMethod;
     36 import android.view.View;
     37 import android.view.ViewGroup;
     38 import android.view.LayoutInflater.Filter;
     39 import android.view.View.OnClickListener;
     40 
     41 import java.lang.annotation.ElementType;
     42 import java.lang.annotation.Retention;
     43 import java.lang.annotation.RetentionPolicy;
     44 import java.lang.annotation.Target;
     45 import java.lang.reflect.Method;
     46 import java.util.ArrayList;
     47 
     48 
     49 /**
     50  * A class that describes a view hierarchy that can be displayed in
     51  * another process. The hierarchy is inflated from a layout resource
     52  * file, and this class provides some basic operations for modifying
     53  * the content of the inflated hierarchy.
     54  */
     55 public class RemoteViews implements Parcelable, Filter {
     56 
     57     private static final String LOG_TAG = "RemoteViews";
     58 
     59     /**
     60      * The package name of the package containing the layout
     61      * resource. (Added to the parcel)
     62      */
     63     private String mPackage;
     64 
     65     /**
     66      * The resource ID of the layout file. (Added to the parcel)
     67      */
     68     private int mLayoutId;
     69 
     70     /**
     71      * An array of actions to perform on the view tree once it has been
     72      * inflated
     73      */
     74     private ArrayList<Action> mActions;
     75 
     76 
     77     /**
     78      * This annotation indicates that a subclass of View is alllowed to be used
     79      * with the {@link RemoteViews} mechanism.
     80      */
     81     @Target({ ElementType.TYPE })
     82     @Retention(RetentionPolicy.RUNTIME)
     83     public @interface RemoteView {
     84     }
     85 
     86     /**
     87      * Exception to send when something goes wrong executing an action
     88      *
     89      */
     90     public static class ActionException extends RuntimeException {
     91         public ActionException(Exception ex) {
     92             super(ex);
     93         }
     94         public ActionException(String message) {
     95             super(message);
     96         }
     97     }
     98 
     99     /**
    100      * Base class for all actions that can be performed on an
    101      * inflated view.
    102      *
    103      *  SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!!
    104      */
    105     private abstract static class Action implements Parcelable {
    106         public abstract void apply(View root) throws ActionException;
    107 
    108         public int describeContents() {
    109             return 0;
    110         }
    111     }
    112 
    113     /**
    114      * Equivalent to calling
    115      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
    116      * to launch the provided {@link PendingIntent}.
    117      */
    118     private class SetOnClickPendingIntent extends Action {
    119         public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) {
    120             this.viewId = id;
    121             this.pendingIntent = pendingIntent;
    122         }
    123 
    124         public SetOnClickPendingIntent(Parcel parcel) {
    125             viewId = parcel.readInt();
    126             pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel);
    127         }
    128 
    129         public void writeToParcel(Parcel dest, int flags) {
    130             dest.writeInt(TAG);
    131             dest.writeInt(viewId);
    132             pendingIntent.writeToParcel(dest, 0 /* no flags */);
    133         }
    134 
    135         @Override
    136         public void apply(View root) {
    137             final View target = root.findViewById(viewId);
    138             if (target != null && pendingIntent != null) {
    139                 OnClickListener listener = new OnClickListener() {
    140                     public void onClick(View v) {
    141                         // Find target view location in screen coordinates and
    142                         // fill into PendingIntent before sending.
    143                         final float appScale = v.getContext().getResources()
    144                                 .getCompatibilityInfo().applicationScale;
    145                         final int[] pos = new int[2];
    146                         v.getLocationOnScreen(pos);
    147 
    148                         final Rect rect = new Rect();
    149                         rect.left = (int) (pos[0] * appScale + 0.5f);
    150                         rect.top = (int) (pos[1] * appScale + 0.5f);
    151                         rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f);
    152                         rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f);
    153 
    154                         final Intent intent = new Intent();
    155                         intent.setSourceBounds(rect);
    156                         try {
    157                             // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT?
    158                             v.getContext().startIntentSender(
    159                                     pendingIntent.getIntentSender(), intent,
    160                                     Intent.FLAG_ACTIVITY_NEW_TASK,
    161                                     Intent.FLAG_ACTIVITY_NEW_TASK, 0);
    162                         } catch (IntentSender.SendIntentException e) {
    163                             android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e);
    164                         }
    165                     }
    166                 };
    167                 target.setOnClickListener(listener);
    168             }
    169         }
    170 
    171         int viewId;
    172         PendingIntent pendingIntent;
    173 
    174         public final static int TAG = 1;
    175     }
    176 
    177     /**
    178      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
    179      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
    180      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view.
    181      * <p>
    182      * These operations will be performed on the {@link Drawable} returned by the
    183      * target {@link View#getBackground()} by default.  If targetBackground is false,
    184      * we assume the target is an {@link ImageView} and try applying the operations
    185      * to {@link ImageView#getDrawable()}.
    186      * <p>
    187      * You can omit specific calls by marking their values with null or -1.
    188      */
    189     private class SetDrawableParameters extends Action {
    190         public SetDrawableParameters(int id, boolean targetBackground, int alpha,
    191                 int colorFilter, PorterDuff.Mode mode, int level) {
    192             this.viewId = id;
    193             this.targetBackground = targetBackground;
    194             this.alpha = alpha;
    195             this.colorFilter = colorFilter;
    196             this.filterMode = mode;
    197             this.level = level;
    198         }
    199 
    200         public SetDrawableParameters(Parcel parcel) {
    201             viewId = parcel.readInt();
    202             targetBackground = parcel.readInt() != 0;
    203             alpha = parcel.readInt();
    204             colorFilter = parcel.readInt();
    205             boolean hasMode = parcel.readInt() != 0;
    206             if (hasMode) {
    207                 filterMode = PorterDuff.Mode.valueOf(parcel.readString());
    208             } else {
    209                 filterMode = null;
    210             }
    211             level = parcel.readInt();
    212         }
    213 
    214         public void writeToParcel(Parcel dest, int flags) {
    215             dest.writeInt(TAG);
    216             dest.writeInt(viewId);
    217             dest.writeInt(targetBackground ? 1 : 0);
    218             dest.writeInt(alpha);
    219             dest.writeInt(colorFilter);
    220             if (filterMode != null) {
    221                 dest.writeInt(1);
    222                 dest.writeString(filterMode.toString());
    223             } else {
    224                 dest.writeInt(0);
    225             }
    226             dest.writeInt(level);
    227         }
    228 
    229         @Override
    230         public void apply(View root) {
    231             final View target = root.findViewById(viewId);
    232             if (target == null) {
    233                 return;
    234             }
    235 
    236             // Pick the correct drawable to modify for this view
    237             Drawable targetDrawable = null;
    238             if (targetBackground) {
    239                 targetDrawable = target.getBackground();
    240             } else if (target instanceof ImageView) {
    241                 ImageView imageView = (ImageView) target;
    242                 targetDrawable = imageView.getDrawable();
    243             }
    244 
    245             if (targetDrawable != null) {
    246                 // Perform modifications only if values are set correctly
    247                 if (alpha != -1) {
    248                     targetDrawable.setAlpha(alpha);
    249                 }
    250                 if (colorFilter != -1 && filterMode != null) {
    251                     targetDrawable.setColorFilter(colorFilter, filterMode);
    252                 }
    253                 if (level != -1) {
    254                     targetDrawable.setLevel(level);
    255                 }
    256             }
    257         }
    258 
    259         int viewId;
    260         boolean targetBackground;
    261         int alpha;
    262         int colorFilter;
    263         PorterDuff.Mode filterMode;
    264         int level;
    265 
    266         public final static int TAG = 3;
    267     }
    268 
    269     /**
    270      * Base class for the reflection actions.
    271      */
    272     private class ReflectionAction extends Action {
    273         static final int TAG = 2;
    274 
    275         static final int BOOLEAN = 1;
    276         static final int BYTE = 2;
    277         static final int SHORT = 3;
    278         static final int INT = 4;
    279         static final int LONG = 5;
    280         static final int FLOAT = 6;
    281         static final int DOUBLE = 7;
    282         static final int CHAR = 8;
    283         static final int STRING = 9;
    284         static final int CHAR_SEQUENCE = 10;
    285         static final int URI = 11;
    286         static final int BITMAP = 12;
    287         static final int BUNDLE = 13;
    288 
    289         int viewId;
    290         String methodName;
    291         int type;
    292         Object value;
    293 
    294         ReflectionAction(int viewId, String methodName, int type, Object value) {
    295             this.viewId = viewId;
    296             this.methodName = methodName;
    297             this.type = type;
    298             this.value = value;
    299         }
    300 
    301         ReflectionAction(Parcel in) {
    302             this.viewId = in.readInt();
    303             this.methodName = in.readString();
    304             this.type = in.readInt();
    305             //noinspection ConstantIfStatement
    306             if (false) {
    307                 Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId)
    308                         + " methodName=" + this.methodName + " type=" + this.type);
    309             }
    310             switch (this.type) {
    311                 case BOOLEAN:
    312                     this.value = in.readInt() != 0;
    313                     break;
    314                 case BYTE:
    315                     this.value = in.readByte();
    316                     break;
    317                 case SHORT:
    318                     this.value = (short)in.readInt();
    319                     break;
    320                 case INT:
    321                     this.value = in.readInt();
    322                     break;
    323                 case LONG:
    324                     this.value = in.readLong();
    325                     break;
    326                 case FLOAT:
    327                     this.value = in.readFloat();
    328                     break;
    329                 case DOUBLE:
    330                     this.value = in.readDouble();
    331                     break;
    332                 case CHAR:
    333                     this.value = (char)in.readInt();
    334                     break;
    335                 case STRING:
    336                     this.value = in.readString();
    337                     break;
    338                 case CHAR_SEQUENCE:
    339                     this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
    340                     break;
    341                 case URI:
    342                     this.value = Uri.CREATOR.createFromParcel(in);
    343                     break;
    344                 case BITMAP:
    345                     this.value = Bitmap.CREATOR.createFromParcel(in);
    346                     break;
    347                 case BUNDLE:
    348                     this.value = in.readBundle();
    349                     break;
    350                 default:
    351                     break;
    352             }
    353         }
    354 
    355         public void writeToParcel(Parcel out, int flags) {
    356             out.writeInt(TAG);
    357             out.writeInt(this.viewId);
    358             out.writeString(this.methodName);
    359             out.writeInt(this.type);
    360             //noinspection ConstantIfStatement
    361             if (false) {
    362                 Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId)
    363                         + " methodName=" + this.methodName + " type=" + this.type);
    364             }
    365             switch (this.type) {
    366                 case BOOLEAN:
    367                     out.writeInt((Boolean) this.value ? 1 : 0);
    368                     break;
    369                 case BYTE:
    370                     out.writeByte((Byte) this.value);
    371                     break;
    372                 case SHORT:
    373                     out.writeInt((Short) this.value);
    374                     break;
    375                 case INT:
    376                     out.writeInt((Integer) this.value);
    377                     break;
    378                 case LONG:
    379                     out.writeLong((Long) this.value);
    380                     break;
    381                 case FLOAT:
    382                     out.writeFloat((Float) this.value);
    383                     break;
    384                 case DOUBLE:
    385                     out.writeDouble((Double) this.value);
    386                     break;
    387                 case CHAR:
    388                     out.writeInt((int)((Character)this.value).charValue());
    389                     break;
    390                 case STRING:
    391                     out.writeString((String)this.value);
    392                     break;
    393                 case CHAR_SEQUENCE:
    394                     TextUtils.writeToParcel((CharSequence)this.value, out, flags);
    395                     break;
    396                 case URI:
    397                     ((Uri)this.value).writeToParcel(out, flags);
    398                     break;
    399                 case BITMAP:
    400                     ((Bitmap)this.value).writeToParcel(out, flags);
    401                     break;
    402                 case BUNDLE:
    403                     out.writeBundle((Bundle) this.value);
    404                     break;
    405                 default:
    406                     break;
    407             }
    408         }
    409 
    410         private Class getParameterType() {
    411             switch (this.type) {
    412                 case BOOLEAN:
    413                     return boolean.class;
    414                 case BYTE:
    415                     return byte.class;
    416                 case SHORT:
    417                     return short.class;
    418                 case INT:
    419                     return int.class;
    420                 case LONG:
    421                     return long.class;
    422                 case FLOAT:
    423                     return float.class;
    424                 case DOUBLE:
    425                     return double.class;
    426                 case CHAR:
    427                     return char.class;
    428                 case STRING:
    429                     return String.class;
    430                 case CHAR_SEQUENCE:
    431                     return CharSequence.class;
    432                 case URI:
    433                     return Uri.class;
    434                 case BITMAP:
    435                     return Bitmap.class;
    436                 case BUNDLE:
    437                     return Bundle.class;
    438                 default:
    439                     return null;
    440             }
    441         }
    442 
    443         @Override
    444         public void apply(View root) {
    445             final View view = root.findViewById(viewId);
    446             if (view == null) {
    447                 throw new ActionException("can't find view: 0x" + Integer.toHexString(viewId));
    448             }
    449 
    450             Class param = getParameterType();
    451             if (param == null) {
    452                 throw new ActionException("bad type: " + this.type);
    453             }
    454 
    455             Class klass = view.getClass();
    456             Method method;
    457             try {
    458                 method = klass.getMethod(this.methodName, getParameterType());
    459             }
    460             catch (NoSuchMethodException ex) {
    461                 throw new ActionException("view: " + klass.getName() + " doesn't have method: "
    462                         + this.methodName + "(" + param.getName() + ")");
    463             }
    464 
    465             if (!method.isAnnotationPresent(RemotableViewMethod.class)) {
    466                 throw new ActionException("view: " + klass.getName()
    467                         + " can't use method with RemoteViews: "
    468                         + this.methodName + "(" + param.getName() + ")");
    469             }
    470 
    471             try {
    472                 //noinspection ConstantIfStatement
    473                 if (false) {
    474                     Log.d("RemoteViews", "view: " + klass.getName() + " calling method: "
    475                         + this.methodName + "(" + param.getName() + ") with "
    476                         + (this.value == null ? "null" : this.value.getClass().getName()));
    477                 }
    478                 method.invoke(view, this.value);
    479             }
    480             catch (Exception ex) {
    481                 throw new ActionException(ex);
    482             }
    483         }
    484     }
    485 
    486     /**
    487      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
    488      * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()}
    489      * when null. This allows users to build "nested" {@link RemoteViews}.
    490      */
    491     private class ViewGroupAction extends Action {
    492         public ViewGroupAction(int viewId, RemoteViews nestedViews) {
    493             this.viewId = viewId;
    494             this.nestedViews = nestedViews;
    495         }
    496 
    497         public ViewGroupAction(Parcel parcel) {
    498             viewId = parcel.readInt();
    499             nestedViews = parcel.readParcelable(null);
    500         }
    501 
    502         public void writeToParcel(Parcel dest, int flags) {
    503             dest.writeInt(TAG);
    504             dest.writeInt(viewId);
    505             dest.writeParcelable(nestedViews, 0 /* no flags */);
    506         }
    507 
    508         @Override
    509         public void apply(View root) {
    510             final Context context = root.getContext();
    511             final ViewGroup target = (ViewGroup) root.findViewById(viewId);
    512             if (nestedViews != null) {
    513                 // Inflate nested views and add as children
    514                 target.addView(nestedViews.apply(context, target));
    515             } else if (target != null) {
    516                 // Clear all children when nested views omitted
    517                 target.removeAllViews();
    518             }
    519         }
    520 
    521         int viewId;
    522         RemoteViews nestedViews;
    523 
    524         public final static int TAG = 4;
    525     }
    526 
    527     /**
    528      * Create a new RemoteViews object that will display the views contained
    529      * in the specified layout file.
    530      *
    531      * @param packageName Name of the package that contains the layout resource
    532      * @param layoutId The id of the layout resource
    533      */
    534     public RemoteViews(String packageName, int layoutId) {
    535         mPackage = packageName;
    536         mLayoutId = layoutId;
    537     }
    538 
    539     /**
    540      * Reads a RemoteViews object from a parcel.
    541      *
    542      * @param parcel
    543      */
    544     public RemoteViews(Parcel parcel) {
    545         mPackage = parcel.readString();
    546         mLayoutId = parcel.readInt();
    547         int count = parcel.readInt();
    548         if (count > 0) {
    549             mActions = new ArrayList<Action>(count);
    550             for (int i=0; i<count; i++) {
    551                 int tag = parcel.readInt();
    552                 switch (tag) {
    553                 case SetOnClickPendingIntent.TAG:
    554                     mActions.add(new SetOnClickPendingIntent(parcel));
    555                     break;
    556                 case SetDrawableParameters.TAG:
    557                     mActions.add(new SetDrawableParameters(parcel));
    558                     break;
    559                 case ReflectionAction.TAG:
    560                     mActions.add(new ReflectionAction(parcel));
    561                     break;
    562                 case ViewGroupAction.TAG:
    563                     mActions.add(new ViewGroupAction(parcel));
    564                     break;
    565                 default:
    566                     throw new ActionException("Tag " + tag + " not found");
    567                 }
    568             }
    569         }
    570     }
    571 
    572     public RemoteViews clone() {
    573         final RemoteViews that = new RemoteViews(mPackage, mLayoutId);
    574         if (mActions != null) {
    575             that.mActions = (ArrayList<Action>)mActions.clone();
    576         }
    577         return that;
    578     }
    579 
    580     public String getPackage() {
    581         return mPackage;
    582     }
    583 
    584     public int getLayoutId() {
    585         return mLayoutId;
    586     }
    587 
    588     /**
    589      * Add an action to be executed on the remote side when apply is called.
    590      *
    591      * @param a The action to add
    592      */
    593     private void addAction(Action a) {
    594         if (mActions == null) {
    595             mActions = new ArrayList<Action>();
    596         }
    597         mActions.add(a);
    598     }
    599 
    600     /**
    601      * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the
    602      * given {@link RemoteViews}. This allows users to build "nested"
    603      * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may
    604      * recycle layouts, use {@link #removeAllViews(int)} to clear any existing
    605      * children.
    606      *
    607      * @param viewId The id of the parent {@link ViewGroup} to add child into.
    608      * @param nestedView {@link RemoteViews} that describes the child.
    609      */
    610     public void addView(int viewId, RemoteViews nestedView) {
    611         addAction(new ViewGroupAction(viewId, nestedView));
    612     }
    613 
    614     /**
    615      * Equivalent to calling {@link ViewGroup#removeAllViews()}.
    616      *
    617      * @param viewId The id of the parent {@link ViewGroup} to remove all
    618      *            children from.
    619      */
    620     public void removeAllViews(int viewId) {
    621         addAction(new ViewGroupAction(viewId, null));
    622     }
    623 
    624     /**
    625      * Equivalent to calling View.setVisibility
    626      *
    627      * @param viewId The id of the view whose visibility should change
    628      * @param visibility The new visibility for the view
    629      */
    630     public void setViewVisibility(int viewId, int visibility) {
    631         setInt(viewId, "setVisibility", visibility);
    632     }
    633 
    634     /**
    635      * Equivalent to calling TextView.setText
    636      *
    637      * @param viewId The id of the view whose text should change
    638      * @param text The new text for the view
    639      */
    640     public void setTextViewText(int viewId, CharSequence text) {
    641         setCharSequence(viewId, "setText", text);
    642     }
    643 
    644     /**
    645      * Equivalent to calling ImageView.setImageResource
    646      *
    647      * @param viewId The id of the view whose drawable should change
    648      * @param srcId The new resource id for the drawable
    649      */
    650     public void setImageViewResource(int viewId, int srcId) {
    651         setInt(viewId, "setImageResource", srcId);
    652     }
    653 
    654     /**
    655      * Equivalent to calling ImageView.setImageURI
    656      *
    657      * @param viewId The id of the view whose drawable should change
    658      * @param uri The Uri for the image
    659      */
    660     public void setImageViewUri(int viewId, Uri uri) {
    661         setUri(viewId, "setImageURI", uri);
    662     }
    663 
    664     /**
    665      * Equivalent to calling ImageView.setImageBitmap
    666      *
    667      * @param viewId The id of the view whose drawable should change
    668      * @param bitmap The new Bitmap for the drawable
    669      */
    670     public void setImageViewBitmap(int viewId, Bitmap bitmap) {
    671         setBitmap(viewId, "setImageBitmap", bitmap);
    672     }
    673 
    674     /**
    675      * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase},
    676      * {@link Chronometer#setFormat Chronometer.setFormat},
    677      * and {@link Chronometer#start Chronometer.start()} or
    678      * {@link Chronometer#stop Chronometer.stop()}.
    679      *
    680      * @param viewId The id of the view whose text should change
    681      * @param base The time at which the timer would have read 0:00.  This
    682      *             time should be based off of
    683      *             {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}.
    684      * @param format The Chronometer format string, or null to
    685      *               simply display the timer value.
    686      * @param started True if you want the clock to be started, false if not.
    687      */
    688     public void setChronometer(int viewId, long base, String format, boolean started) {
    689         setLong(viewId, "setBase", base);
    690         setString(viewId, "setFormat", format);
    691         setBoolean(viewId, "setStarted", started);
    692     }
    693 
    694     /**
    695      * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax},
    696      * {@link ProgressBar#setProgress ProgressBar.setProgress}, and
    697      * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate}
    698      *
    699      * If indeterminate is true, then the values for max and progress are ignored.
    700      *
    701      * @param viewId The id of the view whose text should change
    702      * @param max The 100% value for the progress bar
    703      * @param progress The current value of the progress bar.
    704      * @param indeterminate True if the progress bar is indeterminate,
    705      *                false if not.
    706      */
    707     public void setProgressBar(int viewId, int max, int progress,
    708             boolean indeterminate) {
    709         setBoolean(viewId, "setIndeterminate", indeterminate);
    710         if (!indeterminate) {
    711             setInt(viewId, "setMax", max);
    712             setInt(viewId, "setProgress", progress);
    713         }
    714     }
    715 
    716     /**
    717      * Equivalent to calling
    718      * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)}
    719      * to launch the provided {@link PendingIntent}.
    720      *
    721      * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked
    722      * @param pendingIntent The {@link PendingIntent} to send when user clicks
    723      */
    724     public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) {
    725         addAction(new SetOnClickPendingIntent(viewId, pendingIntent));
    726     }
    727 
    728     /**
    729      * @hide
    730      * Equivalent to calling a combination of {@link Drawable#setAlpha(int)},
    731      * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)},
    732      * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given
    733      * view.
    734      * <p>
    735      * You can omit specific calls by marking their values with null or -1.
    736      *
    737      * @param viewId The id of the view that contains the target
    738      *            {@link Drawable}
    739      * @param targetBackground If true, apply these parameters to the
    740      *            {@link Drawable} returned by
    741      *            {@link android.view.View#getBackground()}. Otherwise, assume
    742      *            the target view is an {@link ImageView} and apply them to
    743      *            {@link ImageView#getDrawable()}.
    744      * @param alpha Specify an alpha value for the drawable, or -1 to leave
    745      *            unchanged.
    746      * @param colorFilter Specify a color for a
    747      *            {@link android.graphics.ColorFilter} for this drawable, or -1
    748      *            to leave unchanged.
    749      * @param mode Specify a PorterDuff mode for this drawable, or null to leave
    750      *            unchanged.
    751      * @param level Specify the level for the drawable, or -1 to leave
    752      *            unchanged.
    753      */
    754     public void setDrawableParameters(int viewId, boolean targetBackground, int alpha,
    755             int colorFilter, PorterDuff.Mode mode, int level) {
    756         addAction(new SetDrawableParameters(viewId, targetBackground, alpha,
    757                 colorFilter, mode, level));
    758     }
    759 
    760     /**
    761      * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}.
    762      *
    763      * @param viewId The id of the view whose text should change
    764      * @param color Sets the text color for all the states (normal, selected,
    765      *            focused) to be this color.
    766      */
    767     public void setTextColor(int viewId, int color) {
    768         setInt(viewId, "setTextColor", color);
    769     }
    770 
    771     /**
    772      * Call a method taking one boolean on a view in the layout for this RemoteViews.
    773      *
    774      * @param viewId The id of the view whose text should change
    775      * @param methodName The name of the method to call.
    776      * @param value The value to pass to the method.
    777      */
    778     public void setBoolean(int viewId, String methodName, boolean value) {
    779         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value));
    780     }
    781 
    782     /**
    783      * Call a method taking one byte on a view in the layout for this RemoteViews.
    784      *
    785      * @param viewId The id of the view whose text should change
    786      * @param methodName The name of the method to call.
    787      * @param value The value to pass to the method.
    788      */
    789     public void setByte(int viewId, String methodName, byte value) {
    790         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value));
    791     }
    792 
    793     /**
    794      * Call a method taking one short on a view in the layout for this RemoteViews.
    795      *
    796      * @param viewId The id of the view whose text should change
    797      * @param methodName The name of the method to call.
    798      * @param value The value to pass to the method.
    799      */
    800     public void setShort(int viewId, String methodName, short value) {
    801         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value));
    802     }
    803 
    804     /**
    805      * Call a method taking one int on a view in the layout for this RemoteViews.
    806      *
    807      * @param viewId The id of the view whose text should change
    808      * @param methodName The name of the method to call.
    809      * @param value The value to pass to the method.
    810      */
    811     public void setInt(int viewId, String methodName, int value) {
    812         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value));
    813     }
    814 
    815     /**
    816      * Call a method taking one long on a view in the layout for this RemoteViews.
    817      *
    818      * @param viewId The id of the view whose text should change
    819      * @param methodName The name of the method to call.
    820      * @param value The value to pass to the method.
    821      */
    822     public void setLong(int viewId, String methodName, long value) {
    823         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value));
    824     }
    825 
    826     /**
    827      * Call a method taking one float on a view in the layout for this RemoteViews.
    828      *
    829      * @param viewId The id of the view whose text should change
    830      * @param methodName The name of the method to call.
    831      * @param value The value to pass to the method.
    832      */
    833     public void setFloat(int viewId, String methodName, float value) {
    834         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value));
    835     }
    836 
    837     /**
    838      * Call a method taking one double on a view in the layout for this RemoteViews.
    839      *
    840      * @param viewId The id of the view whose text should change
    841      * @param methodName The name of the method to call.
    842      * @param value The value to pass to the method.
    843      */
    844     public void setDouble(int viewId, String methodName, double value) {
    845         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value));
    846     }
    847 
    848     /**
    849      * Call a method taking one char on a view in the layout for this RemoteViews.
    850      *
    851      * @param viewId The id of the view whose text should change
    852      * @param methodName The name of the method to call.
    853      * @param value The value to pass to the method.
    854      */
    855     public void setChar(int viewId, String methodName, char value) {
    856         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value));
    857     }
    858 
    859     /**
    860      * Call a method taking one String on a view in the layout for this RemoteViews.
    861      *
    862      * @param viewId The id of the view whose text should change
    863      * @param methodName The name of the method to call.
    864      * @param value The value to pass to the method.
    865      */
    866     public void setString(int viewId, String methodName, String value) {
    867         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value));
    868     }
    869 
    870     /**
    871      * Call a method taking one CharSequence on a view in the layout for this RemoteViews.
    872      *
    873      * @param viewId The id of the view whose text should change
    874      * @param methodName The name of the method to call.
    875      * @param value The value to pass to the method.
    876      */
    877     public void setCharSequence(int viewId, String methodName, CharSequence value) {
    878         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value));
    879     }
    880 
    881     /**
    882      * Call a method taking one Uri on a view in the layout for this RemoteViews.
    883      *
    884      * @param viewId The id of the view whose text should change
    885      * @param methodName The name of the method to call.
    886      * @param value The value to pass to the method.
    887      */
    888     public void setUri(int viewId, String methodName, Uri value) {
    889         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
    890     }
    891 
    892     /**
    893      * Call a method taking one Bitmap on a view in the layout for this RemoteViews.
    894      * @more
    895      * <p class="note">The bitmap will be flattened into the parcel if this object is
    896      * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p>
    897      *
    898      * @param viewId The id of the view whose text should change
    899      * @param methodName The name of the method to call.
    900      * @param value The value to pass to the method.
    901      */
    902     public void setBitmap(int viewId, String methodName, Bitmap value) {
    903         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value));
    904     }
    905 
    906     /**
    907      * Call a method taking one Bundle on a view in the layout for this RemoteViews.
    908      *
    909      * @param viewId The id of the view whose text should change
    910      * @param methodName The name of the method to call.
    911      * @param value The value to pass to the method.
    912      */
    913     public void setBundle(int viewId, String methodName, Bundle value) {
    914         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value));
    915     }
    916 
    917     /**
    918      * Inflates the view hierarchy represented by this object and applies
    919      * all of the actions.
    920      *
    921      * <p><strong>Caller beware: this may throw</strong>
    922      *
    923      * @param context Default context to use
    924      * @param parent Parent that the resulting view hierarchy will be attached to. This method
    925      * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate.
    926      * @return The inflated view hierarchy
    927      */
    928     public View apply(Context context, ViewGroup parent) {
    929         View result;
    930 
    931         Context c = prepareContext(context);
    932 
    933         LayoutInflater inflater = (LayoutInflater)
    934                 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    935 
    936         inflater = inflater.cloneInContext(c);
    937         inflater.setFilter(this);
    938 
    939         result = inflater.inflate(mLayoutId, parent, false);
    940 
    941         performApply(result);
    942 
    943         return result;
    944     }
    945 
    946     /**
    947      * Applies all of the actions to the provided view.
    948      *
    949      * <p><strong>Caller beware: this may throw</strong>
    950      *
    951      * @param v The view to apply the actions to.  This should be the result of
    952      * the {@link #apply(Context,ViewGroup)} call.
    953      */
    954     public void reapply(Context context, View v) {
    955         prepareContext(context);
    956         performApply(v);
    957     }
    958 
    959     private void performApply(View v) {
    960         if (mActions != null) {
    961             final int count = mActions.size();
    962             for (int i = 0; i < count; i++) {
    963                 Action a = mActions.get(i);
    964                 a.apply(v);
    965             }
    966         }
    967     }
    968 
    969     private Context prepareContext(Context context) {
    970         Context c;
    971         String packageName = mPackage;
    972 
    973         if (packageName != null) {
    974             try {
    975                 c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED);
    976             } catch (NameNotFoundException e) {
    977                 Log.e(LOG_TAG, "Package name " + packageName + " not found");
    978                 c = context;
    979             }
    980         } else {
    981             c = context;
    982         }
    983 
    984         return c;
    985     }
    986 
    987     /* (non-Javadoc)
    988      * Used to restrict the views which can be inflated
    989      *
    990      * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class)
    991      */
    992     public boolean onLoadClass(Class clazz) {
    993         return clazz.isAnnotationPresent(RemoteView.class);
    994     }
    995 
    996     public int describeContents() {
    997         return 0;
    998     }
    999 
   1000     public void writeToParcel(Parcel dest, int flags) {
   1001         dest.writeString(mPackage);
   1002         dest.writeInt(mLayoutId);
   1003         int count;
   1004         if (mActions != null) {
   1005             count = mActions.size();
   1006         } else {
   1007             count = 0;
   1008         }
   1009         dest.writeInt(count);
   1010         for (int i=0; i<count; i++) {
   1011             Action a = mActions.get(i);
   1012             a.writeToParcel(dest, 0);
   1013         }
   1014     }
   1015 
   1016     /**
   1017      * Parcelable.Creator that instantiates RemoteViews objects
   1018      */
   1019     public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() {
   1020         public RemoteViews createFromParcel(Parcel parcel) {
   1021             return new RemoteViews(parcel);
   1022         }
   1023 
   1024         public RemoteViews[] newArray(int size) {
   1025             return new RemoteViews[size];
   1026         }
   1027     };
   1028 }
   1029