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