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