RemoteViews.java revision 384804b42deaa7a679f8afbb0c7c69cf4aa68f06
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 static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED; 20 21import android.annotation.ColorInt; 22import android.annotation.DimenRes; 23import android.annotation.NonNull; 24import android.app.ActivityOptions; 25import android.app.ActivityThread; 26import android.app.Application; 27import android.app.PendingIntent; 28import android.app.RemoteInput; 29import android.appwidget.AppWidgetHostView; 30import android.content.Context; 31import android.content.ContextWrapper; 32import android.content.Intent; 33import android.content.IntentSender; 34import android.content.pm.ApplicationInfo; 35import android.content.pm.PackageManager.NameNotFoundException; 36import android.content.res.ColorStateList; 37import android.content.res.Configuration; 38import android.content.res.Resources; 39import android.content.res.TypedArray; 40import android.graphics.Bitmap; 41import android.graphics.PorterDuff; 42import android.graphics.Rect; 43import android.graphics.drawable.Drawable; 44import android.graphics.drawable.Icon; 45import android.net.Uri; 46import android.os.AsyncTask; 47import android.os.Binder; 48import android.os.Build; 49import android.os.Bundle; 50import android.os.CancellationSignal; 51import android.os.Parcel; 52import android.os.Parcelable; 53import android.os.Process; 54import android.os.StrictMode; 55import android.os.UserHandle; 56import android.text.TextUtils; 57import android.util.ArrayMap; 58import android.util.Log; 59import android.view.LayoutInflater; 60import android.view.LayoutInflater.Filter; 61import android.view.RemotableViewMethod; 62import android.view.View; 63import android.view.View.OnClickListener; 64import android.view.ViewGroup; 65import android.view.ViewStub; 66import android.widget.AdapterView.OnItemClickListener; 67 68import com.android.internal.R; 69import com.android.internal.util.NotificationColorUtil; 70import com.android.internal.util.Preconditions; 71 72import java.lang.annotation.ElementType; 73import java.lang.annotation.Retention; 74import java.lang.annotation.RetentionPolicy; 75import java.lang.annotation.Target; 76import java.lang.invoke.MethodHandle; 77import java.lang.invoke.MethodHandles; 78import java.lang.invoke.MethodType; 79import java.lang.reflect.Method; 80import java.util.ArrayList; 81import java.util.HashMap; 82import java.util.Map; 83import java.util.Objects; 84import java.util.Stack; 85import java.util.concurrent.Executor; 86import java.util.function.Consumer; 87 88/** 89 * A class that describes a view hierarchy that can be displayed in 90 * another process. The hierarchy is inflated from a layout resource 91 * file, and this class provides some basic operations for modifying 92 * the content of the inflated hierarchy. 93 * 94 * <p>{@code RemoteViews} is limited to support for the following layouts:</p> 95 * <ul> 96 * <li>{@link android.widget.AdapterViewFlipper}</li> 97 * <li>{@link android.widget.FrameLayout}</li> 98 * <li>{@link android.widget.GridLayout}</li> 99 * <li>{@link android.widget.GridView}</li> 100 * <li>{@link android.widget.LinearLayout}</li> 101 * <li>{@link android.widget.ListView}</li> 102 * <li>{@link android.widget.RelativeLayout}</li> 103 * <li>{@link android.widget.StackView}</li> 104 * <li>{@link android.widget.ViewFlipper}</li> 105 * </ul> 106 * <p>And the following widgets:</p> 107 * <ul> 108 * <li>{@link android.widget.AnalogClock}</li> 109 * <li>{@link android.widget.Button}</li> 110 * <li>{@link android.widget.Chronometer}</li> 111 * <li>{@link android.widget.ImageButton}</li> 112 * <li>{@link android.widget.ImageView}</li> 113 * <li>{@link android.widget.ProgressBar}</li> 114 * <li>{@link android.widget.TextClock}</li> 115 * <li>{@link android.widget.TextView}</li> 116 * </ul> 117 * <p>Descendants of these classes are not supported.</p> 118 */ 119public class RemoteViews implements Parcelable, Filter { 120 121 private static final String LOG_TAG = "RemoteViews"; 122 123 /** 124 * The intent extra that contains the appWidgetId. 125 * @hide 126 */ 127 static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; 128 129 /** 130 * Maximum depth of nested views calls from {@link #addView(int, RemoteViews)} and 131 * {@link #RemoteViews(RemoteViews, RemoteViews)}. 132 */ 133 private static final int MAX_NESTED_VIEWS = 10; 134 135 // The unique identifiers for each custom {@link Action}. 136 private static final int SET_ON_CLICK_PENDING_INTENT_TAG = 1; 137 private static final int REFLECTION_ACTION_TAG = 2; 138 private static final int SET_DRAWABLE_TINT_TAG = 3; 139 private static final int VIEW_GROUP_ACTION_ADD_TAG = 4; 140 private static final int VIEW_CONTENT_NAVIGATION_TAG = 5; 141 private static final int SET_EMPTY_VIEW_ACTION_TAG = 6; 142 private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7; 143 private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8; 144 private static final int SET_ON_CLICK_FILL_IN_INTENT_TAG = 9; 145 private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10; 146 private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11; 147 private static final int BITMAP_REFLECTION_ACTION_TAG = 12; 148 private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13; 149 private static final int VIEW_PADDING_ACTION_TAG = 14; 150 private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15; 151 private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18; 152 private static final int LAYOUT_PARAM_ACTION_TAG = 19; 153 private static final int OVERRIDE_TEXT_COLORS_TAG = 20; 154 155 /** 156 * Application that hosts the remote views. 157 * 158 * @hide 159 */ 160 public ApplicationInfo mApplication; 161 162 /** 163 * The resource ID of the layout file. (Added to the parcel) 164 */ 165 private final int mLayoutId; 166 167 /** 168 * An array of actions to perform on the view tree once it has been 169 * inflated 170 */ 171 private ArrayList<Action> mActions; 172 173 /** 174 * Maps bitmaps to unique indicies to avoid Bitmap duplication. 175 */ 176 private BitmapCache mBitmapCache; 177 178 /** 179 * Indicates whether or not this RemoteViews object is contained as a child of any other 180 * RemoteViews. 181 */ 182 private boolean mIsRoot = true; 183 184 /** 185 * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify 186 * the layout in a way that isn't recoverable, since views are being removed. 187 */ 188 private boolean mReapplyDisallowed; 189 190 /** 191 * Constants to whether or not this RemoteViews is composed of a landscape and portrait 192 * RemoteViews. 193 */ 194 private static final int MODE_NORMAL = 0; 195 private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; 196 197 /** 198 * Used in conjunction with the special constructor 199 * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait 200 * RemoteViews. 201 */ 202 private RemoteViews mLandscape = null; 203 private RemoteViews mPortrait = null; 204 205 /** 206 * This flag indicates whether this RemoteViews object is being created from a 207 * RemoteViewsService for use as a child of a widget collection. This flag is used 208 * to determine whether or not certain features are available, in particular, 209 * setting on click extras and setting on click pending intents. The former is enabled, 210 * and the latter disabled when this flag is true. 211 */ 212 private boolean mIsWidgetCollectionChild = false; 213 214 /** Class cookies of the Parcel this instance was read from. */ 215 private final Map<Class, Object> mClassCookies; 216 217 private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); 218 219 private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); 220 221 /** 222 * This key is used to perform lookups in sMethods without causing allocations. 223 */ 224 private static final MethodKey sLookupKey = new MethodKey(); 225 226 /** 227 * @hide 228 */ 229 public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) { 230 mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); 231 } 232 233 /** 234 * Reduces all images and ensures that they are all below the given sizes. 235 * 236 * @param maxWidth the maximum width allowed 237 * @param maxHeight the maximum height allowed 238 * 239 * @hide 240 */ 241 public void reduceImageSizes(int maxWidth, int maxHeight) { 242 ArrayList<Bitmap> cache = mBitmapCache.mBitmaps; 243 for (int i = 0; i < cache.size(); i++) { 244 Bitmap bitmap = cache.get(i); 245 cache.set(i, Icon.scaleDownIfNecessary(bitmap, maxWidth, maxHeight)); 246 } 247 } 248 249 /** 250 * Override all text colors in this layout and replace them by the given text color. 251 * 252 * @param textColor The color to use. 253 * 254 * @hide 255 */ 256 public void overrideTextColors(int textColor) { 257 addAction(new OverrideTextColorsAction(textColor)); 258 } 259 260 /** 261 * Set that it is disallowed to reapply another remoteview with the same layout as this view. 262 * This should be done if an action is destroying the view tree of the base layout. 263 * 264 * @hide 265 */ 266 public void setReapplyDisallowed() { 267 mReapplyDisallowed = true; 268 } 269 270 /** 271 * @return Whether it is disallowed to reapply another remoteview with the same layout as this 272 * view. True if this remoteview has actions that destroyed view tree of the base layout. 273 * 274 * @hide 275 */ 276 public boolean isReapplyDisallowed() { 277 return mReapplyDisallowed; 278 } 279 280 /** 281 * Stores information related to reflection method lookup. 282 */ 283 static class MethodKey { 284 public Class targetClass; 285 public Class paramClass; 286 public String methodName; 287 288 @Override 289 public boolean equals(Object o) { 290 if (!(o instanceof MethodKey)) { 291 return false; 292 } 293 MethodKey p = (MethodKey) o; 294 return Objects.equals(p.targetClass, targetClass) 295 && Objects.equals(p.paramClass, paramClass) 296 && Objects.equals(p.methodName, methodName); 297 } 298 299 @Override 300 public int hashCode() { 301 return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) 302 ^ Objects.hashCode(methodName); 303 } 304 305 public void set(Class targetClass, Class paramClass, String methodName) { 306 this.targetClass = targetClass; 307 this.paramClass = paramClass; 308 this.methodName = methodName; 309 } 310 } 311 312 313 /** 314 * Stores information related to reflection method lookup result. 315 */ 316 static class MethodArgs { 317 public MethodHandle syncMethod; 318 public MethodHandle asyncMethod; 319 public String asyncMethodName; 320 } 321 322 /** 323 * This annotation indicates that a subclass of View is allowed to be used 324 * with the {@link RemoteViews} mechanism. 325 */ 326 @Target({ ElementType.TYPE }) 327 @Retention(RetentionPolicy.RUNTIME) 328 public @interface RemoteView { 329 } 330 331 /** 332 * Exception to send when something goes wrong executing an action 333 * 334 */ 335 public static class ActionException extends RuntimeException { 336 public ActionException(Exception ex) { 337 super(ex); 338 } 339 public ActionException(String message) { 340 super(message); 341 } 342 /** 343 * @hide 344 */ 345 public ActionException(Throwable t) { 346 super(t); 347 } 348 } 349 350 /** @hide */ 351 public static class OnClickHandler { 352 353 private int mEnterAnimationId; 354 355 public boolean onClickHandler(View view, PendingIntent pendingIntent, 356 Intent fillInIntent) { 357 return onClickHandler(view, pendingIntent, fillInIntent, WINDOWING_MODE_UNDEFINED); 358 } 359 360 public boolean onClickHandler(View view, PendingIntent pendingIntent, 361 Intent fillInIntent, int windowingMode) { 362 try { 363 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 364 Context context = view.getContext(); 365 ActivityOptions opts; 366 if (mEnterAnimationId != 0) { 367 opts = ActivityOptions.makeCustomAnimation(context, mEnterAnimationId, 0); 368 } else { 369 opts = ActivityOptions.makeBasic(); 370 } 371 372 if (windowingMode != WINDOWING_MODE_UNDEFINED) { 373 opts.setLaunchWindowingMode(windowingMode); 374 } 375 context.startIntentSender( 376 pendingIntent.getIntentSender(), fillInIntent, 377 Intent.FLAG_ACTIVITY_NEW_TASK, 378 Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); 379 } catch (IntentSender.SendIntentException e) { 380 android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e); 381 return false; 382 } catch (Exception e) { 383 android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " + 384 "unknown exception: ", e); 385 return false; 386 } 387 return true; 388 } 389 390 public void setEnterAnimationId(int enterAnimationId) { 391 mEnterAnimationId = enterAnimationId; 392 } 393 } 394 395 /** 396 * Base class for all actions that can be performed on an 397 * inflated view. 398 * 399 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 400 */ 401 private abstract static class Action implements Parcelable { 402 public abstract void apply(View root, ViewGroup rootParent, 403 OnClickHandler handler) throws ActionException; 404 405 public static final int MERGE_REPLACE = 0; 406 public static final int MERGE_APPEND = 1; 407 public static final int MERGE_IGNORE = 2; 408 409 public int describeContents() { 410 return 0; 411 } 412 413 public void setBitmapCache(BitmapCache bitmapCache) { 414 // Do nothing 415 } 416 417 public int mergeBehavior() { 418 return MERGE_REPLACE; 419 } 420 421 public abstract int getActionTag(); 422 423 public String getUniqueKey() { 424 return (getActionTag() + "_" + viewId); 425 } 426 427 /** 428 * This is called on the background thread. It should perform any non-ui computations 429 * and return the final action which will run on the UI thread. 430 * Override this if some of the tasks can be performed async. 431 */ 432 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 433 return this; 434 } 435 436 public boolean prefersAsyncApply() { 437 return false; 438 } 439 440 /** 441 * Overridden by subclasses which have (or inherit) an ApplicationInfo instance 442 * as member variable 443 */ 444 public boolean hasSameAppInfo(ApplicationInfo parentInfo) { 445 return true; 446 } 447 448 public void visitUris(@NonNull Consumer<Uri> visitor) { 449 // Nothing to visit by default 450 } 451 452 int viewId; 453 } 454 455 /** 456 * Action class used during async inflation of RemoteViews. Subclasses are not parcelable. 457 */ 458 private static abstract class RuntimeAction extends Action { 459 @Override 460 public final int getActionTag() { 461 return 0; 462 } 463 464 @Override 465 public final void writeToParcel(Parcel dest, int flags) { 466 throw new UnsupportedOperationException(); 467 } 468 } 469 470 // Constant used during async execution. It is not parcelable. 471 private static final Action ACTION_NOOP = new RuntimeAction() { 472 @Override 473 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { } 474 }; 475 476 /** 477 * Merges the passed RemoteViews actions with this RemoteViews actions according to 478 * action-specific merge rules. 479 * 480 * @param newRv 481 * 482 * @hide 483 */ 484 public void mergeRemoteViews(RemoteViews newRv) { 485 if (newRv == null) return; 486 // We first copy the new RemoteViews, as the process of merging modifies the way the actions 487 // reference the bitmap cache. We don't want to modify the object as it may need to 488 // be merged and applied multiple times. 489 RemoteViews copy = new RemoteViews(newRv); 490 491 HashMap<String, Action> map = new HashMap<String, Action>(); 492 if (mActions == null) { 493 mActions = new ArrayList<Action>(); 494 } 495 496 int count = mActions.size(); 497 for (int i = 0; i < count; i++) { 498 Action a = mActions.get(i); 499 map.put(a.getUniqueKey(), a); 500 } 501 502 ArrayList<Action> newActions = copy.mActions; 503 if (newActions == null) return; 504 count = newActions.size(); 505 for (int i = 0; i < count; i++) { 506 Action a = newActions.get(i); 507 String key = newActions.get(i).getUniqueKey(); 508 int mergeBehavior = newActions.get(i).mergeBehavior(); 509 if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) { 510 mActions.remove(map.get(key)); 511 map.remove(key); 512 } 513 514 // If the merge behavior is ignore, we don't bother keeping the extra action 515 if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) { 516 mActions.add(a); 517 } 518 } 519 520 // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache 521 mBitmapCache = new BitmapCache(); 522 setBitmapCache(mBitmapCache); 523 } 524 525 /** 526 * Note all {@link Uri} that are referenced internally, with the expectation 527 * that Uri permission grants will need to be issued to ensure the recipient 528 * of this object is able to render its contents. 529 * 530 * @hide 531 */ 532 public void visitUris(@NonNull Consumer<Uri> visitor) { 533 if (mActions != null) { 534 for (int i = 0; i < mActions.size(); i++) { 535 mActions.get(i).visitUris(visitor); 536 } 537 } 538 } 539 540 private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { 541 if (icon != null && icon.getType() == Icon.TYPE_URI) { 542 visitor.accept(icon.getUri()); 543 } 544 } 545 546 private static class RemoteViewsContextWrapper extends ContextWrapper { 547 private final Context mContextForResources; 548 549 RemoteViewsContextWrapper(Context context, Context contextForResources) { 550 super(context); 551 mContextForResources = contextForResources; 552 } 553 554 @Override 555 public Resources getResources() { 556 return mContextForResources.getResources(); 557 } 558 559 @Override 560 public Resources.Theme getTheme() { 561 return mContextForResources.getTheme(); 562 } 563 564 @Override 565 public String getPackageName() { 566 return mContextForResources.getPackageName(); 567 } 568 } 569 570 private class SetEmptyView extends Action { 571 int emptyViewId; 572 573 SetEmptyView(int viewId, int emptyViewId) { 574 this.viewId = viewId; 575 this.emptyViewId = emptyViewId; 576 } 577 578 SetEmptyView(Parcel in) { 579 this.viewId = in.readInt(); 580 this.emptyViewId = in.readInt(); 581 } 582 583 public void writeToParcel(Parcel out, int flags) { 584 out.writeInt(this.viewId); 585 out.writeInt(this.emptyViewId); 586 } 587 588 @Override 589 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 590 final View view = root.findViewById(viewId); 591 if (!(view instanceof AdapterView<?>)) return; 592 593 AdapterView<?> adapterView = (AdapterView<?>) view; 594 595 final View emptyView = root.findViewById(emptyViewId); 596 if (emptyView == null) return; 597 598 adapterView.setEmptyView(emptyView); 599 } 600 601 @Override 602 public int getActionTag() { 603 return SET_EMPTY_VIEW_ACTION_TAG; 604 } 605 } 606 607 private class SetOnClickFillInIntent extends Action { 608 public SetOnClickFillInIntent(int id, Intent fillInIntent) { 609 this.viewId = id; 610 this.fillInIntent = fillInIntent; 611 } 612 613 public SetOnClickFillInIntent(Parcel parcel) { 614 viewId = parcel.readInt(); 615 fillInIntent = parcel.readTypedObject(Intent.CREATOR); 616 } 617 618 public void writeToParcel(Parcel dest, int flags) { 619 dest.writeInt(viewId); 620 dest.writeTypedObject(fillInIntent, 0 /* no flags */); 621 } 622 623 @Override 624 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 625 final View target = root.findViewById(viewId); 626 if (target == null) return; 627 628 if (!mIsWidgetCollectionChild) { 629 Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " + 630 "only from RemoteViewsFactory (ie. on collection items)."); 631 return; 632 } 633 if (target == root) { 634 target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent); 635 } else if (fillInIntent != null) { 636 OnClickListener listener = new OnClickListener() { 637 public void onClick(View v) { 638 // Insure that this view is a child of an AdapterView 639 View parent = (View) v.getParent(); 640 // Break the for loop on the first encounter of: 641 // 1) an AdapterView, 642 // 2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or 643 // 3) a null parent. 644 // 2) and 3) are unexpected and catch the case where a child is not 645 // correctly parented in an AdapterView. 646 while (parent != null && !(parent instanceof AdapterView<?>) 647 && !((parent instanceof AppWidgetHostView) && 648 !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) { 649 parent = (View) parent.getParent(); 650 } 651 652 if (!(parent instanceof AdapterView<?>)) { 653 // Somehow they've managed to get this far without having 654 // and AdapterView as a parent. 655 Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent"); 656 return; 657 } 658 659 // Insure that a template pending intent has been set on an ancestor 660 if (!(parent.getTag() instanceof PendingIntent)) { 661 Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" + 662 " calling setPendingIntentTemplate on parent."); 663 return; 664 } 665 666 PendingIntent pendingIntent = (PendingIntent) parent.getTag(); 667 668 final Rect rect = getSourceBounds(v); 669 670 fillInIntent.setSourceBounds(rect); 671 handler.onClickHandler(v, pendingIntent, fillInIntent); 672 } 673 674 }; 675 target.setOnClickListener(listener); 676 } 677 } 678 679 @Override 680 public int getActionTag() { 681 return SET_ON_CLICK_FILL_IN_INTENT_TAG; 682 } 683 684 Intent fillInIntent; 685 } 686 687 private class SetPendingIntentTemplate extends Action { 688 public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { 689 this.viewId = id; 690 this.pendingIntentTemplate = pendingIntentTemplate; 691 } 692 693 public SetPendingIntentTemplate(Parcel parcel) { 694 viewId = parcel.readInt(); 695 pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 696 } 697 698 public void writeToParcel(Parcel dest, int flags) { 699 dest.writeInt(viewId); 700 PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest); 701 } 702 703 @Override 704 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 705 final View target = root.findViewById(viewId); 706 if (target == null) return; 707 708 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 709 if (target instanceof AdapterView<?>) { 710 AdapterView<?> av = (AdapterView<?>) target; 711 // The PendingIntent template is stored in the view's tag. 712 OnItemClickListener listener = new OnItemClickListener() { 713 public void onItemClick(AdapterView<?> parent, View view, 714 int position, long id) { 715 // The view should be a frame layout 716 if (view instanceof ViewGroup) { 717 ViewGroup vg = (ViewGroup) view; 718 719 // AdapterViews contain their children in a frame 720 // so we need to go one layer deeper here. 721 if (parent instanceof AdapterViewAnimator) { 722 vg = (ViewGroup) vg.getChildAt(0); 723 } 724 if (vg == null) return; 725 726 Intent fillInIntent = null; 727 int childCount = vg.getChildCount(); 728 for (int i = 0; i < childCount; i++) { 729 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent); 730 if (tag instanceof Intent) { 731 fillInIntent = (Intent) tag; 732 break; 733 } 734 } 735 if (fillInIntent == null) return; 736 737 final Rect rect = getSourceBounds(view); 738 739 final Intent intent = new Intent(); 740 intent.setSourceBounds(rect); 741 handler.onClickHandler(view, pendingIntentTemplate, fillInIntent); 742 } 743 } 744 }; 745 av.setOnItemClickListener(listener); 746 av.setTag(pendingIntentTemplate); 747 } else { 748 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" + 749 "an AdapterView (id: " + viewId + ")"); 750 return; 751 } 752 } 753 754 @Override 755 public int getActionTag() { 756 return SET_PENDING_INTENT_TEMPLATE_TAG; 757 } 758 759 PendingIntent pendingIntentTemplate; 760 } 761 762 private class SetRemoteViewsAdapterList extends Action { 763 public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) { 764 this.viewId = id; 765 this.list = list; 766 this.viewTypeCount = viewTypeCount; 767 } 768 769 public SetRemoteViewsAdapterList(Parcel parcel) { 770 viewId = parcel.readInt(); 771 viewTypeCount = parcel.readInt(); 772 list = parcel.createTypedArrayList(RemoteViews.CREATOR); 773 } 774 775 public void writeToParcel(Parcel dest, int flags) { 776 dest.writeInt(viewId); 777 dest.writeInt(viewTypeCount); 778 dest.writeTypedList(list, flags); 779 } 780 781 @Override 782 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 783 final View target = root.findViewById(viewId); 784 if (target == null) return; 785 786 // Ensure that we are applying to an AppWidget root 787 if (!(rootParent instanceof AppWidgetHostView)) { 788 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + 789 "AppWidgets (root id: " + viewId + ")"); 790 return; 791 } 792 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 793 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 794 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + 795 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 796 return; 797 } 798 799 if (target instanceof AbsListView) { 800 AbsListView v = (AbsListView) target; 801 Adapter a = v.getAdapter(); 802 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 803 ((RemoteViewsListAdapter) a).setViewsList(list); 804 } else { 805 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); 806 } 807 } else if (target instanceof AdapterViewAnimator) { 808 AdapterViewAnimator v = (AdapterViewAnimator) target; 809 Adapter a = v.getAdapter(); 810 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 811 ((RemoteViewsListAdapter) a).setViewsList(list); 812 } else { 813 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); 814 } 815 } 816 } 817 818 @Override 819 public int getActionTag() { 820 return SET_REMOTE_VIEW_ADAPTER_LIST_TAG; 821 } 822 823 int viewTypeCount; 824 ArrayList<RemoteViews> list; 825 } 826 827 private class SetRemoteViewsAdapterIntent extends Action { 828 public SetRemoteViewsAdapterIntent(int id, Intent intent) { 829 this.viewId = id; 830 this.intent = intent; 831 } 832 833 public SetRemoteViewsAdapterIntent(Parcel parcel) { 834 viewId = parcel.readInt(); 835 intent = parcel.readTypedObject(Intent.CREATOR); 836 } 837 838 public void writeToParcel(Parcel dest, int flags) { 839 dest.writeInt(viewId); 840 dest.writeTypedObject(intent, flags); 841 } 842 843 @Override 844 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 845 final View target = root.findViewById(viewId); 846 if (target == null) return; 847 848 // Ensure that we are applying to an AppWidget root 849 if (!(rootParent instanceof AppWidgetHostView)) { 850 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + 851 "AppWidgets (root id: " + viewId + ")"); 852 return; 853 } 854 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 855 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 856 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + 857 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 858 return; 859 } 860 861 // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent 862 // RemoteViewsService 863 AppWidgetHostView host = (AppWidgetHostView) rootParent; 864 intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()); 865 if (target instanceof AbsListView) { 866 AbsListView v = (AbsListView) target; 867 v.setRemoteViewsAdapter(intent, isAsync); 868 v.setRemoteViewsOnClickHandler(handler); 869 } else if (target instanceof AdapterViewAnimator) { 870 AdapterViewAnimator v = (AdapterViewAnimator) target; 871 v.setRemoteViewsAdapter(intent, isAsync); 872 v.setRemoteViewsOnClickHandler(handler); 873 } 874 } 875 876 @Override 877 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 878 OnClickHandler handler) { 879 SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent); 880 copy.isAsync = true; 881 return copy; 882 } 883 884 @Override 885 public int getActionTag() { 886 return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG; 887 } 888 889 Intent intent; 890 boolean isAsync = false; 891 } 892 893 /** 894 * Equivalent to calling 895 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 896 * to launch the provided {@link PendingIntent}. 897 */ 898 private class SetOnClickPendingIntent extends Action { 899 public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { 900 this.viewId = id; 901 this.pendingIntent = pendingIntent; 902 } 903 904 public SetOnClickPendingIntent(Parcel parcel) { 905 viewId = parcel.readInt(); 906 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 907 } 908 909 public void writeToParcel(Parcel dest, int flags) { 910 dest.writeInt(viewId); 911 PendingIntent.writePendingIntentOrNullToParcel(pendingIntent, dest); 912 } 913 914 @Override 915 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 916 final View target = root.findViewById(viewId); 917 if (target == null) return; 918 919 // If the view is an AdapterView, setting a PendingIntent on click doesn't make much 920 // sense, do they mean to set a PendingIntent template for the AdapterView's children? 921 if (mIsWidgetCollectionChild) { 922 Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " + 923 "(id: " + viewId + ")"); 924 ApplicationInfo appInfo = root.getContext().getApplicationInfo(); 925 926 // We let this slide for HC and ICS so as to not break compatibility. It should have 927 // been disabled from the outset, but was left open by accident. 928 if (appInfo != null && 929 appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { 930 return; 931 } 932 } 933 934 // If the pendingIntent is null, we clear the onClickListener 935 OnClickListener listener = null; 936 if (pendingIntent != null) { 937 listener = new OnClickListener() { 938 public void onClick(View v) { 939 // Find target view location in screen coordinates and 940 // fill into PendingIntent before sending. 941 final Rect rect = getSourceBounds(v); 942 943 final Intent intent = new Intent(); 944 intent.setSourceBounds(rect); 945 handler.onClickHandler(v, pendingIntent, intent); 946 } 947 }; 948 } 949 target.setOnClickListener(listener); 950 } 951 952 @Override 953 public int getActionTag() { 954 return SET_ON_CLICK_PENDING_INTENT_TAG; 955 } 956 957 PendingIntent pendingIntent; 958 } 959 960 private static Rect getSourceBounds(View v) { 961 final float appScale = v.getContext().getResources() 962 .getCompatibilityInfo().applicationScale; 963 final int[] pos = new int[2]; 964 v.getLocationOnScreen(pos); 965 966 final Rect rect = new Rect(); 967 rect.left = (int) (pos[0] * appScale + 0.5f); 968 rect.top = (int) (pos[1] * appScale + 0.5f); 969 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 970 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 971 return rect; 972 } 973 974 private MethodHandle getMethod(View view, String methodName, Class<?> paramType, 975 boolean async) { 976 MethodArgs result; 977 Class<? extends View> klass = view.getClass(); 978 979 synchronized (sMethods) { 980 // The key is defined by the view class, param class and method name. 981 sLookupKey.set(klass, paramType, methodName); 982 result = sMethods.get(sLookupKey); 983 984 if (result == null) { 985 Method method; 986 try { 987 if (paramType == null) { 988 method = klass.getMethod(methodName); 989 } else { 990 method = klass.getMethod(methodName, paramType); 991 } 992 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 993 throw new ActionException("view: " + klass.getName() 994 + " can't use method with RemoteViews: " 995 + methodName + getParameters(paramType)); 996 } 997 998 result = new MethodArgs(); 999 result.syncMethod = MethodHandles.publicLookup().unreflect(method); 1000 result.asyncMethodName = 1001 method.getAnnotation(RemotableViewMethod.class).asyncImpl(); 1002 } catch (NoSuchMethodException | IllegalAccessException ex) { 1003 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 1004 + methodName + getParameters(paramType)); 1005 } 1006 1007 MethodKey key = new MethodKey(); 1008 key.set(klass, paramType, methodName); 1009 sMethods.put(key, result); 1010 } 1011 1012 if (!async) { 1013 return result.syncMethod; 1014 } 1015 // Check this so see if async method is implemented or not. 1016 if (result.asyncMethodName.isEmpty()) { 1017 return null; 1018 } 1019 // Async method is lazily loaded. If it is not yet loaded, load now. 1020 if (result.asyncMethod == null) { 1021 MethodType asyncType = result.syncMethod.type() 1022 .dropParameterTypes(0, 1).changeReturnType(Runnable.class); 1023 try { 1024 result.asyncMethod = MethodHandles.publicLookup().findVirtual( 1025 klass, result.asyncMethodName, asyncType); 1026 } catch (NoSuchMethodException | IllegalAccessException ex) { 1027 throw new ActionException("Async implementation declared as " 1028 + result.asyncMethodName + " but not defined for " + methodName 1029 + ": public Runnable " + result.asyncMethodName + " (" 1030 + TextUtils.join(",", asyncType.parameterArray()) + ")"); 1031 } 1032 } 1033 return result.asyncMethod; 1034 } 1035 } 1036 1037 private static String getParameters(Class<?> paramType) { 1038 if (paramType == null) return "()"; 1039 return "(" + paramType + ")"; 1040 } 1041 1042 /** 1043 * Equivalent to calling 1044 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 1045 * on the {@link Drawable} of a given view. 1046 * <p> 1047 * The operation will be performed on the {@link Drawable} returned by the 1048 * target {@link View#getBackground()} by default. If targetBackground is false, 1049 * we assume the target is an {@link ImageView} and try applying the operations 1050 * to {@link ImageView#getDrawable()}. 1051 * <p> 1052 */ 1053 private class SetDrawableTint extends Action { 1054 SetDrawableTint(int id, boolean targetBackground, 1055 int colorFilter, @NonNull PorterDuff.Mode mode) { 1056 this.viewId = id; 1057 this.targetBackground = targetBackground; 1058 this.colorFilter = colorFilter; 1059 this.filterMode = mode; 1060 } 1061 1062 SetDrawableTint(Parcel parcel) { 1063 viewId = parcel.readInt(); 1064 targetBackground = parcel.readInt() != 0; 1065 colorFilter = parcel.readInt(); 1066 filterMode = PorterDuff.intToMode(parcel.readInt()); 1067 } 1068 1069 public void writeToParcel(Parcel dest, int flags) { 1070 dest.writeInt(viewId); 1071 dest.writeInt(targetBackground ? 1 : 0); 1072 dest.writeInt(colorFilter); 1073 dest.writeInt(PorterDuff.modeToInt(filterMode)); 1074 } 1075 1076 @Override 1077 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1078 final View target = root.findViewById(viewId); 1079 if (target == null) return; 1080 1081 // Pick the correct drawable to modify for this view 1082 Drawable targetDrawable = null; 1083 if (targetBackground) { 1084 targetDrawable = target.getBackground(); 1085 } else if (target instanceof ImageView) { 1086 ImageView imageView = (ImageView) target; 1087 targetDrawable = imageView.getDrawable(); 1088 } 1089 1090 if (targetDrawable != null) { 1091 targetDrawable.mutate().setColorFilter(colorFilter, filterMode); 1092 } 1093 } 1094 1095 @Override 1096 public int getActionTag() { 1097 return SET_DRAWABLE_TINT_TAG; 1098 } 1099 1100 boolean targetBackground; 1101 int colorFilter; 1102 PorterDuff.Mode filterMode; 1103 } 1104 1105 private final class ViewContentNavigation extends Action { 1106 final boolean mNext; 1107 1108 ViewContentNavigation(int viewId, boolean next) { 1109 this.viewId = viewId; 1110 this.mNext = next; 1111 } 1112 1113 ViewContentNavigation(Parcel in) { 1114 this.viewId = in.readInt(); 1115 this.mNext = in.readBoolean(); 1116 } 1117 1118 public void writeToParcel(Parcel out, int flags) { 1119 out.writeInt(this.viewId); 1120 out.writeBoolean(this.mNext); 1121 } 1122 1123 @Override 1124 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1125 final View view = root.findViewById(viewId); 1126 if (view == null) return; 1127 1128 try { 1129 getMethod(view, 1130 mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view); 1131 } catch (Throwable ex) { 1132 throw new ActionException(ex); 1133 } 1134 } 1135 1136 public int mergeBehavior() { 1137 return MERGE_IGNORE; 1138 } 1139 1140 @Override 1141 public int getActionTag() { 1142 return VIEW_CONTENT_NAVIGATION_TAG; 1143 } 1144 } 1145 1146 private static class BitmapCache { 1147 1148 ArrayList<Bitmap> mBitmaps; 1149 int mBitmapMemory = -1; 1150 1151 public BitmapCache() { 1152 mBitmaps = new ArrayList<>(); 1153 } 1154 1155 public BitmapCache(Parcel source) { 1156 mBitmaps = source.createTypedArrayList(Bitmap.CREATOR); 1157 } 1158 1159 public int getBitmapId(Bitmap b) { 1160 if (b == null) { 1161 return -1; 1162 } else { 1163 if (mBitmaps.contains(b)) { 1164 return mBitmaps.indexOf(b); 1165 } else { 1166 mBitmaps.add(b); 1167 mBitmapMemory = -1; 1168 return (mBitmaps.size() - 1); 1169 } 1170 } 1171 } 1172 1173 public Bitmap getBitmapForId(int id) { 1174 if (id == -1 || id >= mBitmaps.size()) { 1175 return null; 1176 } else { 1177 return mBitmaps.get(id); 1178 } 1179 } 1180 1181 public void writeBitmapsToParcel(Parcel dest, int flags) { 1182 dest.writeTypedList(mBitmaps, flags); 1183 } 1184 1185 public int getBitmapMemory() { 1186 if (mBitmapMemory < 0) { 1187 mBitmapMemory = 0; 1188 int count = mBitmaps.size(); 1189 for (int i = 0; i < count; i++) { 1190 mBitmapMemory += mBitmaps.get(i).getAllocationByteCount(); 1191 } 1192 } 1193 return mBitmapMemory; 1194 } 1195 } 1196 1197 private class BitmapReflectionAction extends Action { 1198 int bitmapId; 1199 Bitmap bitmap; 1200 String methodName; 1201 1202 BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { 1203 this.bitmap = bitmap; 1204 this.viewId = viewId; 1205 this.methodName = methodName; 1206 bitmapId = mBitmapCache.getBitmapId(bitmap); 1207 } 1208 1209 BitmapReflectionAction(Parcel in) { 1210 viewId = in.readInt(); 1211 methodName = in.readString(); 1212 bitmapId = in.readInt(); 1213 bitmap = mBitmapCache.getBitmapForId(bitmapId); 1214 } 1215 1216 @Override 1217 public void writeToParcel(Parcel dest, int flags) { 1218 dest.writeInt(viewId); 1219 dest.writeString(methodName); 1220 dest.writeInt(bitmapId); 1221 } 1222 1223 @Override 1224 public void apply(View root, ViewGroup rootParent, 1225 OnClickHandler handler) throws ActionException { 1226 ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, 1227 bitmap); 1228 ra.apply(root, rootParent, handler); 1229 } 1230 1231 @Override 1232 public void setBitmapCache(BitmapCache bitmapCache) { 1233 bitmapId = bitmapCache.getBitmapId(bitmap); 1234 } 1235 1236 @Override 1237 public int getActionTag() { 1238 return BITMAP_REFLECTION_ACTION_TAG; 1239 } 1240 } 1241 1242 /** 1243 * Base class for the reflection actions. 1244 */ 1245 private final class ReflectionAction extends Action { 1246 static final int BOOLEAN = 1; 1247 static final int BYTE = 2; 1248 static final int SHORT = 3; 1249 static final int INT = 4; 1250 static final int LONG = 5; 1251 static final int FLOAT = 6; 1252 static final int DOUBLE = 7; 1253 static final int CHAR = 8; 1254 static final int STRING = 9; 1255 static final int CHAR_SEQUENCE = 10; 1256 static final int URI = 11; 1257 // BITMAP actions are never stored in the list of actions. They are only used locally 1258 // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache. 1259 static final int BITMAP = 12; 1260 static final int BUNDLE = 13; 1261 static final int INTENT = 14; 1262 static final int COLOR_STATE_LIST = 15; 1263 static final int ICON = 16; 1264 1265 String methodName; 1266 int type; 1267 Object value; 1268 1269 ReflectionAction(int viewId, String methodName, int type, Object value) { 1270 this.viewId = viewId; 1271 this.methodName = methodName; 1272 this.type = type; 1273 this.value = value; 1274 } 1275 1276 ReflectionAction(Parcel in) { 1277 this.viewId = in.readInt(); 1278 this.methodName = in.readString(); 1279 this.type = in.readInt(); 1280 //noinspection ConstantIfStatement 1281 if (false) { 1282 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId) 1283 + " methodName=" + this.methodName + " type=" + this.type); 1284 } 1285 1286 // For some values that may have been null, we first check a flag to see if they were 1287 // written to the parcel. 1288 switch (this.type) { 1289 case BOOLEAN: 1290 this.value = in.readBoolean(); 1291 break; 1292 case BYTE: 1293 this.value = in.readByte(); 1294 break; 1295 case SHORT: 1296 this.value = (short)in.readInt(); 1297 break; 1298 case INT: 1299 this.value = in.readInt(); 1300 break; 1301 case LONG: 1302 this.value = in.readLong(); 1303 break; 1304 case FLOAT: 1305 this.value = in.readFloat(); 1306 break; 1307 case DOUBLE: 1308 this.value = in.readDouble(); 1309 break; 1310 case CHAR: 1311 this.value = (char)in.readInt(); 1312 break; 1313 case STRING: 1314 this.value = in.readString(); 1315 break; 1316 case CHAR_SEQUENCE: 1317 this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1318 break; 1319 case URI: 1320 this.value = in.readTypedObject(Uri.CREATOR); 1321 break; 1322 case BITMAP: 1323 this.value = in.readTypedObject(Bitmap.CREATOR); 1324 break; 1325 case BUNDLE: 1326 this.value = in.readBundle(); 1327 break; 1328 case INTENT: 1329 this.value = in.readTypedObject(Intent.CREATOR); 1330 break; 1331 case COLOR_STATE_LIST: 1332 this.value = in.readTypedObject(ColorStateList.CREATOR); 1333 break; 1334 case ICON: 1335 this.value = in.readTypedObject(Icon.CREATOR); 1336 default: 1337 break; 1338 } 1339 } 1340 1341 public void writeToParcel(Parcel out, int flags) { 1342 out.writeInt(this.viewId); 1343 out.writeString(this.methodName); 1344 out.writeInt(this.type); 1345 //noinspection ConstantIfStatement 1346 if (false) { 1347 Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId) 1348 + " methodName=" + this.methodName + " type=" + this.type); 1349 } 1350 1351 // For some values which are null, we record an integer flag to indicate whether 1352 // we have written a valid value to the parcel. 1353 switch (this.type) { 1354 case BOOLEAN: 1355 out.writeBoolean((Boolean) this.value); 1356 break; 1357 case BYTE: 1358 out.writeByte((Byte) this.value); 1359 break; 1360 case SHORT: 1361 out.writeInt((Short) this.value); 1362 break; 1363 case INT: 1364 out.writeInt((Integer) this.value); 1365 break; 1366 case LONG: 1367 out.writeLong((Long) this.value); 1368 break; 1369 case FLOAT: 1370 out.writeFloat((Float) this.value); 1371 break; 1372 case DOUBLE: 1373 out.writeDouble((Double) this.value); 1374 break; 1375 case CHAR: 1376 out.writeInt((int)((Character)this.value).charValue()); 1377 break; 1378 case STRING: 1379 out.writeString((String)this.value); 1380 break; 1381 case CHAR_SEQUENCE: 1382 TextUtils.writeToParcel((CharSequence)this.value, out, flags); 1383 break; 1384 case BUNDLE: 1385 out.writeBundle((Bundle) this.value); 1386 break; 1387 case URI: 1388 case BITMAP: 1389 case INTENT: 1390 case COLOR_STATE_LIST: 1391 case ICON: 1392 out.writeTypedObject((Parcelable) this.value, flags); 1393 break; 1394 default: 1395 break; 1396 } 1397 } 1398 1399 private Class<?> getParameterType() { 1400 switch (this.type) { 1401 case BOOLEAN: 1402 return boolean.class; 1403 case BYTE: 1404 return byte.class; 1405 case SHORT: 1406 return short.class; 1407 case INT: 1408 return int.class; 1409 case LONG: 1410 return long.class; 1411 case FLOAT: 1412 return float.class; 1413 case DOUBLE: 1414 return double.class; 1415 case CHAR: 1416 return char.class; 1417 case STRING: 1418 return String.class; 1419 case CHAR_SEQUENCE: 1420 return CharSequence.class; 1421 case URI: 1422 return Uri.class; 1423 case BITMAP: 1424 return Bitmap.class; 1425 case BUNDLE: 1426 return Bundle.class; 1427 case INTENT: 1428 return Intent.class; 1429 case COLOR_STATE_LIST: 1430 return ColorStateList.class; 1431 case ICON: 1432 return Icon.class; 1433 default: 1434 return null; 1435 } 1436 } 1437 1438 @Override 1439 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1440 final View view = root.findViewById(viewId); 1441 if (view == null) return; 1442 1443 Class<?> param = getParameterType(); 1444 if (param == null) { 1445 throw new ActionException("bad type: " + this.type); 1446 } 1447 try { 1448 getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value); 1449 } catch (Throwable ex) { 1450 throw new ActionException(ex); 1451 } 1452 } 1453 1454 @Override 1455 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1456 final View view = root.findViewById(viewId); 1457 if (view == null) return ACTION_NOOP; 1458 1459 Class<?> param = getParameterType(); 1460 if (param == null) { 1461 throw new ActionException("bad type: " + this.type); 1462 } 1463 1464 try { 1465 MethodHandle method = getMethod(view, this.methodName, param, true /* async */); 1466 1467 if (method != null) { 1468 Runnable endAction = (Runnable) method.invoke(view, this.value); 1469 if (endAction == null) { 1470 return ACTION_NOOP; 1471 } else { 1472 // Special case view stub 1473 if (endAction instanceof ViewStub.ViewReplaceRunnable) { 1474 root.createTree(); 1475 // Replace child tree 1476 root.findViewTreeById(viewId).replaceView( 1477 ((ViewStub.ViewReplaceRunnable) endAction).view); 1478 } 1479 return new RunnableAction(endAction); 1480 } 1481 } 1482 } catch (Throwable ex) { 1483 throw new ActionException(ex); 1484 } 1485 1486 return this; 1487 } 1488 1489 public int mergeBehavior() { 1490 // smoothScrollBy is cumulative, everything else overwites. 1491 if (methodName.equals("smoothScrollBy")) { 1492 return MERGE_APPEND; 1493 } else { 1494 return MERGE_REPLACE; 1495 } 1496 } 1497 1498 @Override 1499 public int getActionTag() { 1500 return REFLECTION_ACTION_TAG; 1501 } 1502 1503 @Override 1504 public String getUniqueKey() { 1505 // Each type of reflection action corresponds to a setter, so each should be seen as 1506 // unique from the standpoint of merging. 1507 return super.getUniqueKey() + this.methodName + this.type; 1508 } 1509 1510 @Override 1511 public boolean prefersAsyncApply() { 1512 return this.type == URI || this.type == ICON; 1513 } 1514 1515 @Override 1516 public void visitUris(@NonNull Consumer<Uri> visitor) { 1517 switch (this.type) { 1518 case URI: 1519 final Uri uri = (Uri) this.value; 1520 visitor.accept(uri); 1521 break; 1522 case ICON: 1523 final Icon icon = (Icon) this.value; 1524 visitIconUri(icon, visitor); 1525 break; 1526 } 1527 } 1528 } 1529 1530 /** 1531 * This is only used for async execution of actions and it not parcelable. 1532 */ 1533 private static final class RunnableAction extends RuntimeAction { 1534 private final Runnable mRunnable; 1535 1536 RunnableAction(Runnable r) { 1537 mRunnable = r; 1538 } 1539 1540 @Override 1541 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1542 mRunnable.run(); 1543 } 1544 } 1545 1546 private void configureRemoteViewsAsChild(RemoteViews rv) { 1547 rv.setBitmapCache(mBitmapCache); 1548 rv.setNotRoot(); 1549 } 1550 1551 void setNotRoot() { 1552 mIsRoot = false; 1553 } 1554 1555 /** 1556 * ViewGroup methods that are related to adding Views. 1557 */ 1558 private class ViewGroupActionAdd extends Action { 1559 private RemoteViews mNestedViews; 1560 private int mIndex; 1561 1562 ViewGroupActionAdd(int viewId, RemoteViews nestedViews) { 1563 this(viewId, nestedViews, -1 /* index */); 1564 } 1565 1566 ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index) { 1567 this.viewId = viewId; 1568 mNestedViews = nestedViews; 1569 mIndex = index; 1570 if (nestedViews != null) { 1571 configureRemoteViewsAsChild(nestedViews); 1572 } 1573 } 1574 1575 ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, 1576 int depth, Map<Class, Object> classCookies) { 1577 viewId = parcel.readInt(); 1578 mIndex = parcel.readInt(); 1579 mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies); 1580 } 1581 1582 public void writeToParcel(Parcel dest, int flags) { 1583 dest.writeInt(viewId); 1584 dest.writeInt(mIndex); 1585 mNestedViews.writeToParcel(dest, flags); 1586 } 1587 1588 @Override 1589 public boolean hasSameAppInfo(ApplicationInfo parentInfo) { 1590 return mNestedViews.hasSameAppInfo(parentInfo); 1591 } 1592 1593 @Override 1594 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1595 final Context context = root.getContext(); 1596 final ViewGroup target = root.findViewById(viewId); 1597 1598 if (target == null) { 1599 return; 1600 } 1601 1602 // Inflate nested views and add as children 1603 target.addView(mNestedViews.apply(context, target, handler), mIndex); 1604 } 1605 1606 @Override 1607 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1608 // In the async implementation, update the view tree so that subsequent calls to 1609 // findViewById return the current view. 1610 root.createTree(); 1611 ViewTree target = root.findViewTreeById(viewId); 1612 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 1613 return ACTION_NOOP; 1614 } 1615 final ViewGroup targetVg = (ViewGroup) target.mRoot; 1616 1617 // Inflate nested views and perform all the async tasks for the child remoteView. 1618 final Context context = root.mRoot.getContext(); 1619 final AsyncApplyTask task = mNestedViews.getAsyncApplyTask( 1620 context, targetVg, null, handler); 1621 final ViewTree tree = task.doInBackground(); 1622 1623 if (tree == null) { 1624 throw new ActionException(task.mError); 1625 } 1626 1627 // Update the global view tree, so that next call to findViewTreeById 1628 // goes through the subtree as well. 1629 target.addChild(tree, mIndex); 1630 1631 return new RuntimeAction() { 1632 @Override 1633 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) 1634 throws ActionException { 1635 task.onPostExecute(tree); 1636 targetVg.addView(task.mResult, mIndex); 1637 } 1638 }; 1639 } 1640 1641 @Override 1642 public void setBitmapCache(BitmapCache bitmapCache) { 1643 mNestedViews.setBitmapCache(bitmapCache); 1644 } 1645 1646 @Override 1647 public int mergeBehavior() { 1648 return MERGE_APPEND; 1649 } 1650 1651 @Override 1652 public boolean prefersAsyncApply() { 1653 return mNestedViews.prefersAsyncApply(); 1654 } 1655 1656 @Override 1657 public int getActionTag() { 1658 return VIEW_GROUP_ACTION_ADD_TAG; 1659 } 1660 } 1661 1662 /** 1663 * ViewGroup methods related to removing child views. 1664 */ 1665 private class ViewGroupActionRemove extends Action { 1666 /** 1667 * Id that indicates that all child views of the affected ViewGroup should be removed. 1668 * 1669 * <p>Using -2 because the default id is -1. This avoids accidentally matching that. 1670 */ 1671 private static final int REMOVE_ALL_VIEWS_ID = -2; 1672 1673 private int mViewIdToKeep; 1674 1675 ViewGroupActionRemove(int viewId) { 1676 this(viewId, REMOVE_ALL_VIEWS_ID); 1677 } 1678 1679 ViewGroupActionRemove(int viewId, int viewIdToKeep) { 1680 this.viewId = viewId; 1681 mViewIdToKeep = viewIdToKeep; 1682 } 1683 1684 ViewGroupActionRemove(Parcel parcel) { 1685 viewId = parcel.readInt(); 1686 mViewIdToKeep = parcel.readInt(); 1687 } 1688 1689 public void writeToParcel(Parcel dest, int flags) { 1690 dest.writeInt(viewId); 1691 dest.writeInt(mViewIdToKeep); 1692 } 1693 1694 @Override 1695 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1696 final ViewGroup target = root.findViewById(viewId); 1697 1698 if (target == null) { 1699 return; 1700 } 1701 1702 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 1703 target.removeAllViews(); 1704 return; 1705 } 1706 1707 removeAllViewsExceptIdToKeep(target); 1708 } 1709 1710 @Override 1711 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1712 // In the async implementation, update the view tree so that subsequent calls to 1713 // findViewById return the current view. 1714 root.createTree(); 1715 ViewTree target = root.findViewTreeById(viewId); 1716 1717 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 1718 return ACTION_NOOP; 1719 } 1720 1721 final ViewGroup targetVg = (ViewGroup) target.mRoot; 1722 1723 // Clear all children when nested views omitted 1724 target.mChildren = null; 1725 return new RuntimeAction() { 1726 @Override 1727 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) 1728 throws ActionException { 1729 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 1730 targetVg.removeAllViews(); 1731 return; 1732 } 1733 1734 removeAllViewsExceptIdToKeep(targetVg); 1735 } 1736 }; 1737 } 1738 1739 /** 1740 * Iterates through the children in the given ViewGroup and removes all the views that 1741 * do not have an id of {@link #mViewIdToKeep}. 1742 */ 1743 private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) { 1744 // Otherwise, remove all the views that do not match the id to keep. 1745 int index = viewGroup.getChildCount() - 1; 1746 while (index >= 0) { 1747 if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) { 1748 viewGroup.removeViewAt(index); 1749 } 1750 index--; 1751 } 1752 } 1753 1754 @Override 1755 public int getActionTag() { 1756 return VIEW_GROUP_ACTION_REMOVE_TAG; 1757 } 1758 1759 @Override 1760 public int mergeBehavior() { 1761 return MERGE_APPEND; 1762 } 1763 } 1764 1765 /** 1766 * Helper action to set compound drawables on a TextView. Supports relative 1767 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 1768 */ 1769 private class TextViewDrawableAction extends Action { 1770 public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) { 1771 this.viewId = viewId; 1772 this.isRelative = isRelative; 1773 this.useIcons = false; 1774 this.d1 = d1; 1775 this.d2 = d2; 1776 this.d3 = d3; 1777 this.d4 = d4; 1778 } 1779 1780 public TextViewDrawableAction(int viewId, boolean isRelative, 1781 Icon i1, Icon i2, Icon i3, Icon i4) { 1782 this.viewId = viewId; 1783 this.isRelative = isRelative; 1784 this.useIcons = true; 1785 this.i1 = i1; 1786 this.i2 = i2; 1787 this.i3 = i3; 1788 this.i4 = i4; 1789 } 1790 1791 public TextViewDrawableAction(Parcel parcel) { 1792 viewId = parcel.readInt(); 1793 isRelative = (parcel.readInt() != 0); 1794 useIcons = (parcel.readInt() != 0); 1795 if (useIcons) { 1796 i1 = parcel.readTypedObject(Icon.CREATOR); 1797 i2 = parcel.readTypedObject(Icon.CREATOR); 1798 i3 = parcel.readTypedObject(Icon.CREATOR); 1799 i4 = parcel.readTypedObject(Icon.CREATOR); 1800 } else { 1801 d1 = parcel.readInt(); 1802 d2 = parcel.readInt(); 1803 d3 = parcel.readInt(); 1804 d4 = parcel.readInt(); 1805 } 1806 } 1807 1808 public void writeToParcel(Parcel dest, int flags) { 1809 dest.writeInt(viewId); 1810 dest.writeInt(isRelative ? 1 : 0); 1811 dest.writeInt(useIcons ? 1 : 0); 1812 if (useIcons) { 1813 dest.writeTypedObject(i1, 0); 1814 dest.writeTypedObject(i2, 0); 1815 dest.writeTypedObject(i3, 0); 1816 dest.writeTypedObject(i4, 0); 1817 } else { 1818 dest.writeInt(d1); 1819 dest.writeInt(d2); 1820 dest.writeInt(d3); 1821 dest.writeInt(d4); 1822 } 1823 } 1824 1825 @Override 1826 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1827 final TextView target = root.findViewById(viewId); 1828 if (target == null) return; 1829 if (drawablesLoaded) { 1830 if (isRelative) { 1831 target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4); 1832 } else { 1833 target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4); 1834 } 1835 } else if (useIcons) { 1836 final Context ctx = target.getContext(); 1837 final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx); 1838 final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx); 1839 final Drawable id3 = i3 == null ? null : i3.loadDrawable(ctx); 1840 final Drawable id4 = i4 == null ? null : i4.loadDrawable(ctx); 1841 if (isRelative) { 1842 target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4); 1843 } else { 1844 target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4); 1845 } 1846 } else { 1847 if (isRelative) { 1848 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4); 1849 } else { 1850 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4); 1851 } 1852 } 1853 } 1854 1855 @Override 1856 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1857 final TextView target = root.findViewById(viewId); 1858 if (target == null) return ACTION_NOOP; 1859 1860 TextViewDrawableAction copy = useIcons ? 1861 new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) : 1862 new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4); 1863 1864 // Load the drawables on the background thread. 1865 copy.drawablesLoaded = true; 1866 final Context ctx = target.getContext(); 1867 1868 if (useIcons) { 1869 copy.id1 = i1 == null ? null : i1.loadDrawable(ctx); 1870 copy.id2 = i2 == null ? null : i2.loadDrawable(ctx); 1871 copy.id3 = i3 == null ? null : i3.loadDrawable(ctx); 1872 copy.id4 = i4 == null ? null : i4.loadDrawable(ctx); 1873 } else { 1874 copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1); 1875 copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2); 1876 copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3); 1877 copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4); 1878 } 1879 return copy; 1880 } 1881 1882 @Override 1883 public boolean prefersAsyncApply() { 1884 return useIcons; 1885 } 1886 1887 @Override 1888 public int getActionTag() { 1889 return TEXT_VIEW_DRAWABLE_ACTION_TAG; 1890 } 1891 1892 @Override 1893 public void visitUris(@NonNull Consumer<Uri> visitor) { 1894 if (useIcons) { 1895 visitIconUri(i1, visitor); 1896 visitIconUri(i2, visitor); 1897 visitIconUri(i3, visitor); 1898 visitIconUri(i4, visitor); 1899 } 1900 } 1901 1902 boolean isRelative = false; 1903 boolean useIcons = false; 1904 int d1, d2, d3, d4; 1905 Icon i1, i2, i3, i4; 1906 1907 boolean drawablesLoaded = false; 1908 Drawable id1, id2, id3, id4; 1909 } 1910 1911 /** 1912 * Helper action to set text size on a TextView in any supported units. 1913 */ 1914 private class TextViewSizeAction extends Action { 1915 public TextViewSizeAction(int viewId, int units, float size) { 1916 this.viewId = viewId; 1917 this.units = units; 1918 this.size = size; 1919 } 1920 1921 public TextViewSizeAction(Parcel parcel) { 1922 viewId = parcel.readInt(); 1923 units = parcel.readInt(); 1924 size = parcel.readFloat(); 1925 } 1926 1927 public void writeToParcel(Parcel dest, int flags) { 1928 dest.writeInt(viewId); 1929 dest.writeInt(units); 1930 dest.writeFloat(size); 1931 } 1932 1933 @Override 1934 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1935 final TextView target = root.findViewById(viewId); 1936 if (target == null) return; 1937 target.setTextSize(units, size); 1938 } 1939 1940 @Override 1941 public int getActionTag() { 1942 return TEXT_VIEW_SIZE_ACTION_TAG; 1943 } 1944 1945 int units; 1946 float size; 1947 } 1948 1949 /** 1950 * Helper action to set padding on a View. 1951 */ 1952 private class ViewPaddingAction extends Action { 1953 public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) { 1954 this.viewId = viewId; 1955 this.left = left; 1956 this.top = top; 1957 this.right = right; 1958 this.bottom = bottom; 1959 } 1960 1961 public ViewPaddingAction(Parcel parcel) { 1962 viewId = parcel.readInt(); 1963 left = parcel.readInt(); 1964 top = parcel.readInt(); 1965 right = parcel.readInt(); 1966 bottom = parcel.readInt(); 1967 } 1968 1969 public void writeToParcel(Parcel dest, int flags) { 1970 dest.writeInt(viewId); 1971 dest.writeInt(left); 1972 dest.writeInt(top); 1973 dest.writeInt(right); 1974 dest.writeInt(bottom); 1975 } 1976 1977 @Override 1978 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1979 final View target = root.findViewById(viewId); 1980 if (target == null) return; 1981 target.setPadding(left, top, right, bottom); 1982 } 1983 1984 @Override 1985 public int getActionTag() { 1986 return VIEW_PADDING_ACTION_TAG; 1987 } 1988 1989 int left, top, right, bottom; 1990 } 1991 1992 /** 1993 * Helper action to set layout params on a View. 1994 */ 1995 private static class LayoutParamAction extends Action { 1996 1997 /** Set marginEnd */ 1998 public static final int LAYOUT_MARGIN_END_DIMEN = 1; 1999 /** Set width */ 2000 public static final int LAYOUT_WIDTH = 2; 2001 public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3; 2002 public static final int LAYOUT_MARGIN_END = 4; 2003 2004 final int mProperty; 2005 final int mValue; 2006 2007 /** 2008 * @param viewId ID of the view alter 2009 * @param property which layout parameter to alter 2010 * @param value new value of the layout parameter 2011 */ 2012 public LayoutParamAction(int viewId, int property, int value) { 2013 this.viewId = viewId; 2014 this.mProperty = property; 2015 this.mValue = value; 2016 } 2017 2018 public LayoutParamAction(Parcel parcel) { 2019 viewId = parcel.readInt(); 2020 mProperty = parcel.readInt(); 2021 mValue = parcel.readInt(); 2022 } 2023 2024 public void writeToParcel(Parcel dest, int flags) { 2025 dest.writeInt(viewId); 2026 dest.writeInt(mProperty); 2027 dest.writeInt(mValue); 2028 } 2029 2030 @Override 2031 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 2032 final View target = root.findViewById(viewId); 2033 if (target == null) { 2034 return; 2035 } 2036 ViewGroup.LayoutParams layoutParams = target.getLayoutParams(); 2037 if (layoutParams == null) { 2038 return; 2039 } 2040 int value = mValue; 2041 switch (mProperty) { 2042 case LAYOUT_MARGIN_END_DIMEN: 2043 value = resolveDimenPixelOffset(target, mValue); 2044 // fall-through 2045 case LAYOUT_MARGIN_END: 2046 if (layoutParams instanceof ViewGroup.MarginLayoutParams) { 2047 ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(value); 2048 target.setLayoutParams(layoutParams); 2049 } 2050 break; 2051 case LAYOUT_MARGIN_BOTTOM_DIMEN: 2052 if (layoutParams instanceof ViewGroup.MarginLayoutParams) { 2053 int resolved = resolveDimenPixelOffset(target, mValue); 2054 ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved; 2055 target.setLayoutParams(layoutParams); 2056 } 2057 break; 2058 case LAYOUT_WIDTH: 2059 layoutParams.width = mValue; 2060 target.setLayoutParams(layoutParams); 2061 break; 2062 default: 2063 throw new IllegalArgumentException("Unknown property " + mProperty); 2064 } 2065 } 2066 2067 private static int resolveDimenPixelOffset(View target, int value) { 2068 if (value == 0) { 2069 return 0; 2070 } 2071 return target.getContext().getResources().getDimensionPixelOffset(value); 2072 } 2073 2074 @Override 2075 public int getActionTag() { 2076 return LAYOUT_PARAM_ACTION_TAG; 2077 } 2078 2079 @Override 2080 public String getUniqueKey() { 2081 return super.getUniqueKey() + mProperty; 2082 } 2083 } 2084 2085 /** 2086 * Helper action to add a view tag with RemoteInputs. 2087 */ 2088 private class SetRemoteInputsAction extends Action { 2089 2090 public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) { 2091 this.viewId = viewId; 2092 this.remoteInputs = remoteInputs; 2093 } 2094 2095 public SetRemoteInputsAction(Parcel parcel) { 2096 viewId = parcel.readInt(); 2097 remoteInputs = parcel.createTypedArray(RemoteInput.CREATOR); 2098 } 2099 2100 public void writeToParcel(Parcel dest, int flags) { 2101 dest.writeInt(viewId); 2102 dest.writeTypedArray(remoteInputs, flags); 2103 } 2104 2105 @Override 2106 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 2107 final View target = root.findViewById(viewId); 2108 if (target == null) return; 2109 2110 target.setTagInternal(R.id.remote_input_tag, remoteInputs); 2111 } 2112 2113 @Override 2114 public int getActionTag() { 2115 return SET_REMOTE_INPUTS_ACTION_TAG; 2116 } 2117 2118 final Parcelable[] remoteInputs; 2119 } 2120 2121 /** 2122 * Helper action to override all textViewColors 2123 */ 2124 private class OverrideTextColorsAction extends Action { 2125 2126 private final int textColor; 2127 2128 public OverrideTextColorsAction(int textColor) { 2129 this.textColor = textColor; 2130 } 2131 2132 public OverrideTextColorsAction(Parcel parcel) { 2133 textColor = parcel.readInt(); 2134 } 2135 2136 public void writeToParcel(Parcel dest, int flags) { 2137 dest.writeInt(textColor); 2138 } 2139 2140 @Override 2141 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 2142 // Let's traverse the viewtree and override all textColors! 2143 Stack<View> viewsToProcess = new Stack<>(); 2144 viewsToProcess.add(root); 2145 while (!viewsToProcess.isEmpty()) { 2146 View v = viewsToProcess.pop(); 2147 if (v instanceof TextView) { 2148 TextView textView = (TextView) v; 2149 textView.setText(NotificationColorUtil.clearColorSpans(textView.getText())); 2150 textView.setTextColor(textColor); 2151 } 2152 if (v instanceof ViewGroup) { 2153 ViewGroup viewGroup = (ViewGroup) v; 2154 for (int i = 0; i < viewGroup.getChildCount(); i++) { 2155 viewsToProcess.push(viewGroup.getChildAt(i)); 2156 } 2157 } 2158 } 2159 } 2160 2161 @Override 2162 public int getActionTag() { 2163 return OVERRIDE_TEXT_COLORS_TAG; 2164 } 2165 } 2166 2167 /** 2168 * Create a new RemoteViews object that will display the views contained 2169 * in the specified layout file. 2170 * 2171 * @param packageName Name of the package that contains the layout resource 2172 * @param layoutId The id of the layout resource 2173 */ 2174 public RemoteViews(String packageName, int layoutId) { 2175 this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId); 2176 } 2177 2178 /** 2179 * Create a new RemoteViews object that will display the views contained 2180 * in the specified layout file. 2181 * 2182 * @param packageName Name of the package that contains the layout resource. 2183 * @param userId The user under which the package is running. 2184 * @param layoutId The id of the layout resource. 2185 * 2186 * @hide 2187 */ 2188 public RemoteViews(String packageName, int userId, int layoutId) { 2189 this(getApplicationInfo(packageName, userId), layoutId); 2190 } 2191 2192 /** 2193 * Create a new RemoteViews object that will display the views contained 2194 * in the specified layout file. 2195 * 2196 * @param application The application whose content is shown by the views. 2197 * @param layoutId The id of the layout resource. 2198 * 2199 * @hide 2200 */ 2201 protected RemoteViews(ApplicationInfo application, int layoutId) { 2202 mApplication = application; 2203 mLayoutId = layoutId; 2204 mBitmapCache = new BitmapCache(); 2205 mClassCookies = null; 2206 } 2207 2208 private boolean hasLandscapeAndPortraitLayouts() { 2209 return (mLandscape != null) && (mPortrait != null); 2210 } 2211 2212 /** 2213 * Create a new RemoteViews object that will inflate as the specified 2214 * landspace or portrait RemoteViews, depending on the current configuration. 2215 * 2216 * @param landscape The RemoteViews to inflate in landscape configuration 2217 * @param portrait The RemoteViews to inflate in portrait configuration 2218 */ 2219 public RemoteViews(RemoteViews landscape, RemoteViews portrait) { 2220 if (landscape == null || portrait == null) { 2221 throw new RuntimeException("Both RemoteViews must be non-null"); 2222 } 2223 if (!landscape.hasSameAppInfo(portrait.mApplication)) { 2224 throw new RuntimeException("Both RemoteViews must share the same package and user"); 2225 } 2226 mApplication = portrait.mApplication; 2227 mLayoutId = portrait.getLayoutId(); 2228 2229 mLandscape = landscape; 2230 mPortrait = portrait; 2231 2232 mBitmapCache = new BitmapCache(); 2233 configureRemoteViewsAsChild(landscape); 2234 configureRemoteViewsAsChild(portrait); 2235 2236 mClassCookies = (portrait.mClassCookies != null) 2237 ? portrait.mClassCookies : landscape.mClassCookies; 2238 } 2239 2240 /** 2241 * Creates a copy of another RemoteViews. 2242 */ 2243 public RemoteViews(RemoteViews src) { 2244 mBitmapCache = src.mBitmapCache; 2245 mApplication = src.mApplication; 2246 mIsRoot = src.mIsRoot; 2247 mLayoutId = src.mLayoutId; 2248 mIsWidgetCollectionChild = src.mIsWidgetCollectionChild; 2249 mReapplyDisallowed = src.mReapplyDisallowed; 2250 mClassCookies = src.mClassCookies; 2251 2252 if (src.hasLandscapeAndPortraitLayouts()) { 2253 mLandscape = new RemoteViews(src.mLandscape); 2254 mPortrait = new RemoteViews(src.mPortrait); 2255 } 2256 2257 if (src.mActions != null) { 2258 Parcel p = Parcel.obtain(); 2259 p.putClassCookies(mClassCookies); 2260 src.writeActionsToParcel(p); 2261 p.setDataPosition(0); 2262 // Since src is already in memory, we do not care about stack overflow as it has 2263 // already been read once. 2264 readActionsFromParcel(p, 0); 2265 p.recycle(); 2266 } 2267 2268 // Now that everything is initialized and duplicated, setting a new BitmapCache will 2269 // re-initialize the cache. 2270 setBitmapCache(new BitmapCache()); 2271 } 2272 2273 /** 2274 * Reads a RemoteViews object from a parcel. 2275 * 2276 * @param parcel 2277 */ 2278 public RemoteViews(Parcel parcel) { 2279 this(parcel, null, null, 0, null); 2280 } 2281 2282 private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, 2283 Map<Class, Object> classCookies) { 2284 if (depth > MAX_NESTED_VIEWS 2285 && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) { 2286 throw new IllegalArgumentException("Too many nested views."); 2287 } 2288 depth++; 2289 2290 int mode = parcel.readInt(); 2291 2292 // We only store a bitmap cache in the root of the RemoteViews. 2293 if (bitmapCache == null) { 2294 mBitmapCache = new BitmapCache(parcel); 2295 // Store the class cookies such that they are available when we clone this RemoteView. 2296 mClassCookies = parcel.copyClassCookies(); 2297 } else { 2298 setBitmapCache(bitmapCache); 2299 mClassCookies = classCookies; 2300 setNotRoot(); 2301 } 2302 2303 if (mode == MODE_NORMAL) { 2304 mApplication = parcel.readInt() == 0 ? info : 2305 ApplicationInfo.CREATOR.createFromParcel(parcel); 2306 mLayoutId = parcel.readInt(); 2307 mIsWidgetCollectionChild = parcel.readInt() == 1; 2308 2309 readActionsFromParcel(parcel, depth); 2310 } else { 2311 // MODE_HAS_LANDSCAPE_AND_PORTRAIT 2312 mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies); 2313 mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth, 2314 mClassCookies); 2315 mApplication = mPortrait.mApplication; 2316 mLayoutId = mPortrait.getLayoutId(); 2317 } 2318 mReapplyDisallowed = parcel.readInt() == 0; 2319 } 2320 2321 private void readActionsFromParcel(Parcel parcel, int depth) { 2322 int count = parcel.readInt(); 2323 if (count > 0) { 2324 mActions = new ArrayList<>(count); 2325 for (int i = 0; i < count; i++) { 2326 mActions.add(getActionFromParcel(parcel, depth)); 2327 } 2328 } 2329 } 2330 2331 private Action getActionFromParcel(Parcel parcel, int depth) { 2332 int tag = parcel.readInt(); 2333 switch (tag) { 2334 case SET_ON_CLICK_PENDING_INTENT_TAG: 2335 return new SetOnClickPendingIntent(parcel); 2336 case SET_DRAWABLE_TINT_TAG: 2337 return new SetDrawableTint(parcel); 2338 case REFLECTION_ACTION_TAG: 2339 return new ReflectionAction(parcel); 2340 case VIEW_GROUP_ACTION_ADD_TAG: 2341 return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth, 2342 mClassCookies); 2343 case VIEW_GROUP_ACTION_REMOVE_TAG: 2344 return new ViewGroupActionRemove(parcel); 2345 case VIEW_CONTENT_NAVIGATION_TAG: 2346 return new ViewContentNavigation(parcel); 2347 case SET_EMPTY_VIEW_ACTION_TAG: 2348 return new SetEmptyView(parcel); 2349 case SET_PENDING_INTENT_TEMPLATE_TAG: 2350 return new SetPendingIntentTemplate(parcel); 2351 case SET_ON_CLICK_FILL_IN_INTENT_TAG: 2352 return new SetOnClickFillInIntent(parcel); 2353 case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG: 2354 return new SetRemoteViewsAdapterIntent(parcel); 2355 case TEXT_VIEW_DRAWABLE_ACTION_TAG: 2356 return new TextViewDrawableAction(parcel); 2357 case TEXT_VIEW_SIZE_ACTION_TAG: 2358 return new TextViewSizeAction(parcel); 2359 case VIEW_PADDING_ACTION_TAG: 2360 return new ViewPaddingAction(parcel); 2361 case BITMAP_REFLECTION_ACTION_TAG: 2362 return new BitmapReflectionAction(parcel); 2363 case SET_REMOTE_VIEW_ADAPTER_LIST_TAG: 2364 return new SetRemoteViewsAdapterList(parcel); 2365 case SET_REMOTE_INPUTS_ACTION_TAG: 2366 return new SetRemoteInputsAction(parcel); 2367 case LAYOUT_PARAM_ACTION_TAG: 2368 return new LayoutParamAction(parcel); 2369 case OVERRIDE_TEXT_COLORS_TAG: 2370 return new OverrideTextColorsAction(parcel); 2371 default: 2372 throw new ActionException("Tag " + tag + " not found"); 2373 } 2374 }; 2375 2376 /** 2377 * Returns a deep copy of the RemoteViews object. The RemoteView may not be 2378 * attached to another RemoteView -- it must be the root of a hierarchy. 2379 * 2380 * @deprecated use {@link #RemoteViews(RemoteViews)} instead. 2381 * @throws IllegalStateException if this is not the root of a RemoteView 2382 * hierarchy 2383 */ 2384 @Override 2385 @Deprecated 2386 public RemoteViews clone() { 2387 Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. " 2388 + "May only clone the root of a RemoteView hierarchy."); 2389 2390 return new RemoteViews(this); 2391 } 2392 2393 public String getPackage() { 2394 return (mApplication != null) ? mApplication.packageName : null; 2395 } 2396 2397 /** 2398 * Returns the layout id of the root layout associated with this RemoteViews. In the case 2399 * that the RemoteViews has both a landscape and portrait root, this will return the layout 2400 * id associated with the portrait layout. 2401 * 2402 * @return the layout id. 2403 */ 2404 public int getLayoutId() { 2405 return mLayoutId; 2406 } 2407 2408 /* 2409 * This flag indicates whether this RemoteViews object is being created from a 2410 * RemoteViewsService for use as a child of a widget collection. This flag is used 2411 * to determine whether or not certain features are available, in particular, 2412 * setting on click extras and setting on click pending intents. The former is enabled, 2413 * and the latter disabled when this flag is true. 2414 */ 2415 void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) { 2416 mIsWidgetCollectionChild = isWidgetCollectionChild; 2417 } 2418 2419 /** 2420 * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. 2421 */ 2422 private void setBitmapCache(BitmapCache bitmapCache) { 2423 mBitmapCache = bitmapCache; 2424 if (!hasLandscapeAndPortraitLayouts()) { 2425 if (mActions != null) { 2426 final int count = mActions.size(); 2427 for (int i= 0; i < count; ++i) { 2428 mActions.get(i).setBitmapCache(bitmapCache); 2429 } 2430 } 2431 } else { 2432 mLandscape.setBitmapCache(bitmapCache); 2433 mPortrait.setBitmapCache(bitmapCache); 2434 } 2435 } 2436 2437 /** 2438 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 2439 */ 2440 /** @hide */ 2441 public int estimateMemoryUsage() { 2442 return mBitmapCache.getBitmapMemory(); 2443 } 2444 2445 /** 2446 * Add an action to be executed on the remote side when apply is called. 2447 * 2448 * @param a The action to add 2449 */ 2450 private void addAction(Action a) { 2451 if (hasLandscapeAndPortraitLayouts()) { 2452 throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + 2453 " layouts cannot be modified. Instead, fully configure the landscape and" + 2454 " portrait layouts individually before constructing the combined layout."); 2455 } 2456 if (mActions == null) { 2457 mActions = new ArrayList<>(); 2458 } 2459 mActions.add(a); 2460 } 2461 2462 /** 2463 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 2464 * given {@link RemoteViews}. This allows users to build "nested" 2465 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 2466 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 2467 * children. 2468 * 2469 * @param viewId The id of the parent {@link ViewGroup} to add child into. 2470 * @param nestedView {@link RemoteViews} that describes the child. 2471 */ 2472 public void addView(int viewId, RemoteViews nestedView) { 2473 addAction(nestedView == null 2474 ? new ViewGroupActionRemove(viewId) 2475 : new ViewGroupActionAdd(viewId, nestedView)); 2476 } 2477 2478 /** 2479 * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the 2480 * given {@link RemoteViews}. 2481 * 2482 * @param viewId The id of the parent {@link ViewGroup} to add the child into. 2483 * @param nestedView {@link RemoteViews} of the child to add. 2484 * @param index The position at which to add the child. 2485 * 2486 * @hide 2487 */ 2488 public void addView(int viewId, RemoteViews nestedView, int index) { 2489 addAction(new ViewGroupActionAdd(viewId, nestedView, index)); 2490 } 2491 2492 /** 2493 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 2494 * 2495 * @param viewId The id of the parent {@link ViewGroup} to remove all 2496 * children from. 2497 */ 2498 public void removeAllViews(int viewId) { 2499 addAction(new ViewGroupActionRemove(viewId)); 2500 } 2501 2502 /** 2503 * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any 2504 * child that has the {@code viewIdToKeep} as its id. 2505 * 2506 * @param viewId The id of the parent {@link ViewGroup} to remove children from. 2507 * @param viewIdToKeep The id of a child that should not be removed. 2508 * 2509 * @hide 2510 */ 2511 public void removeAllViewsExceptId(int viewId, int viewIdToKeep) { 2512 addAction(new ViewGroupActionRemove(viewId, viewIdToKeep)); 2513 } 2514 2515 /** 2516 * Equivalent to calling {@link AdapterViewAnimator#showNext()} 2517 * 2518 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} 2519 */ 2520 public void showNext(int viewId) { 2521 addAction(new ViewContentNavigation(viewId, true /* next */)); 2522 } 2523 2524 /** 2525 * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} 2526 * 2527 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} 2528 */ 2529 public void showPrevious(int viewId) { 2530 addAction(new ViewContentNavigation(viewId, false /* next */)); 2531 } 2532 2533 /** 2534 * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} 2535 * 2536 * @param viewId The id of the view on which to call 2537 * {@link AdapterViewAnimator#setDisplayedChild(int)} 2538 */ 2539 public void setDisplayedChild(int viewId, int childIndex) { 2540 setInt(viewId, "setDisplayedChild", childIndex); 2541 } 2542 2543 /** 2544 * Equivalent to calling {@link View#setVisibility(int)} 2545 * 2546 * @param viewId The id of the view whose visibility should change 2547 * @param visibility The new visibility for the view 2548 */ 2549 public void setViewVisibility(int viewId, int visibility) { 2550 setInt(viewId, "setVisibility", visibility); 2551 } 2552 2553 /** 2554 * Equivalent to calling {@link TextView#setText(CharSequence)} 2555 * 2556 * @param viewId The id of the view whose text should change 2557 * @param text The new text for the view 2558 */ 2559 public void setTextViewText(int viewId, CharSequence text) { 2560 setCharSequence(viewId, "setText", text); 2561 } 2562 2563 /** 2564 * Equivalent to calling {@link TextView#setTextSize(int, float)} 2565 * 2566 * @param viewId The id of the view whose text size should change 2567 * @param units The units of size (e.g. COMPLEX_UNIT_SP) 2568 * @param size The size of the text 2569 */ 2570 public void setTextViewTextSize(int viewId, int units, float size) { 2571 addAction(new TextViewSizeAction(viewId, units, size)); 2572 } 2573 2574 /** 2575 * Equivalent to calling 2576 * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. 2577 * 2578 * @param viewId The id of the view whose text should change 2579 * @param left The id of a drawable to place to the left of the text, or 0 2580 * @param top The id of a drawable to place above the text, or 0 2581 * @param right The id of a drawable to place to the right of the text, or 0 2582 * @param bottom The id of a drawable to place below the text, or 0 2583 */ 2584 public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { 2585 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 2586 } 2587 2588 /** 2589 * Equivalent to calling {@link 2590 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. 2591 * 2592 * @param viewId The id of the view whose text should change 2593 * @param start The id of a drawable to place before the text (relative to the 2594 * layout direction), or 0 2595 * @param top The id of a drawable to place above the text, or 0 2596 * @param end The id of a drawable to place after the text, or 0 2597 * @param bottom The id of a drawable to place below the text, or 0 2598 */ 2599 public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { 2600 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 2601 } 2602 2603 /** 2604 * Equivalent to calling {@link 2605 * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 2606 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 2607 * 2608 * @param viewId The id of the view whose text should change 2609 * @param left an Icon to place to the left of the text, or 0 2610 * @param top an Icon to place above the text, or 0 2611 * @param right an Icon to place to the right of the text, or 0 2612 * @param bottom an Icon to place below the text, or 0 2613 * 2614 * @hide 2615 */ 2616 public void setTextViewCompoundDrawables(int viewId, Icon left, Icon top, Icon right, Icon bottom) { 2617 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 2618 } 2619 2620 /** 2621 * Equivalent to calling {@link 2622 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 2623 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 2624 * 2625 * @param viewId The id of the view whose text should change 2626 * @param start an Icon to place before the text (relative to the 2627 * layout direction), or 0 2628 * @param top an Icon to place above the text, or 0 2629 * @param end an Icon to place after the text, or 0 2630 * @param bottom an Icon to place below the text, or 0 2631 * 2632 * @hide 2633 */ 2634 public void setTextViewCompoundDrawablesRelative(int viewId, Icon start, Icon top, Icon end, Icon bottom) { 2635 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 2636 } 2637 2638 /** 2639 * Equivalent to calling {@link ImageView#setImageResource(int)} 2640 * 2641 * @param viewId The id of the view whose drawable should change 2642 * @param srcId The new resource id for the drawable 2643 */ 2644 public void setImageViewResource(int viewId, int srcId) { 2645 setInt(viewId, "setImageResource", srcId); 2646 } 2647 2648 /** 2649 * Equivalent to calling {@link ImageView#setImageURI(Uri)} 2650 * 2651 * @param viewId The id of the view whose drawable should change 2652 * @param uri The Uri for the image 2653 */ 2654 public void setImageViewUri(int viewId, Uri uri) { 2655 setUri(viewId, "setImageURI", uri); 2656 } 2657 2658 /** 2659 * Equivalent to calling {@link ImageView#setImageBitmap(Bitmap)} 2660 * 2661 * @param viewId The id of the view whose bitmap should change 2662 * @param bitmap The new Bitmap for the drawable 2663 */ 2664 public void setImageViewBitmap(int viewId, Bitmap bitmap) { 2665 setBitmap(viewId, "setImageBitmap", bitmap); 2666 } 2667 2668 /** 2669 * Equivalent to calling {@link ImageView#setImageIcon(Icon)} 2670 * 2671 * @param viewId The id of the view whose bitmap should change 2672 * @param icon The new Icon for the ImageView 2673 */ 2674 public void setImageViewIcon(int viewId, Icon icon) { 2675 setIcon(viewId, "setImageIcon", icon); 2676 } 2677 2678 /** 2679 * Equivalent to calling {@link AdapterView#setEmptyView(View)} 2680 * 2681 * @param viewId The id of the view on which to set the empty view 2682 * @param emptyViewId The view id of the empty view 2683 */ 2684 public void setEmptyView(int viewId, int emptyViewId) { 2685 addAction(new SetEmptyView(viewId, emptyViewId)); 2686 } 2687 2688 /** 2689 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 2690 * {@link Chronometer#setFormat Chronometer.setFormat}, 2691 * and {@link Chronometer#start Chronometer.start()} or 2692 * {@link Chronometer#stop Chronometer.stop()}. 2693 * 2694 * @param viewId The id of the {@link Chronometer} to change 2695 * @param base The time at which the timer would have read 0:00. This 2696 * time should be based off of 2697 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 2698 * @param format The Chronometer format string, or null to 2699 * simply display the timer value. 2700 * @param started True if you want the clock to be started, false if not. 2701 * 2702 * @see #setChronometerCountDown(int, boolean) 2703 */ 2704 public void setChronometer(int viewId, long base, String format, boolean started) { 2705 setLong(viewId, "setBase", base); 2706 setString(viewId, "setFormat", format); 2707 setBoolean(viewId, "setStarted", started); 2708 } 2709 2710 /** 2711 * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on 2712 * the chronometer with the given viewId. 2713 * 2714 * @param viewId The id of the {@link Chronometer} to change 2715 * @param isCountDown True if you want the chronometer to count down to base instead of 2716 * counting up. 2717 */ 2718 public void setChronometerCountDown(int viewId, boolean isCountDown) { 2719 setBoolean(viewId, "setCountDown", isCountDown); 2720 } 2721 2722 /** 2723 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 2724 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 2725 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 2726 * 2727 * If indeterminate is true, then the values for max and progress are ignored. 2728 * 2729 * @param viewId The id of the {@link ProgressBar} to change 2730 * @param max The 100% value for the progress bar 2731 * @param progress The current value of the progress bar. 2732 * @param indeterminate True if the progress bar is indeterminate, 2733 * false if not. 2734 */ 2735 public void setProgressBar(int viewId, int max, int progress, 2736 boolean indeterminate) { 2737 setBoolean(viewId, "setIndeterminate", indeterminate); 2738 if (!indeterminate) { 2739 setInt(viewId, "setMax", max); 2740 setInt(viewId, "setProgress", progress); 2741 } 2742 } 2743 2744 /** 2745 * Equivalent to calling 2746 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 2747 * to launch the provided {@link PendingIntent}. The source bounds 2748 * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked 2749 * view in screen space. 2750 * Note that any activity options associated with the pendingIntent may get overridden 2751 * before starting the intent. 2752 * 2753 * When setting the on-click action of items within collections (eg. {@link ListView}, 2754 * {@link StackView} etc.), this method will not work. Instead, use {@link 2755 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)} in conjunction with 2756 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 2757 * 2758 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 2759 * @param pendingIntent The {@link PendingIntent} to send when user clicks 2760 */ 2761 public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { 2762 addAction(new SetOnClickPendingIntent(viewId, pendingIntent)); 2763 } 2764 2765 /** 2766 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 2767 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 2768 * this method should be used to set a single PendingIntent template on the collection, and 2769 * individual items can differentiate their on-click behavior using 2770 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 2771 * 2772 * @param viewId The id of the collection who's children will use this PendingIntent template 2773 * when clicked 2774 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 2775 * by a child of viewId and executed when that child is clicked 2776 */ 2777 public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { 2778 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 2779 } 2780 2781 /** 2782 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 2783 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 2784 * a single PendingIntent template can be set on the collection, see {@link 2785 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 2786 * action of a given item can be distinguished by setting a fillInIntent on that item. The 2787 * fillInIntent is then combined with the PendingIntent template in order to determine the final 2788 * intent which will be executed when the item is clicked. This works as follows: any fields 2789 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 2790 * will be overwritten, and the resulting PendingIntent will be used. The rest 2791 * of the PendingIntent template will then be filled in with the associated fields that are 2792 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 2793 * 2794 * @param viewId The id of the view on which to set the fillInIntent 2795 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 2796 * in order to determine the on-click behavior of the view specified by viewId 2797 */ 2798 public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { 2799 addAction(new SetOnClickFillInIntent(viewId, fillInIntent)); 2800 } 2801 2802 /** 2803 * @hide 2804 * Equivalent to calling 2805 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 2806 * on the {@link Drawable} of a given view. 2807 * <p> 2808 * 2809 * @param viewId The id of the view that contains the target 2810 * {@link Drawable} 2811 * @param targetBackground If true, apply these parameters to the 2812 * {@link Drawable} returned by 2813 * {@link android.view.View#getBackground()}. Otherwise, assume 2814 * the target view is an {@link ImageView} and apply them to 2815 * {@link ImageView#getDrawable()}. 2816 * @param colorFilter Specify a color for a 2817 * {@link android.graphics.ColorFilter} for this drawable. This will be ignored if 2818 * {@code mode} is {@code null}. 2819 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 2820 * unchanged. 2821 */ 2822 public void setDrawableTint(int viewId, boolean targetBackground, 2823 int colorFilter, @NonNull PorterDuff.Mode mode) { 2824 addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode)); 2825 } 2826 2827 /** 2828 * @hide 2829 * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}. 2830 * 2831 * @param viewId The id of the view whose tint should change 2832 * @param tint the tint to apply, may be {@code null} to clear tint 2833 */ 2834 public void setProgressTintList(int viewId, ColorStateList tint) { 2835 addAction(new ReflectionAction(viewId, "setProgressTintList", 2836 ReflectionAction.COLOR_STATE_LIST, tint)); 2837 } 2838 2839 /** 2840 * @hide 2841 * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}. 2842 * 2843 * @param viewId The id of the view whose tint should change 2844 * @param tint the tint to apply, may be {@code null} to clear tint 2845 */ 2846 public void setProgressBackgroundTintList(int viewId, ColorStateList tint) { 2847 addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList", 2848 ReflectionAction.COLOR_STATE_LIST, tint)); 2849 } 2850 2851 /** 2852 * @hide 2853 * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}. 2854 * 2855 * @param viewId The id of the view whose tint should change 2856 * @param tint the tint to apply, may be {@code null} to clear tint 2857 */ 2858 public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) { 2859 addAction(new ReflectionAction(viewId, "setIndeterminateTintList", 2860 ReflectionAction.COLOR_STATE_LIST, tint)); 2861 } 2862 2863 /** 2864 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 2865 * 2866 * @param viewId The id of the view whose text color should change 2867 * @param color Sets the text color for all the states (normal, selected, 2868 * focused) to be this color. 2869 */ 2870 public void setTextColor(int viewId, @ColorInt int color) { 2871 setInt(viewId, "setTextColor", color); 2872 } 2873 2874 /** 2875 * @hide 2876 * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}. 2877 * 2878 * @param viewId The id of the view whose text color should change 2879 * @param colors the text colors to set 2880 */ 2881 public void setTextColor(int viewId, @ColorInt ColorStateList colors) { 2882 addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST, 2883 colors)); 2884 } 2885 2886 /** 2887 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2888 * 2889 * @param appWidgetId The id of the app widget which contains the specified view. (This 2890 * parameter is ignored in this deprecated method) 2891 * @param viewId The id of the {@link AdapterView} 2892 * @param intent The intent of the service which will be 2893 * providing data to the RemoteViewsAdapter 2894 * @deprecated This method has been deprecated. See 2895 * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} 2896 */ 2897 @Deprecated 2898 public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) { 2899 setRemoteAdapter(viewId, intent); 2900 } 2901 2902 /** 2903 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2904 * Can only be used for App Widgets. 2905 * 2906 * @param viewId The id of the {@link AdapterView} 2907 * @param intent The intent of the service which will be 2908 * providing data to the RemoteViewsAdapter 2909 */ 2910 public void setRemoteAdapter(int viewId, Intent intent) { 2911 addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); 2912 } 2913 2914 /** 2915 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 2916 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 2917 * This is a simpler but less flexible approach to populating collection widgets. Its use is 2918 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 2919 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 2920 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 2921 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 2922 * 2923 * This API is supported in the compatibility library for previous API levels, see 2924 * RemoteViewsCompat. 2925 * 2926 * @param viewId The id of the {@link AdapterView} 2927 * @param list The list of RemoteViews which will populate the view specified by viewId. 2928 * @param viewTypeCount The maximum number of unique layout id's used to construct the list of 2929 * RemoteViews. This count cannot change during the life-cycle of a given widget, so this 2930 * parameter should account for the maximum possible number of types that may appear in the 2931 * See {@link Adapter#getViewTypeCount()}. 2932 * 2933 * @hide 2934 */ 2935 public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) { 2936 addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); 2937 } 2938 2939 /** 2940 * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}. 2941 * 2942 * @param viewId The id of the view to change 2943 * @param position Scroll to this adapter position 2944 */ 2945 public void setScrollPosition(int viewId, int position) { 2946 setInt(viewId, "smoothScrollToPosition", position); 2947 } 2948 2949 /** 2950 * Equivalent to calling {@link ListView#smoothScrollByOffset(int)}. 2951 * 2952 * @param viewId The id of the view to change 2953 * @param offset Scroll by this adapter position offset 2954 */ 2955 public void setRelativeScrollPosition(int viewId, int offset) { 2956 setInt(viewId, "smoothScrollByOffset", offset); 2957 } 2958 2959 /** 2960 * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}. 2961 * 2962 * @param viewId The id of the view to change 2963 * @param left the left padding in pixels 2964 * @param top the top padding in pixels 2965 * @param right the right padding in pixels 2966 * @param bottom the bottom padding in pixels 2967 */ 2968 public void setViewPadding(int viewId, int left, int top, int right, int bottom) { 2969 addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); 2970 } 2971 2972 /** 2973 * @hide 2974 * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. 2975 * Only works if the {@link View#getLayoutParams()} supports margins. 2976 * Hidden for now since we don't want to support this for all different layout margins yet. 2977 * 2978 * @param viewId The id of the view to change 2979 * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin. 2980 */ 2981 public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) { 2982 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN, 2983 endMarginDimen)); 2984 } 2985 2986 /** 2987 * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. 2988 * Only works if the {@link View#getLayoutParams()} supports margins. 2989 * Hidden for now since we don't want to support this for all different layout margins yet. 2990 * 2991 * @param viewId The id of the view to change 2992 * @param endMargin a value in pixels for the end margin. 2993 * @hide 2994 */ 2995 public void setViewLayoutMarginEnd(int viewId, @DimenRes int endMargin) { 2996 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END, 2997 endMargin)); 2998 } 2999 3000 /** 3001 * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. 3002 * 3003 * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin. 3004 * @hide 3005 */ 3006 public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) { 3007 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN, 3008 bottomMarginDimen)); 3009 } 3010 3011 /** 3012 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. 3013 * 3014 * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed 3015 * because they behave poorly when the density changes. 3016 * @hide 3017 */ 3018 public void setViewLayoutWidth(int viewId, int layoutWidth) { 3019 if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT 3020 && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) { 3021 throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT"); 3022 } 3023 mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth)); 3024 } 3025 3026 /** 3027 * Call a method taking one boolean on a view in the layout for this RemoteViews. 3028 * 3029 * @param viewId The id of the view on which to call the method. 3030 * @param methodName The name of the method to call. 3031 * @param value The value to pass to the method. 3032 */ 3033 public void setBoolean(int viewId, String methodName, boolean value) { 3034 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); 3035 } 3036 3037 /** 3038 * Call a method taking one byte on a view in the layout for this RemoteViews. 3039 * 3040 * @param viewId The id of the view on which to call the method. 3041 * @param methodName The name of the method to call. 3042 * @param value The value to pass to the method. 3043 */ 3044 public void setByte(int viewId, String methodName, byte value) { 3045 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); 3046 } 3047 3048 /** 3049 * Call a method taking one short on a view in the layout for this RemoteViews. 3050 * 3051 * @param viewId The id of the view on which to call the method. 3052 * @param methodName The name of the method to call. 3053 * @param value The value to pass to the method. 3054 */ 3055 public void setShort(int viewId, String methodName, short value) { 3056 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); 3057 } 3058 3059 /** 3060 * Call a method taking one int on a view in the layout for this RemoteViews. 3061 * 3062 * @param viewId The id of the view on which to call the method. 3063 * @param methodName The name of the method to call. 3064 * @param value The value to pass to the method. 3065 */ 3066 public void setInt(int viewId, String methodName, int value) { 3067 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); 3068 } 3069 3070 /** 3071 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 3072 * 3073 * @param viewId The id of the view on which to call the method. 3074 * @param methodName The name of the method to call. 3075 * @param value The value to pass to the method. 3076 * 3077 * @hide 3078 */ 3079 public void setColorStateList(int viewId, String methodName, ColorStateList value) { 3080 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.COLOR_STATE_LIST, 3081 value)); 3082 } 3083 3084 3085 /** 3086 * Call a method taking one long on a view in the layout for this RemoteViews. 3087 * 3088 * @param viewId The id of the view on which to call the method. 3089 * @param methodName The name of the method to call. 3090 * @param value The value to pass to the method. 3091 */ 3092 public void setLong(int viewId, String methodName, long value) { 3093 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); 3094 } 3095 3096 /** 3097 * Call a method taking one float on a view in the layout for this RemoteViews. 3098 * 3099 * @param viewId The id of the view on which to call the method. 3100 * @param methodName The name of the method to call. 3101 * @param value The value to pass to the method. 3102 */ 3103 public void setFloat(int viewId, String methodName, float value) { 3104 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); 3105 } 3106 3107 /** 3108 * Call a method taking one double on a view in the layout for this RemoteViews. 3109 * 3110 * @param viewId The id of the view on which to call the method. 3111 * @param methodName The name of the method to call. 3112 * @param value The value to pass to the method. 3113 */ 3114 public void setDouble(int viewId, String methodName, double value) { 3115 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); 3116 } 3117 3118 /** 3119 * Call a method taking one char on a view in the layout for this RemoteViews. 3120 * 3121 * @param viewId The id of the view on which to call the method. 3122 * @param methodName The name of the method to call. 3123 * @param value The value to pass to the method. 3124 */ 3125 public void setChar(int viewId, String methodName, char value) { 3126 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); 3127 } 3128 3129 /** 3130 * Call a method taking one String on a view in the layout for this RemoteViews. 3131 * 3132 * @param viewId The id of the view on which to call the method. 3133 * @param methodName The name of the method to call. 3134 * @param value The value to pass to the method. 3135 */ 3136 public void setString(int viewId, String methodName, String value) { 3137 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); 3138 } 3139 3140 /** 3141 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 3142 * 3143 * @param viewId The id of the view on which to call the method. 3144 * @param methodName The name of the method to call. 3145 * @param value The value to pass to the method. 3146 */ 3147 public void setCharSequence(int viewId, String methodName, CharSequence value) { 3148 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); 3149 } 3150 3151 /** 3152 * Call a method taking one Uri on a view in the layout for this RemoteViews. 3153 * 3154 * @param viewId The id of the view on which to call the method. 3155 * @param methodName The name of the method to call. 3156 * @param value The value to pass to the method. 3157 */ 3158 public void setUri(int viewId, String methodName, Uri value) { 3159 if (value != null) { 3160 // Resolve any filesystem path before sending remotely 3161 value = value.getCanonicalUri(); 3162 if (StrictMode.vmFileUriExposureEnabled()) { 3163 value.checkFileUriExposed("RemoteViews.setUri()"); 3164 } 3165 } 3166 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); 3167 } 3168 3169 /** 3170 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 3171 * @more 3172 * <p class="note">The bitmap will be flattened into the parcel if this object is 3173 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 3174 * 3175 * @param viewId The id of the view on which to call the method. 3176 * @param methodName The name of the method to call. 3177 * @param value The value to pass to the method. 3178 */ 3179 public void setBitmap(int viewId, String methodName, Bitmap value) { 3180 addAction(new BitmapReflectionAction(viewId, methodName, value)); 3181 } 3182 3183 /** 3184 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 3185 * 3186 * @param viewId The id of the view on which to call the method. 3187 * @param methodName The name of the method to call. 3188 * @param value The value to pass to the method. 3189 */ 3190 public void setBundle(int viewId, String methodName, Bundle value) { 3191 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); 3192 } 3193 3194 /** 3195 * Call a method taking one Intent on a view in the layout for this RemoteViews. 3196 * 3197 * @param viewId The id of the view on which to call the method. 3198 * @param methodName The name of the method to call. 3199 * @param value The {@link android.content.Intent} to pass the method. 3200 */ 3201 public void setIntent(int viewId, String methodName, Intent value) { 3202 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); 3203 } 3204 3205 /** 3206 * Call a method taking one Icon on a view in the layout for this RemoteViews. 3207 * 3208 * @param viewId The id of the view on which to call the method. 3209 * @param methodName The name of the method to call. 3210 * @param value The {@link android.graphics.drawable.Icon} to pass the method. 3211 */ 3212 public void setIcon(int viewId, String methodName, Icon value) { 3213 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value)); 3214 } 3215 3216 /** 3217 * Equivalent to calling View.setContentDescription(CharSequence). 3218 * 3219 * @param viewId The id of the view whose content description should change. 3220 * @param contentDescription The new content description for the view. 3221 */ 3222 public void setContentDescription(int viewId, CharSequence contentDescription) { 3223 setCharSequence(viewId, "setContentDescription", contentDescription); 3224 } 3225 3226 /** 3227 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. 3228 * 3229 * @param viewId The id of the view whose before view in accessibility traversal to set. 3230 * @param nextId The id of the next in the accessibility traversal. 3231 **/ 3232 public void setAccessibilityTraversalBefore(int viewId, int nextId) { 3233 setInt(viewId, "setAccessibilityTraversalBefore", nextId); 3234 } 3235 3236 /** 3237 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. 3238 * 3239 * @param viewId The id of the view whose after view in accessibility traversal to set. 3240 * @param nextId The id of the next in the accessibility traversal. 3241 **/ 3242 public void setAccessibilityTraversalAfter(int viewId, int nextId) { 3243 setInt(viewId, "setAccessibilityTraversalAfter", nextId); 3244 } 3245 3246 /** 3247 * Equivalent to calling {@link View#setLabelFor(int)}. 3248 * 3249 * @param viewId The id of the view whose property to set. 3250 * @param labeledId The id of a view for which this view serves as a label. 3251 */ 3252 public void setLabelFor(int viewId, int labeledId) { 3253 setInt(viewId, "setLabelFor", labeledId); 3254 } 3255 3256 private RemoteViews getRemoteViewsToApply(Context context) { 3257 if (hasLandscapeAndPortraitLayouts()) { 3258 int orientation = context.getResources().getConfiguration().orientation; 3259 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 3260 return mLandscape; 3261 } else { 3262 return mPortrait; 3263 } 3264 } 3265 return this; 3266 } 3267 3268 /** 3269 * Inflates the view hierarchy represented by this object and applies 3270 * all of the actions. 3271 * 3272 * <p><strong>Caller beware: this may throw</strong> 3273 * 3274 * @param context Default context to use 3275 * @param parent Parent that the resulting view hierarchy will be attached to. This method 3276 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 3277 * @return The inflated view hierarchy 3278 */ 3279 public View apply(Context context, ViewGroup parent) { 3280 return apply(context, parent, null); 3281 } 3282 3283 /** @hide */ 3284 public View apply(Context context, ViewGroup parent, OnClickHandler handler) { 3285 RemoteViews rvToApply = getRemoteViewsToApply(context); 3286 3287 View result = inflateView(context, rvToApply, parent); 3288 loadTransitionOverride(context, handler); 3289 3290 rvToApply.performApply(result, parent, handler); 3291 3292 return result; 3293 } 3294 3295 private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { 3296 // RemoteViews may be built by an application installed in another 3297 // user. So build a context that loads resources from that user but 3298 // still returns the current users userId so settings like data / time formats 3299 // are loaded without requiring cross user persmissions. 3300 final Context contextForResources = getContextForResources(context); 3301 Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources); 3302 3303 LayoutInflater inflater = (LayoutInflater) 3304 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 3305 3306 // Clone inflater so we load resources from correct context and 3307 // we don't add a filter to the static version returned by getSystemService. 3308 inflater = inflater.cloneInContext(inflationContext); 3309 inflater.setFilter(this); 3310 View v = inflater.inflate(rv.getLayoutId(), parent, false); 3311 v.setTagInternal(R.id.widget_frame, rv.getLayoutId()); 3312 return v; 3313 } 3314 3315 private static void loadTransitionOverride(Context context, 3316 RemoteViews.OnClickHandler handler) { 3317 if (handler != null && context.getResources().getBoolean( 3318 com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { 3319 TypedArray windowStyle = context.getTheme().obtainStyledAttributes( 3320 com.android.internal.R.styleable.Window); 3321 int windowAnimations = windowStyle.getResourceId( 3322 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 3323 TypedArray windowAnimationStyle = context.obtainStyledAttributes( 3324 windowAnimations, com.android.internal.R.styleable.WindowAnimation); 3325 handler.setEnterAnimationId(windowAnimationStyle.getResourceId( 3326 com.android.internal.R.styleable. 3327 WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0)); 3328 windowStyle.recycle(); 3329 windowAnimationStyle.recycle(); 3330 } 3331 } 3332 3333 /** 3334 * Implement this interface to receive a callback when 3335 * {@link #applyAsync} or {@link #reapplyAsync} is finished. 3336 * @hide 3337 */ 3338 public interface OnViewAppliedListener { 3339 void onViewApplied(View v); 3340 3341 void onError(Exception e); 3342 } 3343 3344 /** 3345 * Applies the views asynchronously, moving as much of the task on the background 3346 * thread as possible. 3347 * 3348 * @see #apply(Context, ViewGroup) 3349 * @param context Default context to use 3350 * @param parent Parent that the resulting view hierarchy will be attached to. This method 3351 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 3352 * @param listener the callback to run when all actions have been applied. May be null. 3353 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used. 3354 * @return CancellationSignal 3355 * @hide 3356 */ 3357 public CancellationSignal applyAsync( 3358 Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) { 3359 return applyAsync(context, parent, executor, listener, null); 3360 } 3361 3362 private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) { 3363 CancellationSignal cancelSignal = new CancellationSignal(); 3364 cancelSignal.setOnCancelListener(task); 3365 3366 task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor); 3367 return cancelSignal; 3368 } 3369 3370 /** @hide */ 3371 public CancellationSignal applyAsync(Context context, ViewGroup parent, 3372 Executor executor, OnViewAppliedListener listener, OnClickHandler handler) { 3373 return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor); 3374 } 3375 3376 private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, 3377 OnViewAppliedListener listener, OnClickHandler handler) { 3378 return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener, 3379 handler, null); 3380 } 3381 3382 private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> 3383 implements CancellationSignal.OnCancelListener { 3384 final RemoteViews mRV; 3385 final ViewGroup mParent; 3386 final Context mContext; 3387 final OnViewAppliedListener mListener; 3388 final OnClickHandler mHandler; 3389 3390 private View mResult; 3391 private ViewTree mTree; 3392 private Action[] mActions; 3393 private Exception mError; 3394 3395 private AsyncApplyTask( 3396 RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, 3397 OnClickHandler handler, View result) { 3398 mRV = rv; 3399 mParent = parent; 3400 mContext = context; 3401 mListener = listener; 3402 mHandler = handler; 3403 3404 mResult = result; 3405 loadTransitionOverride(context, handler); 3406 } 3407 3408 @Override 3409 protected ViewTree doInBackground(Void... params) { 3410 try { 3411 if (mResult == null) { 3412 mResult = inflateView(mContext, mRV, mParent); 3413 } 3414 3415 mTree = new ViewTree(mResult); 3416 if (mRV.mActions != null) { 3417 int count = mRV.mActions.size(); 3418 mActions = new Action[count]; 3419 for (int i = 0; i < count && !isCancelled(); i++) { 3420 // TODO: check if isCancelled in nested views. 3421 mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler); 3422 } 3423 } else { 3424 mActions = null; 3425 } 3426 return mTree; 3427 } catch (Exception e) { 3428 mError = e; 3429 return null; 3430 } 3431 } 3432 3433 @Override 3434 protected void onPostExecute(ViewTree viewTree) { 3435 if (mError == null) { 3436 try { 3437 if (mActions != null) { 3438 OnClickHandler handler = mHandler == null 3439 ? DEFAULT_ON_CLICK_HANDLER : mHandler; 3440 for (Action a : mActions) { 3441 a.apply(viewTree.mRoot, mParent, handler); 3442 } 3443 } 3444 } catch (Exception e) { 3445 mError = e; 3446 } 3447 } 3448 3449 if (mListener != null) { 3450 if (mError != null) { 3451 mListener.onError(mError); 3452 } else { 3453 mListener.onViewApplied(viewTree.mRoot); 3454 } 3455 } else if (mError != null) { 3456 if (mError instanceof ActionException) { 3457 throw (ActionException) mError; 3458 } else { 3459 throw new ActionException(mError); 3460 } 3461 } 3462 } 3463 3464 @Override 3465 public void onCancel() { 3466 cancel(true); 3467 } 3468 } 3469 3470 /** 3471 * Applies all of the actions to the provided view. 3472 * 3473 * <p><strong>Caller beware: this may throw</strong> 3474 * 3475 * @param v The view to apply the actions to. This should be the result of 3476 * the {@link #apply(Context,ViewGroup)} call. 3477 */ 3478 public void reapply(Context context, View v) { 3479 reapply(context, v, null); 3480 } 3481 3482 /** @hide */ 3483 public void reapply(Context context, View v, OnClickHandler handler) { 3484 RemoteViews rvToApply = getRemoteViewsToApply(context); 3485 3486 // In the case that a view has this RemoteViews applied in one orientation, is persisted 3487 // across orientation change, and has the RemoteViews re-applied in the new orientation, 3488 // we throw an exception, since the layouts may be completely unrelated. 3489 if (hasLandscapeAndPortraitLayouts()) { 3490 if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { 3491 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 3492 " that does not share the same root layout id."); 3493 } 3494 } 3495 3496 rvToApply.performApply(v, (ViewGroup) v.getParent(), handler); 3497 } 3498 3499 /** 3500 * Applies all the actions to the provided view, moving as much of the task on the background 3501 * thread as possible. 3502 * 3503 * @see #reapply(Context, View) 3504 * @param context Default context to use 3505 * @param v The view to apply the actions to. This should be the result of 3506 * the {@link #apply(Context,ViewGroup)} call. 3507 * @param listener the callback to run when all actions have been applied. May be null. 3508 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used 3509 * @return CancellationSignal 3510 * @hide 3511 */ 3512 public CancellationSignal reapplyAsync( 3513 Context context, View v, Executor executor, OnViewAppliedListener listener) { 3514 return reapplyAsync(context, v, executor, listener, null); 3515 } 3516 3517 /** @hide */ 3518 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 3519 OnViewAppliedListener listener, OnClickHandler handler) { 3520 RemoteViews rvToApply = getRemoteViewsToApply(context); 3521 3522 // In the case that a view has this RemoteViews applied in one orientation, is persisted 3523 // across orientation change, and has the RemoteViews re-applied in the new orientation, 3524 // we throw an exception, since the layouts may be completely unrelated. 3525 if (hasLandscapeAndPortraitLayouts()) { 3526 if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { 3527 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 3528 " that does not share the same root layout id."); 3529 } 3530 } 3531 3532 return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), 3533 context, listener, handler, v), executor); 3534 } 3535 3536 private void performApply(View v, ViewGroup parent, OnClickHandler handler) { 3537 if (mActions != null) { 3538 handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler; 3539 final int count = mActions.size(); 3540 for (int i = 0; i < count; i++) { 3541 Action a = mActions.get(i); 3542 a.apply(v, parent, handler); 3543 } 3544 } 3545 } 3546 3547 /** 3548 * Returns true if the RemoteViews contains potentially costly operations and should be 3549 * applied asynchronously. 3550 * 3551 * @hide 3552 */ 3553 public boolean prefersAsyncApply() { 3554 if (mActions != null) { 3555 final int count = mActions.size(); 3556 for (int i = 0; i < count; i++) { 3557 if (mActions.get(i).prefersAsyncApply()) { 3558 return true; 3559 } 3560 } 3561 } 3562 return false; 3563 } 3564 3565 private Context getContextForResources(Context context) { 3566 if (mApplication != null) { 3567 if (context.getUserId() == UserHandle.getUserId(mApplication.uid) 3568 && context.getPackageName().equals(mApplication.packageName)) { 3569 return context; 3570 } 3571 try { 3572 return context.createApplicationContext(mApplication, 3573 Context.CONTEXT_RESTRICTED); 3574 } catch (NameNotFoundException e) { 3575 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); 3576 } 3577 } 3578 3579 return context; 3580 } 3581 3582 /** 3583 * Returns the number of actions in this RemoteViews. Can be used as a sequence number. 3584 * 3585 * @hide 3586 */ 3587 public int getSequenceNumber() { 3588 return (mActions == null) ? 0 : mActions.size(); 3589 } 3590 3591 /* (non-Javadoc) 3592 * Used to restrict the views which can be inflated 3593 * 3594 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 3595 */ 3596 public boolean onLoadClass(Class clazz) { 3597 return clazz.isAnnotationPresent(RemoteView.class); 3598 } 3599 3600 public int describeContents() { 3601 return 0; 3602 } 3603 3604 public void writeToParcel(Parcel dest, int flags) { 3605 if (!hasLandscapeAndPortraitLayouts()) { 3606 dest.writeInt(MODE_NORMAL); 3607 // We only write the bitmap cache if we are the root RemoteViews, as this cache 3608 // is shared by all children. 3609 if (mIsRoot) { 3610 mBitmapCache.writeBitmapsToParcel(dest, flags); 3611 } 3612 if (!mIsRoot && (flags & PARCELABLE_ELIDE_DUPLICATES) != 0) { 3613 dest.writeInt(0); 3614 } else { 3615 dest.writeInt(1); 3616 mApplication.writeToParcel(dest, flags); 3617 } 3618 dest.writeInt(mLayoutId); 3619 dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); 3620 writeActionsToParcel(dest); 3621 } else { 3622 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 3623 // We only write the bitmap cache if we are the root RemoteViews, as this cache 3624 // is shared by all children. 3625 if (mIsRoot) { 3626 mBitmapCache.writeBitmapsToParcel(dest, flags); 3627 } 3628 mLandscape.writeToParcel(dest, flags); 3629 // Both RemoteViews already share the same package and user 3630 mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES); 3631 } 3632 dest.writeInt(mReapplyDisallowed ? 1 : 0); 3633 } 3634 3635 private void writeActionsToParcel(Parcel parcel) { 3636 int count; 3637 if (mActions != null) { 3638 count = mActions.size(); 3639 } else { 3640 count = 0; 3641 } 3642 parcel.writeInt(count); 3643 for (int i = 0; i < count; i++) { 3644 Action a = mActions.get(i); 3645 parcel.writeInt(a.getActionTag()); 3646 a.writeToParcel(parcel, a.hasSameAppInfo(mApplication) 3647 ? PARCELABLE_ELIDE_DUPLICATES : 0); 3648 } 3649 } 3650 3651 private static ApplicationInfo getApplicationInfo(String packageName, int userId) { 3652 if (packageName == null) { 3653 return null; 3654 } 3655 3656 // Get the application for the passed in package and user. 3657 Application application = ActivityThread.currentApplication(); 3658 if (application == null) { 3659 throw new IllegalStateException("Cannot create remote views out of an aplication."); 3660 } 3661 3662 ApplicationInfo applicationInfo = application.getApplicationInfo(); 3663 if (UserHandle.getUserId(applicationInfo.uid) != userId 3664 || !applicationInfo.packageName.equals(packageName)) { 3665 try { 3666 Context context = application.getBaseContext().createPackageContextAsUser( 3667 packageName, 0, new UserHandle(userId)); 3668 applicationInfo = context.getApplicationInfo(); 3669 } catch (NameNotFoundException nnfe) { 3670 throw new IllegalArgumentException("No such package " + packageName); 3671 } 3672 } 3673 3674 return applicationInfo; 3675 } 3676 3677 /** 3678 * Returns true if the {@link #mApplication} is same as the provided info. 3679 * 3680 * @hide 3681 */ 3682 public boolean hasSameAppInfo(ApplicationInfo info) { 3683 return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid; 3684 } 3685 3686 /** 3687 * Parcelable.Creator that instantiates RemoteViews objects 3688 */ 3689 public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 3690 public RemoteViews createFromParcel(Parcel parcel) { 3691 return new RemoteViews(parcel); 3692 } 3693 3694 public RemoteViews[] newArray(int size) { 3695 return new RemoteViews[size]; 3696 } 3697 }; 3698 3699 /** 3700 * A representation of the view hierarchy. Only views which have a valid ID are added 3701 * and can be searched. 3702 */ 3703 private static class ViewTree { 3704 private static final int INSERT_AT_END_INDEX = -1; 3705 private View mRoot; 3706 private ArrayList<ViewTree> mChildren; 3707 3708 private ViewTree(View root) { 3709 mRoot = root; 3710 } 3711 3712 public void createTree() { 3713 if (mChildren != null) { 3714 return; 3715 } 3716 3717 mChildren = new ArrayList<>(); 3718 if (mRoot instanceof ViewGroup) { 3719 ViewGroup vg = (ViewGroup) mRoot; 3720 int count = vg.getChildCount(); 3721 for (int i = 0; i < count; i++) { 3722 addViewChild(vg.getChildAt(i)); 3723 } 3724 } 3725 } 3726 3727 public ViewTree findViewTreeById(int id) { 3728 if (mRoot.getId() == id) { 3729 return this; 3730 } 3731 if (mChildren == null) { 3732 return null; 3733 } 3734 for (ViewTree tree : mChildren) { 3735 ViewTree result = tree.findViewTreeById(id); 3736 if (result != null) { 3737 return result; 3738 } 3739 } 3740 return null; 3741 } 3742 3743 public void replaceView(View v) { 3744 mRoot = v; 3745 mChildren = null; 3746 createTree(); 3747 } 3748 3749 public <T extends View> T findViewById(int id) { 3750 if (mChildren == null) { 3751 return mRoot.findViewById(id); 3752 } 3753 ViewTree tree = findViewTreeById(id); 3754 return tree == null ? null : (T) tree.mRoot; 3755 } 3756 3757 public void addChild(ViewTree child) { 3758 addChild(child, INSERT_AT_END_INDEX); 3759 } 3760 3761 /** 3762 * Adds the given {@link ViewTree} as a child at the given index. 3763 * 3764 * @param index The position at which to add the child or -1 to add last. 3765 */ 3766 public void addChild(ViewTree child, int index) { 3767 if (mChildren == null) { 3768 mChildren = new ArrayList<>(); 3769 } 3770 child.createTree(); 3771 3772 if (index == INSERT_AT_END_INDEX) { 3773 mChildren.add(child); 3774 return; 3775 } 3776 3777 mChildren.add(index, child); 3778 } 3779 3780 private void addViewChild(View v) { 3781 // ViewTree only contains Views which can be found using findViewById. 3782 // If isRootNamespace is true, this view is skipped. 3783 // @see ViewGroup#findViewTraversal(int) 3784 if (v.isRootNamespace()) { 3785 return; 3786 } 3787 final ViewTree target; 3788 3789 // If the view has a valid id, i.e., if can be found using findViewById, add it to the 3790 // tree, otherwise skip this view and add its children instead. 3791 if (v.getId() != 0) { 3792 ViewTree tree = new ViewTree(v); 3793 mChildren.add(tree); 3794 target = tree; 3795 } else { 3796 target = this; 3797 } 3798 3799 if (v instanceof ViewGroup) { 3800 if (target.mChildren == null) { 3801 target.mChildren = new ArrayList<>(); 3802 ViewGroup vg = (ViewGroup) v; 3803 int count = vg.getChildCount(); 3804 for (int i = 0; i < count; i++) { 3805 target.addViewChild(vg.getChildAt(i)); 3806 } 3807 } 3808 } 3809 } 3810 } 3811} 3812