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