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