RemoteViews.java revision a32edd4b4c894f4fb3d9fd7e9d5b80321df79e20
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 int bpp = 4; 800 switch (c) { 801 case ALPHA_8: 802 bpp = 1; 803 break; 804 case RGB_565: 805 case ARGB_4444: 806 bpp = 2; 807 break; 808 case ARGB_8888: 809 bpp = 4; 810 break; 811 } 812 counter.bitmapIncrement(b.getWidth() * b.getHeight() * bpp); 813 } 814 break; 815 default: 816 break; 817 } 818 } 819 } 820 821 /** 822 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 823 * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()} 824 * when null. This allows users to build "nested" {@link RemoteViews}. 825 */ 826 private class ViewGroupAction extends Action { 827 public ViewGroupAction(int viewId, RemoteViews nestedViews) { 828 this.viewId = viewId; 829 this.nestedViews = nestedViews; 830 } 831 832 public ViewGroupAction(Parcel parcel) { 833 viewId = parcel.readInt(); 834 nestedViews = parcel.readParcelable(null); 835 } 836 837 public void writeToParcel(Parcel dest, int flags) { 838 dest.writeInt(TAG); 839 dest.writeInt(viewId); 840 dest.writeParcelable(nestedViews, 0 /* no flags */); 841 } 842 843 @Override 844 public void apply(View root) { 845 final Context context = root.getContext(); 846 final ViewGroup target = (ViewGroup) root.findViewById(viewId); 847 if (target == null) return; 848 if (nestedViews != null) { 849 // Inflate nested views and add as children 850 target.addView(nestedViews.apply(context, target)); 851 } else { 852 // Clear all children when nested views omitted 853 target.removeAllViews(); 854 } 855 } 856 857 @Override 858 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 859 if (nestedViews != null) { 860 counter.bitmapIncrement(nestedViews.estimateBitmapMemoryUsage()); 861 } 862 } 863 864 int viewId; 865 RemoteViews nestedViews; 866 867 public final static int TAG = 4; 868 } 869 870 /** 871 * Simple class used to keep track of memory usage in a RemoteViews. 872 * 873 */ 874 private class MemoryUsageCounter { 875 public void clear() { 876 mBitmapHeapMemoryUsage = 0; 877 } 878 879 public void bitmapIncrement(int numBytes) { 880 mBitmapHeapMemoryUsage += numBytes; 881 } 882 883 public int getBitmapHeapMemoryUsage() { 884 return mBitmapHeapMemoryUsage; 885 } 886 887 int mBitmapHeapMemoryUsage; 888 } 889 890 /** 891 * Create a new RemoteViews object that will display the views contained 892 * in the specified layout file. 893 * 894 * @param packageName Name of the package that contains the layout resource 895 * @param layoutId The id of the layout resource 896 */ 897 public RemoteViews(String packageName, int layoutId) { 898 mPackage = packageName; 899 mLayoutId = layoutId; 900 901 // setup the memory usage statistics 902 mMemoryUsageCounter = new MemoryUsageCounter(); 903 recalculateMemoryUsage(); 904 } 905 906 /** 907 * Reads a RemoteViews object from a parcel. 908 * 909 * @param parcel 910 */ 911 public RemoteViews(Parcel parcel) { 912 mPackage = parcel.readString(); 913 mLayoutId = parcel.readInt(); 914 mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false; 915 916 int count = parcel.readInt(); 917 if (count > 0) { 918 mActions = new ArrayList<Action>(count); 919 for (int i=0; i<count; i++) { 920 int tag = parcel.readInt(); 921 switch (tag) { 922 case SetOnClickPendingIntent.TAG: 923 mActions.add(new SetOnClickPendingIntent(parcel)); 924 break; 925 case SetDrawableParameters.TAG: 926 mActions.add(new SetDrawableParameters(parcel)); 927 break; 928 case ReflectionAction.TAG: 929 mActions.add(new ReflectionAction(parcel)); 930 break; 931 case ViewGroupAction.TAG: 932 mActions.add(new ViewGroupAction(parcel)); 933 break; 934 case ReflectionActionWithoutParams.TAG: 935 mActions.add(new ReflectionActionWithoutParams(parcel)); 936 break; 937 case SetEmptyView.TAG: 938 mActions.add(new SetEmptyView(parcel)); 939 break; 940 case SetPendingIntentTemplate.TAG: 941 mActions.add(new SetPendingIntentTemplate(parcel)); 942 break; 943 case SetOnClickFillInIntent.TAG: 944 mActions.add(new SetOnClickFillInIntent(parcel)); 945 break; 946 default: 947 throw new ActionException("Tag " + tag + " not found"); 948 } 949 } 950 } 951 952 // setup the memory usage statistics 953 mMemoryUsageCounter = new MemoryUsageCounter(); 954 recalculateMemoryUsage(); 955 } 956 957 @Override 958 public RemoteViews clone() { 959 final RemoteViews that = new RemoteViews(mPackage, mLayoutId); 960 if (mActions != null) { 961 that.mActions = (ArrayList<Action>)mActions.clone(); 962 } 963 964 // update the memory usage stats of the cloned RemoteViews 965 that.recalculateMemoryUsage(); 966 return that; 967 } 968 969 public String getPackage() { 970 return mPackage; 971 } 972 973 public int getLayoutId() { 974 return mLayoutId; 975 } 976 977 /* 978 * This flag indicates whether this RemoteViews object is being created from a 979 * RemoteViewsService for use as a child of a widget collection. This flag is used 980 * to determine whether or not certain features are available, in particular, 981 * setting on click extras and setting on click pending intents. The former is enabled, 982 * and the latter disabled when this flag is true. 983 */ 984 void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) { 985 mIsWidgetCollectionChild = isWidgetCollectionChild; 986 } 987 988 /** 989 * Updates the memory usage statistics. 990 */ 991 private void recalculateMemoryUsage() { 992 mMemoryUsageCounter.clear(); 993 994 // Accumulate the memory usage for each action 995 if (mActions != null) { 996 final int count = mActions.size(); 997 for (int i= 0; i < count; ++i) { 998 mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter); 999 } 1000 } 1001 } 1002 1003 /** 1004 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 1005 */ 1006 int estimateBitmapMemoryUsage() { 1007 return mMemoryUsageCounter.getBitmapHeapMemoryUsage(); 1008 } 1009 1010 /** 1011 * Add an action to be executed on the remote side when apply is called. 1012 * 1013 * @param a The action to add 1014 */ 1015 private void addAction(Action a) { 1016 if (mActions == null) { 1017 mActions = new ArrayList<Action>(); 1018 } 1019 mActions.add(a); 1020 1021 // update the memory usage stats 1022 a.updateMemoryUsageEstimate(mMemoryUsageCounter); 1023 } 1024 1025 /** 1026 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1027 * given {@link RemoteViews}. This allows users to build "nested" 1028 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 1029 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 1030 * children. 1031 * 1032 * @param viewId The id of the parent {@link ViewGroup} to add child into. 1033 * @param nestedView {@link RemoteViews} that describes the child. 1034 */ 1035 public void addView(int viewId, RemoteViews nestedView) { 1036 addAction(new ViewGroupAction(viewId, nestedView)); 1037 } 1038 1039 /** 1040 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 1041 * 1042 * @param viewId The id of the parent {@link ViewGroup} to remove all 1043 * children from. 1044 */ 1045 public void removeAllViews(int viewId) { 1046 addAction(new ViewGroupAction(viewId, null)); 1047 } 1048 1049 /** 1050 * Equivalent to calling {@link AdapterViewFlipper#showNext()} 1051 * 1052 * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showNext()} 1053 */ 1054 public void showNext(int viewId) { 1055 addAction(new ReflectionActionWithoutParams(viewId, "showNext")); 1056 } 1057 1058 /** 1059 * Equivalent to calling {@link AdapterViewFlipper#showPrevious()} 1060 * 1061 * @param viewId The id of the view on which to call {@link AdapterViewFlipper#showPrevious()} 1062 */ 1063 public void showPrevious(int viewId) { 1064 addAction(new ReflectionActionWithoutParams(viewId, "showPrevious")); 1065 } 1066 1067 /** 1068 * Equivalent to calling View.setVisibility 1069 * 1070 * @param viewId The id of the view whose visibility should change 1071 * @param visibility The new visibility for the view 1072 */ 1073 public void setViewVisibility(int viewId, int visibility) { 1074 setInt(viewId, "setVisibility", visibility); 1075 } 1076 1077 /** 1078 * Equivalent to calling TextView.setText 1079 * 1080 * @param viewId The id of the view whose text should change 1081 * @param text The new text for the view 1082 */ 1083 public void setTextViewText(int viewId, CharSequence text) { 1084 setCharSequence(viewId, "setText", text); 1085 } 1086 1087 /** 1088 * Equivalent to calling ImageView.setImageResource 1089 * 1090 * @param viewId The id of the view whose drawable should change 1091 * @param srcId The new resource id for the drawable 1092 */ 1093 public void setImageViewResource(int viewId, int srcId) { 1094 setInt(viewId, "setImageResource", srcId); 1095 } 1096 1097 /** 1098 * Equivalent to calling ImageView.setImageURI 1099 * 1100 * @param viewId The id of the view whose drawable should change 1101 * @param uri The Uri for the image 1102 */ 1103 public void setImageViewUri(int viewId, Uri uri) { 1104 setUri(viewId, "setImageURI", uri); 1105 } 1106 1107 /** 1108 * Equivalent to calling ImageView.setImageBitmap 1109 * 1110 * @param viewId The id of the view whose drawable should change 1111 * @param bitmap The new Bitmap for the drawable 1112 */ 1113 public void setImageViewBitmap(int viewId, Bitmap bitmap) { 1114 setBitmap(viewId, "setImageBitmap", bitmap); 1115 } 1116 1117 /** 1118 * Equivalent to calling AdapterView.setEmptyView 1119 * 1120 * @param viewId The id of the view on which to set the empty view 1121 * @param emptyViewId The view id of the empty view 1122 */ 1123 public void setEmptyView(int viewId, int emptyViewId) { 1124 addAction(new SetEmptyView(viewId, emptyViewId)); 1125 } 1126 1127 /** 1128 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 1129 * {@link Chronometer#setFormat Chronometer.setFormat}, 1130 * and {@link Chronometer#start Chronometer.start()} or 1131 * {@link Chronometer#stop Chronometer.stop()}. 1132 * 1133 * @param viewId The id of the view whose text should change 1134 * @param base The time at which the timer would have read 0:00. This 1135 * time should be based off of 1136 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 1137 * @param format The Chronometer format string, or null to 1138 * simply display the timer value. 1139 * @param started True if you want the clock to be started, false if not. 1140 */ 1141 public void setChronometer(int viewId, long base, String format, boolean started) { 1142 setLong(viewId, "setBase", base); 1143 setString(viewId, "setFormat", format); 1144 setBoolean(viewId, "setStarted", started); 1145 } 1146 1147 /** 1148 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 1149 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 1150 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 1151 * 1152 * If indeterminate is true, then the values for max and progress are ignored. 1153 * 1154 * @param viewId The id of the view whose text should change 1155 * @param max The 100% value for the progress bar 1156 * @param progress The current value of the progress bar. 1157 * @param indeterminate True if the progress bar is indeterminate, 1158 * false if not. 1159 */ 1160 public void setProgressBar(int viewId, int max, int progress, 1161 boolean indeterminate) { 1162 setBoolean(viewId, "setIndeterminate", indeterminate); 1163 if (!indeterminate) { 1164 setInt(viewId, "setMax", max); 1165 setInt(viewId, "setProgress", progress); 1166 } 1167 } 1168 1169 /** 1170 * Equivalent to calling 1171 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 1172 * to launch the provided {@link PendingIntent}. 1173 * 1174 * When setting the on-click action of items within collections (eg. {@link ListView}, 1175 * {@link StackView} etc.), this method will not work. Instead, use {@link 1176 * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with 1177 * RemoteViews#setOnClickFillInIntent(int, Intent). 1178 * 1179 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 1180 * @param pendingIntent The {@link PendingIntent} to send when user clicks 1181 */ 1182 public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { 1183 addAction(new SetOnClickPendingIntent(viewId, pendingIntent)); 1184 } 1185 1186 /** 1187 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 1188 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 1189 * this method should be used to set a single PendingIntent template on the collection, and 1190 * individual items can differentiate their on-click behavior using 1191 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 1192 * 1193 * @param viewId The id of the collection who's children will use this PendingIntent template 1194 * when clicked 1195 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 1196 * by a child of viewId and executed when that child is clicked 1197 */ 1198 public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { 1199 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 1200 } 1201 1202 /** 1203 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 1204 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 1205 * a single PendingIntent template can be set on the collection, see {@link 1206 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 1207 * action of a given item can be distinguished by setting a fillInIntent on that item. The 1208 * fillInIntent is then combined with the PendingIntent template in order to determine the final 1209 * intent which will be executed when the item is clicked. This works as follows: any fields 1210 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 1211 * will be overwritten, and the resulting PendingIntent will be used. 1212 * 1213 * 1214 * of the PendingIntent template will then be filled in with the associated fields that are 1215 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 1216 * 1217 * @param viewId The id of the view on which to set the fillInIntent 1218 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 1219 * in order to determine the on-click behavior of the view specified by viewId 1220 */ 1221 public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { 1222 addAction(new SetOnClickFillInIntent(viewId, fillInIntent)); 1223 } 1224 1225 /** 1226 * @hide 1227 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 1228 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 1229 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given 1230 * view. 1231 * <p> 1232 * You can omit specific calls by marking their values with null or -1. 1233 * 1234 * @param viewId The id of the view that contains the target 1235 * {@link Drawable} 1236 * @param targetBackground If true, apply these parameters to the 1237 * {@link Drawable} returned by 1238 * {@link android.view.View#getBackground()}. Otherwise, assume 1239 * the target view is an {@link ImageView} and apply them to 1240 * {@link ImageView#getDrawable()}. 1241 * @param alpha Specify an alpha value for the drawable, or -1 to leave 1242 * unchanged. 1243 * @param colorFilter Specify a color for a 1244 * {@link android.graphics.ColorFilter} for this drawable, or -1 1245 * to leave unchanged. 1246 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 1247 * unchanged. 1248 * @param level Specify the level for the drawable, or -1 to leave 1249 * unchanged. 1250 */ 1251 public void setDrawableParameters(int viewId, boolean targetBackground, int alpha, 1252 int colorFilter, PorterDuff.Mode mode, int level) { 1253 addAction(new SetDrawableParameters(viewId, targetBackground, alpha, 1254 colorFilter, mode, level)); 1255 } 1256 1257 /** 1258 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 1259 * 1260 * @param viewId The id of the view whose text should change 1261 * @param color Sets the text color for all the states (normal, selected, 1262 * focused) to be this color. 1263 */ 1264 public void setTextColor(int viewId, int color) { 1265 setInt(viewId, "setTextColor", color); 1266 } 1267 1268 /** 1269 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 1270 * 1271 * @param viewId The id of the view whose text should change 1272 * @param intent The intent of the service which will be 1273 * providing data to the RemoteViewsAdapter 1274 */ 1275 public void setRemoteAdapter(int viewId, Intent intent) { 1276 setIntent(viewId, "setRemoteViewsAdapter", intent); 1277 } 1278 1279 /** 1280 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 1281 * 1282 * @param viewId The id of the view whose text should change 1283 * @param position Scroll to this adapter position 1284 */ 1285 public void setScrollPosition(int viewId, int position) { 1286 setInt(viewId, "smoothScrollToPosition", position); 1287 } 1288 1289 /** 1290 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 1291 * 1292 * @param viewId The id of the view whose text should change 1293 * @param offset Scroll by this adapter position offset 1294 */ 1295 public void setRelativeScrollPosition(int viewId, int offset) { 1296 setInt(viewId, "smoothScrollByOffset", offset); 1297 } 1298 1299 /** 1300 * Call a method taking one boolean on a view in the layout for this RemoteViews. 1301 * 1302 * @param viewId The id of the view whose text should change 1303 * @param methodName The name of the method to call. 1304 * @param value The value to pass to the method. 1305 */ 1306 public void setBoolean(int viewId, String methodName, boolean value) { 1307 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); 1308 } 1309 1310 /** 1311 * Call a method taking one byte on a view in the layout for this RemoteViews. 1312 * 1313 * @param viewId The id of the view whose text should change 1314 * @param methodName The name of the method to call. 1315 * @param value The value to pass to the method. 1316 */ 1317 public void setByte(int viewId, String methodName, byte value) { 1318 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); 1319 } 1320 1321 /** 1322 * Call a method taking one short on a view in the layout for this RemoteViews. 1323 * 1324 * @param viewId The id of the view whose text should change 1325 * @param methodName The name of the method to call. 1326 * @param value The value to pass to the method. 1327 */ 1328 public void setShort(int viewId, String methodName, short value) { 1329 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); 1330 } 1331 1332 /** 1333 * Call a method taking one int on a view in the layout for this RemoteViews. 1334 * 1335 * @param viewId The id of the view whose text should change 1336 * @param methodName The name of the method to call. 1337 * @param value The value to pass to the method. 1338 */ 1339 public void setInt(int viewId, String methodName, int value) { 1340 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); 1341 } 1342 1343 /** 1344 * Call a method taking one long on a view in the layout for this RemoteViews. 1345 * 1346 * @param viewId The id of the view whose text should change 1347 * @param methodName The name of the method to call. 1348 * @param value The value to pass to the method. 1349 */ 1350 public void setLong(int viewId, String methodName, long value) { 1351 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); 1352 } 1353 1354 /** 1355 * Call a method taking one float on a view in the layout for this RemoteViews. 1356 * 1357 * @param viewId The id of the view whose text should change 1358 * @param methodName The name of the method to call. 1359 * @param value The value to pass to the method. 1360 */ 1361 public void setFloat(int viewId, String methodName, float value) { 1362 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); 1363 } 1364 1365 /** 1366 * Call a method taking one double on a view in the layout for this RemoteViews. 1367 * 1368 * @param viewId The id of the view whose text should change 1369 * @param methodName The name of the method to call. 1370 * @param value The value to pass to the method. 1371 */ 1372 public void setDouble(int viewId, String methodName, double value) { 1373 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); 1374 } 1375 1376 /** 1377 * Call a method taking one char on a view in the layout for this RemoteViews. 1378 * 1379 * @param viewId The id of the view whose text should change 1380 * @param methodName The name of the method to call. 1381 * @param value The value to pass to the method. 1382 */ 1383 public void setChar(int viewId, String methodName, char value) { 1384 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); 1385 } 1386 1387 /** 1388 * Call a method taking one String on a view in the layout for this RemoteViews. 1389 * 1390 * @param viewId The id of the view whose text should change 1391 * @param methodName The name of the method to call. 1392 * @param value The value to pass to the method. 1393 */ 1394 public void setString(int viewId, String methodName, String value) { 1395 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); 1396 } 1397 1398 /** 1399 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 1400 * 1401 * @param viewId The id of the view whose text should change 1402 * @param methodName The name of the method to call. 1403 * @param value The value to pass to the method. 1404 */ 1405 public void setCharSequence(int viewId, String methodName, CharSequence value) { 1406 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); 1407 } 1408 1409 /** 1410 * Call a method taking one Uri on a view in the layout for this RemoteViews. 1411 * 1412 * @param viewId The id of the view whose text should change 1413 * @param methodName The name of the method to call. 1414 * @param value The value to pass to the method. 1415 */ 1416 public void setUri(int viewId, String methodName, Uri value) { 1417 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); 1418 } 1419 1420 /** 1421 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 1422 * @more 1423 * <p class="note">The bitmap will be flattened into the parcel if this object is 1424 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 1425 * 1426 * @param viewId The id of the view whose text should change 1427 * @param methodName The name of the method to call. 1428 * @param value The value to pass to the method. 1429 */ 1430 public void setBitmap(int viewId, String methodName, Bitmap value) { 1431 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, value)); 1432 } 1433 1434 /** 1435 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 1436 * 1437 * @param viewId The id of the view whose text should change 1438 * @param methodName The name of the method to call. 1439 * @param value The value to pass to the method. 1440 */ 1441 public void setBundle(int viewId, String methodName, Bundle value) { 1442 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); 1443 } 1444 1445 /** 1446 * 1447 * @param viewId 1448 * @param methodName 1449 * @param value 1450 */ 1451 public void setIntent(int viewId, String methodName, Intent value) { 1452 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); 1453 } 1454 1455 /** 1456 * Inflates the view hierarchy represented by this object and applies 1457 * all of the actions. 1458 * 1459 * <p><strong>Caller beware: this may throw</strong> 1460 * 1461 * @param context Default context to use 1462 * @param parent Parent that the resulting view hierarchy will be attached to. This method 1463 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 1464 * @return The inflated view hierarchy 1465 */ 1466 public View apply(Context context, ViewGroup parent) { 1467 View result; 1468 1469 Context c = prepareContext(context); 1470 1471 LayoutInflater inflater = (LayoutInflater) 1472 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 1473 1474 inflater = inflater.cloneInContext(c); 1475 inflater.setFilter(this); 1476 1477 result = inflater.inflate(mLayoutId, parent, false); 1478 1479 performApply(result); 1480 1481 return result; 1482 } 1483 1484 /** 1485 * Applies all of the actions to the provided view. 1486 * 1487 * <p><strong>Caller beware: this may throw</strong> 1488 * 1489 * @param v The view to apply the actions to. This should be the result of 1490 * the {@link #apply(Context,ViewGroup)} call. 1491 */ 1492 public void reapply(Context context, View v) { 1493 prepareContext(context); 1494 performApply(v); 1495 } 1496 1497 private void performApply(View v) { 1498 if (mActions != null) { 1499 final int count = mActions.size(); 1500 for (int i = 0; i < count; i++) { 1501 Action a = mActions.get(i); 1502 a.apply(v); 1503 } 1504 } 1505 } 1506 1507 private Context prepareContext(Context context) { 1508 Context c; 1509 String packageName = mPackage; 1510 1511 if (packageName != null) { 1512 try { 1513 c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED); 1514 } catch (NameNotFoundException e) { 1515 Log.e(LOG_TAG, "Package name " + packageName + " not found"); 1516 c = context; 1517 } 1518 } else { 1519 c = context; 1520 } 1521 1522 return c; 1523 } 1524 1525 /* (non-Javadoc) 1526 * Used to restrict the views which can be inflated 1527 * 1528 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 1529 */ 1530 public boolean onLoadClass(Class clazz) { 1531 return clazz.isAnnotationPresent(RemoteView.class); 1532 } 1533 1534 public int describeContents() { 1535 return 0; 1536 } 1537 1538 public void writeToParcel(Parcel dest, int flags) { 1539 dest.writeString(mPackage); 1540 dest.writeInt(mLayoutId); 1541 dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); 1542 int count; 1543 if (mActions != null) { 1544 count = mActions.size(); 1545 } else { 1546 count = 0; 1547 } 1548 dest.writeInt(count); 1549 for (int i=0; i<count; i++) { 1550 Action a = mActions.get(i); 1551 a.writeToParcel(dest, 0); 1552 } 1553 } 1554 1555 /** 1556 * Parcelable.Creator that instantiates RemoteViews objects 1557 */ 1558 public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 1559 public RemoteViews createFromParcel(Parcel parcel) { 1560 return new RemoteViews(parcel); 1561 } 1562 1563 public RemoteViews[] newArray(int size) { 1564 return new RemoteViews[size]; 1565 } 1566 }; 1567} 1568