RemoteViews.java revision 311c79c3e93589c6fc720fe6c58ed522af591376
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 android.app.ActivityOptions; 20import android.app.PendingIntent; 21import android.appwidget.AppWidgetHostView; 22import android.content.Context; 23import android.content.Intent; 24import android.content.IntentSender; 25import android.content.pm.ApplicationInfo; 26import android.content.pm.PackageManager; 27import android.content.pm.PackageManager.NameNotFoundException; 28import android.content.res.Configuration; 29import android.graphics.Bitmap; 30import android.graphics.PorterDuff; 31import android.graphics.Rect; 32import android.graphics.drawable.Drawable; 33import android.net.Uri; 34import android.os.Build; 35import android.os.Bundle; 36import android.os.Parcel; 37import android.os.Parcelable; 38import android.text.TextUtils; 39import android.util.Log; 40import android.view.LayoutInflater; 41import android.view.LayoutInflater.Filter; 42import android.view.RemotableViewMethod; 43import android.view.View; 44import android.view.View.OnClickListener; 45import android.view.ViewGroup; 46import android.widget.AdapterView.OnItemClickListener; 47 48import java.lang.annotation.ElementType; 49import java.lang.annotation.Retention; 50import java.lang.annotation.RetentionPolicy; 51import java.lang.annotation.Target; 52import java.lang.reflect.Method; 53import java.util.ArrayList; 54 55 56/** 57 * A class that describes a view hierarchy that can be displayed in 58 * another process. The hierarchy is inflated from a layout resource 59 * file, and this class provides some basic operations for modifying 60 * the content of the inflated hierarchy. 61 */ 62public class RemoteViews implements Parcelable, Filter { 63 64 private static final String LOG_TAG = "RemoteViews"; 65 66 /** 67 * The intent extra that contains the appWidgetId. 68 * @hide 69 */ 70 static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; 71 72 /** 73 * The package name of the package containing the layout 74 * resource. (Added to the parcel) 75 */ 76 private final String mPackage; 77 78 /** 79 * The resource ID of the layout file. (Added to the parcel) 80 */ 81 private final int mLayoutId; 82 83 /** 84 * An array of actions to perform on the view tree once it has been 85 * inflated 86 */ 87 private ArrayList<Action> mActions; 88 89 /** 90 * A class to keep track of memory usage by this RemoteViews 91 */ 92 private MemoryUsageCounter mMemoryUsageCounter; 93 94 /** 95 * Maps bitmaps to unique indicies to avoid Bitmap duplication. 96 */ 97 private BitmapCache mBitmapCache; 98 99 /** 100 * Indicates whether or not this RemoteViews object is contained as a child of any other 101 * RemoteViews. 102 */ 103 private boolean mIsRoot = true; 104 105 /** 106 * Constants to whether or not this RemoteViews is composed of a landscape and portrait 107 * RemoteViews. 108 */ 109 private static final int MODE_NORMAL = 0; 110 private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; 111 112 /** 113 * Used in conjunction with the special constructor 114 * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait 115 * RemoteViews. 116 */ 117 private RemoteViews mLandscape = null; 118 private RemoteViews mPortrait = null; 119 120 /** 121 * This flag indicates whether this RemoteViews object is being created from a 122 * RemoteViewsService for use as a child of a widget collection. This flag is used 123 * to determine whether or not certain features are available, in particular, 124 * setting on click extras and setting on click pending intents. The former is enabled, 125 * and the latter disabled when this flag is true. 126 */ 127 private boolean mIsWidgetCollectionChild = false; 128 129 /** 130 * This annotation indicates that a subclass of View is alllowed to be used 131 * with the {@link RemoteViews} mechanism. 132 */ 133 @Target({ ElementType.TYPE }) 134 @Retention(RetentionPolicy.RUNTIME) 135 public @interface RemoteView { 136 } 137 138 /** 139 * Exception to send when something goes wrong executing an action 140 * 141 */ 142 public static class ActionException extends RuntimeException { 143 public ActionException(Exception ex) { 144 super(ex); 145 } 146 public ActionException(String message) { 147 super(message); 148 } 149 } 150 151 /** 152 * Base class for all actions that can be performed on an 153 * inflated view. 154 * 155 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 156 */ 157 private abstract static class Action implements Parcelable { 158 public abstract void apply(View root, ViewGroup rootParent) throws ActionException; 159 160 public int describeContents() { 161 return 0; 162 } 163 164 /** 165 * Overridden by each class to report on it's own memory usage 166 */ 167 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 168 // We currently only calculate Bitmap memory usage, so by default, don't do anything 169 // here 170 return; 171 } 172 173 protected boolean startIntentSafely(View view, PendingIntent pendingIntent, 174 Intent fillInIntent) { 175 try { 176 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 177 Context context = view.getContext(); 178 ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view, 179 0, 0, 180 view.getMeasuredWidth(), view.getMeasuredHeight()); 181 context.startIntentSender( 182 pendingIntent.getIntentSender(), fillInIntent, 183 Intent.FLAG_ACTIVITY_NEW_TASK, 184 Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); 185 } catch (IntentSender.SendIntentException e) { 186 android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e); 187 return false; 188 } catch (Exception e) { 189 android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " + 190 "unknown exception: ", e); 191 return false; 192 } 193 return true; 194 } 195 196 public void setBitmapCache(BitmapCache bitmapCache) { 197 // Do nothing 198 } 199 } 200 201 private class SetEmptyView extends Action { 202 int viewId; 203 int emptyViewId; 204 205 public final static int TAG = 6; 206 207 SetEmptyView(int viewId, int emptyViewId) { 208 this.viewId = viewId; 209 this.emptyViewId = emptyViewId; 210 } 211 212 SetEmptyView(Parcel in) { 213 this.viewId = in.readInt(); 214 this.emptyViewId = in.readInt(); 215 } 216 217 public void writeToParcel(Parcel out, int flags) { 218 out.writeInt(TAG); 219 out.writeInt(this.viewId); 220 out.writeInt(this.emptyViewId); 221 } 222 223 @Override 224 public void apply(View root, ViewGroup rootParent) { 225 final View view = root.findViewById(viewId); 226 if (!(view instanceof AdapterView<?>)) return; 227 228 AdapterView<?> adapterView = (AdapterView<?>) view; 229 230 final View emptyView = root.findViewById(emptyViewId); 231 if (emptyView == null) return; 232 233 adapterView.setEmptyView(emptyView); 234 } 235 } 236 237 private class SetOnClickFillInIntent extends Action { 238 public SetOnClickFillInIntent(int id, Intent fillInIntent) { 239 this.viewId = id; 240 this.fillInIntent = fillInIntent; 241 } 242 243 public SetOnClickFillInIntent(Parcel parcel) { 244 viewId = parcel.readInt(); 245 fillInIntent = Intent.CREATOR.createFromParcel(parcel); 246 } 247 248 public void writeToParcel(Parcel dest, int flags) { 249 dest.writeInt(TAG); 250 dest.writeInt(viewId); 251 fillInIntent.writeToParcel(dest, 0 /* no flags */); 252 } 253 254 @Override 255 public void apply(View root, ViewGroup rootParent) { 256 final View target = root.findViewById(viewId); 257 if (target == null) return; 258 259 if (!mIsWidgetCollectionChild) { 260 Log.e("RemoteViews", "The method setOnClickFillInIntent is available " + 261 "only from RemoteViewsFactory (ie. on collection items)."); 262 return; 263 } 264 if (target == root) { 265 target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent); 266 } else if (target != null && fillInIntent != null) { 267 OnClickListener listener = new OnClickListener() { 268 public void onClick(View v) { 269 // Insure that this view is a child of an AdapterView 270 View parent = (View) v.getParent(); 271 while (!(parent instanceof AdapterView<?>) 272 && !(parent instanceof AppWidgetHostView)) { 273 parent = (View) parent.getParent(); 274 } 275 276 if (parent instanceof AppWidgetHostView) { 277 // Somehow they've managed to get this far without having 278 // and AdapterView as a parent. 279 Log.e("RemoteViews", "Collection item doesn't have AdapterView parent"); 280 return; 281 } 282 283 // Insure that a template pending intent has been set on an ancestor 284 if (!(parent.getTag() instanceof PendingIntent)) { 285 Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" + 286 " calling setPendingIntentTemplate on parent."); 287 return; 288 } 289 290 PendingIntent pendingIntent = (PendingIntent) parent.getTag(); 291 292 final float appScale = v.getContext().getResources() 293 .getCompatibilityInfo().applicationScale; 294 final int[] pos = new int[2]; 295 v.getLocationOnScreen(pos); 296 297 final Rect rect = new Rect(); 298 rect.left = (int) (pos[0] * appScale + 0.5f); 299 rect.top = (int) (pos[1] * appScale + 0.5f); 300 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 301 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 302 303 fillInIntent.setSourceBounds(rect); 304 startIntentSafely(v, pendingIntent, fillInIntent); 305 } 306 307 }; 308 target.setOnClickListener(listener); 309 } 310 } 311 312 int viewId; 313 Intent fillInIntent; 314 315 public final static int TAG = 9; 316 } 317 318 private class SetPendingIntentTemplate extends Action { 319 public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { 320 this.viewId = id; 321 this.pendingIntentTemplate = pendingIntentTemplate; 322 } 323 324 public SetPendingIntentTemplate(Parcel parcel) { 325 viewId = parcel.readInt(); 326 pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 327 } 328 329 public void writeToParcel(Parcel dest, int flags) { 330 dest.writeInt(TAG); 331 dest.writeInt(viewId); 332 pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */); 333 } 334 335 @Override 336 public void apply(View root, ViewGroup rootParent) { 337 final View target = root.findViewById(viewId); 338 if (target == null) return; 339 340 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 341 if (target instanceof AdapterView<?>) { 342 AdapterView<?> av = (AdapterView<?>) target; 343 // The PendingIntent template is stored in the view's tag. 344 OnItemClickListener listener = new OnItemClickListener() { 345 public void onItemClick(AdapterView<?> parent, View view, 346 int position, long id) { 347 // The view should be a frame layout 348 if (view instanceof ViewGroup) { 349 ViewGroup vg = (ViewGroup) view; 350 351 // AdapterViews contain their children in a frame 352 // so we need to go one layer deeper here. 353 if (parent instanceof AdapterViewAnimator) { 354 vg = (ViewGroup) vg.getChildAt(0); 355 } 356 if (vg == null) return; 357 358 Intent fillInIntent = null; 359 int childCount = vg.getChildCount(); 360 for (int i = 0; i < childCount; i++) { 361 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent); 362 if (tag instanceof Intent) { 363 fillInIntent = (Intent) tag; 364 break; 365 } 366 } 367 if (fillInIntent == null) return; 368 369 final float appScale = view.getContext().getResources() 370 .getCompatibilityInfo().applicationScale; 371 final int[] pos = new int[2]; 372 view.getLocationOnScreen(pos); 373 374 final Rect rect = new Rect(); 375 rect.left = (int) (pos[0] * appScale + 0.5f); 376 rect.top = (int) (pos[1] * appScale + 0.5f); 377 rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f); 378 rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f); 379 380 final Intent intent = new Intent(); 381 intent.setSourceBounds(rect); 382 startIntentSafely(view, pendingIntentTemplate, fillInIntent); 383 } 384 } 385 }; 386 av.setOnItemClickListener(listener); 387 av.setTag(pendingIntentTemplate); 388 } else { 389 Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" + 390 "an AdapterView (id: " + viewId + ")"); 391 return; 392 } 393 } 394 395 int viewId; 396 PendingIntent pendingIntentTemplate; 397 398 public final static int TAG = 8; 399 } 400 401 private class SetRemoteViewsAdapterIntent extends Action { 402 public SetRemoteViewsAdapterIntent(int id, Intent intent) { 403 this.viewId = id; 404 this.intent = intent; 405 } 406 407 public SetRemoteViewsAdapterIntent(Parcel parcel) { 408 viewId = parcel.readInt(); 409 intent = Intent.CREATOR.createFromParcel(parcel); 410 } 411 412 public void writeToParcel(Parcel dest, int flags) { 413 dest.writeInt(TAG); 414 dest.writeInt(viewId); 415 intent.writeToParcel(dest, flags); 416 } 417 418 @Override 419 public void apply(View root, ViewGroup rootParent) { 420 final View target = root.findViewById(viewId); 421 if (target == null) return; 422 423 // Ensure that we are applying to an AppWidget root 424 if (!(rootParent instanceof AppWidgetHostView)) { 425 Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " + 426 "AppWidgets (root id: " + viewId + ")"); 427 return; 428 } 429 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 430 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 431 Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " + 432 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 433 return; 434 } 435 436 // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent 437 // RemoteViewsService 438 AppWidgetHostView host = (AppWidgetHostView) rootParent; 439 intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()); 440 if (target instanceof AbsListView) { 441 AbsListView v = (AbsListView) target; 442 v.setRemoteViewsAdapter(intent); 443 } else if (target instanceof AdapterViewAnimator) { 444 AdapterViewAnimator v = (AdapterViewAnimator) target; 445 v.setRemoteViewsAdapter(intent); 446 } 447 } 448 449 int viewId; 450 Intent intent; 451 452 public final static int TAG = 10; 453 } 454 455 /** 456 * Equivalent to calling 457 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 458 * to launch the provided {@link PendingIntent}. 459 */ 460 private class SetOnClickPendingIntent extends Action { 461 public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { 462 this.viewId = id; 463 this.pendingIntent = pendingIntent; 464 } 465 466 public SetOnClickPendingIntent(Parcel parcel) { 467 viewId = parcel.readInt(); 468 469 // We check a flag to determine if the parcel contains a PendingIntent. 470 if (parcel.readInt() != 0) { 471 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 472 } 473 } 474 475 public void writeToParcel(Parcel dest, int flags) { 476 dest.writeInt(TAG); 477 dest.writeInt(viewId); 478 479 // We use a flag to indicate whether the parcel contains a valid object. 480 dest.writeInt(pendingIntent != null ? 1 : 0); 481 if (pendingIntent != null) { 482 pendingIntent.writeToParcel(dest, 0 /* no flags */); 483 } 484 } 485 486 @Override 487 public void apply(View root, ViewGroup rootParent) { 488 final View target = root.findViewById(viewId); 489 if (target == null) return; 490 491 // If the view is an AdapterView, setting a PendingIntent on click doesn't make much 492 // sense, do they mean to set a PendingIntent template for the AdapterView's children? 493 if (mIsWidgetCollectionChild) { 494 Log.e("RemoteViews", "Cannot setOnClickPendingIntent for collection item " + 495 "(id: " + viewId + ")"); 496 ApplicationInfo appInfo = root.getContext().getApplicationInfo(); 497 498 // We let this slide for HC and ICS so as to not break compatibility. It should have 499 // been disabled from the outset, but was left open by accident. 500 if (appInfo != null && 501 appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { 502 return; 503 } 504 } 505 506 if (target != null) { 507 // If the pendingIntent is null, we clear the onClickListener 508 OnClickListener listener = null; 509 if (pendingIntent != null) { 510 listener = new OnClickListener() { 511 public void onClick(View v) { 512 // Find target view location in screen coordinates and 513 // fill into PendingIntent before sending. 514 final float appScale = v.getContext().getResources() 515 .getCompatibilityInfo().applicationScale; 516 final int[] pos = new int[2]; 517 v.getLocationOnScreen(pos); 518 519 final Rect rect = new Rect(); 520 rect.left = (int) (pos[0] * appScale + 0.5f); 521 rect.top = (int) (pos[1] * appScale + 0.5f); 522 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 523 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 524 525 final Intent intent = new Intent(); 526 intent.setSourceBounds(rect); 527 startIntentSafely(v, pendingIntent, intent); 528 } 529 }; 530 } 531 target.setOnClickListener(listener); 532 } 533 } 534 535 int viewId; 536 PendingIntent pendingIntent; 537 538 public final static int TAG = 1; 539 } 540 541 /** 542 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 543 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 544 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view. 545 * <p> 546 * These operations will be performed on the {@link Drawable} returned by the 547 * target {@link View#getBackground()} by default. If targetBackground is false, 548 * we assume the target is an {@link ImageView} and try applying the operations 549 * to {@link ImageView#getDrawable()}. 550 * <p> 551 * You can omit specific calls by marking their values with null or -1. 552 */ 553 private class SetDrawableParameters extends Action { 554 public SetDrawableParameters(int id, boolean targetBackground, int alpha, 555 int colorFilter, PorterDuff.Mode mode, int level) { 556 this.viewId = id; 557 this.targetBackground = targetBackground; 558 this.alpha = alpha; 559 this.colorFilter = colorFilter; 560 this.filterMode = mode; 561 this.level = level; 562 } 563 564 public SetDrawableParameters(Parcel parcel) { 565 viewId = parcel.readInt(); 566 targetBackground = parcel.readInt() != 0; 567 alpha = parcel.readInt(); 568 colorFilter = parcel.readInt(); 569 boolean hasMode = parcel.readInt() != 0; 570 if (hasMode) { 571 filterMode = PorterDuff.Mode.valueOf(parcel.readString()); 572 } else { 573 filterMode = null; 574 } 575 level = parcel.readInt(); 576 } 577 578 public void writeToParcel(Parcel dest, int flags) { 579 dest.writeInt(TAG); 580 dest.writeInt(viewId); 581 dest.writeInt(targetBackground ? 1 : 0); 582 dest.writeInt(alpha); 583 dest.writeInt(colorFilter); 584 if (filterMode != null) { 585 dest.writeInt(1); 586 dest.writeString(filterMode.toString()); 587 } else { 588 dest.writeInt(0); 589 } 590 dest.writeInt(level); 591 } 592 593 @Override 594 public void apply(View root, ViewGroup rootParent) { 595 final View target = root.findViewById(viewId); 596 if (target == null) return; 597 598 // Pick the correct drawable to modify for this view 599 Drawable targetDrawable = null; 600 if (targetBackground) { 601 targetDrawable = target.getBackground(); 602 } else if (target instanceof ImageView) { 603 ImageView imageView = (ImageView) target; 604 targetDrawable = imageView.getDrawable(); 605 } 606 607 if (targetDrawable != null) { 608 // Perform modifications only if values are set correctly 609 if (alpha != -1) { 610 targetDrawable.setAlpha(alpha); 611 } 612 if (colorFilter != -1 && filterMode != null) { 613 targetDrawable.setColorFilter(colorFilter, filterMode); 614 } 615 if (level != -1) { 616 targetDrawable.setLevel(level); 617 } 618 } 619 } 620 621 int viewId; 622 boolean targetBackground; 623 int alpha; 624 int colorFilter; 625 PorterDuff.Mode filterMode; 626 int level; 627 628 public final static int TAG = 3; 629 } 630 631 private class ReflectionActionWithoutParams extends Action { 632 int viewId; 633 String methodName; 634 635 public final static int TAG = 5; 636 637 ReflectionActionWithoutParams(int viewId, String methodName) { 638 this.viewId = viewId; 639 this.methodName = methodName; 640 } 641 642 ReflectionActionWithoutParams(Parcel in) { 643 this.viewId = in.readInt(); 644 this.methodName = in.readString(); 645 } 646 647 public void writeToParcel(Parcel out, int flags) { 648 out.writeInt(TAG); 649 out.writeInt(this.viewId); 650 out.writeString(this.methodName); 651 } 652 653 @Override 654 public void apply(View root, ViewGroup rootParent) { 655 final View view = root.findViewById(viewId); 656 if (view == null) return; 657 658 Class klass = view.getClass(); 659 Method method; 660 try { 661 method = klass.getMethod(this.methodName); 662 } catch (NoSuchMethodException ex) { 663 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 664 + this.methodName + "()"); 665 } 666 667 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 668 throw new ActionException("view: " + klass.getName() 669 + " can't use method with RemoteViews: " 670 + this.methodName + "()"); 671 } 672 673 try { 674 //noinspection ConstantIfStatement 675 if (false) { 676 Log.d("RemoteViews", "view: " + klass.getName() + " calling method: " 677 + this.methodName + "()"); 678 } 679 method.invoke(view); 680 } catch (Exception ex) { 681 throw new ActionException(ex); 682 } 683 } 684 } 685 686 private static class BitmapCache { 687 ArrayList<Bitmap> mBitmaps; 688 689 public BitmapCache() { 690 mBitmaps = new ArrayList<Bitmap>(); 691 } 692 693 public BitmapCache(Parcel source) { 694 int count = source.readInt(); 695 mBitmaps = new ArrayList<Bitmap>(); 696 for (int i = 0; i < count; i++) { 697 Bitmap b = Bitmap.CREATOR.createFromParcel(source); 698 mBitmaps.add(b); 699 } 700 } 701 702 public int getBitmapId(Bitmap b) { 703 if (b == null) { 704 return -1; 705 } else { 706 if (mBitmaps.contains(b)) { 707 return mBitmaps.indexOf(b); 708 } else { 709 mBitmaps.add(b); 710 return (mBitmaps.size() - 1); 711 } 712 } 713 } 714 715 public Bitmap getBitmapForId(int id) { 716 if (id == -1 || id >= mBitmaps.size()) { 717 return null; 718 } else { 719 return mBitmaps.get(id); 720 } 721 } 722 723 public void writeBitmapsToParcel(Parcel dest, int flags) { 724 int count = mBitmaps.size(); 725 dest.writeInt(count); 726 for (int i = 0; i < count; i++) { 727 mBitmaps.get(i).writeToParcel(dest, flags); 728 } 729 } 730 731 public void assimilate(BitmapCache bitmapCache) { 732 ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps; 733 int count = bitmapsToBeAdded.size(); 734 for (int i = 0; i < count; i++) { 735 Bitmap b = bitmapsToBeAdded.get(i); 736 if (!mBitmaps.contains(b)) { 737 mBitmaps.add(b); 738 } 739 } 740 } 741 742 public void addBitmapMemory(MemoryUsageCounter memoryCounter) { 743 for (int i = 0; i < mBitmaps.size(); i++) { 744 memoryCounter.addBitmapMemory(mBitmaps.get(i)); 745 } 746 } 747 } 748 749 private class BitmapReflectionAction extends Action { 750 int bitmapId; 751 int viewId; 752 Bitmap bitmap; 753 String methodName; 754 755 BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { 756 this.bitmap = bitmap; 757 this.viewId = viewId; 758 this.methodName = methodName; 759 bitmapId = mBitmapCache.getBitmapId(bitmap); 760 } 761 762 BitmapReflectionAction(Parcel in) { 763 viewId = in.readInt(); 764 methodName = in.readString(); 765 bitmapId = in.readInt(); 766 bitmap = mBitmapCache.getBitmapForId(bitmapId); 767 } 768 769 @Override 770 public void writeToParcel(Parcel dest, int flags) { 771 dest.writeInt(TAG); 772 dest.writeInt(viewId); 773 dest.writeString(methodName); 774 dest.writeInt(bitmapId); 775 } 776 777 @Override 778 public void apply(View root, ViewGroup rootParent) throws ActionException { 779 ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, 780 bitmap); 781 ra.apply(root, rootParent); 782 } 783 784 @Override 785 public void setBitmapCache(BitmapCache bitmapCache) { 786 bitmapId = bitmapCache.getBitmapId(bitmap); 787 } 788 789 public final static int TAG = 12; 790 } 791 792 /** 793 * Base class for the reflection actions. 794 */ 795 private class ReflectionAction extends Action { 796 static final int TAG = 2; 797 798 static final int BOOLEAN = 1; 799 static final int BYTE = 2; 800 static final int SHORT = 3; 801 static final int INT = 4; 802 static final int LONG = 5; 803 static final int FLOAT = 6; 804 static final int DOUBLE = 7; 805 static final int CHAR = 8; 806 static final int STRING = 9; 807 static final int CHAR_SEQUENCE = 10; 808 static final int URI = 11; 809 static final int BITMAP = 12; 810 static final int BUNDLE = 13; 811 static final int INTENT = 14; 812 813 int viewId; 814 String methodName; 815 int type; 816 Object value; 817 818 ReflectionAction(int viewId, String methodName, int type, Object value) { 819 this.viewId = viewId; 820 this.methodName = methodName; 821 this.type = type; 822 this.value = value; 823 } 824 825 ReflectionAction(Parcel in) { 826 this.viewId = in.readInt(); 827 this.methodName = in.readString(); 828 this.type = in.readInt(); 829 //noinspection ConstantIfStatement 830 if (false) { 831 Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId) 832 + " methodName=" + this.methodName + " type=" + this.type); 833 } 834 835 // For some values that may have been null, we first check a flag to see if they were 836 // written to the parcel. 837 switch (this.type) { 838 case BOOLEAN: 839 this.value = in.readInt() != 0; 840 break; 841 case BYTE: 842 this.value = in.readByte(); 843 break; 844 case SHORT: 845 this.value = (short)in.readInt(); 846 break; 847 case INT: 848 this.value = in.readInt(); 849 break; 850 case LONG: 851 this.value = in.readLong(); 852 break; 853 case FLOAT: 854 this.value = in.readFloat(); 855 break; 856 case DOUBLE: 857 this.value = in.readDouble(); 858 break; 859 case CHAR: 860 this.value = (char)in.readInt(); 861 break; 862 case STRING: 863 this.value = in.readString(); 864 break; 865 case CHAR_SEQUENCE: 866 this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 867 break; 868 case URI: 869 if (in.readInt() != 0) { 870 this.value = Uri.CREATOR.createFromParcel(in); 871 } 872 break; 873 case BITMAP: 874 if (in.readInt() != 0) { 875 this.value = Bitmap.CREATOR.createFromParcel(in); 876 } 877 break; 878 case BUNDLE: 879 this.value = in.readBundle(); 880 break; 881 case INTENT: 882 if (in.readInt() != 0) { 883 this.value = Intent.CREATOR.createFromParcel(in); 884 } 885 break; 886 default: 887 break; 888 } 889 } 890 891 public void writeToParcel(Parcel out, int flags) { 892 out.writeInt(TAG); 893 out.writeInt(this.viewId); 894 out.writeString(this.methodName); 895 out.writeInt(this.type); 896 //noinspection ConstantIfStatement 897 if (false) { 898 Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId) 899 + " methodName=" + this.methodName + " type=" + this.type); 900 } 901 902 // For some values which are null, we record an integer flag to indicate whether 903 // we have written a valid value to the parcel. 904 switch (this.type) { 905 case BOOLEAN: 906 out.writeInt((Boolean) this.value ? 1 : 0); 907 break; 908 case BYTE: 909 out.writeByte((Byte) this.value); 910 break; 911 case SHORT: 912 out.writeInt((Short) this.value); 913 break; 914 case INT: 915 out.writeInt((Integer) this.value); 916 break; 917 case LONG: 918 out.writeLong((Long) this.value); 919 break; 920 case FLOAT: 921 out.writeFloat((Float) this.value); 922 break; 923 case DOUBLE: 924 out.writeDouble((Double) this.value); 925 break; 926 case CHAR: 927 out.writeInt((int)((Character)this.value).charValue()); 928 break; 929 case STRING: 930 out.writeString((String)this.value); 931 break; 932 case CHAR_SEQUENCE: 933 TextUtils.writeToParcel((CharSequence)this.value, out, flags); 934 break; 935 case URI: 936 out.writeInt(this.value != null ? 1 : 0); 937 if (this.value != null) { 938 ((Uri)this.value).writeToParcel(out, flags); 939 } 940 break; 941 case BITMAP: 942 out.writeInt(this.value != null ? 1 : 0); 943 if (this.value != null) { 944 ((Bitmap)this.value).writeToParcel(out, flags); 945 } 946 break; 947 case BUNDLE: 948 out.writeBundle((Bundle) this.value); 949 break; 950 case INTENT: 951 out.writeInt(this.value != null ? 1 : 0); 952 if (this.value != null) { 953 ((Intent)this.value).writeToParcel(out, flags); 954 } 955 break; 956 default: 957 break; 958 } 959 } 960 961 private Class getParameterType() { 962 switch (this.type) { 963 case BOOLEAN: 964 return boolean.class; 965 case BYTE: 966 return byte.class; 967 case SHORT: 968 return short.class; 969 case INT: 970 return int.class; 971 case LONG: 972 return long.class; 973 case FLOAT: 974 return float.class; 975 case DOUBLE: 976 return double.class; 977 case CHAR: 978 return char.class; 979 case STRING: 980 return String.class; 981 case CHAR_SEQUENCE: 982 return CharSequence.class; 983 case URI: 984 return Uri.class; 985 case BITMAP: 986 return Bitmap.class; 987 case BUNDLE: 988 return Bundle.class; 989 case INTENT: 990 return Intent.class; 991 default: 992 return null; 993 } 994 } 995 996 @Override 997 public void apply(View root, ViewGroup rootParent) { 998 final View view = root.findViewById(viewId); 999 if (view == null) return; 1000 1001 Class param = getParameterType(); 1002 if (param == null) { 1003 throw new ActionException("bad type: " + this.type); 1004 } 1005 1006 Class klass = view.getClass(); 1007 Method method; 1008 try { 1009 method = klass.getMethod(this.methodName, getParameterType()); 1010 } 1011 catch (NoSuchMethodException ex) { 1012 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 1013 + this.methodName + "(" + param.getName() + ")"); 1014 } 1015 1016 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 1017 throw new ActionException("view: " + klass.getName() 1018 + " can't use method with RemoteViews: " 1019 + this.methodName + "(" + param.getName() + ")"); 1020 } 1021 1022 try { 1023 //noinspection ConstantIfStatement 1024 if (false) { 1025 Log.d("RemoteViews", "view: " + klass.getName() + " calling method: " 1026 + this.methodName + "(" + param.getName() + ") with " 1027 + (this.value == null ? "null" : this.value.getClass().getName())); 1028 } 1029 method.invoke(view, this.value); 1030 } 1031 catch (Exception ex) { 1032 throw new ActionException(ex); 1033 } 1034 } 1035 1036 @Override 1037 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 1038 // We currently only calculate Bitmap memory usage 1039 switch (this.type) { 1040 case BITMAP: 1041 if (this.value != null) { 1042 final Bitmap b = (Bitmap) this.value; 1043 counter.addBitmapMemory(b); 1044 } 1045 break; 1046 default: 1047 break; 1048 } 1049 } 1050 } 1051 1052 private void configureRemoteViewsAsChild(RemoteViews rv) { 1053 mBitmapCache.assimilate(rv.mBitmapCache); 1054 rv.setBitmapCache(mBitmapCache); 1055 rv.setNotRoot(); 1056 } 1057 1058 void setNotRoot() { 1059 mIsRoot = false; 1060 } 1061 1062 /** 1063 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1064 * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()} 1065 * when null. This allows users to build "nested" {@link RemoteViews}. 1066 */ 1067 private class ViewGroupAction extends Action { 1068 public ViewGroupAction(int viewId, RemoteViews nestedViews) { 1069 this.viewId = viewId; 1070 this.nestedViews = nestedViews; 1071 if (nestedViews != null) { 1072 configureRemoteViewsAsChild(nestedViews); 1073 } 1074 } 1075 1076 public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) { 1077 viewId = parcel.readInt(); 1078 boolean nestedViewsNull = parcel.readInt() == 0; 1079 if (!nestedViewsNull) { 1080 nestedViews = new RemoteViews(parcel, bitmapCache); 1081 } else { 1082 nestedViews = null; 1083 } 1084 } 1085 1086 public void writeToParcel(Parcel dest, int flags) { 1087 dest.writeInt(TAG); 1088 dest.writeInt(viewId); 1089 if (nestedViews != null) { 1090 dest.writeInt(1); 1091 nestedViews.writeToParcel(dest, flags); 1092 } else { 1093 // signifies null 1094 dest.writeInt(0); 1095 } 1096 } 1097 1098 @Override 1099 public void apply(View root, ViewGroup rootParent) { 1100 final Context context = root.getContext(); 1101 final ViewGroup target = (ViewGroup) root.findViewById(viewId); 1102 if (target == null) return; 1103 if (nestedViews != null) { 1104 // Inflate nested views and add as children 1105 target.addView(nestedViews.apply(context, target)); 1106 } else { 1107 // Clear all children when nested views omitted 1108 target.removeAllViews(); 1109 } 1110 } 1111 1112 @Override 1113 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 1114 if (nestedViews != null) { 1115 counter.increment(nestedViews.estimateMemoryUsage()); 1116 } 1117 } 1118 1119 @Override 1120 public void setBitmapCache(BitmapCache bitmapCache) { 1121 if (nestedViews != null) { 1122 nestedViews.setBitmapCache(bitmapCache); 1123 } 1124 } 1125 1126 int viewId; 1127 RemoteViews nestedViews; 1128 1129 public final static int TAG = 4; 1130 } 1131 1132 /** 1133 * Helper action to set compound drawables on a TextView. Supports relative 1134 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 1135 */ 1136 private class TextViewDrawableAction extends Action { 1137 public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) { 1138 this.viewId = viewId; 1139 this.isRelative = isRelative; 1140 this.d1 = d1; 1141 this.d2 = d2; 1142 this.d3 = d3; 1143 this.d4 = d4; 1144 } 1145 1146 public TextViewDrawableAction(Parcel parcel) { 1147 viewId = parcel.readInt(); 1148 isRelative = (parcel.readInt() != 0); 1149 d1 = parcel.readInt(); 1150 d2 = parcel.readInt(); 1151 d3 = parcel.readInt(); 1152 d4 = parcel.readInt(); 1153 } 1154 1155 public void writeToParcel(Parcel dest, int flags) { 1156 dest.writeInt(TAG); 1157 dest.writeInt(viewId); 1158 dest.writeInt(isRelative ? 1 : 0); 1159 dest.writeInt(d1); 1160 dest.writeInt(d2); 1161 dest.writeInt(d3); 1162 dest.writeInt(d4); 1163 } 1164 1165 @Override 1166 public void apply(View root, ViewGroup rootParent) { 1167 final Context context = root.getContext(); 1168 final TextView target = (TextView) root.findViewById(viewId); 1169 if (target == null) return; 1170 if (isRelative) { 1171 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4); 1172 } else { 1173 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4); 1174 } 1175 } 1176 1177 int viewId; 1178 boolean isRelative = false; 1179 int d1, d2, d3, d4; 1180 1181 public final static int TAG = 11; 1182 } 1183 1184 /** 1185 * Simple class used to keep track of memory usage in a RemoteViews. 1186 * 1187 */ 1188 private class MemoryUsageCounter { 1189 public void clear() { 1190 mMemoryUsage = 0; 1191 } 1192 1193 public void increment(int numBytes) { 1194 mMemoryUsage += numBytes; 1195 } 1196 1197 public int getMemoryUsage() { 1198 return mMemoryUsage; 1199 } 1200 1201 public void addBitmapMemory(Bitmap b) { 1202 final Bitmap.Config c = b.getConfig(); 1203 // If we don't know, be pessimistic and assume 4 1204 int bpp = 4; 1205 if (c != null) { 1206 switch (c) { 1207 case ALPHA_8: 1208 bpp = 1; 1209 break; 1210 case RGB_565: 1211 case ARGB_4444: 1212 bpp = 2; 1213 break; 1214 case ARGB_8888: 1215 bpp = 4; 1216 break; 1217 } 1218 } 1219 increment(b.getWidth() * b.getHeight() * bpp); 1220 } 1221 1222 int mMemoryUsage; 1223 } 1224 1225 /** 1226 * Create a new RemoteViews object that will display the views contained 1227 * in the specified layout file. 1228 * 1229 * @param packageName Name of the package that contains the layout resource 1230 * @param layoutId The id of the layout resource 1231 */ 1232 public RemoteViews(String packageName, int layoutId) { 1233 mPackage = packageName; 1234 mLayoutId = layoutId; 1235 mBitmapCache = new BitmapCache(); 1236 1237 // setup the memory usage statistics 1238 mMemoryUsageCounter = new MemoryUsageCounter(); 1239 recalculateMemoryUsage(); 1240 } 1241 1242 private boolean hasLandscapeAndPortraitLayouts() { 1243 return (mLandscape != null) && (mPortrait != null); 1244 } 1245 1246 /** 1247 * Create a new RemoteViews object that will inflate as the specified 1248 * landspace or portrait RemoteViews, depending on the current configuration. 1249 * 1250 * @param landscape The RemoteViews to inflate in landscape configuration 1251 * @param portrait The RemoteViews to inflate in portrait configuration 1252 */ 1253 public RemoteViews(RemoteViews landscape, RemoteViews portrait) { 1254 if (landscape == null || portrait == null) { 1255 throw new RuntimeException("Both RemoteViews must be non-null"); 1256 } 1257 if (landscape.getPackage().compareTo(portrait.getPackage()) != 0) { 1258 throw new RuntimeException("Both RemoteViews must share the same package"); 1259 } 1260 mPackage = portrait.getPackage(); 1261 mLayoutId = portrait.getLayoutId(); 1262 1263 mLandscape = landscape; 1264 mPortrait = portrait; 1265 1266 // setup the memory usage statistics 1267 mMemoryUsageCounter = new MemoryUsageCounter(); 1268 1269 mBitmapCache = new BitmapCache(); 1270 configureRemoteViewsAsChild(landscape); 1271 configureRemoteViewsAsChild(portrait); 1272 1273 recalculateMemoryUsage(); 1274 } 1275 1276 /** 1277 * Reads a RemoteViews object from a parcel. 1278 * 1279 * @param parcel 1280 */ 1281 public RemoteViews(Parcel parcel) { 1282 this(parcel, null); 1283 } 1284 1285 private RemoteViews(Parcel parcel, BitmapCache bitmapCache) { 1286 int mode = parcel.readInt(); 1287 1288 // We only store a bitmap cache in the root of the RemoteViews. 1289 if (bitmapCache == null) { 1290 mBitmapCache = new BitmapCache(parcel); 1291 } else { 1292 setBitmapCache(bitmapCache); 1293 setNotRoot(); 1294 } 1295 1296 if (mode == MODE_NORMAL) { 1297 mPackage = parcel.readString(); 1298 mLayoutId = parcel.readInt(); 1299 mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false; 1300 1301 int count = parcel.readInt(); 1302 if (count > 0) { 1303 mActions = new ArrayList<Action>(count); 1304 for (int i=0; i<count; i++) { 1305 int tag = parcel.readInt(); 1306 switch (tag) { 1307 case SetOnClickPendingIntent.TAG: 1308 mActions.add(new SetOnClickPendingIntent(parcel)); 1309 break; 1310 case SetDrawableParameters.TAG: 1311 mActions.add(new SetDrawableParameters(parcel)); 1312 break; 1313 case ReflectionAction.TAG: 1314 mActions.add(new ReflectionAction(parcel)); 1315 break; 1316 case ViewGroupAction.TAG: 1317 mActions.add(new ViewGroupAction(parcel, mBitmapCache)); 1318 break; 1319 case ReflectionActionWithoutParams.TAG: 1320 mActions.add(new ReflectionActionWithoutParams(parcel)); 1321 break; 1322 case SetEmptyView.TAG: 1323 mActions.add(new SetEmptyView(parcel)); 1324 break; 1325 case SetPendingIntentTemplate.TAG: 1326 mActions.add(new SetPendingIntentTemplate(parcel)); 1327 break; 1328 case SetOnClickFillInIntent.TAG: 1329 mActions.add(new SetOnClickFillInIntent(parcel)); 1330 break; 1331 case SetRemoteViewsAdapterIntent.TAG: 1332 mActions.add(new SetRemoteViewsAdapterIntent(parcel)); 1333 break; 1334 case TextViewDrawableAction.TAG: 1335 mActions.add(new TextViewDrawableAction(parcel)); 1336 break; 1337 case BitmapReflectionAction.TAG: 1338 mActions.add(new BitmapReflectionAction(parcel)); 1339 break; 1340 default: 1341 throw new ActionException("Tag " + tag + " not found"); 1342 } 1343 } 1344 } 1345 } else { 1346 // MODE_HAS_LANDSCAPE_AND_PORTRAIT 1347 mLandscape = new RemoteViews(parcel, mBitmapCache); 1348 mPortrait = new RemoteViews(parcel, mBitmapCache); 1349 mPackage = mPortrait.getPackage(); 1350 mLayoutId = mPortrait.getLayoutId(); 1351 } 1352 1353 // setup the memory usage statistics 1354 mMemoryUsageCounter = new MemoryUsageCounter(); 1355 recalculateMemoryUsage(); 1356 } 1357 1358 @Override 1359 public RemoteViews clone() { 1360 RemoteViews that; 1361 if (!hasLandscapeAndPortraitLayouts()) { 1362 that = new RemoteViews(mPackage, mLayoutId); 1363 1364 if (mActions != null) { 1365 that.mActions = (ArrayList<Action>)mActions.clone(); 1366 } 1367 } else { 1368 RemoteViews land = mLandscape.clone(); 1369 RemoteViews port = mPortrait.clone(); 1370 that = new RemoteViews(land, port); 1371 } 1372 // update the memory usage stats of the cloned RemoteViews 1373 that.recalculateMemoryUsage(); 1374 return that; 1375 } 1376 1377 public String getPackage() { 1378 return mPackage; 1379 } 1380 1381 /** 1382 * Reutrns the layout id of the root layout associated with this RemoteViews. In the case 1383 * that the RemoteViews has both a landscape and portrait root, this will return the layout 1384 * id associated with the portrait layout. 1385 * 1386 * @return the layout id. 1387 */ 1388 public int getLayoutId() { 1389 return mLayoutId; 1390 } 1391 1392 /* 1393 * This flag indicates whether this RemoteViews object is being created from a 1394 * RemoteViewsService for use as a child of a widget collection. This flag is used 1395 * to determine whether or not certain features are available, in particular, 1396 * setting on click extras and setting on click pending intents. The former is enabled, 1397 * and the latter disabled when this flag is true. 1398 */ 1399 void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) { 1400 mIsWidgetCollectionChild = isWidgetCollectionChild; 1401 } 1402 1403 /** 1404 * Updates the memory usage statistics. 1405 */ 1406 private void recalculateMemoryUsage() { 1407 mMemoryUsageCounter.clear(); 1408 1409 if (!hasLandscapeAndPortraitLayouts()) { 1410 // Accumulate the memory usage for each action 1411 if (mActions != null) { 1412 final int count = mActions.size(); 1413 for (int i= 0; i < count; ++i) { 1414 mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter); 1415 } 1416 } 1417 if (mIsRoot) { 1418 mBitmapCache.addBitmapMemory(mMemoryUsageCounter); 1419 } 1420 } else { 1421 mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage()); 1422 mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage()); 1423 mBitmapCache.addBitmapMemory(mMemoryUsageCounter); 1424 } 1425 } 1426 1427 /** 1428 * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. 1429 */ 1430 private void setBitmapCache(BitmapCache bitmapCache) { 1431 mBitmapCache = bitmapCache; 1432 if (!hasLandscapeAndPortraitLayouts()) { 1433 if (mActions != null) { 1434 final int count = mActions.size(); 1435 for (int i= 0; i < count; ++i) { 1436 mActions.get(i).setBitmapCache(bitmapCache); 1437 } 1438 } 1439 } else { 1440 mLandscape.setBitmapCache(bitmapCache); 1441 mPortrait.setBitmapCache(bitmapCache); 1442 } 1443 } 1444 1445 /** 1446 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 1447 */ 1448 /** @hide */ 1449 public int estimateMemoryUsage() { 1450 return mMemoryUsageCounter.getMemoryUsage(); 1451 } 1452 1453 /** 1454 * Add an action to be executed on the remote side when apply is called. 1455 * 1456 * @param a The action to add 1457 */ 1458 private void addAction(Action a) { 1459 if (hasLandscapeAndPortraitLayouts()) { 1460 throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + 1461 " layouts cannot be modified. Instead, fully configure the landscape and" + 1462 " portrait layouts individually before constructing the combined layout."); 1463 } 1464 if (mActions == null) { 1465 mActions = new ArrayList<Action>(); 1466 } 1467 mActions.add(a); 1468 1469 // update the memory usage stats 1470 a.updateMemoryUsageEstimate(mMemoryUsageCounter); 1471 } 1472 1473 /** 1474 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1475 * given {@link RemoteViews}. This allows users to build "nested" 1476 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 1477 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 1478 * children. 1479 * 1480 * @param viewId The id of the parent {@link ViewGroup} to add child into. 1481 * @param nestedView {@link RemoteViews} that describes the child. 1482 */ 1483 public void addView(int viewId, RemoteViews nestedView) { 1484 addAction(new ViewGroupAction(viewId, nestedView)); 1485 } 1486 1487 /** 1488 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 1489 * 1490 * @param viewId The id of the parent {@link ViewGroup} to remove all 1491 * children from. 1492 */ 1493 public void removeAllViews(int viewId) { 1494 addAction(new ViewGroupAction(viewId, null)); 1495 } 1496 1497 /** 1498 * Equivalent to calling {@link AdapterViewAnimator#showNext()} 1499 * 1500 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} 1501 */ 1502 public void showNext(int viewId) { 1503 addAction(new ReflectionActionWithoutParams(viewId, "showNext")); 1504 } 1505 1506 /** 1507 * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} 1508 * 1509 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} 1510 */ 1511 public void showPrevious(int viewId) { 1512 addAction(new ReflectionActionWithoutParams(viewId, "showPrevious")); 1513 } 1514 1515 /** 1516 * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} 1517 * 1518 * @param viewId The id of the view on which to call 1519 * {@link AdapterViewAnimator#setDisplayedChild(int)} 1520 */ 1521 public void setDisplayedChild(int viewId, int childIndex) { 1522 setInt(viewId, "setDisplayedChild", childIndex); 1523 } 1524 1525 /** 1526 * Equivalent to calling View.setVisibility 1527 * 1528 * @param viewId The id of the view whose visibility should change 1529 * @param visibility The new visibility for the view 1530 */ 1531 public void setViewVisibility(int viewId, int visibility) { 1532 setInt(viewId, "setVisibility", visibility); 1533 } 1534 1535 /** 1536 * Equivalent to calling TextView.setText 1537 * 1538 * @param viewId The id of the view whose text should change 1539 * @param text The new text for the view 1540 */ 1541 public void setTextViewText(int viewId, CharSequence text) { 1542 setCharSequence(viewId, "setText", text); 1543 } 1544 1545 /** 1546 * Equivalent to calling 1547 * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. 1548 * 1549 * @param viewId The id of the view whose text should change 1550 * @param left The id of a drawable to place to the left of the text, or 0 1551 * @param top The id of a drawable to place above the text, or 0 1552 * @param right The id of a drawable to place to the right of the text, or 0 1553 * @param bottom The id of a drawable to place below the text, or 0 1554 */ 1555 public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { 1556 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 1557 } 1558 1559 /** 1560 * @param viewId The id of the view whose text should change 1561 * @param start The id of a drawable to place before the text (relative to the 1562 * layout direction), or 0 1563 * @param top The id of a drawable to place above the text, or 0 1564 * @param end The id of a drawable to place after the text, or 0 1565 * @param bottom The id of a drawable to place below the text, or 0 1566 * @hide 1567 */ 1568 public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { 1569 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 1570 } 1571 1572 /** 1573 * Equivalent to calling ImageView.setImageResource 1574 * 1575 * @param viewId The id of the view whose drawable should change 1576 * @param srcId The new resource id for the drawable 1577 */ 1578 public void setImageViewResource(int viewId, int srcId) { 1579 setInt(viewId, "setImageResource", srcId); 1580 } 1581 1582 /** 1583 * Equivalent to calling ImageView.setImageURI 1584 * 1585 * @param viewId The id of the view whose drawable should change 1586 * @param uri The Uri for the image 1587 */ 1588 public void setImageViewUri(int viewId, Uri uri) { 1589 setUri(viewId, "setImageURI", uri); 1590 } 1591 1592 /** 1593 * Equivalent to calling ImageView.setImageBitmap 1594 * 1595 * @param viewId The id of the view whose bitmap should change 1596 * @param bitmap The new Bitmap for the drawable 1597 */ 1598 public void setImageViewBitmap(int viewId, Bitmap bitmap) { 1599 setBitmap(viewId, "setImageBitmap", bitmap); 1600 } 1601 1602 /** 1603 * Equivalent to calling AdapterView.setEmptyView 1604 * 1605 * @param viewId The id of the view on which to set the empty view 1606 * @param emptyViewId The view id of the empty view 1607 */ 1608 public void setEmptyView(int viewId, int emptyViewId) { 1609 addAction(new SetEmptyView(viewId, emptyViewId)); 1610 } 1611 1612 /** 1613 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 1614 * {@link Chronometer#setFormat Chronometer.setFormat}, 1615 * and {@link Chronometer#start Chronometer.start()} or 1616 * {@link Chronometer#stop Chronometer.stop()}. 1617 * 1618 * @param viewId The id of the {@link Chronometer} to change 1619 * @param base The time at which the timer would have read 0:00. This 1620 * time should be based off of 1621 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 1622 * @param format The Chronometer format string, or null to 1623 * simply display the timer value. 1624 * @param started True if you want the clock to be started, false if not. 1625 */ 1626 public void setChronometer(int viewId, long base, String format, boolean started) { 1627 setLong(viewId, "setBase", base); 1628 setString(viewId, "setFormat", format); 1629 setBoolean(viewId, "setStarted", started); 1630 } 1631 1632 /** 1633 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 1634 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 1635 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 1636 * 1637 * If indeterminate is true, then the values for max and progress are ignored. 1638 * 1639 * @param viewId The id of the {@link ProgressBar} to change 1640 * @param max The 100% value for the progress bar 1641 * @param progress The current value of the progress bar. 1642 * @param indeterminate True if the progress bar is indeterminate, 1643 * false if not. 1644 */ 1645 public void setProgressBar(int viewId, int max, int progress, 1646 boolean indeterminate) { 1647 setBoolean(viewId, "setIndeterminate", indeterminate); 1648 if (!indeterminate) { 1649 setInt(viewId, "setMax", max); 1650 setInt(viewId, "setProgress", progress); 1651 } 1652 } 1653 1654 /** 1655 * Equivalent to calling 1656 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 1657 * to launch the provided {@link PendingIntent}. 1658 * 1659 * When setting the on-click action of items within collections (eg. {@link ListView}, 1660 * {@link StackView} etc.), this method will not work. Instead, use {@link 1661 * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with 1662 * RemoteViews#setOnClickFillInIntent(int, Intent). 1663 * 1664 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 1665 * @param pendingIntent The {@link PendingIntent} to send when user clicks 1666 */ 1667 public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { 1668 addAction(new SetOnClickPendingIntent(viewId, pendingIntent)); 1669 } 1670 1671 /** 1672 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 1673 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 1674 * this method should be used to set a single PendingIntent template on the collection, and 1675 * individual items can differentiate their on-click behavior using 1676 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 1677 * 1678 * @param viewId The id of the collection who's children will use this PendingIntent template 1679 * when clicked 1680 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 1681 * by a child of viewId and executed when that child is clicked 1682 */ 1683 public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { 1684 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 1685 } 1686 1687 /** 1688 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 1689 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 1690 * a single PendingIntent template can be set on the collection, see {@link 1691 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 1692 * action of a given item can be distinguished by setting a fillInIntent on that item. The 1693 * fillInIntent is then combined with the PendingIntent template in order to determine the final 1694 * intent which will be executed when the item is clicked. This works as follows: any fields 1695 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 1696 * will be overwritten, and the resulting PendingIntent will be used. 1697 * 1698 * 1699 * of the PendingIntent template will then be filled in with the associated fields that are 1700 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 1701 * 1702 * @param viewId The id of the view on which to set the fillInIntent 1703 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 1704 * in order to determine the on-click behavior of the view specified by viewId 1705 */ 1706 public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { 1707 addAction(new SetOnClickFillInIntent(viewId, fillInIntent)); 1708 } 1709 1710 /** 1711 * @hide 1712 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 1713 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 1714 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given 1715 * view. 1716 * <p> 1717 * You can omit specific calls by marking their values with null or -1. 1718 * 1719 * @param viewId The id of the view that contains the target 1720 * {@link Drawable} 1721 * @param targetBackground If true, apply these parameters to the 1722 * {@link Drawable} returned by 1723 * {@link android.view.View#getBackground()}. Otherwise, assume 1724 * the target view is an {@link ImageView} and apply them to 1725 * {@link ImageView#getDrawable()}. 1726 * @param alpha Specify an alpha value for the drawable, or -1 to leave 1727 * unchanged. 1728 * @param colorFilter Specify a color for a 1729 * {@link android.graphics.ColorFilter} for this drawable, or -1 1730 * to leave unchanged. 1731 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 1732 * unchanged. 1733 * @param level Specify the level for the drawable, or -1 to leave 1734 * unchanged. 1735 */ 1736 public void setDrawableParameters(int viewId, boolean targetBackground, int alpha, 1737 int colorFilter, PorterDuff.Mode mode, int level) { 1738 addAction(new SetDrawableParameters(viewId, targetBackground, alpha, 1739 colorFilter, mode, level)); 1740 } 1741 1742 /** 1743 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 1744 * 1745 * @param viewId The id of the view whose text color should change 1746 * @param color Sets the text color for all the states (normal, selected, 1747 * focused) to be this color. 1748 */ 1749 public void setTextColor(int viewId, int color) { 1750 setInt(viewId, "setTextColor", color); 1751 } 1752 1753 /** 1754 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 1755 * 1756 * @param appWidgetId The id of the app widget which contains the specified view. (This 1757 * parameter is ignored in this deprecated method) 1758 * @param viewId The id of the {@link AbsListView} 1759 * @param intent The intent of the service which will be 1760 * providing data to the RemoteViewsAdapter 1761 * @deprecated This method has been deprecated. See 1762 * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} 1763 */ 1764 @Deprecated 1765 public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) { 1766 setRemoteAdapter(viewId, intent); 1767 } 1768 1769 /** 1770 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 1771 * Can only be used for App Widgets. 1772 * 1773 * @param viewId The id of the {@link AbsListView} 1774 * @param intent The intent of the service which will be 1775 * providing data to the RemoteViewsAdapter 1776 */ 1777 public void setRemoteAdapter(int viewId, Intent intent) { 1778 addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); 1779 } 1780 1781 /** 1782 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 1783 * 1784 * @param viewId The id of the view to change 1785 * @param position Scroll to this adapter position 1786 */ 1787 public void setScrollPosition(int viewId, int position) { 1788 setInt(viewId, "smoothScrollToPosition", position); 1789 } 1790 1791 /** 1792 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 1793 * 1794 * @param viewId The id of the view to change 1795 * @param offset Scroll by this adapter position offset 1796 */ 1797 public void setRelativeScrollPosition(int viewId, int offset) { 1798 setInt(viewId, "smoothScrollByOffset", offset); 1799 } 1800 1801 /** 1802 * Call a method taking one boolean on a view in the layout for this RemoteViews. 1803 * 1804 * @param viewId The id of the view on which to call the method. 1805 * @param methodName The name of the method to call. 1806 * @param value The value to pass to the method. 1807 */ 1808 public void setBoolean(int viewId, String methodName, boolean value) { 1809 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); 1810 } 1811 1812 /** 1813 * Call a method taking one byte on a view in the layout for this RemoteViews. 1814 * 1815 * @param viewId The id of the view on which to call the method. 1816 * @param methodName The name of the method to call. 1817 * @param value The value to pass to the method. 1818 */ 1819 public void setByte(int viewId, String methodName, byte value) { 1820 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); 1821 } 1822 1823 /** 1824 * Call a method taking one short on a view in the layout for this RemoteViews. 1825 * 1826 * @param viewId The id of the view on which to call the method. 1827 * @param methodName The name of the method to call. 1828 * @param value The value to pass to the method. 1829 */ 1830 public void setShort(int viewId, String methodName, short value) { 1831 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); 1832 } 1833 1834 /** 1835 * Call a method taking one int on a view in the layout for this RemoteViews. 1836 * 1837 * @param viewId The id of the view on which to call the method. 1838 * @param methodName The name of the method to call. 1839 * @param value The value to pass to the method. 1840 */ 1841 public void setInt(int viewId, String methodName, int value) { 1842 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); 1843 } 1844 1845 /** 1846 * Call a method taking one long on a view in the layout for this RemoteViews. 1847 * 1848 * @param viewId The id of the view on which to call the method. 1849 * @param methodName The name of the method to call. 1850 * @param value The value to pass to the method. 1851 */ 1852 public void setLong(int viewId, String methodName, long value) { 1853 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); 1854 } 1855 1856 /** 1857 * Call a method taking one float on a view in the layout for this RemoteViews. 1858 * 1859 * @param viewId The id of the view on which to call the method. 1860 * @param methodName The name of the method to call. 1861 * @param value The value to pass to the method. 1862 */ 1863 public void setFloat(int viewId, String methodName, float value) { 1864 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); 1865 } 1866 1867 /** 1868 * Call a method taking one double on a view in the layout for this RemoteViews. 1869 * 1870 * @param viewId The id of the view on which to call the method. 1871 * @param methodName The name of the method to call. 1872 * @param value The value to pass to the method. 1873 */ 1874 public void setDouble(int viewId, String methodName, double value) { 1875 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); 1876 } 1877 1878 /** 1879 * Call a method taking one char on a view in the layout for this RemoteViews. 1880 * 1881 * @param viewId The id of the view on which to call the method. 1882 * @param methodName The name of the method to call. 1883 * @param value The value to pass to the method. 1884 */ 1885 public void setChar(int viewId, String methodName, char value) { 1886 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); 1887 } 1888 1889 /** 1890 * Call a method taking one String on a view in the layout for this RemoteViews. 1891 * 1892 * @param viewId The id of the view on which to call the method. 1893 * @param methodName The name of the method to call. 1894 * @param value The value to pass to the method. 1895 */ 1896 public void setString(int viewId, String methodName, String value) { 1897 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); 1898 } 1899 1900 /** 1901 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 1902 * 1903 * @param viewId The id of the view on which to call the method. 1904 * @param methodName The name of the method to call. 1905 * @param value The value to pass to the method. 1906 */ 1907 public void setCharSequence(int viewId, String methodName, CharSequence value) { 1908 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); 1909 } 1910 1911 /** 1912 * Call a method taking one Uri on a view in the layout for this RemoteViews. 1913 * 1914 * @param viewId The id of the view on which to call the method. 1915 * @param methodName The name of the method to call. 1916 * @param value The value to pass to the method. 1917 */ 1918 public void setUri(int viewId, String methodName, Uri value) { 1919 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); 1920 } 1921 1922 /** 1923 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 1924 * @more 1925 * <p class="note">The bitmap will be flattened into the parcel if this object is 1926 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 1927 * 1928 * @param viewId The id of the view on which to call the method. 1929 * @param methodName The name of the method to call. 1930 * @param value The value to pass to the method. 1931 */ 1932 public void setBitmap(int viewId, String methodName, Bitmap value) { 1933 addAction(new BitmapReflectionAction(viewId, methodName, value)); 1934 } 1935 1936 /** 1937 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 1938 * 1939 * @param viewId The id of the view on which to call the method. 1940 * @param methodName The name of the method to call. 1941 * @param value The value to pass to the method. 1942 */ 1943 public void setBundle(int viewId, String methodName, Bundle value) { 1944 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); 1945 } 1946 1947 /** 1948 * Call a method taking one Intent on a view in the layout for this RemoteViews. 1949 * 1950 * @param viewId The id of the view on which to call the method. 1951 * @param methodName The name of the method to call. 1952 * @param value The {@link android.content.Intent} to pass the method. 1953 */ 1954 public void setIntent(int viewId, String methodName, Intent value) { 1955 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); 1956 } 1957 1958 /** 1959 * Equivalent to calling View.setContentDescription 1960 * 1961 * @param viewId The id of the view whose content description should change 1962 * @param contentDescription The new content description for the view 1963 */ 1964 public void setContentDescription(int viewId, CharSequence contentDescription) { 1965 setCharSequence(viewId, "setContentDescription", contentDescription); 1966 } 1967 1968 private RemoteViews getRemoteViewsToApply(Context context) { 1969 if (hasLandscapeAndPortraitLayouts()) { 1970 int orientation = context.getResources().getConfiguration().orientation; 1971 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 1972 return mLandscape; 1973 } else { 1974 return mPortrait; 1975 } 1976 } 1977 return this; 1978 } 1979 1980 /** 1981 * Inflates the view hierarchy represented by this object and applies 1982 * all of the actions. 1983 * 1984 * <p><strong>Caller beware: this may throw</strong> 1985 * 1986 * @param context Default context to use 1987 * @param parent Parent that the resulting view hierarchy will be attached to. This method 1988 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 1989 * @return The inflated view hierarchy 1990 */ 1991 public View apply(Context context, ViewGroup parent) { 1992 RemoteViews rvToApply = getRemoteViewsToApply(context); 1993 1994 View result; 1995 1996 Context c = prepareContext(context); 1997 1998 LayoutInflater inflater = (LayoutInflater) 1999 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2000 2001 inflater = inflater.cloneInContext(c); 2002 inflater.setFilter(this); 2003 2004 result = inflater.inflate(rvToApply.getLayoutId(), parent, false); 2005 2006 rvToApply.performApply(result, parent); 2007 2008 return result; 2009 } 2010 2011 /** 2012 * Applies all of the actions to the provided view. 2013 * 2014 * <p><strong>Caller beware: this may throw</strong> 2015 * 2016 * @param v The view to apply the actions to. This should be the result of 2017 * the {@link #apply(Context,ViewGroup)} call. 2018 */ 2019 public void reapply(Context context, View v) { 2020 RemoteViews rvToApply = getRemoteViewsToApply(context); 2021 2022 // In the case that a view has this RemoteViews applied in one orientation, is persisted 2023 // across orientation change, and has the RemoteViews re-applied in the new orientation, 2024 // we throw an exception, since the layouts may be completely unrelated. 2025 if (hasLandscapeAndPortraitLayouts()) { 2026 if (v.getId() != rvToApply.getLayoutId()) { 2027 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 2028 " that does not share the same root layout id."); 2029 } 2030 } 2031 2032 prepareContext(context); 2033 rvToApply.performApply(v, (ViewGroup) v.getParent()); 2034 } 2035 2036 private void performApply(View v, ViewGroup parent) { 2037 if (mActions != null) { 2038 final int count = mActions.size(); 2039 for (int i = 0; i < count; i++) { 2040 Action a = mActions.get(i); 2041 a.apply(v, parent); 2042 } 2043 } 2044 } 2045 2046 private Context prepareContext(Context context) { 2047 Context c; 2048 String packageName = mPackage; 2049 2050 if (packageName != null) { 2051 try { 2052 c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED); 2053 } catch (NameNotFoundException e) { 2054 Log.e(LOG_TAG, "Package name " + packageName + " not found"); 2055 c = context; 2056 } 2057 } else { 2058 c = context; 2059 } 2060 2061 return c; 2062 } 2063 2064 /* (non-Javadoc) 2065 * Used to restrict the views which can be inflated 2066 * 2067 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 2068 */ 2069 public boolean onLoadClass(Class clazz) { 2070 return clazz.isAnnotationPresent(RemoteView.class); 2071 } 2072 2073 public int describeContents() { 2074 return 0; 2075 } 2076 2077 public void writeToParcel(Parcel dest, int flags) { 2078 if (!hasLandscapeAndPortraitLayouts()) { 2079 dest.writeInt(MODE_NORMAL); 2080 // We only write the bitmap cache if we are the root RemoteViews, as this cache 2081 // is shared by all children. 2082 if (mIsRoot) { 2083 mBitmapCache.writeBitmapsToParcel(dest, flags); 2084 } 2085 dest.writeString(mPackage); 2086 dest.writeInt(mLayoutId); 2087 dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); 2088 int count; 2089 if (mActions != null) { 2090 count = mActions.size(); 2091 } else { 2092 count = 0; 2093 } 2094 dest.writeInt(count); 2095 for (int i=0; i<count; i++) { 2096 Action a = mActions.get(i); 2097 a.writeToParcel(dest, 0); 2098 } 2099 } else { 2100 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 2101 // We only write the bitmap cache if we are the root RemoteViews, as this cache 2102 // is shared by all children. 2103 if (mIsRoot) { 2104 mBitmapCache.writeBitmapsToParcel(dest, flags); 2105 } 2106 mLandscape.writeToParcel(dest, flags); 2107 mPortrait.writeToParcel(dest, flags); 2108 } 2109 } 2110 2111 /** 2112 * Parcelable.Creator that instantiates RemoteViews objects 2113 */ 2114 public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 2115 public RemoteViews createFromParcel(Parcel parcel) { 2116 return new RemoteViews(parcel); 2117 } 2118 2119 public RemoteViews[] newArray(int size) { 2120 return new RemoteViews[size]; 2121 } 2122 }; 2123} 2124