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