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