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 { ActionException(Exception ex)91 public ActionException(Exception ex) { 92 super(ex); 93 } ActionException(String message)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 { apply(View root)106 public abstract void apply(View root) throws ActionException; 107 describeContents()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 { SetOnClickPendingIntent(int id, PendingIntent pendingIntent)119 public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { 120 this.viewId = id; 121 this.pendingIntent = pendingIntent; 122 } 123 SetOnClickPendingIntent(Parcel parcel)124 public SetOnClickPendingIntent(Parcel parcel) { 125 viewId = parcel.readInt(); 126 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 127 } 128 writeToParcel(Parcel dest, int flags)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 apply(View root)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 { SetDrawableParameters(int id, boolean targetBackground, int alpha, int colorFilter, PorterDuff.Mode mode, int level)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 SetDrawableParameters(Parcel parcel)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 writeToParcel(Parcel dest, int flags)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 apply(View root)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 ReflectionAction(int viewId, String methodName, int type, Object value)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 ReflectionAction(Parcel in)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 writeToParcel(Parcel out, int flags)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 getParameterType()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 apply(View root)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 { ViewGroupAction(int viewId, RemoteViews nestedViews)492 public ViewGroupAction(int viewId, RemoteViews nestedViews) { 493 this.viewId = viewId; 494 this.nestedViews = nestedViews; 495 } 496 ViewGroupAction(Parcel parcel)497 public ViewGroupAction(Parcel parcel) { 498 viewId = parcel.readInt(); 499 nestedViews = parcel.readParcelable(null); 500 } 501 writeToParcel(Parcel dest, int flags)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 apply(View root)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 */ RemoteViews(String packageName, int layoutId)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 */ RemoteViews(Parcel parcel)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 clone()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 getPackage()580 public String getPackage() { 581 return mPackage; 582 } 583 getLayoutId()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 */ addAction(Action a)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 */ addView(int viewId, RemoteViews nestedView)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 */ removeAllViews(int viewId)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 */ setViewVisibility(int viewId, int visibility)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 */ setTextViewText(int viewId, CharSequence text)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 */ setImageViewResource(int viewId, int srcId)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 */ setImageViewUri(int viewId, Uri uri)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 */ setImageViewBitmap(int viewId, Bitmap bitmap)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 */ setChronometer(int viewId, long base, String format, boolean started)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 */ setProgressBar(int viewId, int max, int progress, boolean indeterminate)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 */ setOnClickPendingIntent(int viewId, PendingIntent pendingIntent)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 */ setDrawableParameters(int viewId, boolean targetBackground, int alpha, int colorFilter, PorterDuff.Mode mode, int level)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 */ setTextColor(int viewId, int color)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 */ setBoolean(int viewId, String methodName, boolean value)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 */ setByte(int viewId, String methodName, byte value)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 */ setShort(int viewId, String methodName, short value)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 */ setInt(int viewId, String methodName, int value)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 */ setLong(int viewId, String methodName, long value)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 */ setFloat(int viewId, String methodName, float value)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 */ setDouble(int viewId, String methodName, double value)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 */ setChar(int viewId, String methodName, char value)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 */ setString(int viewId, String methodName, String value)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 */ setCharSequence(int viewId, String methodName, CharSequence value)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 */ setUri(int viewId, String methodName, Uri value)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 */ setBitmap(int viewId, String methodName, Bitmap value)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 */ setBundle(int viewId, String methodName, Bundle value)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 */ apply(Context context, ViewGroup parent)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 */ reapply(Context context, View v)954 public void reapply(Context context, View v) { 955 prepareContext(context); 956 performApply(v); 957 } 958 performApply(View v)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 prepareContext(Context context)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 */ onLoadClass(Class clazz)992 public boolean onLoadClass(Class clazz) { 993 return clazz.isAnnotationPresent(RemoteView.class); 994 } 995 describeContents()996 public int describeContents() { 997 return 0; 998 } 999 writeToParcel(Parcel dest, int flags)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