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