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 17 package android.widget; 18 19 import android.app.ActivityOptions; 20 import android.app.PendingIntent; 21 import android.appwidget.AppWidgetHostView; 22 import android.content.Context; 23 import android.content.Intent; 24 import android.content.IntentSender; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.PackageManager; 27 import android.content.pm.PackageManager.NameNotFoundException; 28 import android.content.res.Configuration; 29 import android.graphics.Bitmap; 30 import android.graphics.PorterDuff; 31 import android.graphics.Rect; 32 import android.graphics.drawable.Drawable; 33 import android.net.Uri; 34 import android.os.Build; 35 import android.os.Bundle; 36 import android.os.Parcel; 37 import android.os.Parcelable; 38 import android.text.TextUtils; 39 import android.util.Log; 40 import android.util.TypedValue; 41 import android.view.LayoutInflater; 42 import android.view.LayoutInflater.Filter; 43 import android.view.RemotableViewMethod; 44 import android.view.View; 45 import android.view.View.OnClickListener; 46 import android.view.ViewGroup; 47 import android.widget.AdapterView.OnItemClickListener; 48 49 import java.lang.annotation.ElementType; 50 import java.lang.annotation.Retention; 51 import java.lang.annotation.RetentionPolicy; 52 import java.lang.annotation.Target; 53 import java.lang.reflect.Method; 54 import 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 */ 63 public 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 private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); 131 132 /** 133 * This annotation indicates that a subclass of View is alllowed to be used 134 * with the {@link RemoteViews} mechanism. 135 */ 136 @Target({ ElementType.TYPE }) 137 @Retention(RetentionPolicy.RUNTIME) 138 public @interface RemoteView { 139 } 140 141 /** 142 * Exception to send when something goes wrong executing an action 143 * 144 */ 145 public static class ActionException extends RuntimeException { 146 public ActionException(Exception ex) { 147 super(ex); 148 } 149 public ActionException(String message) { 150 super(message); 151 } 152 } 153 154 /** @hide */ 155 public static class OnClickHandler { 156 public boolean onClickHandler(View view, PendingIntent pendingIntent, 157 Intent fillInIntent) { 158 try { 159 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 160 Context context = view.getContext(); 161 ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view, 162 0, 0, 163 view.getMeasuredWidth(), view.getMeasuredHeight()); 164 context.startIntentSender( 165 pendingIntent.getIntentSender(), fillInIntent, 166 Intent.FLAG_ACTIVITY_NEW_TASK, 167 Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); 168 } catch (IntentSender.SendIntentException e) { 169 android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e); 170 return false; 171 } catch (Exception e) { 172 android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " + 173 "unknown exception: ", e); 174 return false; 175 } 176 return true; 177 } 178 } 179 180 /** 181 * Base class for all actions that can be performed on an 182 * inflated view. 183 * 184 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 185 */ 186 private abstract static class Action implements Parcelable { 187 public abstract void apply(View root, ViewGroup rootParent, 188 OnClickHandler handler) throws ActionException; 189 190 public int describeContents() { 191 return 0; 192 } 193 194 /** 195 * Overridden by each class to report on it's own memory usage 196 */ 197 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 198 // We currently only calculate Bitmap memory usage, so by default, don't do anything 199 // here 200 return; 201 } 202 203 public void setBitmapCache(BitmapCache bitmapCache) { 204 // Do nothing 205 } 206 } 207 208 private class SetEmptyView extends Action { 209 int viewId; 210 int emptyViewId; 211 212 public final static int TAG = 6; 213 214 SetEmptyView(int viewId, int emptyViewId) { 215 this.viewId = viewId; 216 this.emptyViewId = emptyViewId; 217 } 218 219 SetEmptyView(Parcel in) { 220 this.viewId = in.readInt(); 221 this.emptyViewId = in.readInt(); 222 } 223 224 public void writeToParcel(Parcel out, int flags) { 225 out.writeInt(TAG); 226 out.writeInt(this.viewId); 227 out.writeInt(this.emptyViewId); 228 } 229 230 @Override 231 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 232 final View view = root.findViewById(viewId); 233 if (!(view instanceof AdapterView<?>)) return; 234 235 AdapterView<?> adapterView = (AdapterView<?>) view; 236 237 final View emptyView = root.findViewById(emptyViewId); 238 if (emptyView == null) return; 239 240 adapterView.setEmptyView(emptyView); 241 } 242 } 243 244 private class SetOnClickFillInIntent extends Action { 245 public SetOnClickFillInIntent(int id, Intent fillInIntent) { 246 this.viewId = id; 247 this.fillInIntent = fillInIntent; 248 } 249 250 public SetOnClickFillInIntent(Parcel parcel) { 251 viewId = parcel.readInt(); 252 fillInIntent = Intent.CREATOR.createFromParcel(parcel); 253 } 254 255 public void writeToParcel(Parcel dest, int flags) { 256 dest.writeInt(TAG); 257 dest.writeInt(viewId); 258 fillInIntent.writeToParcel(dest, 0 /* no flags */); 259 } 260 261 @Override 262 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 263 final View target = root.findViewById(viewId); 264 if (target == null) return; 265 266 if (!mIsWidgetCollectionChild) { 267 Log.e("RemoteViews", "The method setOnClickFillInIntent is available " + 268 "only from RemoteViewsFactory (ie. on collection items)."); 269 return; 270 } 271 if (target == root) { 272 target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent); 273 } else if (target != null && fillInIntent != null) { 274 OnClickListener listener = new OnClickListener() { 275 public void onClick(View v) { 276 // Insure that this view is a child of an AdapterView 277 View parent = (View) v.getParent(); 278 while (!(parent instanceof AdapterView<?>) 279 && !(parent instanceof AppWidgetHostView)) { 280 parent = (View) parent.getParent(); 281 } 282 283 if (parent instanceof AppWidgetHostView) { 284 // Somehow they've managed to get this far without having 285 // and AdapterView as a parent. 286 Log.e("RemoteViews", "Collection item doesn't have AdapterView parent"); 287 return; 288 } 289 290 // Insure that a template pending intent has been set on an ancestor 291 if (!(parent.getTag() instanceof PendingIntent)) { 292 Log.e("RemoteViews", "Attempting setOnClickFillInIntent without" + 293 " calling setPendingIntentTemplate on parent."); 294 return; 295 } 296 297 PendingIntent pendingIntent = (PendingIntent) parent.getTag(); 298 299 final float appScale = v.getContext().getResources() 300 .getCompatibilityInfo().applicationScale; 301 final int[] pos = new int[2]; 302 v.getLocationOnScreen(pos); 303 304 final Rect rect = new Rect(); 305 rect.left = (int) (pos[0] * appScale + 0.5f); 306 rect.top = (int) (pos[1] * appScale + 0.5f); 307 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 308 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 309 310 fillInIntent.setSourceBounds(rect); 311 handler.onClickHandler(v, pendingIntent, fillInIntent); 312 } 313 314 }; 315 target.setOnClickListener(listener); 316 } 317 } 318 319 int viewId; 320 Intent fillInIntent; 321 322 public final static int TAG = 9; 323 } 324 325 private class SetPendingIntentTemplate extends Action { 326 public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { 327 this.viewId = id; 328 this.pendingIntentTemplate = pendingIntentTemplate; 329 } 330 331 public SetPendingIntentTemplate(Parcel parcel) { 332 viewId = parcel.readInt(); 333 pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 334 } 335 336 public void writeToParcel(Parcel dest, int flags) { 337 dest.writeInt(TAG); 338 dest.writeInt(viewId); 339 pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */); 340 } 341 342 @Override 343 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 344 final View target = root.findViewById(viewId); 345 if (target == null) return; 346 347 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 348 if (target instanceof AdapterView<?>) { 349 AdapterView<?> av = (AdapterView<?>) target; 350 // The PendingIntent template is stored in the view's tag. 351 OnItemClickListener listener = new OnItemClickListener() { 352 public void onItemClick(AdapterView<?> parent, View view, 353 int position, long id) { 354 // The view should be a frame layout 355 if (view instanceof ViewGroup) { 356 ViewGroup vg = (ViewGroup) view; 357 358 // AdapterViews contain their children in a frame 359 // so we need to go one layer deeper here. 360 if (parent instanceof AdapterViewAnimator) { 361 vg = (ViewGroup) vg.getChildAt(0); 362 } 363 if (vg == null) return; 364 365 Intent fillInIntent = null; 366 int childCount = vg.getChildCount(); 367 for (int i = 0; i < childCount; i++) { 368 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent); 369 if (tag instanceof Intent) { 370 fillInIntent = (Intent) tag; 371 break; 372 } 373 } 374 if (fillInIntent == null) return; 375 376 final float appScale = view.getContext().getResources() 377 .getCompatibilityInfo().applicationScale; 378 final int[] pos = new int[2]; 379 view.getLocationOnScreen(pos); 380 381 final Rect rect = new Rect(); 382 rect.left = (int) (pos[0] * appScale + 0.5f); 383 rect.top = (int) (pos[1] * appScale + 0.5f); 384 rect.right = (int) ((pos[0] + view.getWidth()) * appScale + 0.5f); 385 rect.bottom = (int) ((pos[1] + view.getHeight()) * appScale + 0.5f); 386 387 final Intent intent = new Intent(); 388 intent.setSourceBounds(rect); 389 handler.onClickHandler(view, pendingIntentTemplate, fillInIntent); 390 } 391 } 392 }; 393 av.setOnItemClickListener(listener); 394 av.setTag(pendingIntentTemplate); 395 } else { 396 Log.e("RemoteViews", "Cannot setPendingIntentTemplate on a view which is not" + 397 "an AdapterView (id: " + viewId + ")"); 398 return; 399 } 400 } 401 402 int viewId; 403 PendingIntent pendingIntentTemplate; 404 405 public final static int TAG = 8; 406 } 407 408 private class SetRemoteViewsAdapterIntent extends Action { 409 public SetRemoteViewsAdapterIntent(int id, Intent intent) { 410 this.viewId = id; 411 this.intent = intent; 412 } 413 414 public SetRemoteViewsAdapterIntent(Parcel parcel) { 415 viewId = parcel.readInt(); 416 intent = Intent.CREATOR.createFromParcel(parcel); 417 } 418 419 public void writeToParcel(Parcel dest, int flags) { 420 dest.writeInt(TAG); 421 dest.writeInt(viewId); 422 intent.writeToParcel(dest, flags); 423 } 424 425 @Override 426 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 427 final View target = root.findViewById(viewId); 428 if (target == null) return; 429 430 // Ensure that we are applying to an AppWidget root 431 if (!(rootParent instanceof AppWidgetHostView)) { 432 Log.e("RemoteViews", "SetRemoteViewsAdapterIntent action can only be used for " + 433 "AppWidgets (root id: " + viewId + ")"); 434 return; 435 } 436 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 437 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 438 Log.e("RemoteViews", "Cannot setRemoteViewsAdapter on a view which is not " + 439 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 440 return; 441 } 442 443 // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent 444 // RemoteViewsService 445 AppWidgetHostView host = (AppWidgetHostView) rootParent; 446 intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()); 447 if (target instanceof AbsListView) { 448 AbsListView v = (AbsListView) target; 449 v.setRemoteViewsAdapter(intent); 450 } else if (target instanceof AdapterViewAnimator) { 451 AdapterViewAnimator v = (AdapterViewAnimator) target; 452 v.setRemoteViewsAdapter(intent); 453 } 454 } 455 456 int viewId; 457 Intent intent; 458 459 public final static int TAG = 10; 460 } 461 462 /** 463 * Equivalent to calling 464 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 465 * to launch the provided {@link PendingIntent}. 466 */ 467 private class SetOnClickPendingIntent extends Action { 468 public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { 469 this.viewId = id; 470 this.pendingIntent = pendingIntent; 471 } 472 473 public SetOnClickPendingIntent(Parcel parcel) { 474 viewId = parcel.readInt(); 475 476 // We check a flag to determine if the parcel contains a PendingIntent. 477 if (parcel.readInt() != 0) { 478 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 479 } 480 } 481 482 public void writeToParcel(Parcel dest, int flags) { 483 dest.writeInt(TAG); 484 dest.writeInt(viewId); 485 486 // We use a flag to indicate whether the parcel contains a valid object. 487 dest.writeInt(pendingIntent != null ? 1 : 0); 488 if (pendingIntent != null) { 489 pendingIntent.writeToParcel(dest, 0 /* no flags */); 490 } 491 } 492 493 @Override 494 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 495 final View target = root.findViewById(viewId); 496 if (target == null) return; 497 498 // If the view is an AdapterView, setting a PendingIntent on click doesn't make much 499 // sense, do they mean to set a PendingIntent template for the AdapterView's children? 500 if (mIsWidgetCollectionChild) { 501 Log.w("RemoteViews", "Cannot setOnClickPendingIntent for collection item " + 502 "(id: " + viewId + ")"); 503 ApplicationInfo appInfo = root.getContext().getApplicationInfo(); 504 505 // We let this slide for HC and ICS so as to not break compatibility. It should have 506 // been disabled from the outset, but was left open by accident. 507 if (appInfo != null && 508 appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { 509 return; 510 } 511 } 512 513 if (target != null) { 514 // If the pendingIntent is null, we clear the onClickListener 515 OnClickListener listener = null; 516 if (pendingIntent != null) { 517 listener = new OnClickListener() { 518 public void onClick(View v) { 519 // Find target view location in screen coordinates and 520 // fill into PendingIntent before sending. 521 final float appScale = v.getContext().getResources() 522 .getCompatibilityInfo().applicationScale; 523 final int[] pos = new int[2]; 524 v.getLocationOnScreen(pos); 525 526 final Rect rect = new Rect(); 527 rect.left = (int) (pos[0] * appScale + 0.5f); 528 rect.top = (int) (pos[1] * appScale + 0.5f); 529 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 530 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 531 532 final Intent intent = new Intent(); 533 intent.setSourceBounds(rect); 534 handler.onClickHandler(v, pendingIntent, intent); 535 } 536 }; 537 } 538 target.setOnClickListener(listener); 539 } 540 } 541 542 int viewId; 543 PendingIntent pendingIntent; 544 545 public final static int TAG = 1; 546 } 547 548 /** 549 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 550 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 551 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view. 552 * <p> 553 * These operations will be performed on the {@link Drawable} returned by the 554 * target {@link View#getBackground()} by default. If targetBackground is false, 555 * we assume the target is an {@link ImageView} and try applying the operations 556 * to {@link ImageView#getDrawable()}. 557 * <p> 558 * You can omit specific calls by marking their values with null or -1. 559 */ 560 private class SetDrawableParameters extends Action { 561 public SetDrawableParameters(int id, boolean targetBackground, int alpha, 562 int colorFilter, PorterDuff.Mode mode, int level) { 563 this.viewId = id; 564 this.targetBackground = targetBackground; 565 this.alpha = alpha; 566 this.colorFilter = colorFilter; 567 this.filterMode = mode; 568 this.level = level; 569 } 570 571 public SetDrawableParameters(Parcel parcel) { 572 viewId = parcel.readInt(); 573 targetBackground = parcel.readInt() != 0; 574 alpha = parcel.readInt(); 575 colorFilter = parcel.readInt(); 576 boolean hasMode = parcel.readInt() != 0; 577 if (hasMode) { 578 filterMode = PorterDuff.Mode.valueOf(parcel.readString()); 579 } else { 580 filterMode = null; 581 } 582 level = parcel.readInt(); 583 } 584 585 public void writeToParcel(Parcel dest, int flags) { 586 dest.writeInt(TAG); 587 dest.writeInt(viewId); 588 dest.writeInt(targetBackground ? 1 : 0); 589 dest.writeInt(alpha); 590 dest.writeInt(colorFilter); 591 if (filterMode != null) { 592 dest.writeInt(1); 593 dest.writeString(filterMode.toString()); 594 } else { 595 dest.writeInt(0); 596 } 597 dest.writeInt(level); 598 } 599 600 @Override 601 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 602 final View target = root.findViewById(viewId); 603 if (target == null) return; 604 605 // Pick the correct drawable to modify for this view 606 Drawable targetDrawable = null; 607 if (targetBackground) { 608 targetDrawable = target.getBackground(); 609 } else if (target instanceof ImageView) { 610 ImageView imageView = (ImageView) target; 611 targetDrawable = imageView.getDrawable(); 612 } 613 614 if (targetDrawable != null) { 615 // Perform modifications only if values are set correctly 616 if (alpha != -1) { 617 targetDrawable.setAlpha(alpha); 618 } 619 if (colorFilter != -1 && filterMode != null) { 620 targetDrawable.setColorFilter(colorFilter, filterMode); 621 } 622 if (level != -1) { 623 targetDrawable.setLevel(level); 624 } 625 } 626 } 627 628 int viewId; 629 boolean targetBackground; 630 int alpha; 631 int colorFilter; 632 PorterDuff.Mode filterMode; 633 int level; 634 635 public final static int TAG = 3; 636 } 637 638 private class ReflectionActionWithoutParams extends Action { 639 int viewId; 640 String methodName; 641 642 public final static int TAG = 5; 643 644 ReflectionActionWithoutParams(int viewId, String methodName) { 645 this.viewId = viewId; 646 this.methodName = methodName; 647 } 648 649 ReflectionActionWithoutParams(Parcel in) { 650 this.viewId = in.readInt(); 651 this.methodName = in.readString(); 652 } 653 654 public void writeToParcel(Parcel out, int flags) { 655 out.writeInt(TAG); 656 out.writeInt(this.viewId); 657 out.writeString(this.methodName); 658 } 659 660 @Override 661 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 662 final View view = root.findViewById(viewId); 663 if (view == null) return; 664 665 Class klass = view.getClass(); 666 Method method; 667 try { 668 method = klass.getMethod(this.methodName); 669 } catch (NoSuchMethodException ex) { 670 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 671 + this.methodName + "()"); 672 } 673 674 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 675 throw new ActionException("view: " + klass.getName() 676 + " can't use method with RemoteViews: " 677 + this.methodName + "()"); 678 } 679 680 try { 681 //noinspection ConstantIfStatement 682 if (false) { 683 Log.d("RemoteViews", "view: " + klass.getName() + " calling method: " 684 + this.methodName + "()"); 685 } 686 method.invoke(view); 687 } catch (Exception ex) { 688 throw new ActionException(ex); 689 } 690 } 691 } 692 693 private static class BitmapCache { 694 ArrayList<Bitmap> mBitmaps; 695 696 public BitmapCache() { 697 mBitmaps = new ArrayList<Bitmap>(); 698 } 699 700 public BitmapCache(Parcel source) { 701 int count = source.readInt(); 702 mBitmaps = new ArrayList<Bitmap>(); 703 for (int i = 0; i < count; i++) { 704 Bitmap b = Bitmap.CREATOR.createFromParcel(source); 705 mBitmaps.add(b); 706 } 707 } 708 709 public int getBitmapId(Bitmap b) { 710 if (b == null) { 711 return -1; 712 } else { 713 if (mBitmaps.contains(b)) { 714 return mBitmaps.indexOf(b); 715 } else { 716 mBitmaps.add(b); 717 return (mBitmaps.size() - 1); 718 } 719 } 720 } 721 722 public Bitmap getBitmapForId(int id) { 723 if (id == -1 || id >= mBitmaps.size()) { 724 return null; 725 } else { 726 return mBitmaps.get(id); 727 } 728 } 729 730 public void writeBitmapsToParcel(Parcel dest, int flags) { 731 int count = mBitmaps.size(); 732 dest.writeInt(count); 733 for (int i = 0; i < count; i++) { 734 mBitmaps.get(i).writeToParcel(dest, flags); 735 } 736 } 737 738 public void assimilate(BitmapCache bitmapCache) { 739 ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps; 740 int count = bitmapsToBeAdded.size(); 741 for (int i = 0; i < count; i++) { 742 Bitmap b = bitmapsToBeAdded.get(i); 743 if (!mBitmaps.contains(b)) { 744 mBitmaps.add(b); 745 } 746 } 747 } 748 749 public void addBitmapMemory(MemoryUsageCounter memoryCounter) { 750 for (int i = 0; i < mBitmaps.size(); i++) { 751 memoryCounter.addBitmapMemory(mBitmaps.get(i)); 752 } 753 } 754 } 755 756 private class BitmapReflectionAction extends Action { 757 int bitmapId; 758 int viewId; 759 Bitmap bitmap; 760 String methodName; 761 762 BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { 763 this.bitmap = bitmap; 764 this.viewId = viewId; 765 this.methodName = methodName; 766 bitmapId = mBitmapCache.getBitmapId(bitmap); 767 } 768 769 BitmapReflectionAction(Parcel in) { 770 viewId = in.readInt(); 771 methodName = in.readString(); 772 bitmapId = in.readInt(); 773 bitmap = mBitmapCache.getBitmapForId(bitmapId); 774 } 775 776 @Override 777 public void writeToParcel(Parcel dest, int flags) { 778 dest.writeInt(TAG); 779 dest.writeInt(viewId); 780 dest.writeString(methodName); 781 dest.writeInt(bitmapId); 782 } 783 784 @Override 785 public void apply(View root, ViewGroup rootParent, 786 OnClickHandler handler) throws ActionException { 787 ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, 788 bitmap); 789 ra.apply(root, rootParent, handler); 790 } 791 792 @Override 793 public void setBitmapCache(BitmapCache bitmapCache) { 794 bitmapId = bitmapCache.getBitmapId(bitmap); 795 } 796 797 public final static int TAG = 12; 798 } 799 800 /** 801 * Base class for the reflection actions. 802 */ 803 private class ReflectionAction extends Action { 804 static final int TAG = 2; 805 806 static final int BOOLEAN = 1; 807 static final int BYTE = 2; 808 static final int SHORT = 3; 809 static final int INT = 4; 810 static final int LONG = 5; 811 static final int FLOAT = 6; 812 static final int DOUBLE = 7; 813 static final int CHAR = 8; 814 static final int STRING = 9; 815 static final int CHAR_SEQUENCE = 10; 816 static final int URI = 11; 817 static final int BITMAP = 12; 818 static final int BUNDLE = 13; 819 static final int INTENT = 14; 820 821 int viewId; 822 String methodName; 823 int type; 824 Object value; 825 826 ReflectionAction(int viewId, String methodName, int type, Object value) { 827 this.viewId = viewId; 828 this.methodName = methodName; 829 this.type = type; 830 this.value = value; 831 } 832 833 ReflectionAction(Parcel in) { 834 this.viewId = in.readInt(); 835 this.methodName = in.readString(); 836 this.type = in.readInt(); 837 //noinspection ConstantIfStatement 838 if (false) { 839 Log.d("RemoteViews", "read viewId=0x" + Integer.toHexString(this.viewId) 840 + " methodName=" + this.methodName + " type=" + this.type); 841 } 842 843 // For some values that may have been null, we first check a flag to see if they were 844 // written to the parcel. 845 switch (this.type) { 846 case BOOLEAN: 847 this.value = in.readInt() != 0; 848 break; 849 case BYTE: 850 this.value = in.readByte(); 851 break; 852 case SHORT: 853 this.value = (short)in.readInt(); 854 break; 855 case INT: 856 this.value = in.readInt(); 857 break; 858 case LONG: 859 this.value = in.readLong(); 860 break; 861 case FLOAT: 862 this.value = in.readFloat(); 863 break; 864 case DOUBLE: 865 this.value = in.readDouble(); 866 break; 867 case CHAR: 868 this.value = (char)in.readInt(); 869 break; 870 case STRING: 871 this.value = in.readString(); 872 break; 873 case CHAR_SEQUENCE: 874 this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 875 break; 876 case URI: 877 if (in.readInt() != 0) { 878 this.value = Uri.CREATOR.createFromParcel(in); 879 } 880 break; 881 case BITMAP: 882 if (in.readInt() != 0) { 883 this.value = Bitmap.CREATOR.createFromParcel(in); 884 } 885 break; 886 case BUNDLE: 887 this.value = in.readBundle(); 888 break; 889 case INTENT: 890 if (in.readInt() != 0) { 891 this.value = Intent.CREATOR.createFromParcel(in); 892 } 893 break; 894 default: 895 break; 896 } 897 } 898 899 public void writeToParcel(Parcel out, int flags) { 900 out.writeInt(TAG); 901 out.writeInt(this.viewId); 902 out.writeString(this.methodName); 903 out.writeInt(this.type); 904 //noinspection ConstantIfStatement 905 if (false) { 906 Log.d("RemoteViews", "write viewId=0x" + Integer.toHexString(this.viewId) 907 + " methodName=" + this.methodName + " type=" + this.type); 908 } 909 910 // For some values which are null, we record an integer flag to indicate whether 911 // we have written a valid value to the parcel. 912 switch (this.type) { 913 case BOOLEAN: 914 out.writeInt((Boolean) this.value ? 1 : 0); 915 break; 916 case BYTE: 917 out.writeByte((Byte) this.value); 918 break; 919 case SHORT: 920 out.writeInt((Short) this.value); 921 break; 922 case INT: 923 out.writeInt((Integer) this.value); 924 break; 925 case LONG: 926 out.writeLong((Long) this.value); 927 break; 928 case FLOAT: 929 out.writeFloat((Float) this.value); 930 break; 931 case DOUBLE: 932 out.writeDouble((Double) this.value); 933 break; 934 case CHAR: 935 out.writeInt((int)((Character)this.value).charValue()); 936 break; 937 case STRING: 938 out.writeString((String)this.value); 939 break; 940 case CHAR_SEQUENCE: 941 TextUtils.writeToParcel((CharSequence)this.value, out, flags); 942 break; 943 case URI: 944 out.writeInt(this.value != null ? 1 : 0); 945 if (this.value != null) { 946 ((Uri)this.value).writeToParcel(out, flags); 947 } 948 break; 949 case BITMAP: 950 out.writeInt(this.value != null ? 1 : 0); 951 if (this.value != null) { 952 ((Bitmap)this.value).writeToParcel(out, flags); 953 } 954 break; 955 case BUNDLE: 956 out.writeBundle((Bundle) this.value); 957 break; 958 case INTENT: 959 out.writeInt(this.value != null ? 1 : 0); 960 if (this.value != null) { 961 ((Intent)this.value).writeToParcel(out, flags); 962 } 963 break; 964 default: 965 break; 966 } 967 } 968 969 private Class getParameterType() { 970 switch (this.type) { 971 case BOOLEAN: 972 return boolean.class; 973 case BYTE: 974 return byte.class; 975 case SHORT: 976 return short.class; 977 case INT: 978 return int.class; 979 case LONG: 980 return long.class; 981 case FLOAT: 982 return float.class; 983 case DOUBLE: 984 return double.class; 985 case CHAR: 986 return char.class; 987 case STRING: 988 return String.class; 989 case CHAR_SEQUENCE: 990 return CharSequence.class; 991 case URI: 992 return Uri.class; 993 case BITMAP: 994 return Bitmap.class; 995 case BUNDLE: 996 return Bundle.class; 997 case INTENT: 998 return Intent.class; 999 default: 1000 return null; 1001 } 1002 } 1003 1004 @Override 1005 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1006 final View view = root.findViewById(viewId); 1007 if (view == null) return; 1008 1009 Class param = getParameterType(); 1010 if (param == null) { 1011 throw new ActionException("bad type: " + this.type); 1012 } 1013 1014 Class klass = view.getClass(); 1015 Method method; 1016 try { 1017 method = klass.getMethod(this.methodName, getParameterType()); 1018 } 1019 catch (NoSuchMethodException ex) { 1020 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 1021 + this.methodName + "(" + param.getName() + ")"); 1022 } 1023 1024 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 1025 throw new ActionException("view: " + klass.getName() 1026 + " can't use method with RemoteViews: " 1027 + this.methodName + "(" + param.getName() + ")"); 1028 } 1029 1030 try { 1031 //noinspection ConstantIfStatement 1032 if (false) { 1033 Log.d("RemoteViews", "view: " + klass.getName() + " calling method: " 1034 + this.methodName + "(" + param.getName() + ") with " 1035 + (this.value == null ? "null" : this.value.getClass().getName())); 1036 } 1037 method.invoke(view, this.value); 1038 } 1039 catch (Exception ex) { 1040 throw new ActionException(ex); 1041 } 1042 } 1043 1044 @Override 1045 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 1046 // We currently only calculate Bitmap memory usage 1047 switch (this.type) { 1048 case BITMAP: 1049 if (this.value != null) { 1050 final Bitmap b = (Bitmap) this.value; 1051 counter.addBitmapMemory(b); 1052 } 1053 break; 1054 default: 1055 break; 1056 } 1057 } 1058 } 1059 1060 private void configureRemoteViewsAsChild(RemoteViews rv) { 1061 mBitmapCache.assimilate(rv.mBitmapCache); 1062 rv.setBitmapCache(mBitmapCache); 1063 rv.setNotRoot(); 1064 } 1065 1066 void setNotRoot() { 1067 mIsRoot = false; 1068 } 1069 1070 /** 1071 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1072 * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()} 1073 * when null. This allows users to build "nested" {@link RemoteViews}. 1074 */ 1075 private class ViewGroupAction extends Action { 1076 public ViewGroupAction(int viewId, RemoteViews nestedViews) { 1077 this.viewId = viewId; 1078 this.nestedViews = nestedViews; 1079 if (nestedViews != null) { 1080 configureRemoteViewsAsChild(nestedViews); 1081 } 1082 } 1083 1084 public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) { 1085 viewId = parcel.readInt(); 1086 boolean nestedViewsNull = parcel.readInt() == 0; 1087 if (!nestedViewsNull) { 1088 nestedViews = new RemoteViews(parcel, bitmapCache); 1089 } else { 1090 nestedViews = null; 1091 } 1092 } 1093 1094 public void writeToParcel(Parcel dest, int flags) { 1095 dest.writeInt(TAG); 1096 dest.writeInt(viewId); 1097 if (nestedViews != null) { 1098 dest.writeInt(1); 1099 nestedViews.writeToParcel(dest, flags); 1100 } else { 1101 // signifies null 1102 dest.writeInt(0); 1103 } 1104 } 1105 1106 @Override 1107 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1108 final Context context = root.getContext(); 1109 final ViewGroup target = (ViewGroup) root.findViewById(viewId); 1110 if (target == null) return; 1111 if (nestedViews != null) { 1112 // Inflate nested views and add as children 1113 target.addView(nestedViews.apply(context, target, handler)); 1114 } else { 1115 // Clear all children when nested views omitted 1116 target.removeAllViews(); 1117 } 1118 } 1119 1120 @Override 1121 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 1122 if (nestedViews != null) { 1123 counter.increment(nestedViews.estimateMemoryUsage()); 1124 } 1125 } 1126 1127 @Override 1128 public void setBitmapCache(BitmapCache bitmapCache) { 1129 if (nestedViews != null) { 1130 nestedViews.setBitmapCache(bitmapCache); 1131 } 1132 } 1133 1134 int viewId; 1135 RemoteViews nestedViews; 1136 1137 public final static int TAG = 4; 1138 } 1139 1140 /** 1141 * Helper action to set compound drawables on a TextView. Supports relative 1142 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 1143 */ 1144 private class TextViewDrawableAction extends Action { 1145 public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) { 1146 this.viewId = viewId; 1147 this.isRelative = isRelative; 1148 this.d1 = d1; 1149 this.d2 = d2; 1150 this.d3 = d3; 1151 this.d4 = d4; 1152 } 1153 1154 public TextViewDrawableAction(Parcel parcel) { 1155 viewId = parcel.readInt(); 1156 isRelative = (parcel.readInt() != 0); 1157 d1 = parcel.readInt(); 1158 d2 = parcel.readInt(); 1159 d3 = parcel.readInt(); 1160 d4 = parcel.readInt(); 1161 } 1162 1163 public void writeToParcel(Parcel dest, int flags) { 1164 dest.writeInt(TAG); 1165 dest.writeInt(viewId); 1166 dest.writeInt(isRelative ? 1 : 0); 1167 dest.writeInt(d1); 1168 dest.writeInt(d2); 1169 dest.writeInt(d3); 1170 dest.writeInt(d4); 1171 } 1172 1173 @Override 1174 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1175 final Context context = root.getContext(); 1176 final TextView target = (TextView) root.findViewById(viewId); 1177 if (target == null) return; 1178 if (isRelative) { 1179 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4); 1180 } else { 1181 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4); 1182 } 1183 } 1184 1185 int viewId; 1186 boolean isRelative = false; 1187 int d1, d2, d3, d4; 1188 1189 public final static int TAG = 11; 1190 } 1191 1192 /** 1193 * Helper action to set text size on a TextView in any supported units. 1194 */ 1195 private class TextViewSizeAction extends Action { 1196 public TextViewSizeAction(int viewId, int units, float size) { 1197 this.viewId = viewId; 1198 this.units = units; 1199 this.size = size; 1200 } 1201 1202 public TextViewSizeAction(Parcel parcel) { 1203 viewId = parcel.readInt(); 1204 units = parcel.readInt(); 1205 size = parcel.readFloat(); 1206 } 1207 1208 public void writeToParcel(Parcel dest, int flags) { 1209 dest.writeInt(TAG); 1210 dest.writeInt(viewId); 1211 dest.writeInt(units); 1212 dest.writeFloat(size); 1213 } 1214 1215 @Override 1216 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1217 final Context context = root.getContext(); 1218 final TextView target = (TextView) root.findViewById(viewId); 1219 if (target == null) return; 1220 target.setTextSize(units, size); 1221 } 1222 1223 int viewId; 1224 int units; 1225 float size; 1226 1227 public final static int TAG = 13; 1228 } 1229 1230 /** 1231 * Helper action to set padding on a View. 1232 */ 1233 private class ViewPaddingAction extends Action { 1234 public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) { 1235 this.viewId = viewId; 1236 this.left = left; 1237 this.top = top; 1238 this.right = right; 1239 this.bottom = bottom; 1240 } 1241 1242 public ViewPaddingAction(Parcel parcel) { 1243 viewId = parcel.readInt(); 1244 left = parcel.readInt(); 1245 top = parcel.readInt(); 1246 right = parcel.readInt(); 1247 bottom = parcel.readInt(); 1248 } 1249 1250 public void writeToParcel(Parcel dest, int flags) { 1251 dest.writeInt(TAG); 1252 dest.writeInt(viewId); 1253 dest.writeInt(left); 1254 dest.writeInt(top); 1255 dest.writeInt(right); 1256 dest.writeInt(bottom); 1257 } 1258 1259 @Override 1260 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1261 final Context context = root.getContext(); 1262 final View target = root.findViewById(viewId); 1263 if (target == null) return; 1264 target.setPadding(left, top, right, bottom); 1265 } 1266 1267 int viewId; 1268 int left, top, right, bottom; 1269 1270 public final static int TAG = 14; 1271 } 1272 1273 /** 1274 * Simple class used to keep track of memory usage in a RemoteViews. 1275 * 1276 */ 1277 private class MemoryUsageCounter { 1278 public void clear() { 1279 mMemoryUsage = 0; 1280 } 1281 1282 public void increment(int numBytes) { 1283 mMemoryUsage += numBytes; 1284 } 1285 1286 public int getMemoryUsage() { 1287 return mMemoryUsage; 1288 } 1289 1290 public void addBitmapMemory(Bitmap b) { 1291 final Bitmap.Config c = b.getConfig(); 1292 // If we don't know, be pessimistic and assume 4 1293 int bpp = 4; 1294 if (c != null) { 1295 switch (c) { 1296 case ALPHA_8: 1297 bpp = 1; 1298 break; 1299 case RGB_565: 1300 case ARGB_4444: 1301 bpp = 2; 1302 break; 1303 case ARGB_8888: 1304 bpp = 4; 1305 break; 1306 } 1307 } 1308 increment(b.getWidth() * b.getHeight() * bpp); 1309 } 1310 1311 int mMemoryUsage; 1312 } 1313 1314 /** 1315 * Create a new RemoteViews object that will display the views contained 1316 * in the specified layout file. 1317 * 1318 * @param packageName Name of the package that contains the layout resource 1319 * @param layoutId The id of the layout resource 1320 */ 1321 public RemoteViews(String packageName, int layoutId) { 1322 mPackage = packageName; 1323 mLayoutId = layoutId; 1324 mBitmapCache = new BitmapCache(); 1325 1326 // setup the memory usage statistics 1327 mMemoryUsageCounter = new MemoryUsageCounter(); 1328 recalculateMemoryUsage(); 1329 } 1330 1331 private boolean hasLandscapeAndPortraitLayouts() { 1332 return (mLandscape != null) && (mPortrait != null); 1333 } 1334 1335 /** 1336 * Create a new RemoteViews object that will inflate as the specified 1337 * landspace or portrait RemoteViews, depending on the current configuration. 1338 * 1339 * @param landscape The RemoteViews to inflate in landscape configuration 1340 * @param portrait The RemoteViews to inflate in portrait configuration 1341 */ 1342 public RemoteViews(RemoteViews landscape, RemoteViews portrait) { 1343 if (landscape == null || portrait == null) { 1344 throw new RuntimeException("Both RemoteViews must be non-null"); 1345 } 1346 if (landscape.getPackage().compareTo(portrait.getPackage()) != 0) { 1347 throw new RuntimeException("Both RemoteViews must share the same package"); 1348 } 1349 mPackage = portrait.getPackage(); 1350 mLayoutId = portrait.getLayoutId(); 1351 1352 mLandscape = landscape; 1353 mPortrait = portrait; 1354 1355 // setup the memory usage statistics 1356 mMemoryUsageCounter = new MemoryUsageCounter(); 1357 1358 mBitmapCache = new BitmapCache(); 1359 configureRemoteViewsAsChild(landscape); 1360 configureRemoteViewsAsChild(portrait); 1361 1362 recalculateMemoryUsage(); 1363 } 1364 1365 /** 1366 * Reads a RemoteViews object from a parcel. 1367 * 1368 * @param parcel 1369 */ 1370 public RemoteViews(Parcel parcel) { 1371 this(parcel, null); 1372 } 1373 1374 private RemoteViews(Parcel parcel, BitmapCache bitmapCache) { 1375 int mode = parcel.readInt(); 1376 1377 // We only store a bitmap cache in the root of the RemoteViews. 1378 if (bitmapCache == null) { 1379 mBitmapCache = new BitmapCache(parcel); 1380 } else { 1381 setBitmapCache(bitmapCache); 1382 setNotRoot(); 1383 } 1384 1385 if (mode == MODE_NORMAL) { 1386 mPackage = parcel.readString(); 1387 mLayoutId = parcel.readInt(); 1388 mIsWidgetCollectionChild = parcel.readInt() == 1 ? true : false; 1389 1390 int count = parcel.readInt(); 1391 if (count > 0) { 1392 mActions = new ArrayList<Action>(count); 1393 for (int i=0; i<count; i++) { 1394 int tag = parcel.readInt(); 1395 switch (tag) { 1396 case SetOnClickPendingIntent.TAG: 1397 mActions.add(new SetOnClickPendingIntent(parcel)); 1398 break; 1399 case SetDrawableParameters.TAG: 1400 mActions.add(new SetDrawableParameters(parcel)); 1401 break; 1402 case ReflectionAction.TAG: 1403 mActions.add(new ReflectionAction(parcel)); 1404 break; 1405 case ViewGroupAction.TAG: 1406 mActions.add(new ViewGroupAction(parcel, mBitmapCache)); 1407 break; 1408 case ReflectionActionWithoutParams.TAG: 1409 mActions.add(new ReflectionActionWithoutParams(parcel)); 1410 break; 1411 case SetEmptyView.TAG: 1412 mActions.add(new SetEmptyView(parcel)); 1413 break; 1414 case SetPendingIntentTemplate.TAG: 1415 mActions.add(new SetPendingIntentTemplate(parcel)); 1416 break; 1417 case SetOnClickFillInIntent.TAG: 1418 mActions.add(new SetOnClickFillInIntent(parcel)); 1419 break; 1420 case SetRemoteViewsAdapterIntent.TAG: 1421 mActions.add(new SetRemoteViewsAdapterIntent(parcel)); 1422 break; 1423 case TextViewDrawableAction.TAG: 1424 mActions.add(new TextViewDrawableAction(parcel)); 1425 break; 1426 case TextViewSizeAction.TAG: 1427 mActions.add(new TextViewSizeAction(parcel)); 1428 break; 1429 case ViewPaddingAction.TAG: 1430 mActions.add(new ViewPaddingAction(parcel)); 1431 break; 1432 case BitmapReflectionAction.TAG: 1433 mActions.add(new BitmapReflectionAction(parcel)); 1434 break; 1435 default: 1436 throw new ActionException("Tag " + tag + " not found"); 1437 } 1438 } 1439 } 1440 } else { 1441 // MODE_HAS_LANDSCAPE_AND_PORTRAIT 1442 mLandscape = new RemoteViews(parcel, mBitmapCache); 1443 mPortrait = new RemoteViews(parcel, mBitmapCache); 1444 mPackage = mPortrait.getPackage(); 1445 mLayoutId = mPortrait.getLayoutId(); 1446 } 1447 1448 // setup the memory usage statistics 1449 mMemoryUsageCounter = new MemoryUsageCounter(); 1450 recalculateMemoryUsage(); 1451 } 1452 1453 @Override 1454 public RemoteViews clone() { 1455 RemoteViews that; 1456 if (!hasLandscapeAndPortraitLayouts()) { 1457 that = new RemoteViews(mPackage, mLayoutId); 1458 1459 if (mActions != null) { 1460 that.mActions = (ArrayList<Action>)mActions.clone(); 1461 } 1462 } else { 1463 RemoteViews land = mLandscape.clone(); 1464 RemoteViews port = mPortrait.clone(); 1465 that = new RemoteViews(land, port); 1466 } 1467 // update the memory usage stats of the cloned RemoteViews 1468 that.recalculateMemoryUsage(); 1469 return that; 1470 } 1471 1472 public String getPackage() { 1473 return mPackage; 1474 } 1475 1476 /** 1477 * Reutrns the layout id of the root layout associated with this RemoteViews. In the case 1478 * that the RemoteViews has both a landscape and portrait root, this will return the layout 1479 * id associated with the portrait layout. 1480 * 1481 * @return the layout id. 1482 */ 1483 public int getLayoutId() { 1484 return mLayoutId; 1485 } 1486 1487 /* 1488 * This flag indicates whether this RemoteViews object is being created from a 1489 * RemoteViewsService for use as a child of a widget collection. This flag is used 1490 * to determine whether or not certain features are available, in particular, 1491 * setting on click extras and setting on click pending intents. The former is enabled, 1492 * and the latter disabled when this flag is true. 1493 */ 1494 void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) { 1495 mIsWidgetCollectionChild = isWidgetCollectionChild; 1496 } 1497 1498 /** 1499 * Updates the memory usage statistics. 1500 */ 1501 private void recalculateMemoryUsage() { 1502 mMemoryUsageCounter.clear(); 1503 1504 if (!hasLandscapeAndPortraitLayouts()) { 1505 // Accumulate the memory usage for each action 1506 if (mActions != null) { 1507 final int count = mActions.size(); 1508 for (int i= 0; i < count; ++i) { 1509 mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter); 1510 } 1511 } 1512 if (mIsRoot) { 1513 mBitmapCache.addBitmapMemory(mMemoryUsageCounter); 1514 } 1515 } else { 1516 mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage()); 1517 mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage()); 1518 mBitmapCache.addBitmapMemory(mMemoryUsageCounter); 1519 } 1520 } 1521 1522 /** 1523 * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. 1524 */ 1525 private void setBitmapCache(BitmapCache bitmapCache) { 1526 mBitmapCache = bitmapCache; 1527 if (!hasLandscapeAndPortraitLayouts()) { 1528 if (mActions != null) { 1529 final int count = mActions.size(); 1530 for (int i= 0; i < count; ++i) { 1531 mActions.get(i).setBitmapCache(bitmapCache); 1532 } 1533 } 1534 } else { 1535 mLandscape.setBitmapCache(bitmapCache); 1536 mPortrait.setBitmapCache(bitmapCache); 1537 } 1538 } 1539 1540 /** 1541 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 1542 */ 1543 /** @hide */ 1544 public int estimateMemoryUsage() { 1545 return mMemoryUsageCounter.getMemoryUsage(); 1546 } 1547 1548 /** 1549 * Add an action to be executed on the remote side when apply is called. 1550 * 1551 * @param a The action to add 1552 */ 1553 private void addAction(Action a) { 1554 if (hasLandscapeAndPortraitLayouts()) { 1555 throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + 1556 " layouts cannot be modified. Instead, fully configure the landscape and" + 1557 " portrait layouts individually before constructing the combined layout."); 1558 } 1559 if (mActions == null) { 1560 mActions = new ArrayList<Action>(); 1561 } 1562 mActions.add(a); 1563 1564 // update the memory usage stats 1565 a.updateMemoryUsageEstimate(mMemoryUsageCounter); 1566 } 1567 1568 /** 1569 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1570 * given {@link RemoteViews}. This allows users to build "nested" 1571 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 1572 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 1573 * children. 1574 * 1575 * @param viewId The id of the parent {@link ViewGroup} to add child into. 1576 * @param nestedView {@link RemoteViews} that describes the child. 1577 */ 1578 public void addView(int viewId, RemoteViews nestedView) { 1579 addAction(new ViewGroupAction(viewId, nestedView)); 1580 } 1581 1582 /** 1583 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 1584 * 1585 * @param viewId The id of the parent {@link ViewGroup} to remove all 1586 * children from. 1587 */ 1588 public void removeAllViews(int viewId) { 1589 addAction(new ViewGroupAction(viewId, null)); 1590 } 1591 1592 /** 1593 * Equivalent to calling {@link AdapterViewAnimator#showNext()} 1594 * 1595 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} 1596 */ 1597 public void showNext(int viewId) { 1598 addAction(new ReflectionActionWithoutParams(viewId, "showNext")); 1599 } 1600 1601 /** 1602 * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} 1603 * 1604 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} 1605 */ 1606 public void showPrevious(int viewId) { 1607 addAction(new ReflectionActionWithoutParams(viewId, "showPrevious")); 1608 } 1609 1610 /** 1611 * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} 1612 * 1613 * @param viewId The id of the view on which to call 1614 * {@link AdapterViewAnimator#setDisplayedChild(int)} 1615 */ 1616 public void setDisplayedChild(int viewId, int childIndex) { 1617 setInt(viewId, "setDisplayedChild", childIndex); 1618 } 1619 1620 /** 1621 * Equivalent to calling View.setVisibility 1622 * 1623 * @param viewId The id of the view whose visibility should change 1624 * @param visibility The new visibility for the view 1625 */ 1626 public void setViewVisibility(int viewId, int visibility) { 1627 setInt(viewId, "setVisibility", visibility); 1628 } 1629 1630 /** 1631 * Equivalent to calling TextView.setText 1632 * 1633 * @param viewId The id of the view whose text should change 1634 * @param text The new text for the view 1635 */ 1636 public void setTextViewText(int viewId, CharSequence text) { 1637 setCharSequence(viewId, "setText", text); 1638 } 1639 1640 /** 1641 * Equivalent to calling {@link TextView#setTextSize(int, float)} 1642 * 1643 * @param viewId The id of the view whose text size should change 1644 * @param units The units of size (e.g. COMPLEX_UNIT_SP) 1645 * @param size The size of the text 1646 */ 1647 public void setTextViewTextSize(int viewId, int units, float size) { 1648 addAction(new TextViewSizeAction(viewId, units, size)); 1649 } 1650 1651 /** 1652 * Equivalent to calling 1653 * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. 1654 * 1655 * @param viewId The id of the view whose text should change 1656 * @param left The id of a drawable to place to the left of the text, or 0 1657 * @param top The id of a drawable to place above the text, or 0 1658 * @param right The id of a drawable to place to the right of the text, or 0 1659 * @param bottom The id of a drawable to place below the text, or 0 1660 */ 1661 public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { 1662 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 1663 } 1664 1665 /** 1666 * @param viewId The id of the view whose text should change 1667 * @param start The id of a drawable to place before the text (relative to the 1668 * layout direction), or 0 1669 * @param top The id of a drawable to place above the text, or 0 1670 * @param end The id of a drawable to place after the text, or 0 1671 * @param bottom The id of a drawable to place below the text, or 0 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 android.view.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 return apply(context, parent, DEFAULT_ON_CLICK_HANDLER); 2111 } 2112 2113 /** @hide */ 2114 public View apply(Context context, ViewGroup parent, OnClickHandler handler) { 2115 RemoteViews rvToApply = getRemoteViewsToApply(context); 2116 2117 View result; 2118 2119 Context c = prepareContext(context); 2120 2121 LayoutInflater inflater = (LayoutInflater) 2122 c.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2123 2124 inflater = inflater.cloneInContext(c); 2125 inflater.setFilter(this); 2126 2127 result = inflater.inflate(rvToApply.getLayoutId(), parent, false); 2128 2129 rvToApply.performApply(result, parent, handler); 2130 2131 return result; 2132 } 2133 2134 /** 2135 * Applies all of the actions to the provided view. 2136 * 2137 * <p><strong>Caller beware: this may throw</strong> 2138 * 2139 * @param v The view to apply the actions to. This should be the result of 2140 * the {@link #apply(Context,ViewGroup)} call. 2141 */ 2142 public void reapply(Context context, View v) { 2143 reapply(context, v, DEFAULT_ON_CLICK_HANDLER); 2144 } 2145 2146 /** @hide */ 2147 public void reapply(Context context, View v, OnClickHandler handler) { 2148 RemoteViews rvToApply = getRemoteViewsToApply(context); 2149 2150 // In the case that a view has this RemoteViews applied in one orientation, is persisted 2151 // across orientation change, and has the RemoteViews re-applied in the new orientation, 2152 // we throw an exception, since the layouts may be completely unrelated. 2153 if (hasLandscapeAndPortraitLayouts()) { 2154 if (v.getId() != rvToApply.getLayoutId()) { 2155 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 2156 " that does not share the same root layout id."); 2157 } 2158 } 2159 2160 prepareContext(context); 2161 rvToApply.performApply(v, (ViewGroup) v.getParent(), handler); 2162 } 2163 2164 private void performApply(View v, ViewGroup parent, OnClickHandler handler) { 2165 if (mActions != null) { 2166 final int count = mActions.size(); 2167 for (int i = 0; i < count; i++) { 2168 Action a = mActions.get(i); 2169 a.apply(v, parent, handler); 2170 } 2171 } 2172 } 2173 2174 private Context prepareContext(Context context) { 2175 Context c; 2176 String packageName = mPackage; 2177 2178 if (packageName != null) { 2179 try { 2180 c = context.createPackageContext(packageName, Context.CONTEXT_RESTRICTED); 2181 } catch (NameNotFoundException e) { 2182 Log.e(LOG_TAG, "Package name " + packageName + " not found"); 2183 c = context; 2184 } 2185 } else { 2186 c = context; 2187 } 2188 2189 return c; 2190 } 2191 2192 /* (non-Javadoc) 2193 * Used to restrict the views which can be inflated 2194 * 2195 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 2196 */ 2197 public boolean onLoadClass(Class clazz) { 2198 return clazz.isAnnotationPresent(RemoteView.class); 2199 } 2200 2201 public int describeContents() { 2202 return 0; 2203 } 2204 2205 public void writeToParcel(Parcel dest, int flags) { 2206 if (!hasLandscapeAndPortraitLayouts()) { 2207 dest.writeInt(MODE_NORMAL); 2208 // We only write the bitmap cache if we are the root RemoteViews, as this cache 2209 // is shared by all children. 2210 if (mIsRoot) { 2211 mBitmapCache.writeBitmapsToParcel(dest, flags); 2212 } 2213 dest.writeString(mPackage); 2214 dest.writeInt(mLayoutId); 2215 dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); 2216 int count; 2217 if (mActions != null) { 2218 count = mActions.size(); 2219 } else { 2220 count = 0; 2221 } 2222 dest.writeInt(count); 2223 for (int i=0; i<count; i++) { 2224 Action a = mActions.get(i); 2225 a.writeToParcel(dest, 0); 2226 } 2227 } else { 2228 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 2229 // We only write the bitmap cache if we are the root RemoteViews, as this cache 2230 // is shared by all children. 2231 if (mIsRoot) { 2232 mBitmapCache.writeBitmapsToParcel(dest, flags); 2233 } 2234 mLandscape.writeToParcel(dest, flags); 2235 mPortrait.writeToParcel(dest, flags); 2236 } 2237 } 2238 2239 /** 2240 * Parcelable.Creator that instantiates RemoteViews objects 2241 */ 2242 public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 2243 public RemoteViews createFromParcel(Parcel parcel) { 2244 return new RemoteViews(parcel); 2245 } 2246 2247 public RemoteViews[] newArray(int size) { 2248 return new RemoteViews[size]; 2249 } 2250 }; 2251 } 2252