RemoteViews.java revision f01345e19d6ab39c368d030a7741a06d25b4d2cc
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 17package android.widget; 18 19import java.lang.annotation.ElementType; 20import java.lang.annotation.Retention; 21import java.lang.annotation.RetentionPolicy; 22import java.lang.annotation.Target; 23import java.lang.reflect.Method; 24import java.util.ArrayList; 25 26import android.app.PendingIntent; 27import android.appwidget.AppWidgetHostView; 28import android.content.Context; 29import android.content.Intent; 30import android.content.IntentSender; 31import android.content.pm.PackageManager.NameNotFoundException; 32import android.graphics.Bitmap; 33import android.graphics.PorterDuff; 34import android.graphics.Rect; 35import android.graphics.drawable.Drawable; 36import android.net.Uri; 37import android.os.Bundle; 38import android.os.Parcel; 39import android.os.Parcelable; 40import android.text.TextUtils; 41import android.util.Log; 42import android.view.LayoutInflater; 43import android.view.RemotableViewMethod; 44import android.view.View; 45import android.view.ViewGroup; 46import android.view.LayoutInflater.Filter; 47import android.view.View.OnClickListener; 48import android.widget.AdapterView.OnItemClickListener; 49 50 51/** 52 * A class that describes a view hierarchy that can be displayed in 53 * another process. The hierarchy is inflated from a layout resource 54 * file, and this class provides some basic operations for modifying 55 * the content of the inflated hierarchy. 56 */ 57public class RemoteViews implements Parcelable, Filter { 58 59 private static final String LOG_TAG = "RemoteViews"; 60 61 /** 62 * The package name of the package containing the layout 63 * resource. (Added to the parcel) 64 */ 65 private final String mPackage; 66 67 /** 68 * The resource ID of the layout file. (Added to the parcel) 69 */ 70 private final int mLayoutId; 71 72 /** 73 * An array of actions to perform on the view tree once it has been 74 * inflated 75 */ 76 private ArrayList<Action> mActions; 77 78 /** 79 * A class to keep track of memory usage by this RemoteViews 80 */ 81 private MemoryUsageCounter mMemoryUsageCounter; 82 83 84 /** 85 * This flag indicates whether this RemoteViews object is being created from a 86 * RemoteViewsService for use as a child of a widget collection. This flag is used 87 * to determine whether or not certain features are available, in particular, 88 * setting on click extras and setting on click pending intents. The former is enabled, 89 * and the latter disabled when this flag is true. 90 */ 91 private boolean mIsWidgetCollectionChild = false; 92 93 /** 94 * This annotation indicates that a subclass of View is alllowed to be used 95 * with the {@link RemoteViews} mechanism. 96 */ 97 @Target({ ElementType.TYPE }) 98 @Retention(RetentionPolicy.RUNTIME) 99 public @interface RemoteView { 100 } 101 102 /** 103 * Exception to send when something goes wrong executing an action 104 * 105 */ 106 public static class ActionException extends RuntimeException { 107 public ActionException(Exception ex) { 108 super(ex); 109 } 110 public ActionException(String message) { 111 super(message); 112 } 113 } 114 115 /** 116 * Base class for all actions that can be performed on an 117 * inflated view. 118 * 119 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 120 */ 121 private abstract static class Action implements Parcelable { 122 public abstract void apply(View root) throws ActionException; 123 124 public int describeContents() { 125 return 0; 126 } 127 128 /** 129 * Overridden by each class to report on it's own memory usage 130 */ 131 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 132 // We currently only calculate Bitmap memory usage, so by default, don't do anything 133 // here 134 return; 135 } 136 137 protected boolean startIntentSafely(Context context, PendingIntent pendingIntent, 138 Intent fillInIntent) { 139 try { 140 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 141 context.startIntentSender( 142 pendingIntent.getIntentSender(), fillInIntent, 143 Intent.FLAG_ACTIVITY_NEW_TASK, 144 Intent.FLAG_ACTIVITY_NEW_TASK, 0); 145 } catch (IntentSender.SendIntentException e) { 146 android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e); 147 return false; 148 } catch (Exception e) { 149 android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " + 150 "unknown exception: ", e); 151 return false; 152 } 153 return true; 154 } 155 } 156 157 private class SetEmptyView extends Action { 158 int viewId; 159 int emptyViewId; 160 161 public final static int TAG = 6; 162 163 SetEmptyView(int viewId, int emptyViewId) { 164 this.viewId = viewId; 165 this.emptyViewId = emptyViewId; 166 } 167 168 SetEmptyView(Parcel in) { 169 this.viewId = in.readInt(); 170 this.emptyViewId = in.readInt(); 171 } 172 173 public void writeToParcel(Parcel out, int flags) { 174 out.writeInt(TAG); 175 out.writeInt(this.viewId); 176 out.writeInt(this.emptyViewId); 177 } 178 179 @Override 180 public void apply(View root) { 181 final View view = root.findViewById(viewId); 182 if (!(view instanceof AdapterView<?>)) return; 183 184 AdapterView<?> adapterView = (AdapterView<?>) view; 185 186 final View emptyView = root.findViewById(emptyViewId); 187 if (emptyView == null) return; 188 189 adapterView.setEmptyView(emptyView); 190 } 191 } 192 193 private class SetOnClickFillInIntent extends Action { 194 public SetOnClickFillInIntent(int id, Intent fillInIntent) { 195 this.viewId = id; 196 this.fillInIntent = fillInIntent; 197 } 198 199 public SetOnClickFillInIntent(Parcel parcel) { 200 viewId = parcel.readInt(); 201 fillInIntent = Intent.CREATOR.createFromParcel(parcel); 202 } 203 204 public void writeToParcel(Parcel dest, int flags) { 205 dest.writeInt(TAG); 206 dest.writeInt(viewId); 207 fillInIntent.writeToParcel(dest, 0 /* no flags */); 208 } 209 210 @Override 211 public void apply(View root) { 212 final View target = root.findViewById(viewId); 213 if (target == null) return; 214 215 if (!mIsWidgetCollectionChild) { 216 Log.e("RemoteViews", "The method setOnClickFillInIntent is available " + 217 "only from RemoteViewsFactory (ie. on collection items)."); 218 return; 219 } 220 if (target == root) { 221 target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent); 222 } else if (target != null && fillInIntent != null) { 223 OnClickListener listener = new OnClickListener() { 224 public void onClick(View v) { 225 // Insure that this view is a child of an AdapterView 226 View parent = (View) v.getParent(); 227 while (!(parent instanceof AdapterView<?>) 228 && !(parent instanceof AppWidgetHostView)) { 229 parent = (View) parent.getParent(); 230 } 231 232 if (parent instanceof AppWidgetHostView) { 233 // Somehow they've managed to get this far without having 234 // and AdapterView as a parent. 235 Log.e("RemoteViews", "Collection item doesn't have AdapterView parent"); 236 return; 237 } 238 239 // Insure that a template pending intent has been set on an ancestor 240 if (!(parent.getTag() instanceof PendingIntent)) { 241 Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" + 242 " calling setPendingIntentTemplate on parent."); 243 return; 244 } 245 246 PendingIntent pendingIntent = (PendingIntent) parent.getTag(); 247 248 final float appScale = v.getContext().getResources() 249 .getCompatibilityInfo().applicationScale; 250 final int[] pos = new int[2]; 251 v.getLocationOnScreen(pos); 252 253 final Rect rect = new Rect(); 254 rect.left = (int) (pos[0] * appScale + 0.5f); 255 rect.top = (int) (pos[1] * appScale + 0.5f); 256 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 257 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 258 259 fillInIntent.setSourceBounds(rect); 260 startIntentSafely(v.getContext(), pendingIntent, fillInIntent); 261 } 262 263 }; 264 target.setOnClickListener(listener); 265 } 266 } 267 268 int viewId; 269 Intent fillInIntent; 270 271 public final static int TAG = 9; 272 } 273 274 private class SetPendingIntentTemplate extends Action { 275 public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { 276 this.viewId = id; 277 this.pendingIntentTemplate = pendingIntentTemplate; 278 } 279 280 public SetPendingIntentTemplate(Parcel parcel) { 281 viewId = parcel.readInt(); 282 pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 283 } 284 285 public void writeToParcel(Parcel dest, int flags) { 286 dest.writeInt(TAG); 287 dest.writeInt(viewId); 288 pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */); 289 } 290 291 @Override 292 public void apply(View root) { 293 final View target = root.findViewById(viewId); 294 if (target == null) return; 295 296 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 297 if (target instanceof AdapterView<?>) { 298 AdapterView<?> av = (AdapterView<?>) target; 299 // The PendingIntent template is stored in the view's tag. 300 OnItemClickListener listener = new OnItemClickListener() { 301 public void onItemClick(AdapterView<?> parent, View view, 302 int position, long id) { 303 // The view should be a frame layout 304 if (view instanceof ViewGroup) { 305 ViewGroup vg = (ViewGroup) view; 306 307 // AdapterViews contain their children in a frame 308 // so we need to go one layer deeper here. 309 if (parent instanceof AdapterViewAnimator) { 310 vg = (ViewGroup) vg.getChildAt(0); 311 } 312 if (vg == null) return; 313 314 Intent fillInIntent = null; 315 int childCount = vg.getChildCount(); 316 for (int i = 0; i < childCount; i++) { 317 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent); 318 if (tag instanceof Intent) { 319 fillInIntent = (Intent) tag; 320 break; 321 } 322 } 323 if (fillInIntent == null) return; 324 325 final float appScale = view.getContext().getResources() 326 .getCompatibilityInfo().applicationScale; 327 final int[] pos = new int[2]; 328 view.getLocationOnScreen(pos); 329 330 final Rect rect = new Rect(); 331 rect.left = (int) (pos[0] * appScale + 0.5f); 332 rect.top = (int) (pos[1] * appScale + 0.5f); 333 rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f); 334 rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f); 335 336 final Intent intent = new Intent(); 337 intent.setSourceBounds(rect); 338 startIntentSafely(view.getContext(), pendingIntentTemplate, fillInIntent); 339 } 340 } 341 }; 342 av.setOnItemClickListener(listener); 343 av.setTag(pendingIntentTemplate); 344 } else { 345 Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" + 346 "an AdapterView (id: " + viewId + ")"); 347 return; 348 } 349 } 350 351 int viewId; 352 PendingIntent pendingIntentTemplate; 353 354 public final static int TAG = 8; 355 } 356 357 /** 358 * Equivalent to calling 359 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 360 * to launch the provided {@link PendingIntent}. 361 */ 362 private class SetOnClickPendingIntent extends Action { 363 public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { 364 this.viewId = id; 365 this.pendingIntent = pendingIntent; 366 } 367 368 public SetOnClickPendingIntent(Parcel parcel) { 369 viewId = parcel.readInt(); 370 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 371 } 372 373 public void writeToParcel(Parcel dest, int flags) { 374 dest.writeInt(TAG); 375 dest.writeInt(viewId); 376 pendingIntent.writeToParcel(dest, 0 /* no flags */); 377 } 378 379 @Override 380 public void apply(View root) { 381 final View target = root.findViewById(viewId); 382 if (target == null) return; 383 384 // If the view is an AdapterView, setting a PendingIntent on click doesn't make much 385 // sense, do they mean to set a PendingIntent template for the AdapterView's children? 386 if (mIsWidgetCollectionChild) { 387 Log.e("RemoteViews", "Cannot setOnClickPendingIntent for collection item " + 388 "(id: " + viewId + ")"); 389 // TODO: return; We'll let this slide until apps are up to date. 390 } 391 392 if (target != null && pendingIntent != null) { 393 OnClickListener listener = new OnClickListener() { 394 public void onClick(View v) { 395 // Find target view location in screen coordinates and 396 // fill into PendingIntent before sending. 397 final float appScale = v.getContext().getResources() 398 .getCompatibilityInfo().applicationScale; 399 final int[] pos = new int[2]; 400 v.getLocationOnScreen(pos); 401 402 final Rect rect = new Rect(); 403 rect.left = (int) (pos[0] * appScale + 0.5f); 404 rect.top = (int) (pos[1] * appScale + 0.5f); 405 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 406 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 407 408 final Intent intent = new Intent(); 409 intent.setSourceBounds(rect); 410 startIntentSafely(v.getContext(), pendingIntent, intent); 411 } 412 }; 413 target.setOnClickListener(listener); 414 } 415 } 416 417 int viewId; 418 PendingIntent pendingIntent; 419 420 public final static int TAG = 1; 421 } 422 423 /** 424 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 425 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 426 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view. 427 * <p> 428 * These operations will be performed on the {@link Drawable} returned by the 429 * target {@link View#getBackground()} by default. If targetBackground is false, 430 * we assume the target is an {@link ImageView} and try applying the operations 431 * to {@link ImageView#getDrawable()}. 432 * <p> 433 * You can omit specific calls by marking their values with null or -1. 434 */ 435 private class SetDrawableParameters extends Action { 436 public SetDrawableParameters(int id, boolean targetBackground, int alpha, 437 int colorFilter, PorterDuff.Mode mode, int level) { 438 this.viewId = id; 439 this.targetBackground = targetBackground; 440 this.alpha = alpha; 441 this.colorFilter = colorFilter; 442 this.filterMode = mode; 443 this.level = level; 444 } 445 446 public SetDrawableParameters(Parcel parcel) { 447 viewId = parcel.readInt(); 448 targetBackground = parcel.readInt() != 0; 449 alpha = parcel.readInt(); 450 colorFilter = parcel.readInt(); 451 boolean hasMode = parcel.readInt() != 0; 452 if (hasMode) { 453 filterMode = PorterDuff.Mode.valueOf(parcel.readString()); 454 } else { 455 filterMode = null; 456 } 457 level = parcel.readInt(); 458 } 459 460 public void writeToParcel(Parcel dest, int flags) { 461 dest.writeInt(TAG); 462 dest.writeInt(viewId); 463 dest.writeInt(targetBackground ? 1 : 0); 464 dest.writeInt(alpha); 465 dest.writeInt(colorFilter); 466 if (filterMode != null) { 467 dest.writeInt(1); 468 dest.writeString(filterMode.toString()); 469 } else { 470 dest.writeInt(0); 471 } 472 dest.writeInt(level); 473 } 474 475 @Override 476 public void apply(View root) { 477 final View target = root.findViewById(viewId); 478 if (target == null) return; 479 480 // Pick the correct drawable to modify for this view 481 Drawable targetDrawable = null; 482 if (targetBackground) { 483 targetDrawable = target.getBackground(); 484 } else if (target instanceof ImageView) { 485 ImageView imageView = (ImageView) target; 486 targetDrawable = imageView.getDrawable(); 487 } 488 489 if (targetDrawable != null) { 490 // Perform modifications only if values are set correctly 491 if (alpha != -1) { 492 targetDrawable.setAlpha(alpha); 493 } 494 if (colorFilter != -1 && filterMode != null) { 495 targetDrawable.setColorFilter(colorFilter, filterMode); 496 } 497 if (level != -1) { 498 targetDrawable.setLevel(level); 499 } 500 } 501 } 502 503 int viewId; 504 boolean targetBackground; 505 int alpha; 506 int colorFilter; 507 PorterDuff.Mode filterMode; 508 int level; 509 510 public final static int TAG = 3; 511 } 512 513 private class ReflectionActionWithoutParams extends Action { 514 int viewId; 515 String methodName; 516 517 public final static int TAG = 5; 518 519 ReflectionActionWithoutParams(int viewId, String methodName) { 520 this.viewId = viewId; 521 this.methodName = methodName; 522 } 523 524 ReflectionActionWithoutParams(Parcel in) { 525 this.viewId = in.readInt(); 526 this.methodName = in.readString(); 527 } 528 529 public void writeToParcel(Parcel out, int flags) { 530 out.writeInt(TAG); 531 out.writeInt(this.viewId); 532 out.writeString(this.methodName); 533 } 534 535 @Override 536 public void apply(View root) { 537 final View view = root.findViewById(viewId); 538 if (view == null) return; 539 540 Class klass = view.getClass(); 541 Method method; 542 try { 543 method = klass.getMethod(this.methodName); 544 } catch (NoSuchMethodException ex) { 545 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 546 + this.methodName + "()"); 547 } 548 549 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 550 throw new ActionException("view: " + klass.getName() 551 + " can't use method with RemoteViews: " 552 + this.methodName + "()"); 553 } 554 555 try { 556 //noinspection ConstantIfStatement 557 if (false) { 558 Log.d("RemoteViews", "view: " + klass.getName() + " calling method: " 559 + this.methodName + "()"); 560 } 561 method.invoke(view); 562 } catch (Exception ex) { 563 throw new ActionException(ex); 564 } 565 } 566 } 567 568 /** 569 * Base class for the reflection actions. 570 */ 571 private class ReflectionAction extends Action { 572 static final int TAG = 2; 573 574 static final int BOOLEAN = 1; 575 static final int BYTE = 2; 576 static final int SHORT = 3; 577 static final int INT = 4; 578 static final int LONG = 5; 579 static final int FLOAT = 6; 580 static final int DOUBLE = 7; 581 static final int CHAR = 8; 582 static final int STRING = 9; 583 static final int CHAR_SEQUENCE = 10; 584 static final int URI = 11; 585 static final int BITMAP = 12; 586 static final int BUNDLE = 13; 587 static final int INTENT = 14; 588 589 int viewId; 590 String methodName; 591 int type; 592 Object value; 593 594 ReflectionAction(int viewId, String methodName, int type, Object value) { 595 this.viewId = viewId; 596 this.methodName = methodName; 597 this.type = type; 598 this.value = value; 599 } 600 601 ReflectionAction(Parcel in) { 602 this.viewId = in.readInt(); 603 this.methodName = in.readString(); 604 this.type = in.readInt(); 605 //noinspection ConstantIfStatement 606 if (false) { 607 Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId) 608 + " methodName=" + this.methodName + " type=" + this.type); 609 } 610 switch (this.type) { 611 case BOOLEAN: 612 this.value = in.readInt() != 0; 613 break; 614 case BYTE: 615 this.value = in.readByte(); 616 break; 617 case SHORT: 618 this.value = (short)in.readInt(); 619 break; 620 case INT: 621 this.value = in.readInt(); 622 break; 623 case LONG: 624 this.value = in.readLong(); 625 break; 626 case FLOAT: 627 this.value = in.readFloat(); 628 break; 629 case DOUBLE: 630 this.value = in.readDouble(); 631 break; 632 case CHAR: 633 this.value = (char)in.readInt(); 634 break; 635 case STRING: 636 this.value = in.readString(); 637 break; 638 case CHAR_SEQUENCE: 639 this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 640 break; 641 case URI: 642 this.value = Uri.CREATOR.createFromParcel(in); 643 break; 644 case BITMAP: 645 this.value = Bitmap.CREATOR.createFromParcel(in); 646 break; 647 case BUNDLE: 648 this.value = in.readBundle(); 649 break; 650 case INTENT: 651 this.value = Intent.CREATOR.createFromParcel(in); 652 break; 653 default: 654 break; 655 } 656 } 657 658 public void writeToParcel(Parcel out, int flags) { 659 out.writeInt(TAG); 660 out.writeInt(this.viewId); 661 out.writeString(this.methodName); 662 out.writeInt(this.type); 663 //noinspection ConstantIfStatement 664 if (false) { 665 Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId) 666 + " methodName=" + this.methodName + " type=" + this.type); 667 } 668 switch (this.type) { 669 case BOOLEAN: 670 out.writeInt((Boolean) this.value ? 1 : 0); 671 break; 672 case BYTE: 673 out.writeByte((Byte) this.value); 674 break; 675 case SHORT: 676 out.writeInt((Short) this.value); 677 break; 678 case INT: 679 out.writeInt((Integer) this.value); 680 break; 681 case LONG: 682 out.writeLong((Long) this.value); 683 break; 684 case FLOAT: 685 out.writeFloat((Float) this.value); 686 break; 687 case DOUBLE: 688 out.writeDouble((Double) this.value); 689 break; 690 case CHAR: 691 out.writeInt((int)((Character)this.value).charValue()); 692 break; 693 case STRING: 694 out.writeString((String)this.value); 695 break; 696 case CHAR_SEQUENCE: 697 TextUtils.writeToParcel((CharSequence)this.value, out, flags); 698 break; 699 case URI: 700 ((Uri)this.value).writeToParcel(out, flags); 701 break; 702 case BITMAP: 703 ((Bitmap)this.value).writeToParcel(out, flags); 704 break; 705 case BUNDLE: 706 out.writeBundle((Bundle) this.value); 707 break; 708 case INTENT: 709 ((Intent)this.value).writeToParcel(out, flags); 710 break; 711 default: 712 break; 713 } 714 } 715 716 private Class getParameterType() { 717 switch (this.type) { 718 case BOOLEAN: 719 return boolean.class; 720 case BYTE: 721 return byte.class; 722 case SHORT: 723 return short.class; 724 case INT: 725 return int.class; 726 case LONG: 727 return long.class; 728 case FLOAT: 729 return float.class; 730 case DOUBLE: 731 return double.class; 732 case CHAR: 733 return char.class; 734 case STRING: 735 return String.class; 736 case CHAR_SEQUENCE: 737 return CharSequence.class; 738 case URI: 739 return Uri.class; 740 case BITMAP: 741 return Bitmap.class; 742 case BUNDLE: 743 return Bundle.class; 744 case INTENT: 745 return Intent.class; 746 default: 747 return null; 748 } 749 } 750 751 @Override 752 public void apply(View root) { 753 final View view = root.findViewById(viewId); 754 if (view == null) return; 755 756 Class param = getParameterType(); 757 if (param == null) { 758 throw new ActionException("bad type: " + this.type); 759 } 760 761 Class klass = view.getClass(); 762 Method method; 763 try { 764 method = klass.getMethod(this.methodName, getParameterType()); 765 } 766 catch (NoSuchMethodException ex) { 767 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 768 + this.methodName + "(" + param.getName() + ")"); 769 } 770 771 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 772 throw new ActionException("view: " + klass.getName() 773 + " can't use method with RemoteViews: " 774 + this.methodName + "(" + param.getName() + ")"); 775 } 776 777 try { 778 //noinspection ConstantIfStatement 779 if (false) { 780 Log.d("RemoteViews", "view: " + klass.getName() + " calling method: " 781 + this.methodName + "(" + param.getName() + ") with " 782 + (this.value == null ? "null" : this.value.getClass().getName())); 783 } 784 method.invoke(view, this.value); 785 } 786 catch (Exception ex) { 787 throw new ActionException(ex); 788 } 789 } 790 791 @Override 792 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 793 // We currently only calculate Bitmap memory usage 794 switch (this.type) { 795 case BITMAP: 796 if (this.value != null) { 797 final Bitmap b = (Bitmap) this.value; 798 final Bitmap.Config c = b.getConfig(); 799 // If we don't know, be pessimistic and assume 4 800 int bpp = 4; 801 if (c != null) { 802 switch (c) { 803 case ALPHA_8: 804 bpp = 1; 805 break; 806 case RGB_565: 807 case ARGB_4444: 808 bpp = 2; 809 break; 810 case ARGB_8888: 811 bpp = 4; 812 break; 813 } 814 } 815 counter.bitmapIncrement(b.getWidth() * b.getHeight() * bpp); 816 } 817 break; 818 default: 819 break; 820 } 821 } 822 } 823 824 /** 825 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 826 * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()} 827 * when null. This allows users to build "nested" {@link RemoteViews}. 828 */ 829 private class ViewGroupAction extends Action { 830 public ViewGroupAction(int viewId, RemoteViews nestedViews) { 831 this.viewId = viewId; 832 this.nestedViews = nestedViews; 833 } 834 835 public ViewGroupAction(Parcel parcel) { 836 viewId = parcel.readInt(); 837 nestedViews = parcel.readParcelable(null); 838 } 839 840 public void writeToParcel(Parcel dest, int flags) { 841 dest.writeInt(TAG); 842 dest.writeInt(viewId); 843 dest.writeParcelable(nestedViews, 0 /* no flags */); 844 } 845 846 @Override 847 public void apply(View root) { 848 final Context context = root.getContext(); 849 final ViewGroup target = (ViewGroup) root.findViewById(viewId); 850 if (target == null) return; 851 if (nestedViews != null) { 852 // Inflate nested views and add as children 853 target.addView(nestedViews.apply(context, target)); 854 } else { 855 // Clear all children when nested views omitted 856 target.removeAllViews(); 857 } 858 } 859 860 @Override 861 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 862 if (nestedViews != null) { 863 counter.bitmapIncrement(nestedViews.estimateBitmapMemoryUsage()); 864 } 865 } 866 867 int viewId; 868 RemoteViews nestedViews; 869 870 public final static int TAG = 4; 871 } 872 873 /** 874 * Simple class used to keep track of memory usage in a RemoteViews. 875 * 876 */ 877 private class MemoryUsageCounter { 878 public void clear() { 879 mBitmapHeapMemoryUsage = 0; 880 } 881 882 public void bitmapIncrement(int numBytes) { 883 mBitmapHeapMemoryUsage += numBytes; 884 } 885 886 public int getBitmapHeapMemoryUsage() { 887 return mBitmapHeapMemoryUsage; 888 } 889 890 int mBitmapHeapMemoryUsage; 891 } 892 893 /** 894 * Create a new RemoteViews object that will display the views contained 895 * in the specified layout file. 896 * 897 * @param packageName Name of the package that contains the layout resource 898 * @param layoutId The id of the layout resource 899 */ 900 public RemoteViews(String packageName, int layoutId) { 901 mPackage = packageName; 902 mLayoutId = layoutId; 903 904 // setup the memory usage statistics 905 mMemoryUsageCounter = new MemoryUsageCounter(); 906 recalculateMemoryUsage(); 907 } 908 909 /** 910 * Reads a RemoteViews object from a parcel. 911 * 912 * @param parcel 913 */ 914 public RemoteViews(Parcel parcel) { 915 mPackage = parcel.readString(); 916 mLayoutId = parcel.readInt(); 917 mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false; 918 919 int count = parcel.readInt(); 920 if (count > 0) { 921 mActions = new ArrayList<Action>(count); 922 for (int i=0; i<count; i++) { 923 int tag = parcel.readInt(); 924 switch (tag) { 925 case SetOnClickPendingIntent.TAG: 926 mActions.add(new SetOnClickPendingIntent(parcel)); 927 break; 928 case SetDrawableParameters.TAG: 929 mActions.add(new SetDrawableParameters(parcel)); 930 break; 931 case ReflectionAction.TAG: 932 mActions.add(new ReflectionAction(parcel)); 933 break; 934 case ViewGroupAction.TAG: 935 mActions.add(new ViewGroupAction(parcel)); 936 break; 937 case ReflectionActionWithoutParams.TAG: 938 mActions.add(new ReflectionActionWithoutParams(parcel)); 939 break; 940 case SetEmptyView.TAG: 941 mActions.add(new SetEmptyView(parcel)); 942 break; 943 case SetPendingIntentTemplate.TAG: 944 mActions.add(new SetPendingIntentTemplate(parcel)); 945 break; 946 case SetOnClickFillInIntent.TAG: 947 mActions.add(new SetOnClickFillInIntent(parcel)); 948 break; 949 default: 950 throw new ActionException("Tag " + tag + " not found"); 951 } 952 } 953 } 954 955 // setup the memory usage statistics 956 mMemoryUsageCounter = new MemoryUsageCounter(); 957 recalculateMemoryUsage(); 958 } 959 960 @Override 961 public RemoteViews clone() { 962 final RemoteViews that = new RemoteViews(mPackage, mLayoutId); 963 if (mActions != null) { 964 that.mActions = (ArrayList<Action>)mActions.clone(); 965 } 966 967 // update the memory usage stats of the cloned RemoteViews 968 that.recalculateMemoryUsage(); 969 return that; 970 } 971 972 public String getPackage() { 973 return mPackage; 974 } 975 976 public int getLayoutId() { 977 return mLayoutId; 978 } 979 980 /* 981 * This flag indicates whether this RemoteViews object is being created from a 982 * RemoteViewsService for use as a child of a widget collection. This flag is used 983 * to determine whether or not certain features are available, in particular, 984 * setting on click extras and setting on click pending intents. The former is enabled, 985 * and the latter disabled when this flag is true. 986 */ 987 void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) { 988 mIsWidgetCollectionChild = isWidgetCollectionChild; 989 } 990 991 /** 992 * Updates the memory usage statistics. 993 */ 994 private void recalculateMemoryUsage() { 995 mMemoryUsageCounter.clear(); 996 997 // Accumulate the memory usage for each action 998 if (mActions != null) { 999 final int count = mActions.size(); 1000 for (int i= 0; i < count; ++i) { 1001 mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter); 1002 } 1003 } 1004 } 1005 1006 /** 1007 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 1008 */ 1009 int estimateBitmapMemoryUsage() { 1010 return mMemoryUsageCounter.getBitmapHeapMemoryUsage(); 1011 } 1012 1013 /** 1014 * Add an action to be executed on the remote side when apply is called. 1015 * 1016 * @param a The action to add 1017 */ 1018 private void addAction(Action a) { 1019 if (mActions == null) { 1020 mActions = new ArrayList<Action>(); 1021 } 1022 mActions.add(a); 1023 1024 // update the memory usage stats 1025 a.updateMemoryUsageEstimate(mMemoryUsageCounter); 1026 } 1027 1028 /** 1029 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1030 * given {@link RemoteViews}. This allows users to build "nested" 1031 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 1032 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 1033 * children. 1034 * 1035 * @param viewId The id of the parent {@link ViewGroup} to add child into. 1036 * @param nestedView {@link RemoteViews} that describes the child. 1037 */ 1038 public void addView(int viewId, RemoteViews nestedView) { 1039 addAction(new ViewGroupAction(viewId, nestedView)); 1040 } 1041 1042 /** 1043 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 1044 * 1045 * @param viewId The id of the parent {@link ViewGroup} to remove all 1046 * children from. 1047 */ 1048 public void removeAllViews(int viewId) { 1049 addAction(new ViewGroupAction(viewId, null)); 1050 } 1051 1052 /** 1053 * Equivalent to calling {@link AdapterViewFlipper#showNext()} 1054 * 1055 * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()} 1056 */ 1057 public void showNext(int viewId) { 1058 addAction(new ReflectionActionWithoutParams(viewId, "showNext")); 1059 } 1060 1061 /** 1062 * Equivalent to calling {@link AdapterViewFlipper#showPrevious()} 1063 * 1064 * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()} 1065 */ 1066 public void showPrevious(int viewId) { 1067 addAction(new ReflectionActionWithoutParams(viewId, "showPrevious")); 1068 } 1069 1070 /** 1071 * Equivalent to calling View.setVisibility 1072 * 1073 * @param viewId The id of the view whose visibility should change 1074 * @param visibility The new visibility for the view 1075 */ 1076 public void setViewVisibility(int viewId, int visibility) { 1077 setInt(viewId, "setVisibility", visibility); 1078 } 1079 1080 /** 1081 * Equivalent to calling TextView.setText 1082 * 1083 * @param viewId The id of the view whose text should change 1084 * @param text The new text for the view 1085 */ 1086 public void setTextViewText(int viewId, CharSequence text) { 1087 setCharSequence(viewId, "setText", text); 1088 } 1089 1090 /** 1091 * Equivalent to calling ImageView.setImageResource 1092 * 1093 * @param viewId The id of the view whose drawable should change 1094 * @param srcId The new resource id for the drawable 1095 */ 1096 public void setImageViewResource(int viewId, int srcId) { 1097 setInt(viewId, "setImageResource", srcId); 1098 } 1099 1100 /** 1101 * Equivalent to calling ImageView.setImageURI 1102 * 1103 * @param viewId The id of the view whose drawable should change 1104 * @param uri The Uri for the image 1105 */ 1106 public void setImageViewUri(int viewId, Uri uri) { 1107 setUri(viewId, "setImageURI", uri); 1108 } 1109 1110 /** 1111 * Equivalent to calling ImageView.setImageBitmap 1112 * 1113 * @param viewId The id of the view whose drawable should change 1114 * @param bitmap The new Bitmap for the drawable 1115 */ 1116 public void setImageViewBitmap(int viewId, Bitmap bitmap) { 1117 setBitmap(viewId, "setImageBitmap", bitmap); 1118 } 1119 1120 /** 1121 * Equivalent to calling AdapterView.setEmptyView 1122 * 1123 * @param viewId The id of the view on which to set the empty view 1124 * @param emptyViewId The view id of the empty view 1125 */ 1126 public void setEmptyView(int viewId, int emptyViewId) { 1127 addAction(new SetEmptyView(viewId, emptyViewId)); 1128 } 1129 1130 /** 1131 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 1132 * {@link Chronometer#setFormat Chronometer.setFormat}, 1133 * and {@link Chronometer#start Chronometer.start()} or 1134 * {@link Chronometer#stop Chronometer.stop()}. 1135 * 1136 * @param viewId The id of the view whose text should change 1137 * @param base The time at which the timer would have read 0:00. This 1138 * time should be based off of 1139 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 1140 * @param format The Chronometer format string, or null to 1141 * simply display the timer value. 1142 * @param started True if you want the clock to be started, false if not. 1143 */ 1144 public void setChronometer(int viewId, long base, String format, boolean started) { 1145 setLong(viewId, "setBase", base); 1146 setString(viewId, "setFormat", format); 1147 setBoolean(viewId, "setStarted", started); 1148 } 1149 1150 /** 1151 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 1152 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 1153 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 1154 * 1155 * If indeterminate is true, then the values for max and progress are ignored. 1156 * 1157 * @param viewId The id of the view whose text should change 1158 * @param max The 100% value for the progress bar 1159 * @param progress The current value of the progress bar. 1160 * @param indeterminate True if the progress bar is indeterminate, 1161 * false if not. 1162 */ 1163 public void setProgressBar(int viewId, int max, int progress, 1164 boolean indeterminate) { 1165 setBoolean(viewId, "setIndeterminate", indeterminate); 1166 if (!indeterminate) { 1167 setInt(viewId, "setMax", max); 1168 setInt(viewId, "setProgress", progress); 1169 } 1170 } 1171 1172 /** 1173 * Equivalent to calling 1174 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 1175 * to launch the provided {@link PendingIntent}. 1176 * 1177 * When setting the on-click action of items within collections (eg. {@link ListView}, 1178 * {@link StackView} etc.), this method will not work. Instead, use {@link 1179 * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with 1180 * RemoteViews#setOnClickFillInIntent(int, Intent). 1181 * 1182 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 1183 * @param pendingIntent The {@link PendingIntent} to send when user clicks 1184 */ 1185 public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { 1186 addAction(new SetOnClickPendingIntent(viewId, pendingIntent)); 1187 } 1188 1189 /** 1190 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 1191 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 1192 * this method should be used to set a single PendingIntent template on the collection, and 1193 * individual items can differentiate their on-click behavior using 1194 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 1195 * 1196 * @param viewId The id of the collection who's children will use this PendingIntent template 1197 * when clicked 1198 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 1199 * by a child of viewId and executed when that child is clicked 1200 */ 1201 public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { 1202 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 1203 } 1204 1205 /** 1206 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 1207 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 1208 * a single PendingIntent template can be set on the collection, see {@link 1209 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 1210 * action of a given item can be distinguished by setting a fillInIntent on that item. The 1211 * fillInIntent is then combined with the PendingIntent template in order to determine the final 1212 * intent which will be executed when the item is clicked. This works as follows: any fields 1213 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 1214 * will be overwritten, and the resulting PendingIntent will be used. 1215 * 1216 * 1217 * of the PendingIntent template will then be filled in with the associated fields that are 1218 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 1219 * 1220 * @param viewId The id of the view on which to set the fillInIntent 1221 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 1222 * in order to determine the on-click behavior of the view specified by viewId 1223 */ 1224 public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { 1225 addAction(new SetOnClickFillInIntent(viewId, fillInIntent)); 1226 } 1227 1228 /** 1229 * @hide 1230 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 1231 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 1232 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given 1233 * view. 1234 * <p> 1235 * You can omit specific calls by marking their values with null or -1. 1236 * 1237 * @param viewId The id of the view that contains the target 1238 * {@link Drawable} 1239 * @param targetBackground If true, apply these parameters to the 1240 * {@link Drawable} returned by 1241 * {@link android.view.View#getBackground()}. Otherwise, assume 1242 * the target view is an {@link ImageView} and apply them to 1243 * {@link ImageView#getDrawable()}. 1244 * @param alpha Specify an alpha value for the drawable, or -1 to leave 1245 * unchanged. 1246 * @param colorFilter Specify a color for a 1247 * {@link android.graphics.ColorFilter} for this drawable, or -1 1248 * to leave unchanged. 1249 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 1250 * unchanged. 1251 * @param level Specify the level for the drawable, or -1 to leave 1252 * unchanged. 1253 */ 1254 public void setDrawableParameters(int viewId, boolean targetBackground, int alpha, 1255 int colorFilter, PorterDuff.Mode mode, int level) { 1256 addAction(new SetDrawableParameters(viewId, targetBackground, alpha, 1257 colorFilter, mode, level)); 1258 } 1259 1260 /** 1261 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 1262 * 1263 * @param viewId The id of the view whose text should change 1264 * @param color Sets the text color for all the states (normal, selected, 1265 * focused) to be this color. 1266 */ 1267 public void setTextColor(int viewId, int color) { 1268 setInt(viewId, "setTextColor", color); 1269 } 1270 1271 /** 1272 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 1273 * 1274 * @param viewId The id of the view whose text should change 1275 * @param intent The intent of the service which will be 1276 * providing data to the RemoteViewsAdapter 1277 */ 1278 public void setRemoteAdapter(int viewId, Intent intent) { 1279 setIntent(viewId, "setRemoteViewsAdapter", intent); 1280 } 1281 1282 /** 1283 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 1284 * 1285 * @param viewId The id of the view whose text should change 1286 * @param position Scroll to this adapter position 1287 */ 1288 public void setScrollPosition(int viewId, int position) { 1289 setInt(viewId, "smoothScrollToPosition", position); 1290 } 1291 1292 /** 1293 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 1294 * 1295 * @param viewId The id of the view whose text should change 1296 * @param offset Scroll by this adapter position offset 1297 */ 1298 public void setRelativeScrollPosition(int viewId, int offset) { 1299 setInt(viewId, "smoothScrollByOffset", offset); 1300 } 1301 1302 /** 1303 * Call a method taking one boolean on a view in the layout for this RemoteViews. 1304 * 1305 * @param viewId The id of the view whose text should change 1306 * @param methodName The name of the method to call. 1307 * @param value The value to pass to the method. 1308 */ 1309 public void setBoolean(int viewId, String methodName, boolean value) { 1310 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); 1311 } 1312 1313 /** 1314 * Call a method taking one byte on a view in the layout for this RemoteViews. 1315 * 1316 * @param viewId The id of the view whose text should change 1317 * @param methodName The name of the method to call. 1318 * @param value The value to pass to the method. 1319 */ 1320 public void setByte(int viewId, String methodName, byte value) { 1321 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); 1322 } 1323 1324 /** 1325 * Call a method taking one short on a view in the layout for this RemoteViews. 1326 * 1327 * @param viewId The id of the view whose text should change 1328 * @param methodName The name of the method to call. 1329 * @param value The value to pass to the method. 1330 */ 1331 public void setShort(int viewId, String methodName, short value) { 1332 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); 1333 } 1334 1335 /** 1336 * Call a method taking one int on a view in the layout for this RemoteViews. 1337 * 1338 * @param viewId The id of the view whose text should change 1339 * @param methodName The name of the method to call. 1340 * @param value The value to pass to the method. 1341 */ 1342 public void setInt(int viewId, String methodName, int value) { 1343 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); 1344 } 1345 1346 /** 1347 * Call a method taking one long on a view in the layout for this RemoteViews. 1348 * 1349 * @param viewId The id of the view whose text should change 1350 * @param methodName The name of the method to call. 1351 * @param value The value to pass to the method. 1352 */ 1353 public void setLong(int viewId, String methodName, long value) { 1354 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); 1355 } 1356 1357 /** 1358 * Call a method taking one float on a view in the layout for this RemoteViews. 1359 * 1360 * @param viewId The id of the view whose text should change 1361 * @param methodName The name of the method to call. 1362 * @param value The value to pass to the method. 1363 */ 1364 public void setFloat(int viewId, String methodName, float value) { 1365 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); 1366 } 1367 1368 /** 1369 * Call a method taking one double on a view in the layout for this RemoteViews. 1370 * 1371 * @param viewId The id of the view whose text should change 1372 * @param methodName The name of the method to call. 1373 * @param value The value to pass to the method. 1374 */ 1375 public void setDouble(int viewId, String methodName, double value) { 1376 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); 1377 } 1378 1379 /** 1380 * Call a method taking one char on a view in the layout for this RemoteViews. 1381 * 1382 * @param viewId The id of the view whose text should change 1383 * @param methodName The name of the method to call. 1384 * @param value The value to pass to the method. 1385 */ 1386 public void setChar(int viewId, String methodName, char value) { 1387 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); 1388 } 1389 1390 /** 1391 * Call a method taking one String on a view in the layout for this RemoteViews. 1392 * 1393 * @param viewId The id of the view whose text should change 1394 * @param methodName The name of the method to call. 1395 * @param value The value to pass to the method. 1396 */ 1397 public void setString(int viewId, String methodName, String value) { 1398 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); 1399 } 1400 1401 /** 1402 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 1403 * 1404 * @param viewId The id of the view whose text should change 1405 * @param methodName The name of the method to call. 1406 * @param value The value to pass to the method. 1407 */ 1408 public void setCharSequence(int viewId, String methodName, CharSequence value) { 1409 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); 1410 } 1411 1412 /** 1413 * Call a method taking one Uri on a view in the layout for this RemoteViews. 1414 * 1415 * @param viewId The id of the view whose text should change 1416 * @param methodName The name of the method to call. 1417 * @param value The value to pass to the method. 1418 */ 1419 public void setUri(int viewId, String methodName, Uri value) { 1420 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); 1421 } 1422 1423 /** 1424 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 1425 * @more 1426 * <p class="note">The bitmap will be flattened into the parcel if this object is 1427 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 1428 * 1429 * @param viewId The id of the view whose text should change 1430 * @param methodName The name of the method to call. 1431 * @param value The value to pass to the method. 1432 */ 1433 public void setBitmap(int viewId, String methodName, Bitmap value) { 1434 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value)); 1435 } 1436 1437 /** 1438 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 1439 * 1440 * @param viewId The id of the view whose text should change 1441 * @param methodName The name of the method to call. 1442 * @param value The value to pass to the method. 1443 */ 1444 public void setBundle(int viewId, String methodName, Bundle value) { 1445 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); 1446 } 1447 1448 /** 1449 * 1450 * @param viewId 1451 * @param methodName 1452 * @param value 1453 */ 1454 public void setIntent(int viewId, String methodName, Intent value) { 1455 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); 1456 } 1457 1458 /** 1459 * Inflates the view hierarchy represented by this object and applies 1460 * all of the actions. 1461 * 1462 * <p><strong>Caller beware: this may throw</strong> 1463 * 1464 * @param context Default context to use 1465 * @param parent Parent that the resulting view hierarchy will be attached to. This method 1466 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 1467 * @return The inflated view hierarchy 1468 */ 1469 public View apply(Context context, ViewGroup parent) { 1470 View result; 1471 1472 Context c = prepareContext(context); 1473 1474 LayoutInflater inflater = (LayoutInflater) 1475 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1476 1477 inflater = inflater.cloneInContext(c); 1478 inflater.setFilter(this); 1479 1480 result = inflater.inflate(mLayoutId, parent, false); 1481 1482 performApply(result); 1483 1484 return result; 1485 } 1486 1487 /** 1488 * Applies all of the actions to the provided view. 1489 * 1490 * <p><strong>Caller beware: this may throw</strong> 1491 * 1492 * @param v The view to apply the actions to. This should be the result of 1493 * the {@link #apply(Context,ViewGroup)} call. 1494 */ 1495 public void reapply(Context context, View v) { 1496 prepareContext(context); 1497 performApply(v); 1498 } 1499 1500 private void performApply(View v) { 1501 if (mActions != null) { 1502 final int count = mActions.size(); 1503 for (int i = 0; i < count; i++) { 1504 Action a = mActions.get(i); 1505 a.apply(v); 1506 } 1507 } 1508 } 1509 1510 private Context prepareContext(Context context) { 1511 Context c; 1512 String packageName = mPackage; 1513 1514 if (packageName != null) { 1515 try { 1516 c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED); 1517 } catch (NameNotFoundException e) { 1518 Log.e(LOG_TAG, "Package name " + packageName + " not found"); 1519 c = context; 1520 } 1521 } else { 1522 c = context; 1523 } 1524 1525 return c; 1526 } 1527 1528 /* (non-Javadoc) 1529 * Used to restrict the views which can be inflated 1530 * 1531 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 1532 */ 1533 public boolean onLoadClass(Class clazz) { 1534 return clazz.isAnnotationPresent(RemoteView.class); 1535 } 1536 1537 public int describeContents() { 1538 return 0; 1539 } 1540 1541 public void writeToParcel(Parcel dest, int flags) { 1542 dest.writeString(mPackage); 1543 dest.writeInt(mLayoutId); 1544 dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); 1545 int count; 1546 if (mActions != null) { 1547 count = mActions.size(); 1548 } else { 1549 count = 0; 1550 } 1551 dest.writeInt(count); 1552 for (int i=0; i<count; i++) { 1553 Action a = mActions.get(i); 1554 a.writeToParcel(dest, 0); 1555 } 1556 } 1557 1558 /** 1559 * Parcelable.Creator that instantiates RemoteViews objects 1560 */ 1561 public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 1562 public RemoteViews createFromParcel(Parcel parcel) { 1563 return new RemoteViews(parcel); 1564 } 1565 1566 public RemoteViews[] newArray(int size) { 1567 return new RemoteViews[size]; 1568 } 1569 }; 1570} 1571