1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import android.annotation.AttrRes; 20 import android.annotation.ColorInt; 21 import android.annotation.ColorRes; 22 import android.annotation.DimenRes; 23 import android.annotation.DrawableRes; 24 import android.annotation.IdRes; 25 import android.annotation.IntDef; 26 import android.annotation.LayoutRes; 27 import android.annotation.NonNull; 28 import android.annotation.Nullable; 29 import android.annotation.Px; 30 import android.annotation.StringRes; 31 import android.annotation.StyleRes; 32 import android.annotation.SuppressLint; 33 import android.app.Activity; 34 import android.app.ActivityOptions; 35 import android.app.ActivityThread; 36 import android.app.Application; 37 import android.app.LoadedApk; 38 import android.app.PendingIntent; 39 import android.app.RemoteInput; 40 import android.appwidget.AppWidgetHostView; 41 import android.compat.annotation.UnsupportedAppUsage; 42 import android.content.Context; 43 import android.content.ContextWrapper; 44 import android.content.Intent; 45 import android.content.IntentSender; 46 import android.content.pm.ApplicationInfo; 47 import android.content.pm.PackageManager.NameNotFoundException; 48 import android.content.res.ColorStateList; 49 import android.content.res.Configuration; 50 import android.content.res.Resources; 51 import android.content.res.TypedArray; 52 import android.content.res.loader.ResourcesLoader; 53 import android.content.res.loader.ResourcesProvider; 54 import android.graphics.Bitmap; 55 import android.graphics.BlendMode; 56 import android.graphics.Outline; 57 import android.graphics.PorterDuff; 58 import android.graphics.Rect; 59 import android.graphics.drawable.Drawable; 60 import android.graphics.drawable.Icon; 61 import android.graphics.drawable.RippleDrawable; 62 import android.net.Uri; 63 import android.os.AsyncTask; 64 import android.os.Binder; 65 import android.os.Build; 66 import android.os.Bundle; 67 import android.os.CancellationSignal; 68 import android.os.Parcel; 69 import android.os.ParcelFileDescriptor; 70 import android.os.Parcelable; 71 import android.os.Process; 72 import android.os.StrictMode; 73 import android.os.UserHandle; 74 import android.system.Os; 75 import android.text.TextUtils; 76 import android.util.ArrayMap; 77 import android.util.DisplayMetrics; 78 import android.util.IntArray; 79 import android.util.Log; 80 import android.util.LongArray; 81 import android.util.Pair; 82 import android.util.SizeF; 83 import android.util.SparseIntArray; 84 import android.util.TypedValue; 85 import android.util.TypedValue.ComplexDimensionUnit; 86 import android.view.ContextThemeWrapper; 87 import android.view.LayoutInflater; 88 import android.view.LayoutInflater.Filter; 89 import android.view.RemotableViewMethod; 90 import android.view.View; 91 import android.view.ViewGroup; 92 import android.view.ViewGroup.MarginLayoutParams; 93 import android.view.ViewManager; 94 import android.view.ViewOutlineProvider; 95 import android.view.ViewParent; 96 import android.view.ViewStub; 97 import android.widget.AdapterView.OnItemClickListener; 98 import android.widget.CompoundButton.OnCheckedChangeListener; 99 100 import com.android.internal.R; 101 import com.android.internal.util.ContrastColorUtil; 102 import com.android.internal.util.Preconditions; 103 104 import java.io.ByteArrayOutputStream; 105 import java.io.FileDescriptor; 106 import java.io.FileOutputStream; 107 import java.io.IOException; 108 import java.io.InputStream; 109 import java.io.OutputStream; 110 import java.lang.annotation.ElementType; 111 import java.lang.annotation.Retention; 112 import java.lang.annotation.RetentionPolicy; 113 import java.lang.annotation.Target; 114 import java.lang.invoke.MethodHandle; 115 import java.lang.invoke.MethodHandles; 116 import java.lang.invoke.MethodType; 117 import java.lang.reflect.Method; 118 import java.util.ArrayDeque; 119 import java.util.ArrayList; 120 import java.util.Arrays; 121 import java.util.HashMap; 122 import java.util.Iterator; 123 import java.util.List; 124 import java.util.Map; 125 import java.util.Objects; 126 import java.util.Stack; 127 import java.util.concurrent.Executor; 128 import java.util.function.Consumer; 129 import java.util.function.Predicate; 130 131 /** 132 * A class that describes a view hierarchy that can be displayed in 133 * another process. The hierarchy is inflated from a layout resource 134 * file, and this class provides some basic operations for modifying 135 * the content of the inflated hierarchy. 136 * 137 * <p>{@code RemoteViews} is limited to support for the following layouts:</p> 138 * <ul> 139 * <li>{@link android.widget.AdapterViewFlipper}</li> 140 * <li>{@link android.widget.FrameLayout}</li> 141 * <li>{@link android.widget.GridLayout}</li> 142 * <li>{@link android.widget.GridView}</li> 143 * <li>{@link android.widget.LinearLayout}</li> 144 * <li>{@link android.widget.ListView}</li> 145 * <li>{@link android.widget.RelativeLayout}</li> 146 * <li>{@link android.widget.StackView}</li> 147 * <li>{@link android.widget.ViewFlipper}</li> 148 * </ul> 149 * <p>And the following widgets:</p> 150 * <ul> 151 * <li>{@link android.widget.AnalogClock}</li> 152 * <li>{@link android.widget.Button}</li> 153 * <li>{@link android.widget.Chronometer}</li> 154 * <li>{@link android.widget.ImageButton}</li> 155 * <li>{@link android.widget.ImageView}</li> 156 * <li>{@link android.widget.ProgressBar}</li> 157 * <li>{@link android.widget.TextClock}</li> 158 * <li>{@link android.widget.TextView}</li> 159 * </ul> 160 * <p>As of API 31, the following widgets and layouts may also be used:</p> 161 * <ul> 162 * <li>{@link android.widget.CheckBox}</li> 163 * <li>{@link android.widget.RadioButton}</li> 164 * <li>{@link android.widget.RadioGroup}</li> 165 * <li>{@link android.widget.Switch}</li> 166 * </ul> 167 * <p>Descendants of these classes are not supported.</p> 168 */ 169 public class RemoteViews implements Parcelable, Filter { 170 171 private static final String LOG_TAG = "RemoteViews"; 172 173 /** The intent extra for whether the view whose checked state changed is currently checked. */ 174 public static final String EXTRA_CHECKED = "android.widget.extra.CHECKED"; 175 176 /** 177 * The intent extra that contains the appWidgetId. 178 * @hide 179 */ 180 static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; 181 182 /** 183 * The intent extra that contains {@code true} if inflating as dak text theme. 184 * @hide 185 */ 186 static final String EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND = "remoteAdapterOnLightBackground"; 187 188 /** 189 * The intent extra that contains the bounds for all shared elements. 190 */ 191 public static final String EXTRA_SHARED_ELEMENT_BOUNDS = 192 "android.widget.extra.SHARED_ELEMENT_BOUNDS"; 193 194 /** 195 * Maximum depth of nested views calls from {@link #addView(int, RemoteViews)} and 196 * {@link #RemoteViews(RemoteViews, RemoteViews)}. 197 */ 198 private static final int MAX_NESTED_VIEWS = 10; 199 200 /** 201 * Maximum number of RemoteViews that can be specified in constructor. 202 */ 203 private static final int MAX_INIT_VIEW_COUNT = 16; 204 205 // The unique identifiers for each custom {@link Action}. 206 private static final int SET_ON_CLICK_RESPONSE_TAG = 1; 207 private static final int REFLECTION_ACTION_TAG = 2; 208 private static final int SET_DRAWABLE_TINT_TAG = 3; 209 private static final int VIEW_GROUP_ACTION_ADD_TAG = 4; 210 private static final int VIEW_CONTENT_NAVIGATION_TAG = 5; 211 private static final int SET_EMPTY_VIEW_ACTION_TAG = 6; 212 private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7; 213 private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8; 214 private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10; 215 private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11; 216 private static final int BITMAP_REFLECTION_ACTION_TAG = 12; 217 private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13; 218 private static final int VIEW_PADDING_ACTION_TAG = 14; 219 private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15; 220 private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18; 221 private static final int LAYOUT_PARAM_ACTION_TAG = 19; 222 private static final int OVERRIDE_TEXT_COLORS_TAG = 20; 223 private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21; 224 private static final int SET_INT_TAG_TAG = 22; 225 private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23; 226 private static final int RESOURCE_REFLECTION_ACTION_TAG = 24; 227 private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25; 228 private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26; 229 private static final int SET_RADIO_GROUP_CHECKED = 27; 230 private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28; 231 private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29; 232 private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30; 233 private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31; 234 private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32; 235 236 /** @hide **/ 237 @IntDef(prefix = "MARGIN_", value = { 238 MARGIN_LEFT, 239 MARGIN_TOP, 240 MARGIN_RIGHT, 241 MARGIN_BOTTOM, 242 MARGIN_START, 243 MARGIN_END 244 }) 245 @Retention(RetentionPolicy.SOURCE) 246 public @interface MarginType {} 247 /** The value will apply to the marginLeft. */ 248 public static final int MARGIN_LEFT = 0; 249 /** The value will apply to the marginTop. */ 250 public static final int MARGIN_TOP = 1; 251 /** The value will apply to the marginRight. */ 252 public static final int MARGIN_RIGHT = 2; 253 /** The value will apply to the marginBottom. */ 254 public static final int MARGIN_BOTTOM = 3; 255 /** The value will apply to the marginStart. */ 256 public static final int MARGIN_START = 4; 257 /** The value will apply to the marginEnd. */ 258 public static final int MARGIN_END = 5; 259 260 @IntDef(prefix = "VALUE_TYPE_", value = { 261 VALUE_TYPE_RAW, 262 VALUE_TYPE_COMPLEX_UNIT, 263 VALUE_TYPE_RESOURCE, 264 VALUE_TYPE_ATTRIBUTE 265 }) 266 @Retention(RetentionPolicy.SOURCE) 267 @interface ValueType {} 268 static final int VALUE_TYPE_RAW = 1; 269 static final int VALUE_TYPE_COMPLEX_UNIT = 2; 270 static final int VALUE_TYPE_RESOURCE = 3; 271 static final int VALUE_TYPE_ATTRIBUTE = 4; 272 273 /** @hide **/ 274 @IntDef(flag = true, value = { 275 FLAG_REAPPLY_DISALLOWED, 276 FLAG_WIDGET_IS_COLLECTION_CHILD, 277 FLAG_USE_LIGHT_BACKGROUND_LAYOUT 278 }) 279 @Retention(RetentionPolicy.SOURCE) 280 public @interface ApplyFlags {} 281 /** 282 * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify 283 * the layout in a way that isn't recoverable, since views are being removed. 284 * @hide 285 */ 286 public static final int FLAG_REAPPLY_DISALLOWED = 1; 287 /** 288 * This flag indicates whether this RemoteViews object is being created from a 289 * RemoteViewsService for use as a child of a widget collection. This flag is used 290 * to determine whether or not certain features are available, in particular, 291 * setting on click extras and setting on click pending intents. The former is enabled, 292 * and the latter disabled when this flag is true. 293 * @hide 294 */ 295 public static final int FLAG_WIDGET_IS_COLLECTION_CHILD = 2; 296 /** 297 * When this flag is set, the views is inflated with {@link #mLightBackgroundLayoutId} instead 298 * of {link #mLayoutId} 299 * @hide 300 */ 301 public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4; 302 303 /** 304 * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is 305 * intentionally a different instance in order to trick Bundle reader so that it doesn't allow 306 * lazy initialization. 307 */ 308 private static final Parcel.ReadWriteHelper ALTERNATIVE_DEFAULT = new Parcel.ReadWriteHelper(); 309 310 /** 311 * Used to restrict the views which can be inflated 312 * 313 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 314 */ 315 private static final LayoutInflater.Filter INFLATER_FILTER = 316 (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); 317 318 /** 319 * Application that hosts the remote views. 320 * 321 * @hide 322 */ 323 @UnsupportedAppUsage 324 public ApplicationInfo mApplication; 325 326 /** 327 * The resource ID of the layout file. (Added to the parcel) 328 */ 329 @UnsupportedAppUsage 330 private int mLayoutId; 331 332 /** 333 * The resource ID of the layout file in dark text mode. (Added to the parcel) 334 */ 335 private int mLightBackgroundLayoutId = 0; 336 337 /** 338 * An array of actions to perform on the view tree once it has been 339 * inflated 340 */ 341 @UnsupportedAppUsage 342 private ArrayList<Action> mActions; 343 344 /** 345 * Maps bitmaps to unique indicies to avoid Bitmap duplication. 346 */ 347 @UnsupportedAppUsage 348 private BitmapCache mBitmapCache = new BitmapCache(); 349 350 /** Cache of ApplicationInfos used by collection items. */ 351 private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache(); 352 353 /** 354 * Indicates whether or not this RemoteViews object is contained as a child of any other 355 * RemoteViews. 356 */ 357 private boolean mIsRoot = true; 358 359 /** 360 * Constants to whether or not this RemoteViews is composed of a landscape and portrait 361 * RemoteViews. 362 */ 363 private static final int MODE_NORMAL = 0; 364 private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; 365 private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2; 366 367 /** 368 * Used in conjunction with the special constructor 369 * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait 370 * RemoteViews. 371 */ 372 private RemoteViews mLandscape = null; 373 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 374 private RemoteViews mPortrait = null; 375 /** 376 * List of RemoteViews with their ideal size. There must be at least two if the map is not null. 377 * 378 * The smallest remote view is always the last element in the list. 379 */ 380 private List<RemoteViews> mSizedRemoteViews = null; 381 382 /** 383 * Ideal size for this RemoteViews. 384 * 385 * Only to be used on children views used in a {@link RemoteViews} with 386 * {@link RemoteViews#hasSizedRemoteViews()}. 387 */ 388 private SizeF mIdealSize = null; 389 390 @ApplyFlags 391 private int mApplyFlags = 0; 392 393 /** 394 * Id to use to override the ID of the top-level view in this RemoteViews. 395 * 396 * Only used if this RemoteViews is defined from a XML layout value. 397 */ 398 private int mViewId = View.NO_ID; 399 400 /** 401 * Id used to uniquely identify a {@link RemoteViews} instance coming from a given provider. 402 */ 403 private long mProviderInstanceId = -1; 404 405 /** Class cookies of the Parcel this instance was read from. */ 406 private Map<Class, Object> mClassCookies; 407 408 private static final InteractionHandler DEFAULT_INTERACTION_HANDLER = 409 (view, pendingIntent, response) -> 410 startPendingIntent(view, pendingIntent, response.getLaunchOptions(view)); 411 412 private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); 413 414 /** 415 * This key is used to perform lookups in sMethods without causing allocations. 416 */ 417 private static final MethodKey sLookupKey = new MethodKey(); 418 419 /** 420 * @hide 421 */ setRemoteInputs(@dRes int viewId, RemoteInput[] remoteInputs)422 public void setRemoteInputs(@IdRes int viewId, RemoteInput[] remoteInputs) { 423 mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); 424 } 425 426 /** 427 * Reduces all images and ensures that they are all below the given sizes. 428 * 429 * @param maxWidth the maximum width allowed 430 * @param maxHeight the maximum height allowed 431 * 432 * @hide 433 */ reduceImageSizes(int maxWidth, int maxHeight)434 public void reduceImageSizes(int maxWidth, int maxHeight) { 435 ArrayList<Bitmap> cache = mBitmapCache.mBitmaps; 436 for (int i = 0; i < cache.size(); i++) { 437 Bitmap bitmap = cache.get(i); 438 cache.set(i, Icon.scaleDownIfNecessary(bitmap, maxWidth, maxHeight)); 439 } 440 } 441 442 /** 443 * Override all text colors in this layout and replace them by the given text color. 444 * 445 * @param textColor The color to use. 446 * 447 * @hide 448 */ overrideTextColors(int textColor)449 public void overrideTextColors(int textColor) { 450 addAction(new OverrideTextColorsAction(textColor)); 451 } 452 453 /** 454 * Sets an integer tag to the view. 455 * 456 * @hide 457 */ setIntTag(@dRes int viewId, @IdRes int key, int tag)458 public void setIntTag(@IdRes int viewId, @IdRes int key, int tag) { 459 addAction(new SetIntTagAction(viewId, key, tag)); 460 } 461 462 /** 463 * Set that it is disallowed to reapply another remoteview with the same layout as this view. 464 * This should be done if an action is destroying the view tree of the base layout. 465 * 466 * @hide 467 */ addFlags(@pplyFlags int flags)468 public void addFlags(@ApplyFlags int flags) { 469 mApplyFlags = mApplyFlags | flags; 470 } 471 472 /** 473 * @hide 474 */ hasFlags(@pplyFlags int flag)475 public boolean hasFlags(@ApplyFlags int flag) { 476 return (mApplyFlags & flag) == flag; 477 } 478 479 /** 480 * Stores information related to reflection method lookup. 481 */ 482 static class MethodKey { 483 public Class targetClass; 484 public Class paramClass; 485 public String methodName; 486 487 @Override equals(@ullable Object o)488 public boolean equals(@Nullable Object o) { 489 if (!(o instanceof MethodKey)) { 490 return false; 491 } 492 MethodKey p = (MethodKey) o; 493 return Objects.equals(p.targetClass, targetClass) 494 && Objects.equals(p.paramClass, paramClass) 495 && Objects.equals(p.methodName, methodName); 496 } 497 498 @Override hashCode()499 public int hashCode() { 500 return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) 501 ^ Objects.hashCode(methodName); 502 } 503 set(Class targetClass, Class paramClass, String methodName)504 public void set(Class targetClass, Class paramClass, String methodName) { 505 this.targetClass = targetClass; 506 this.paramClass = paramClass; 507 this.methodName = methodName; 508 } 509 } 510 511 512 /** 513 * Stores information related to reflection method lookup result. 514 */ 515 static class MethodArgs { 516 public MethodHandle syncMethod; 517 public MethodHandle asyncMethod; 518 public String asyncMethodName; 519 } 520 521 /** 522 * This annotation indicates that a subclass of View is allowed to be used 523 * with the {@link RemoteViews} mechanism. 524 */ 525 @Target({ ElementType.TYPE }) 526 @Retention(RetentionPolicy.RUNTIME) 527 public @interface RemoteView { 528 } 529 530 /** 531 * Exception to send when something goes wrong executing an action 532 * 533 */ 534 public static class ActionException extends RuntimeException { ActionException(Exception ex)535 public ActionException(Exception ex) { 536 super(ex); 537 } ActionException(String message)538 public ActionException(String message) { 539 super(message); 540 } 541 /** 542 * @hide 543 */ ActionException(Throwable t)544 public ActionException(Throwable t) { 545 super(t); 546 } 547 } 548 549 /** 550 * Handler for view interactions (such as clicks) within a RemoteViews. 551 * 552 * @hide 553 */ 554 public interface InteractionHandler { 555 /** 556 * Invoked when the user performs an interaction on the View. 557 * 558 * @param view the View with which the user interacted 559 * @param pendingIntent the base PendingIntent associated with the view 560 * @param response the response to the interaction, which knows how to fill in the 561 * attached PendingIntent 562 * 563 * @hide 564 */ onInteraction( View view, PendingIntent pendingIntent, RemoteResponse response)565 boolean onInteraction( 566 View view, 567 PendingIntent pendingIntent, 568 RemoteResponse response); 569 } 570 571 /** 572 * Base class for all actions that can be performed on an 573 * inflated view. 574 * 575 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 576 */ 577 private abstract static class Action implements Parcelable { apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)578 public abstract void apply(View root, ViewGroup rootParent, InteractionHandler handler, 579 ColorResources colorResources) throws ActionException; 580 581 public static final int MERGE_REPLACE = 0; 582 public static final int MERGE_APPEND = 1; 583 public static final int MERGE_IGNORE = 2; 584 describeContents()585 public int describeContents() { 586 return 0; 587 } 588 setHierarchyRootData(HierarchyRootData root)589 public void setHierarchyRootData(HierarchyRootData root) { 590 // Do nothing 591 } 592 593 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) mergeBehavior()594 public int mergeBehavior() { 595 return MERGE_REPLACE; 596 } 597 getActionTag()598 public abstract int getActionTag(); 599 getUniqueKey()600 public String getUniqueKey() { 601 return (getActionTag() + "_" + viewId); 602 } 603 604 /** 605 * This is called on the background thread. It should perform any non-ui computations 606 * and return the final action which will run on the UI thread. 607 * Override this if some of the tasks can be performed async. 608 */ initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)609 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 610 InteractionHandler handler, ColorResources colorResources) { 611 return this; 612 } 613 prefersAsyncApply()614 public boolean prefersAsyncApply() { 615 return false; 616 } 617 visitUris(@onNull Consumer<Uri> visitor)618 public void visitUris(@NonNull Consumer<Uri> visitor) { 619 // Nothing to visit by default 620 } 621 622 @IdRes 623 @UnsupportedAppUsage 624 int viewId; 625 } 626 627 /** 628 * Action class used during async inflation of RemoteViews. Subclasses are not parcelable. 629 */ 630 private static abstract class RuntimeAction extends Action { 631 @Override getActionTag()632 public final int getActionTag() { 633 return 0; 634 } 635 636 @Override writeToParcel(Parcel dest, int flags)637 public final void writeToParcel(Parcel dest, int flags) { 638 throw new UnsupportedOperationException(); 639 } 640 } 641 642 // Constant used during async execution. It is not parcelable. 643 private static final Action ACTION_NOOP = new RuntimeAction() { 644 @Override 645 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 646 ColorResources colorResources) { 647 } 648 }; 649 650 /** 651 * Merges the passed RemoteViews actions with this RemoteViews actions according to 652 * action-specific merge rules. 653 * 654 * @param newRv 655 * 656 * @hide 657 */ 658 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) mergeRemoteViews(RemoteViews newRv)659 public void mergeRemoteViews(RemoteViews newRv) { 660 if (newRv == null) return; 661 // We first copy the new RemoteViews, as the process of merging modifies the way the actions 662 // reference the bitmap cache. We don't want to modify the object as it may need to 663 // be merged and applied multiple times. 664 RemoteViews copy = new RemoteViews(newRv); 665 666 HashMap<String, Action> map = new HashMap<String, Action>(); 667 if (mActions == null) { 668 mActions = new ArrayList<Action>(); 669 } 670 671 int count = mActions.size(); 672 for (int i = 0; i < count; i++) { 673 Action a = mActions.get(i); 674 map.put(a.getUniqueKey(), a); 675 } 676 677 ArrayList<Action> newActions = copy.mActions; 678 if (newActions == null) return; 679 count = newActions.size(); 680 for (int i = 0; i < count; i++) { 681 Action a = newActions.get(i); 682 String key = newActions.get(i).getUniqueKey(); 683 int mergeBehavior = newActions.get(i).mergeBehavior(); 684 if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) { 685 mActions.remove(map.get(key)); 686 map.remove(key); 687 } 688 689 // If the merge behavior is ignore, we don't bother keeping the extra action 690 if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) { 691 mActions.add(a); 692 } 693 } 694 695 // Because pruning can remove the need for bitmaps, we reconstruct the caches. 696 reconstructCaches(); 697 } 698 699 /** 700 * Note all {@link Uri} that are referenced internally, with the expectation 701 * that Uri permission grants will need to be issued to ensure the recipient 702 * of this object is able to render its contents. 703 * 704 * @hide 705 */ visitUris(@onNull Consumer<Uri> visitor)706 public void visitUris(@NonNull Consumer<Uri> visitor) { 707 if (mActions != null) { 708 for (int i = 0; i < mActions.size(); i++) { 709 mActions.get(i).visitUris(visitor); 710 } 711 } 712 } 713 visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor)714 private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { 715 if (icon != null && (icon.getType() == Icon.TYPE_URI 716 || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { 717 visitor.accept(icon.getUri()); 718 } 719 } 720 721 private static class RemoteViewsContextWrapper extends ContextWrapper { 722 private final Context mContextForResources; 723 RemoteViewsContextWrapper(Context context, Context contextForResources)724 RemoteViewsContextWrapper(Context context, Context contextForResources) { 725 super(context); 726 mContextForResources = contextForResources; 727 } 728 729 @Override getResources()730 public Resources getResources() { 731 return mContextForResources.getResources(); 732 } 733 734 @Override getTheme()735 public Resources.Theme getTheme() { 736 return mContextForResources.getTheme(); 737 } 738 739 @Override getPackageName()740 public String getPackageName() { 741 return mContextForResources.getPackageName(); 742 } 743 744 @Override getUser()745 public UserHandle getUser() { 746 return mContextForResources.getUser(); 747 } 748 749 @Override getUserId()750 public int getUserId() { 751 return mContextForResources.getUserId(); 752 } 753 754 @Override isRestricted()755 public boolean isRestricted() { 756 // Override isRestricted and direct to resource's implementation. The isRestricted is 757 // used for determining the risky resources loading, e.g. fonts, thus direct to context 758 // for resource. 759 return mContextForResources.isRestricted(); 760 } 761 } 762 763 private class SetEmptyView extends Action { 764 int emptyViewId; 765 SetEmptyView(@dRes int viewId, @IdRes int emptyViewId)766 SetEmptyView(@IdRes int viewId, @IdRes int emptyViewId) { 767 this.viewId = viewId; 768 this.emptyViewId = emptyViewId; 769 } 770 SetEmptyView(Parcel in)771 SetEmptyView(Parcel in) { 772 this.viewId = in.readInt(); 773 this.emptyViewId = in.readInt(); 774 } 775 writeToParcel(Parcel out, int flags)776 public void writeToParcel(Parcel out, int flags) { 777 out.writeInt(this.viewId); 778 out.writeInt(this.emptyViewId); 779 } 780 781 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)782 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 783 ColorResources colorResources) { 784 final View view = root.findViewById(viewId); 785 if (!(view instanceof AdapterView<?>)) return; 786 787 AdapterView<?> adapterView = (AdapterView<?>) view; 788 789 final View emptyView = root.findViewById(emptyViewId); 790 if (emptyView == null) return; 791 792 adapterView.setEmptyView(emptyView); 793 } 794 795 @Override getActionTag()796 public int getActionTag() { 797 return SET_EMPTY_VIEW_ACTION_TAG; 798 } 799 } 800 801 private class SetPendingIntentTemplate extends Action { SetPendingIntentTemplate(@dRes int id, PendingIntent pendingIntentTemplate)802 public SetPendingIntentTemplate(@IdRes int id, PendingIntent pendingIntentTemplate) { 803 this.viewId = id; 804 this.pendingIntentTemplate = pendingIntentTemplate; 805 } 806 SetPendingIntentTemplate(Parcel parcel)807 public SetPendingIntentTemplate(Parcel parcel) { 808 viewId = parcel.readInt(); 809 pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 810 } 811 writeToParcel(Parcel dest, int flags)812 public void writeToParcel(Parcel dest, int flags) { 813 dest.writeInt(viewId); 814 PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest); 815 } 816 817 @Override apply(View root, ViewGroup rootParent, final InteractionHandler handler, ColorResources colorResources)818 public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, 819 ColorResources colorResources) { 820 final View target = root.findViewById(viewId); 821 if (target == null) return; 822 823 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 824 if (target instanceof AdapterView<?>) { 825 AdapterView<?> av = (AdapterView<?>) target; 826 // The PendingIntent template is stored in the view's tag. 827 OnItemClickListener listener = (parent, view, position, id) -> { 828 RemoteResponse response = findRemoteResponseTag(view); 829 if (response != null) { 830 response.handleViewInteraction(view, handler); 831 } 832 }; 833 av.setOnItemClickListener(listener); 834 av.setTag(pendingIntentTemplate); 835 } else { 836 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" + 837 "an AdapterView (id: " + viewId + ")"); 838 return; 839 } 840 } 841 842 @Nullable findRemoteResponseTag(@ullable View rootView)843 private RemoteResponse findRemoteResponseTag(@Nullable View rootView) { 844 if (rootView == null) return null; 845 846 ArrayDeque<View> viewsToCheck = new ArrayDeque<>(); 847 viewsToCheck.addLast(rootView); 848 849 while (!viewsToCheck.isEmpty()) { 850 View view = viewsToCheck.removeFirst(); 851 Object tag = view.getTag(R.id.fillInIntent); 852 if (tag instanceof RemoteResponse) return (RemoteResponse) tag; 853 if (!(view instanceof ViewGroup)) continue; 854 855 ViewGroup viewGroup = (ViewGroup) view; 856 for (int i = 0; i < viewGroup.getChildCount(); i++) { 857 viewsToCheck.addLast(viewGroup.getChildAt(i)); 858 } 859 } 860 861 return null; 862 } 863 864 @Override getActionTag()865 public int getActionTag() { 866 return SET_PENDING_INTENT_TEMPLATE_TAG; 867 } 868 869 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 870 PendingIntent pendingIntentTemplate; 871 } 872 873 private class SetRemoteViewsAdapterList extends Action { SetRemoteViewsAdapterList(@dRes int id, ArrayList<RemoteViews> list, int viewTypeCount)874 public SetRemoteViewsAdapterList(@IdRes int id, ArrayList<RemoteViews> list, 875 int viewTypeCount) { 876 this.viewId = id; 877 this.list = list; 878 this.viewTypeCount = viewTypeCount; 879 } 880 SetRemoteViewsAdapterList(Parcel parcel)881 public SetRemoteViewsAdapterList(Parcel parcel) { 882 viewId = parcel.readInt(); 883 viewTypeCount = parcel.readInt(); 884 list = parcel.createTypedArrayList(RemoteViews.CREATOR); 885 } 886 writeToParcel(Parcel dest, int flags)887 public void writeToParcel(Parcel dest, int flags) { 888 dest.writeInt(viewId); 889 dest.writeInt(viewTypeCount); 890 dest.writeTypedList(list, flags); 891 } 892 893 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)894 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 895 ColorResources colorResources) { 896 final View target = root.findViewById(viewId); 897 if (target == null) return; 898 899 // Ensure that we are applying to an AppWidget root 900 if (!(rootParent instanceof AppWidgetHostView)) { 901 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + 902 "AppWidgets (root id: " + viewId + ")"); 903 return; 904 } 905 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 906 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 907 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + 908 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 909 return; 910 } 911 912 if (target instanceof AbsListView) { 913 AbsListView v = (AbsListView) target; 914 Adapter a = v.getAdapter(); 915 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 916 ((RemoteViewsListAdapter) a).setViewsList(list); 917 } else { 918 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount, 919 colorResources)); 920 } 921 } else if (target instanceof AdapterViewAnimator) { 922 AdapterViewAnimator v = (AdapterViewAnimator) target; 923 Adapter a = v.getAdapter(); 924 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 925 ((RemoteViewsListAdapter) a).setViewsList(list); 926 } else { 927 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount, 928 colorResources)); 929 } 930 } 931 } 932 933 @Override getActionTag()934 public int getActionTag() { 935 return SET_REMOTE_VIEW_ADAPTER_LIST_TAG; 936 } 937 938 int viewTypeCount; 939 ArrayList<RemoteViews> list; 940 } 941 942 /** 943 * Cache of {@link ApplicationInfo}s that can be used to ensure that the same 944 * {@link ApplicationInfo} instance is used throughout the RemoteViews. 945 */ 946 private static class ApplicationInfoCache { 947 private final Map<Pair<String, Integer>, ApplicationInfo> mPackageUserToApplicationInfo; 948 ApplicationInfoCache()949 ApplicationInfoCache() { 950 mPackageUserToApplicationInfo = new ArrayMap<>(); 951 } 952 953 /** 954 * Adds the {@link ApplicationInfo} to the cache if it's not present. Returns either the 955 * provided {@code applicationInfo} or a previously added value with the same package name 956 * and uid. 957 */ 958 @Nullable getOrPut(@ullable ApplicationInfo applicationInfo)959 ApplicationInfo getOrPut(@Nullable ApplicationInfo applicationInfo) { 960 Pair<String, Integer> key = getPackageUserKey(applicationInfo); 961 if (key == null) return null; 962 return mPackageUserToApplicationInfo.computeIfAbsent(key, ignored -> applicationInfo); 963 } 964 965 /** Puts the {@link ApplicationInfo} in the cache, replacing any previously stored value. */ put(@ullable ApplicationInfo applicationInfo)966 void put(@Nullable ApplicationInfo applicationInfo) { 967 Pair<String, Integer> key = getPackageUserKey(applicationInfo); 968 if (key == null) return; 969 mPackageUserToApplicationInfo.put(key, applicationInfo); 970 } 971 972 /** 973 * Returns the currently stored {@link ApplicationInfo} from the cache matching 974 * {@code applicationInfo}, or null if there wasn't any. 975 */ get(@ullable ApplicationInfo applicationInfo)976 @Nullable ApplicationInfo get(@Nullable ApplicationInfo applicationInfo) { 977 Pair<String, Integer> key = getPackageUserKey(applicationInfo); 978 if (key == null) return null; 979 return mPackageUserToApplicationInfo.get(key); 980 } 981 } 982 983 private class SetRemoteCollectionItemListAdapterAction extends Action { 984 private final RemoteCollectionItems mItems; 985 SetRemoteCollectionItemListAdapterAction(@dRes int id, RemoteCollectionItems items)986 SetRemoteCollectionItemListAdapterAction(@IdRes int id, RemoteCollectionItems items) { 987 viewId = id; 988 mItems = items; 989 mItems.setHierarchyRootData(getHierarchyRootData()); 990 } 991 SetRemoteCollectionItemListAdapterAction(Parcel parcel)992 SetRemoteCollectionItemListAdapterAction(Parcel parcel) { 993 viewId = parcel.readInt(); 994 mItems = new RemoteCollectionItems(parcel, getHierarchyRootData()); 995 } 996 997 @Override setHierarchyRootData(HierarchyRootData rootData)998 public void setHierarchyRootData(HierarchyRootData rootData) { 999 mItems.setHierarchyRootData(rootData); 1000 } 1001 1002 @Override writeToParcel(Parcel dest, int flags)1003 public void writeToParcel(Parcel dest, int flags) { 1004 dest.writeInt(viewId); 1005 mItems.writeToParcel(dest, flags, /* attached= */ true); 1006 } 1007 1008 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1009 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 1010 ColorResources colorResources) throws ActionException { 1011 View target = root.findViewById(viewId); 1012 if (target == null) return; 1013 1014 // Ensure that we are applying to an AppWidget root 1015 if (!(rootParent instanceof AppWidgetHostView)) { 1016 Log.e(LOG_TAG, "setRemoteAdapter can only be used for " 1017 + "AppWidgets (root id: " + viewId + ")"); 1018 return; 1019 } 1020 1021 if (!(target instanceof AdapterView)) { 1022 Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not " 1023 + "an AdapterView (id: " + viewId + ")"); 1024 return; 1025 } 1026 1027 AdapterView adapterView = (AdapterView) target; 1028 Adapter adapter = adapterView.getAdapter(); 1029 // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type 1030 // count hasn't increased. Note that AbsListView allocates a fixed size array for view 1031 // recycling in setAdapter, so we must call setAdapter again if the number of view types 1032 // increases. 1033 if (adapter instanceof RemoteCollectionItemsAdapter 1034 && adapter.getViewTypeCount() >= mItems.getViewTypeCount()) { 1035 try { 1036 ((RemoteCollectionItemsAdapter) adapter).setData( 1037 mItems, handler, colorResources); 1038 } catch (Throwable throwable) { 1039 // setData should never failed with the validation in the items builder, but if 1040 // it does, catch and rethrow. 1041 throw new ActionException(throwable); 1042 } 1043 return; 1044 } 1045 1046 try { 1047 adapterView.setAdapter( 1048 new RemoteCollectionItemsAdapter(mItems, handler, colorResources)); 1049 } catch (Throwable throwable) { 1050 // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to 1051 // a type error. 1052 throw new ActionException(throwable); 1053 } 1054 } 1055 1056 @Override getActionTag()1057 public int getActionTag() { 1058 return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG; 1059 } 1060 } 1061 1062 private class SetRemoteViewsAdapterIntent extends Action { SetRemoteViewsAdapterIntent(@dRes int id, Intent intent)1063 public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) { 1064 this.viewId = id; 1065 this.intent = intent; 1066 } 1067 SetRemoteViewsAdapterIntent(Parcel parcel)1068 public SetRemoteViewsAdapterIntent(Parcel parcel) { 1069 viewId = parcel.readInt(); 1070 intent = parcel.readTypedObject(Intent.CREATOR); 1071 } 1072 writeToParcel(Parcel dest, int flags)1073 public void writeToParcel(Parcel dest, int flags) { 1074 dest.writeInt(viewId); 1075 dest.writeTypedObject(intent, flags); 1076 } 1077 1078 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1079 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 1080 ColorResources colorResources) { 1081 final View target = root.findViewById(viewId); 1082 if (target == null) return; 1083 1084 // Ensure that we are applying to an AppWidget root 1085 if (!(rootParent instanceof AppWidgetHostView)) { 1086 Log.e(LOG_TAG, "setRemoteAdapter can only be used for " 1087 + "AppWidgets (root id: " + viewId + ")"); 1088 return; 1089 } 1090 1091 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 1092 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 1093 Log.e(LOG_TAG, "Cannot setRemoteAdapter on a view which is not " 1094 + "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 1095 return; 1096 } 1097 1098 // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent 1099 // RemoteViewsService 1100 AppWidgetHostView host = (AppWidgetHostView) rootParent; 1101 intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()) 1102 .putExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND, 1103 hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)); 1104 1105 if (target instanceof AbsListView) { 1106 AbsListView v = (AbsListView) target; 1107 v.setRemoteViewsAdapter(intent, isAsync); 1108 v.setRemoteViewsInteractionHandler(handler); 1109 } else if (target instanceof AdapterViewAnimator) { 1110 AdapterViewAnimator v = (AdapterViewAnimator) target; 1111 v.setRemoteViewsAdapter(intent, isAsync); 1112 v.setRemoteViewsOnClickHandler(handler); 1113 } 1114 } 1115 1116 @Override initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1117 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 1118 InteractionHandler handler, ColorResources colorResources) { 1119 SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent); 1120 copy.isAsync = true; 1121 return copy; 1122 } 1123 1124 @Override getActionTag()1125 public int getActionTag() { 1126 return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG; 1127 } 1128 1129 Intent intent; 1130 boolean isAsync = false; 1131 } 1132 1133 /** 1134 * Equivalent to calling 1135 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 1136 * to launch the provided {@link PendingIntent}. 1137 */ 1138 private class SetOnClickResponse extends Action { 1139 SetOnClickResponse(@dRes int id, RemoteResponse response)1140 SetOnClickResponse(@IdRes int id, RemoteResponse response) { 1141 this.viewId = id; 1142 this.mResponse = response; 1143 } 1144 SetOnClickResponse(Parcel parcel)1145 SetOnClickResponse(Parcel parcel) { 1146 viewId = parcel.readInt(); 1147 mResponse = new RemoteResponse(); 1148 mResponse.readFromParcel(parcel); 1149 } 1150 writeToParcel(Parcel dest, int flags)1151 public void writeToParcel(Parcel dest, int flags) { 1152 dest.writeInt(viewId); 1153 mResponse.writeToParcel(dest, flags); 1154 } 1155 1156 @Override apply(View root, ViewGroup rootParent, final InteractionHandler handler, ColorResources colorResources)1157 public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, 1158 ColorResources colorResources) { 1159 final View target = root.findViewById(viewId); 1160 if (target == null) return; 1161 1162 if (mResponse.mPendingIntent != null) { 1163 // If the view is an AdapterView, setting a PendingIntent on click doesn't make 1164 // much sense, do they mean to set a PendingIntent template for the 1165 // AdapterView's children? 1166 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 1167 Log.w(LOG_TAG, "Cannot SetOnClickResponse for collection item " 1168 + "(id: " + viewId + ")"); 1169 ApplicationInfo appInfo = root.getContext().getApplicationInfo(); 1170 1171 // We let this slide for HC and ICS so as to not break compatibility. It should 1172 // have been disabled from the outset, but was left open by accident. 1173 if (appInfo != null 1174 && appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { 1175 return; 1176 } 1177 } 1178 target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent); 1179 } else if (mResponse.mFillIntent != null) { 1180 if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 1181 Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " 1182 + "only from RemoteViewsFactory (ie. on collection items)."); 1183 return; 1184 } 1185 if (target == root) { 1186 // Target is a root node of an AdapterView child. Set the response in the tag. 1187 // Actual click handling is done by OnItemClickListener in 1188 // SetPendingIntentTemplate, which uses this tag information. 1189 target.setTagInternal(com.android.internal.R.id.fillInIntent, mResponse); 1190 return; 1191 } 1192 } else { 1193 // No intent to apply, clear the listener and any tags that were previously set. 1194 target.setOnClickListener(null); 1195 target.setTagInternal(R.id.pending_intent_tag, null); 1196 target.setTagInternal(com.android.internal.R.id.fillInIntent, null); 1197 return; 1198 } 1199 target.setOnClickListener(v -> mResponse.handleViewInteraction(v, handler)); 1200 } 1201 1202 @Override getActionTag()1203 public int getActionTag() { 1204 return SET_ON_CLICK_RESPONSE_TAG; 1205 } 1206 1207 final RemoteResponse mResponse; 1208 } 1209 1210 /** 1211 * Equivalent to calling 1212 * {@link android.widget.CompoundButton#setOnCheckedChangeListener( 1213 * android.widget.CompoundButton.OnCheckedChangeListener)} 1214 * to launch the provided {@link PendingIntent}. 1215 */ 1216 private class SetOnCheckedChangeResponse extends Action { 1217 1218 private final RemoteResponse mResponse; 1219 SetOnCheckedChangeResponse(@dRes int id, RemoteResponse response)1220 SetOnCheckedChangeResponse(@IdRes int id, RemoteResponse response) { 1221 this.viewId = id; 1222 this.mResponse = response; 1223 } 1224 SetOnCheckedChangeResponse(Parcel parcel)1225 SetOnCheckedChangeResponse(Parcel parcel) { 1226 viewId = parcel.readInt(); 1227 mResponse = new RemoteResponse(); 1228 mResponse.readFromParcel(parcel); 1229 } 1230 writeToParcel(Parcel dest, int flags)1231 public void writeToParcel(Parcel dest, int flags) { 1232 dest.writeInt(viewId); 1233 mResponse.writeToParcel(dest, flags); 1234 } 1235 1236 @Override apply(View root, ViewGroup rootParent, final InteractionHandler handler, ColorResources colorResources)1237 public void apply(View root, ViewGroup rootParent, final InteractionHandler handler, 1238 ColorResources colorResources) { 1239 final View target = root.findViewById(viewId); 1240 if (target == null) return; 1241 if (!(target instanceof CompoundButton)) { 1242 Log.w(LOG_TAG, "setOnCheckedChange methods cannot be used on " 1243 + "non-CompoundButton child (id: " + viewId + ")"); 1244 return; 1245 } 1246 CompoundButton button = (CompoundButton) target; 1247 1248 if (mResponse.mPendingIntent != null) { 1249 // setOnCheckedChangePendingIntent cannot be used with collection children, which 1250 // must use setOnCheckedChangeFillInIntent instead. 1251 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 1252 Log.w(LOG_TAG, "Cannot setOnCheckedChangePendingIntent for collection item " 1253 + "(id: " + viewId + ")"); 1254 return; 1255 } 1256 target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent); 1257 } else if (mResponse.mFillIntent != null) { 1258 if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 1259 Log.e(LOG_TAG, "The method setOnCheckedChangeFillInIntent is available " 1260 + "only from RemoteViewsFactory (ie. on collection items)."); 1261 return; 1262 } 1263 } else { 1264 // No intent to apply, clear any existing listener or tag. 1265 button.setOnCheckedChangeListener(null); 1266 button.setTagInternal(R.id.remote_checked_change_listener_tag, null); 1267 return; 1268 } 1269 1270 OnCheckedChangeListener onCheckedChangeListener = 1271 (v, isChecked) -> mResponse.handleViewInteraction(v, handler); 1272 button.setTagInternal(R.id.remote_checked_change_listener_tag, onCheckedChangeListener); 1273 button.setOnCheckedChangeListener(onCheckedChangeListener); 1274 } 1275 1276 @Override getActionTag()1277 public int getActionTag() { 1278 return SET_ON_CHECKED_CHANGE_RESPONSE_TAG; 1279 } 1280 } 1281 1282 /** @hide **/ getSourceBounds(View v)1283 public static Rect getSourceBounds(View v) { 1284 final float appScale = v.getContext().getResources() 1285 .getCompatibilityInfo().applicationScale; 1286 final int[] pos = new int[2]; 1287 v.getLocationOnScreen(pos); 1288 1289 final Rect rect = new Rect(); 1290 rect.left = (int) (pos[0] * appScale + 0.5f); 1291 rect.top = (int) (pos[1] * appScale + 0.5f); 1292 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 1293 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 1294 return rect; 1295 } 1296 1297 @Nullable getParameterType(int type)1298 private static Class<?> getParameterType(int type) { 1299 switch (type) { 1300 case BaseReflectionAction.BOOLEAN: 1301 return boolean.class; 1302 case BaseReflectionAction.BYTE: 1303 return byte.class; 1304 case BaseReflectionAction.SHORT: 1305 return short.class; 1306 case BaseReflectionAction.INT: 1307 return int.class; 1308 case BaseReflectionAction.LONG: 1309 return long.class; 1310 case BaseReflectionAction.FLOAT: 1311 return float.class; 1312 case BaseReflectionAction.DOUBLE: 1313 return double.class; 1314 case BaseReflectionAction.CHAR: 1315 return char.class; 1316 case BaseReflectionAction.STRING: 1317 return String.class; 1318 case BaseReflectionAction.CHAR_SEQUENCE: 1319 return CharSequence.class; 1320 case BaseReflectionAction.URI: 1321 return Uri.class; 1322 case BaseReflectionAction.BITMAP: 1323 return Bitmap.class; 1324 case BaseReflectionAction.BUNDLE: 1325 return Bundle.class; 1326 case BaseReflectionAction.INTENT: 1327 return Intent.class; 1328 case BaseReflectionAction.COLOR_STATE_LIST: 1329 return ColorStateList.class; 1330 case BaseReflectionAction.ICON: 1331 return Icon.class; 1332 case BaseReflectionAction.BLEND_MODE: 1333 return BlendMode.class; 1334 default: 1335 return null; 1336 } 1337 } 1338 1339 @Nullable getMethod(View view, String methodName, Class<?> paramType, boolean async)1340 private MethodHandle getMethod(View view, String methodName, Class<?> paramType, 1341 boolean async) { 1342 MethodArgs result; 1343 Class<? extends View> klass = view.getClass(); 1344 1345 synchronized (sMethods) { 1346 // The key is defined by the view class, param class and method name. 1347 sLookupKey.set(klass, paramType, methodName); 1348 result = sMethods.get(sLookupKey); 1349 1350 if (result == null) { 1351 Method method; 1352 try { 1353 if (paramType == null) { 1354 method = klass.getMethod(methodName); 1355 } else { 1356 method = klass.getMethod(methodName, paramType); 1357 } 1358 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 1359 throw new ActionException("view: " + klass.getName() 1360 + " can't use method with RemoteViews: " 1361 + methodName + getParameters(paramType)); 1362 } 1363 1364 result = new MethodArgs(); 1365 result.syncMethod = MethodHandles.publicLookup().unreflect(method); 1366 result.asyncMethodName = 1367 method.getAnnotation(RemotableViewMethod.class).asyncImpl(); 1368 } catch (NoSuchMethodException | IllegalAccessException ex) { 1369 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 1370 + methodName + getParameters(paramType)); 1371 } 1372 1373 MethodKey key = new MethodKey(); 1374 key.set(klass, paramType, methodName); 1375 sMethods.put(key, result); 1376 } 1377 1378 if (!async) { 1379 return result.syncMethod; 1380 } 1381 // Check this so see if async method is implemented or not. 1382 if (result.asyncMethodName.isEmpty()) { 1383 return null; 1384 } 1385 // Async method is lazily loaded. If it is not yet loaded, load now. 1386 if (result.asyncMethod == null) { 1387 MethodType asyncType = result.syncMethod.type() 1388 .dropParameterTypes(0, 1).changeReturnType(Runnable.class); 1389 try { 1390 result.asyncMethod = MethodHandles.publicLookup().findVirtual( 1391 klass, result.asyncMethodName, asyncType); 1392 } catch (NoSuchMethodException | IllegalAccessException ex) { 1393 throw new ActionException("Async implementation declared as " 1394 + result.asyncMethodName + " but not defined for " + methodName 1395 + ": public Runnable " + result.asyncMethodName + " (" 1396 + TextUtils.join(",", asyncType.parameterArray()) + ")"); 1397 } 1398 } 1399 return result.asyncMethod; 1400 } 1401 } 1402 getParameters(Class<?> paramType)1403 private static String getParameters(Class<?> paramType) { 1404 if (paramType == null) return "()"; 1405 return "(" + paramType + ")"; 1406 } 1407 1408 /** 1409 * Equivalent to calling 1410 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 1411 * on the {@link Drawable} of a given view. 1412 * <p> 1413 * The operation will be performed on the {@link Drawable} returned by the 1414 * target {@link View#getBackground()} by default. If targetBackground is false, 1415 * we assume the target is an {@link ImageView} and try applying the operations 1416 * to {@link ImageView#getDrawable()}. 1417 * <p> 1418 */ 1419 private class SetDrawableTint extends Action { SetDrawableTint(@dRes int id, boolean targetBackground, @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode)1420 SetDrawableTint(@IdRes int id, boolean targetBackground, 1421 @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) { 1422 this.viewId = id; 1423 this.targetBackground = targetBackground; 1424 this.colorFilter = colorFilter; 1425 this.filterMode = mode; 1426 } 1427 SetDrawableTint(Parcel parcel)1428 SetDrawableTint(Parcel parcel) { 1429 viewId = parcel.readInt(); 1430 targetBackground = parcel.readInt() != 0; 1431 colorFilter = parcel.readInt(); 1432 filterMode = PorterDuff.intToMode(parcel.readInt()); 1433 } 1434 writeToParcel(Parcel dest, int flags)1435 public void writeToParcel(Parcel dest, int flags) { 1436 dest.writeInt(viewId); 1437 dest.writeInt(targetBackground ? 1 : 0); 1438 dest.writeInt(colorFilter); 1439 dest.writeInt(PorterDuff.modeToInt(filterMode)); 1440 } 1441 1442 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1443 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 1444 ColorResources colorResources) { 1445 final View target = root.findViewById(viewId); 1446 if (target == null) return; 1447 1448 // Pick the correct drawable to modify for this view 1449 Drawable targetDrawable = null; 1450 if (targetBackground) { 1451 targetDrawable = target.getBackground(); 1452 } else if (target instanceof ImageView) { 1453 ImageView imageView = (ImageView) target; 1454 targetDrawable = imageView.getDrawable(); 1455 } 1456 1457 if (targetDrawable != null) { 1458 targetDrawable.mutate().setColorFilter(colorFilter, filterMode); 1459 } 1460 } 1461 1462 @Override getActionTag()1463 public int getActionTag() { 1464 return SET_DRAWABLE_TINT_TAG; 1465 } 1466 1467 boolean targetBackground; 1468 @ColorInt int colorFilter; 1469 PorterDuff.Mode filterMode; 1470 } 1471 1472 /** 1473 * Equivalent to calling 1474 * {@link RippleDrawable#setColor(ColorStateList)}, 1475 * on the {@link Drawable} of a given view. 1476 * <p> 1477 * The operation will be performed on the {@link Drawable} returned by the 1478 * target {@link View#getBackground()}. 1479 * <p> 1480 */ 1481 private class SetRippleDrawableColor extends Action { 1482 1483 ColorStateList mColorStateList; 1484 SetRippleDrawableColor(@dRes int id, ColorStateList colorStateList)1485 SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) { 1486 this.viewId = id; 1487 this.mColorStateList = colorStateList; 1488 } 1489 SetRippleDrawableColor(Parcel parcel)1490 SetRippleDrawableColor(Parcel parcel) { 1491 viewId = parcel.readInt(); 1492 mColorStateList = parcel.readParcelable(null); 1493 } 1494 writeToParcel(Parcel dest, int flags)1495 public void writeToParcel(Parcel dest, int flags) { 1496 dest.writeInt(viewId); 1497 dest.writeParcelable(mColorStateList, 0); 1498 } 1499 1500 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1501 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 1502 ColorResources colorResources) { 1503 final View target = root.findViewById(viewId); 1504 if (target == null) return; 1505 1506 // Pick the correct drawable to modify for this view 1507 Drawable targetDrawable = target.getBackground(); 1508 1509 if (targetDrawable instanceof RippleDrawable) { 1510 ((RippleDrawable) targetDrawable.mutate()).setColor(mColorStateList); 1511 } 1512 } 1513 1514 @Override getActionTag()1515 public int getActionTag() { 1516 return SET_RIPPLE_DRAWABLE_COLOR_TAG; 1517 } 1518 } 1519 1520 private final class ViewContentNavigation extends Action { 1521 final boolean mNext; 1522 ViewContentNavigation(@dRes int viewId, boolean next)1523 ViewContentNavigation(@IdRes int viewId, boolean next) { 1524 this.viewId = viewId; 1525 this.mNext = next; 1526 } 1527 ViewContentNavigation(Parcel in)1528 ViewContentNavigation(Parcel in) { 1529 this.viewId = in.readInt(); 1530 this.mNext = in.readBoolean(); 1531 } 1532 writeToParcel(Parcel out, int flags)1533 public void writeToParcel(Parcel out, int flags) { 1534 out.writeInt(this.viewId); 1535 out.writeBoolean(this.mNext); 1536 } 1537 1538 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1539 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 1540 ColorResources colorResources) { 1541 final View view = root.findViewById(viewId); 1542 if (view == null) return; 1543 1544 try { 1545 getMethod(view, 1546 mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view); 1547 } catch (Throwable ex) { 1548 throw new ActionException(ex); 1549 } 1550 } 1551 mergeBehavior()1552 public int mergeBehavior() { 1553 return MERGE_IGNORE; 1554 } 1555 1556 @Override getActionTag()1557 public int getActionTag() { 1558 return VIEW_CONTENT_NAVIGATION_TAG; 1559 } 1560 } 1561 1562 private static class BitmapCache { 1563 1564 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1565 ArrayList<Bitmap> mBitmaps; 1566 int mBitmapMemory = -1; 1567 BitmapCache()1568 public BitmapCache() { 1569 mBitmaps = new ArrayList<>(); 1570 } 1571 BitmapCache(Parcel source)1572 public BitmapCache(Parcel source) { 1573 mBitmaps = source.createTypedArrayList(Bitmap.CREATOR); 1574 } 1575 getBitmapId(Bitmap b)1576 public int getBitmapId(Bitmap b) { 1577 if (b == null) { 1578 return -1; 1579 } else { 1580 if (mBitmaps.contains(b)) { 1581 return mBitmaps.indexOf(b); 1582 } else { 1583 mBitmaps.add(b); 1584 mBitmapMemory = -1; 1585 return (mBitmaps.size() - 1); 1586 } 1587 } 1588 } 1589 1590 @Nullable getBitmapForId(int id)1591 public Bitmap getBitmapForId(int id) { 1592 if (id == -1 || id >= mBitmaps.size()) { 1593 return null; 1594 } else { 1595 return mBitmaps.get(id); 1596 } 1597 } 1598 writeBitmapsToParcel(Parcel dest, int flags)1599 public void writeBitmapsToParcel(Parcel dest, int flags) { 1600 dest.writeTypedList(mBitmaps, flags); 1601 } 1602 getBitmapMemory()1603 public int getBitmapMemory() { 1604 if (mBitmapMemory < 0) { 1605 mBitmapMemory = 0; 1606 int count = mBitmaps.size(); 1607 for (int i = 0; i < count; i++) { 1608 mBitmapMemory += mBitmaps.get(i).getAllocationByteCount(); 1609 } 1610 } 1611 return mBitmapMemory; 1612 } 1613 } 1614 1615 private class BitmapReflectionAction extends Action { 1616 int bitmapId; 1617 @UnsupportedAppUsage 1618 Bitmap bitmap; 1619 @UnsupportedAppUsage 1620 String methodName; 1621 BitmapReflectionAction(@dRes int viewId, String methodName, Bitmap bitmap)1622 BitmapReflectionAction(@IdRes int viewId, String methodName, Bitmap bitmap) { 1623 this.bitmap = bitmap; 1624 this.viewId = viewId; 1625 this.methodName = methodName; 1626 bitmapId = mBitmapCache.getBitmapId(bitmap); 1627 } 1628 BitmapReflectionAction(Parcel in)1629 BitmapReflectionAction(Parcel in) { 1630 viewId = in.readInt(); 1631 methodName = in.readString8(); 1632 bitmapId = in.readInt(); 1633 bitmap = mBitmapCache.getBitmapForId(bitmapId); 1634 } 1635 1636 @Override writeToParcel(Parcel dest, int flags)1637 public void writeToParcel(Parcel dest, int flags) { 1638 dest.writeInt(viewId); 1639 dest.writeString8(methodName); 1640 dest.writeInt(bitmapId); 1641 } 1642 1643 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1644 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 1645 ColorResources colorResources) throws ActionException { 1646 ReflectionAction ra = new ReflectionAction(viewId, methodName, 1647 BaseReflectionAction.BITMAP, 1648 bitmap); 1649 ra.apply(root, rootParent, handler, colorResources); 1650 } 1651 1652 @Override setHierarchyRootData(HierarchyRootData rootData)1653 public void setHierarchyRootData(HierarchyRootData rootData) { 1654 bitmapId = rootData.mBitmapCache.getBitmapId(bitmap); 1655 } 1656 1657 @Override getActionTag()1658 public int getActionTag() { 1659 return BITMAP_REFLECTION_ACTION_TAG; 1660 } 1661 } 1662 1663 /** 1664 * Base class for the reflection actions. 1665 */ 1666 private abstract class BaseReflectionAction extends Action { 1667 static final int BOOLEAN = 1; 1668 static final int BYTE = 2; 1669 static final int SHORT = 3; 1670 static final int INT = 4; 1671 static final int LONG = 5; 1672 static final int FLOAT = 6; 1673 static final int DOUBLE = 7; 1674 static final int CHAR = 8; 1675 static final int STRING = 9; 1676 static final int CHAR_SEQUENCE = 10; 1677 static final int URI = 11; 1678 // BITMAP actions are never stored in the list of actions. They are only used locally 1679 // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache. 1680 static final int BITMAP = 12; 1681 static final int BUNDLE = 13; 1682 static final int INTENT = 14; 1683 static final int COLOR_STATE_LIST = 15; 1684 static final int ICON = 16; 1685 static final int BLEND_MODE = 17; 1686 1687 @UnsupportedAppUsage 1688 String methodName; 1689 int type; 1690 BaseReflectionAction(@dRes int viewId, String methodName, int type)1691 BaseReflectionAction(@IdRes int viewId, String methodName, int type) { 1692 this.viewId = viewId; 1693 this.methodName = methodName; 1694 this.type = type; 1695 } 1696 BaseReflectionAction(Parcel in)1697 BaseReflectionAction(Parcel in) { 1698 this.viewId = in.readInt(); 1699 this.methodName = in.readString8(); 1700 this.type = in.readInt(); 1701 //noinspection ConstantIfStatement 1702 if (false) { 1703 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId) 1704 + " methodName=" + this.methodName + " type=" + this.type); 1705 } 1706 } 1707 writeToParcel(Parcel out, int flags)1708 public void writeToParcel(Parcel out, int flags) { 1709 out.writeInt(this.viewId); 1710 out.writeString8(this.methodName); 1711 out.writeInt(this.type); 1712 } 1713 1714 /** 1715 * Returns the value to use as parameter for the method. 1716 * 1717 * The view might be passed as {@code null} if the parameter value is requested outside of 1718 * inflation. If the parameter cannot be determined at that time, the method should return 1719 * {@code null} but not raise any exception. 1720 */ 1721 @Nullable getParameterValue(@ullable View view)1722 protected abstract Object getParameterValue(@Nullable View view) throws ActionException; 1723 1724 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1725 public final void apply(View root, ViewGroup rootParent, InteractionHandler handler, 1726 ColorResources colorResources) { 1727 final View view = root.findViewById(viewId); 1728 if (view == null) return; 1729 1730 Class<?> param = getParameterType(this.type); 1731 if (param == null) { 1732 throw new ActionException("bad type: " + this.type); 1733 } 1734 Object value = getParameterValue(view); 1735 try { 1736 getMethod(view, this.methodName, param, false /* async */).invoke(view, value); 1737 } catch (Throwable ex) { 1738 throw new ActionException(ex); 1739 } 1740 } 1741 1742 @Override initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)1743 public final Action initActionAsync(ViewTree root, ViewGroup rootParent, 1744 InteractionHandler handler, ColorResources colorResources) { 1745 final View view = root.findViewById(viewId); 1746 if (view == null) return ACTION_NOOP; 1747 1748 Class<?> param = getParameterType(this.type); 1749 if (param == null) { 1750 throw new ActionException("bad type: " + this.type); 1751 } 1752 1753 Object value = getParameterValue(view); 1754 try { 1755 MethodHandle method = getMethod(view, this.methodName, param, true /* async */); 1756 1757 if (method != null) { 1758 Runnable endAction = (Runnable) method.invoke(view, value); 1759 if (endAction == null) { 1760 return ACTION_NOOP; 1761 } 1762 // Special case view stub 1763 if (endAction instanceof ViewStub.ViewReplaceRunnable) { 1764 root.createTree(); 1765 // Replace child tree 1766 root.findViewTreeById(viewId).replaceView( 1767 ((ViewStub.ViewReplaceRunnable) endAction).view); 1768 } 1769 return new RunnableAction(endAction); 1770 } 1771 } catch (Throwable ex) { 1772 throw new ActionException(ex); 1773 } 1774 1775 return this; 1776 } 1777 mergeBehavior()1778 public final int mergeBehavior() { 1779 // smoothScrollBy is cumulative, everything else overwites. 1780 if (methodName.equals("smoothScrollBy")) { 1781 return MERGE_APPEND; 1782 } else { 1783 return MERGE_REPLACE; 1784 } 1785 } 1786 1787 @Override getUniqueKey()1788 public final String getUniqueKey() { 1789 // Each type of reflection action corresponds to a setter, so each should be seen as 1790 // unique from the standpoint of merging. 1791 return super.getUniqueKey() + this.methodName + this.type; 1792 } 1793 1794 @Override prefersAsyncApply()1795 public final boolean prefersAsyncApply() { 1796 return this.type == URI || this.type == ICON; 1797 } 1798 1799 @Override visitUris(@onNull Consumer<Uri> visitor)1800 public final void visitUris(@NonNull Consumer<Uri> visitor) { 1801 switch (this.type) { 1802 case URI: 1803 final Uri uri = (Uri) getParameterValue(null); 1804 if (uri != null) visitor.accept(uri); 1805 break; 1806 case ICON: 1807 final Icon icon = (Icon) getParameterValue(null); 1808 if (icon != null) visitIconUri(icon, visitor); 1809 break; 1810 } 1811 } 1812 } 1813 1814 /** Class for the reflection actions. */ 1815 private final class ReflectionAction extends BaseReflectionAction { 1816 @UnsupportedAppUsage 1817 Object value; 1818 ReflectionAction(@dRes int viewId, String methodName, int type, Object value)1819 ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) { 1820 super(viewId, methodName, type); 1821 this.value = value; 1822 } 1823 ReflectionAction(Parcel in)1824 ReflectionAction(Parcel in) { 1825 super(in); 1826 // For some values that may have been null, we first check a flag to see if they were 1827 // written to the parcel. 1828 switch (this.type) { 1829 case BOOLEAN: 1830 this.value = in.readBoolean(); 1831 break; 1832 case BYTE: 1833 this.value = in.readByte(); 1834 break; 1835 case SHORT: 1836 this.value = (short) in.readInt(); 1837 break; 1838 case INT: 1839 this.value = in.readInt(); 1840 break; 1841 case LONG: 1842 this.value = in.readLong(); 1843 break; 1844 case FLOAT: 1845 this.value = in.readFloat(); 1846 break; 1847 case DOUBLE: 1848 this.value = in.readDouble(); 1849 break; 1850 case CHAR: 1851 this.value = (char) in.readInt(); 1852 break; 1853 case STRING: 1854 this.value = in.readString8(); 1855 break; 1856 case CHAR_SEQUENCE: 1857 this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1858 break; 1859 case URI: 1860 this.value = in.readTypedObject(Uri.CREATOR); 1861 break; 1862 case BITMAP: 1863 this.value = in.readTypedObject(Bitmap.CREATOR); 1864 break; 1865 case BUNDLE: 1866 // Because we use Parcel.allowSquashing() when writing, and that affects 1867 // how the contents of Bundles are written, we need to ensure the bundle is 1868 // unparceled immediately, not lazily. Setting a custom ReadWriteHelper 1869 // just happens to have that effect on Bundle.readFromParcel(). 1870 // TODO(b/212731590): build this state tracking into Bundle 1871 if (in.hasReadWriteHelper()) { 1872 this.value = in.readBundle(); 1873 } else { 1874 in.setReadWriteHelper(ALTERNATIVE_DEFAULT); 1875 this.value = in.readBundle(); 1876 in.setReadWriteHelper(null); 1877 } 1878 break; 1879 case INTENT: 1880 this.value = in.readTypedObject(Intent.CREATOR); 1881 break; 1882 case COLOR_STATE_LIST: 1883 this.value = in.readTypedObject(ColorStateList.CREATOR); 1884 break; 1885 case ICON: 1886 this.value = in.readTypedObject(Icon.CREATOR); 1887 break; 1888 case BLEND_MODE: 1889 this.value = BlendMode.fromValue(in.readInt()); 1890 break; 1891 default: 1892 break; 1893 } 1894 } 1895 writeToParcel(Parcel out, int flags)1896 public void writeToParcel(Parcel out, int flags) { 1897 super.writeToParcel(out, flags); 1898 // For some values which are null, we record an integer flag to indicate whether 1899 // we have written a valid value to the parcel. 1900 switch (this.type) { 1901 case BOOLEAN: 1902 out.writeBoolean((Boolean) this.value); 1903 break; 1904 case BYTE: 1905 out.writeByte((Byte) this.value); 1906 break; 1907 case SHORT: 1908 out.writeInt((Short) this.value); 1909 break; 1910 case INT: 1911 out.writeInt((Integer) this.value); 1912 break; 1913 case LONG: 1914 out.writeLong((Long) this.value); 1915 break; 1916 case FLOAT: 1917 out.writeFloat((Float) this.value); 1918 break; 1919 case DOUBLE: 1920 out.writeDouble((Double) this.value); 1921 break; 1922 case CHAR: 1923 out.writeInt((int) ((Character) this.value).charValue()); 1924 break; 1925 case STRING: 1926 out.writeString8((String) this.value); 1927 break; 1928 case CHAR_SEQUENCE: 1929 TextUtils.writeToParcel((CharSequence) this.value, out, flags); 1930 break; 1931 case BUNDLE: 1932 out.writeBundle((Bundle) this.value); 1933 break; 1934 case BLEND_MODE: 1935 out.writeInt(BlendMode.toValue((BlendMode) this.value)); 1936 break; 1937 case URI: 1938 case BITMAP: 1939 case INTENT: 1940 case COLOR_STATE_LIST: 1941 case ICON: 1942 out.writeTypedObject((Parcelable) this.value, flags); 1943 break; 1944 default: 1945 break; 1946 } 1947 } 1948 1949 @Nullable 1950 @Override getParameterValue(@ullable View view)1951 protected Object getParameterValue(@Nullable View view) throws ActionException { 1952 return this.value; 1953 } 1954 1955 @Override getActionTag()1956 public int getActionTag() { 1957 return REFLECTION_ACTION_TAG; 1958 } 1959 } 1960 1961 private final class ResourceReflectionAction extends BaseReflectionAction { 1962 1963 static final int DIMEN_RESOURCE = 1; 1964 static final int COLOR_RESOURCE = 2; 1965 static final int STRING_RESOURCE = 3; 1966 1967 private final int mResourceType; 1968 private final int mResId; 1969 ResourceReflectionAction(@dRes int viewId, String methodName, int parameterType, int resourceType, int resId)1970 ResourceReflectionAction(@IdRes int viewId, String methodName, int parameterType, 1971 int resourceType, int resId) { 1972 super(viewId, methodName, parameterType); 1973 this.mResourceType = resourceType; 1974 this.mResId = resId; 1975 } 1976 ResourceReflectionAction(Parcel in)1977 ResourceReflectionAction(Parcel in) { 1978 super(in); 1979 this.mResourceType = in.readInt(); 1980 this.mResId = in.readInt(); 1981 } 1982 1983 @Override writeToParcel(Parcel dest, int flags)1984 public void writeToParcel(Parcel dest, int flags) { 1985 super.writeToParcel(dest, flags); 1986 dest.writeInt(this.mResourceType); 1987 dest.writeInt(this.mResId); 1988 } 1989 1990 @Nullable 1991 @Override getParameterValue(@ullable View view)1992 protected Object getParameterValue(@Nullable View view) throws ActionException { 1993 if (view == null) return null; 1994 1995 Resources resources = view.getContext().getResources(); 1996 try { 1997 switch (this.mResourceType) { 1998 case DIMEN_RESOURCE: 1999 switch (this.type) { 2000 case BaseReflectionAction.INT: 2001 return mResId == 0 ? 0 : resources.getDimensionPixelSize(mResId); 2002 case BaseReflectionAction.FLOAT: 2003 return mResId == 0 ? 0f : resources.getDimension(mResId); 2004 default: 2005 throw new ActionException( 2006 "dimen resources must be used as INT or FLOAT, " 2007 + "not " + this.type); 2008 } 2009 case COLOR_RESOURCE: 2010 switch (this.type) { 2011 case BaseReflectionAction.INT: 2012 return mResId == 0 ? 0 : view.getContext().getColor(mResId); 2013 case BaseReflectionAction.COLOR_STATE_LIST: 2014 return mResId == 0 2015 ? null : view.getContext().getColorStateList(mResId); 2016 default: 2017 throw new ActionException( 2018 "color resources must be used as INT or COLOR_STATE_LIST," 2019 + " not " + this.type); 2020 } 2021 case STRING_RESOURCE: 2022 switch (this.type) { 2023 case BaseReflectionAction.CHAR_SEQUENCE: 2024 return mResId == 0 ? null : resources.getText(mResId); 2025 case BaseReflectionAction.STRING: 2026 return mResId == 0 ? null : resources.getString(mResId); 2027 default: 2028 throw new ActionException( 2029 "string resources must be used as STRING or CHAR_SEQUENCE," 2030 + " not " + this.type); 2031 } 2032 default: 2033 throw new ActionException("unknown resource type: " + this.mResourceType); 2034 } 2035 } catch (ActionException ex) { 2036 throw ex; 2037 } catch (Throwable t) { 2038 throw new ActionException(t); 2039 } 2040 } 2041 2042 @Override getActionTag()2043 public int getActionTag() { 2044 return RESOURCE_REFLECTION_ACTION_TAG; 2045 } 2046 } 2047 2048 private final class AttributeReflectionAction extends BaseReflectionAction { 2049 2050 static final int DIMEN_RESOURCE = 1; 2051 static final int COLOR_RESOURCE = 2; 2052 static final int STRING_RESOURCE = 3; 2053 2054 private final int mResourceType; 2055 private final int mAttrId; 2056 AttributeReflectionAction(@dRes int viewId, String methodName, int parameterType, int resourceType, int attrId)2057 AttributeReflectionAction(@IdRes int viewId, String methodName, int parameterType, 2058 int resourceType, int attrId) { 2059 super(viewId, methodName, parameterType); 2060 this.mResourceType = resourceType; 2061 this.mAttrId = attrId; 2062 } 2063 AttributeReflectionAction(Parcel in)2064 AttributeReflectionAction(Parcel in) { 2065 super(in); 2066 this.mResourceType = in.readInt(); 2067 this.mAttrId = in.readInt(); 2068 } 2069 2070 @Override writeToParcel(Parcel dest, int flags)2071 public void writeToParcel(Parcel dest, int flags) { 2072 super.writeToParcel(dest, flags); 2073 dest.writeInt(this.mResourceType); 2074 dest.writeInt(this.mAttrId); 2075 } 2076 2077 @Override getParameterValue(View view)2078 protected Object getParameterValue(View view) throws ActionException { 2079 TypedArray typedArray = view.getContext().obtainStyledAttributes(new int[]{mAttrId}); 2080 try { 2081 // When mAttrId == 0, we will depend on the default values below 2082 if (mAttrId != 0 && typedArray.getType(0) == TypedValue.TYPE_NULL) { 2083 throw new ActionException("Attribute 0x" + Integer.toHexString(this.mAttrId) 2084 + " is not defined"); 2085 } 2086 switch (this.mResourceType) { 2087 case DIMEN_RESOURCE: 2088 switch (this.type) { 2089 case BaseReflectionAction.INT: 2090 return typedArray.getDimensionPixelSize(0, 0); 2091 case BaseReflectionAction.FLOAT: 2092 return typedArray.getDimension(0, 0); 2093 default: 2094 throw new ActionException( 2095 "dimen attribute 0x" + Integer.toHexString(this.mAttrId) 2096 + " must be used as INT or FLOAT," 2097 + " not " + this.type); 2098 } 2099 case COLOR_RESOURCE: 2100 switch (this.type) { 2101 case BaseReflectionAction.INT: 2102 return typedArray.getColor(0, 0); 2103 case BaseReflectionAction.COLOR_STATE_LIST: 2104 return typedArray.getColorStateList(0); 2105 default: 2106 throw new ActionException( 2107 "color attribute 0x" + Integer.toHexString(this.mAttrId) 2108 + " must be used as INT or COLOR_STATE_LIST," 2109 + " not " + this.type); 2110 } 2111 case STRING_RESOURCE: 2112 switch (this.type) { 2113 case BaseReflectionAction.CHAR_SEQUENCE: 2114 return typedArray.getText(0); 2115 case BaseReflectionAction.STRING: 2116 return typedArray.getString(0); 2117 default: 2118 throw new ActionException( 2119 "string attribute 0x" + Integer.toHexString(this.mAttrId) 2120 + " must be used as STRING or CHAR_SEQUENCE," 2121 + " not " + this.type); 2122 } 2123 default: 2124 // Note: This can only be an implementation error. 2125 throw new ActionException( 2126 "Unknown resource type: " + this.mResourceType); 2127 } 2128 } catch (ActionException ex) { 2129 throw ex; 2130 } catch (Throwable t) { 2131 throw new ActionException(t); 2132 } finally { 2133 typedArray.recycle(); 2134 } 2135 } 2136 2137 @Override getActionTag()2138 public int getActionTag() { 2139 return ATTRIBUTE_REFLECTION_ACTION_TAG; 2140 } 2141 } 2142 private final class ComplexUnitDimensionReflectionAction extends BaseReflectionAction { 2143 2144 private final float mValue; 2145 @ComplexDimensionUnit 2146 private final int mUnit; 2147 ComplexUnitDimensionReflectionAction(int viewId, String methodName, int parameterType, float value, @ComplexDimensionUnit int unit)2148 ComplexUnitDimensionReflectionAction(int viewId, String methodName, int parameterType, 2149 float value, @ComplexDimensionUnit int unit) { 2150 super(viewId, methodName, parameterType); 2151 this.mValue = value; 2152 this.mUnit = unit; 2153 } 2154 ComplexUnitDimensionReflectionAction(Parcel in)2155 ComplexUnitDimensionReflectionAction(Parcel in) { 2156 super(in); 2157 this.mValue = in.readFloat(); 2158 this.mUnit = in.readInt(); 2159 } 2160 2161 @Override writeToParcel(Parcel dest, int flags)2162 public void writeToParcel(Parcel dest, int flags) { 2163 super.writeToParcel(dest, flags); 2164 dest.writeFloat(this.mValue); 2165 dest.writeInt(this.mUnit); 2166 } 2167 2168 @Nullable 2169 @Override getParameterValue(@ullable View view)2170 protected Object getParameterValue(@Nullable View view) throws ActionException { 2171 if (view == null) return null; 2172 2173 DisplayMetrics dm = view.getContext().getResources().getDisplayMetrics(); 2174 try { 2175 int data = TypedValue.createComplexDimension(this.mValue, this.mUnit); 2176 switch (this.type) { 2177 case ReflectionAction.INT: 2178 return TypedValue.complexToDimensionPixelSize(data, dm); 2179 case ReflectionAction.FLOAT: 2180 return TypedValue.complexToDimension(data, dm); 2181 default: 2182 throw new ActionException( 2183 "parameter type must be INT or FLOAT, not " + this.type); 2184 } 2185 } catch (ActionException ex) { 2186 throw ex; 2187 } catch (Throwable t) { 2188 throw new ActionException(t); 2189 } 2190 } 2191 2192 @Override getActionTag()2193 public int getActionTag() { 2194 return COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG; 2195 } 2196 } 2197 2198 private final class NightModeReflectionAction extends BaseReflectionAction { 2199 2200 private final Object mLightValue; 2201 private final Object mDarkValue; 2202 NightModeReflectionAction( @dRes int viewId, String methodName, int type, Object lightValue, Object darkValue)2203 NightModeReflectionAction( 2204 @IdRes int viewId, 2205 String methodName, 2206 int type, 2207 Object lightValue, 2208 Object darkValue) { 2209 super(viewId, methodName, type); 2210 mLightValue = lightValue; 2211 mDarkValue = darkValue; 2212 } 2213 NightModeReflectionAction(Parcel in)2214 NightModeReflectionAction(Parcel in) { 2215 super(in); 2216 switch (this.type) { 2217 case ICON: 2218 mLightValue = in.readTypedObject(Icon.CREATOR); 2219 mDarkValue = in.readTypedObject(Icon.CREATOR); 2220 break; 2221 case COLOR_STATE_LIST: 2222 mLightValue = in.readTypedObject(ColorStateList.CREATOR); 2223 mDarkValue = in.readTypedObject(ColorStateList.CREATOR); 2224 break; 2225 case INT: 2226 mLightValue = in.readInt(); 2227 mDarkValue = in.readInt(); 2228 break; 2229 default: 2230 throw new ActionException("Unexpected night mode action type: " + this.type); 2231 } 2232 } 2233 2234 @Override writeToParcel(Parcel out, int flags)2235 public void writeToParcel(Parcel out, int flags) { 2236 super.writeToParcel(out, flags); 2237 switch (this.type) { 2238 case ICON: 2239 case COLOR_STATE_LIST: 2240 out.writeTypedObject((Parcelable) mLightValue, flags); 2241 out.writeTypedObject((Parcelable) mDarkValue, flags); 2242 break; 2243 case INT: 2244 out.writeInt((int) mLightValue); 2245 out.writeInt((int) mDarkValue); 2246 break; 2247 } 2248 } 2249 2250 @Nullable 2251 @Override getParameterValue(@ullable View view)2252 protected Object getParameterValue(@Nullable View view) throws ActionException { 2253 if (view == null) return null; 2254 2255 Configuration configuration = view.getResources().getConfiguration(); 2256 return configuration.isNightModeActive() ? mDarkValue : mLightValue; 2257 } 2258 2259 @Override getActionTag()2260 public int getActionTag() { 2261 return NIGHT_MODE_REFLECTION_ACTION_TAG; 2262 } 2263 } 2264 2265 /** 2266 * This is only used for async execution of actions and it not parcelable. 2267 */ 2268 private static final class RunnableAction extends RuntimeAction { 2269 private final Runnable mRunnable; 2270 RunnableAction(Runnable r)2271 RunnableAction(Runnable r) { 2272 mRunnable = r; 2273 } 2274 2275 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)2276 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2277 ColorResources colorResources) { 2278 mRunnable.run(); 2279 } 2280 } 2281 hasStableId(View view)2282 private static boolean hasStableId(View view) { 2283 Object tag = view.getTag(com.android.internal.R.id.remote_views_stable_id); 2284 return tag != null; 2285 } 2286 getStableId(View view)2287 private static int getStableId(View view) { 2288 Integer id = (Integer) view.getTag(com.android.internal.R.id.remote_views_stable_id); 2289 return id == null ? ViewGroupActionAdd.NO_ID : id; 2290 } 2291 setStableId(View view, int stableId)2292 private static void setStableId(View view, int stableId) { 2293 view.setTagInternal(com.android.internal.R.id.remote_views_stable_id, stableId); 2294 } 2295 2296 // Returns the next recyclable child of the view group, or -1 if there are none. getNextRecyclableChild(ViewGroup vg)2297 private static int getNextRecyclableChild(ViewGroup vg) { 2298 Integer tag = (Integer) vg.getTag(com.android.internal.R.id.remote_views_next_child); 2299 return tag == null ? -1 : tag; 2300 } 2301 getViewLayoutId(View v)2302 private static int getViewLayoutId(View v) { 2303 return (Integer) v.getTag(R.id.widget_frame); 2304 } 2305 setNextRecyclableChild(ViewGroup vg, int nextChild, int numChildren)2306 private static void setNextRecyclableChild(ViewGroup vg, int nextChild, int numChildren) { 2307 if (nextChild < 0 || nextChild >= numChildren) { 2308 vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, -1); 2309 } else { 2310 vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, nextChild); 2311 } 2312 } 2313 finalizeViewRecycling(ViewGroup root)2314 private void finalizeViewRecycling(ViewGroup root) { 2315 // Remove any recyclable children that were not used. nextChild should either be -1 or point 2316 // to the next recyclable child that hasn't been recycled. 2317 int nextChild = getNextRecyclableChild(root); 2318 if (nextChild >= 0 && nextChild < root.getChildCount()) { 2319 root.removeViews(nextChild, root.getChildCount() - nextChild); 2320 } 2321 // Make sure on the next round, we don't try to recycle if removeAllViews is not called. 2322 setNextRecyclableChild(root, -1, 0); 2323 // Traverse the view tree. 2324 for (int i = 0; i < root.getChildCount(); i++) { 2325 View child = root.getChildAt(i); 2326 if (child instanceof ViewGroup && !child.isRootNamespace()) { 2327 finalizeViewRecycling((ViewGroup) child); 2328 } 2329 } 2330 } 2331 2332 /** 2333 * ViewGroup methods that are related to adding Views. 2334 */ 2335 private class ViewGroupActionAdd extends Action { 2336 static final int NO_ID = -1; 2337 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 2338 private RemoteViews mNestedViews; 2339 private int mIndex; 2340 private int mStableId; 2341 ViewGroupActionAdd(@dRes int viewId, RemoteViews nestedViews)2342 ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews) { 2343 this(viewId, nestedViews, -1 /* index */, NO_ID /* nestedViewId */); 2344 } 2345 ViewGroupActionAdd(@dRes int viewId, RemoteViews nestedViews, int index)2346 ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index) { 2347 this(viewId, nestedViews, index, NO_ID /* nestedViewId */); 2348 } 2349 ViewGroupActionAdd(@dRes int viewId, RemoteViews nestedViews, int index, int stableId)2350 ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index, int stableId) { 2351 this.viewId = viewId; 2352 mNestedViews = nestedViews; 2353 mIndex = index; 2354 mStableId = stableId; 2355 nestedViews.configureAsChild(getHierarchyRootData()); 2356 } 2357 ViewGroupActionAdd(Parcel parcel, ApplicationInfo info, int depth)2358 ViewGroupActionAdd(Parcel parcel, ApplicationInfo info, int depth) { 2359 viewId = parcel.readInt(); 2360 mIndex = parcel.readInt(); 2361 mStableId = parcel.readInt(); 2362 mNestedViews = new RemoteViews(parcel, getHierarchyRootData(), info, depth); 2363 mNestedViews.addFlags(mApplyFlags); 2364 } 2365 writeToParcel(Parcel dest, int flags)2366 public void writeToParcel(Parcel dest, int flags) { 2367 dest.writeInt(viewId); 2368 dest.writeInt(mIndex); 2369 dest.writeInt(mStableId); 2370 mNestedViews.writeToParcel(dest, flags); 2371 } 2372 2373 @Override setHierarchyRootData(HierarchyRootData root)2374 public void setHierarchyRootData(HierarchyRootData root) { 2375 mNestedViews.configureAsChild(root); 2376 } 2377 findViewIndexToRecycle(ViewGroup target, RemoteViews newContent)2378 private int findViewIndexToRecycle(ViewGroup target, RemoteViews newContent) { 2379 for (int nextChild = getNextRecyclableChild(target); nextChild < target.getChildCount(); 2380 nextChild++) { 2381 View child = target.getChildAt(nextChild); 2382 if (getStableId(child) == mStableId) { 2383 return nextChild; 2384 } 2385 } 2386 return -1; 2387 } 2388 2389 @Override apply(View root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)2390 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2391 ColorResources colorResources) { 2392 final Context context = root.getContext(); 2393 final ViewGroup target = root.findViewById(viewId); 2394 2395 if (target == null) { 2396 return; 2397 } 2398 2399 // If removeAllViews was called, this returns the next potential recycled view. 2400 // If there are no more views to recycle (or removeAllViews was not called), this 2401 // will return -1. 2402 final int nextChild = getNextRecyclableChild(target); 2403 RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); 2404 if (nextChild >= 0 && mStableId != NO_ID) { 2405 // At that point, the views starting at index nextChild are the ones recyclable but 2406 // not yet recycled. All views added on that round of application are placed before. 2407 // Find the next view with the same stable id, or -1. 2408 int recycledViewIndex = findViewIndexToRecycle(target, rvToApply); 2409 if (recycledViewIndex >= 0) { 2410 View child = target.getChildAt(recycledViewIndex); 2411 if (rvToApply.canRecycleView(child)) { 2412 if (nextChild < recycledViewIndex) { 2413 target.removeViews(nextChild, recycledViewIndex - nextChild); 2414 } 2415 setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); 2416 rvToApply.reapply(context, child, handler, null /* size */, colorResources, 2417 false /* topLevel */); 2418 return; 2419 } 2420 // If we cannot recycle the views, we still remove all views in between to 2421 // avoid weird behaviors and insert the new view in place of the old one. 2422 target.removeViews(nextChild, recycledViewIndex - nextChild + 1); 2423 } 2424 } 2425 // If we cannot recycle, insert the new view before the next recyclable child. 2426 2427 // Inflate nested views and add as children 2428 View nestedView = rvToApply.apply(context, target, handler, null /* size */, 2429 colorResources); 2430 if (mStableId != NO_ID) { 2431 setStableId(nestedView, mStableId); 2432 } 2433 target.addView(nestedView, mIndex >= 0 ? mIndex : nextChild); 2434 if (nextChild >= 0) { 2435 // If we are at the end, there is no reason to try to recycle anymore 2436 setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); 2437 } 2438 } 2439 2440 @Override initActionAsync(ViewTree root, ViewGroup rootParent, InteractionHandler handler, ColorResources colorResources)2441 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 2442 InteractionHandler handler, ColorResources colorResources) { 2443 // In the async implementation, update the view tree so that subsequent calls to 2444 // findViewById return the current view. 2445 root.createTree(); 2446 ViewTree target = root.findViewTreeById(viewId); 2447 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 2448 return ACTION_NOOP; 2449 } 2450 final ViewGroup targetVg = (ViewGroup) target.mRoot; 2451 2452 // Inflate nested views and perform all the async tasks for the child remoteView. 2453 final Context context = root.mRoot.getContext(); 2454 2455 // If removeAllViews was called, this returns the next potential recycled view. 2456 // If there are no more views to recycle (or removeAllViews was not called), this 2457 // will return -1. 2458 final int nextChild = getNextRecyclableChild(targetVg); 2459 if (nextChild >= 0 && mStableId != NO_ID) { 2460 RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); 2461 final int recycledViewIndex = target.findChildIndex(nextChild, 2462 view -> getStableId(view) == mStableId); 2463 if (recycledViewIndex >= 0) { 2464 // At that point, the views starting at index nextChild are the ones 2465 // recyclable but not yet recycled. All views added on that round of 2466 // application are placed before. 2467 ViewTree recycled = target.mChildren.get(recycledViewIndex); 2468 // We can only recycle the view if the layout id is the same. 2469 if (rvToApply.canRecycleView(recycled.mRoot)) { 2470 if (recycledViewIndex > nextChild) { 2471 target.removeChildren(nextChild, recycledViewIndex - nextChild); 2472 } 2473 setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size()); 2474 final AsyncApplyTask reapplyTask = rvToApply.getInternalAsyncApplyTask( 2475 context, 2476 targetVg, null /* listener */, handler, null /* size */, 2477 colorResources, 2478 recycled.mRoot); 2479 final ViewTree tree = reapplyTask.doInBackground(); 2480 if (tree == null) { 2481 throw new ActionException(reapplyTask.mError); 2482 } 2483 return new RuntimeAction() { 2484 @Override 2485 public void apply(View root, ViewGroup rootParent, 2486 InteractionHandler handler, ColorResources colorResources) 2487 throws ActionException { 2488 reapplyTask.onPostExecute(tree); 2489 if (recycledViewIndex > nextChild) { 2490 targetVg.removeViews(nextChild, recycledViewIndex - nextChild); 2491 } 2492 } 2493 }; 2494 } 2495 // If the layout id is different, still remove the children as if we recycled 2496 // the view, to insert at the same place. 2497 target.removeChildren(nextChild, recycledViewIndex - nextChild + 1); 2498 return insertNewView(context, target, handler, colorResources, 2499 () -> targetVg.removeViews(nextChild, 2500 recycledViewIndex - nextChild + 1)); 2501 2502 } 2503 } 2504 // If we cannot recycle, simply add the view at the same available slot. 2505 return insertNewView(context, target, handler, colorResources, () -> {}); 2506 } 2507 insertNewView(Context context, ViewTree target, InteractionHandler handler, ColorResources colorResources, Runnable finalizeAction)2508 private Action insertNewView(Context context, ViewTree target, InteractionHandler handler, 2509 ColorResources colorResources, Runnable finalizeAction) { 2510 ViewGroup targetVg = (ViewGroup) target.mRoot; 2511 int nextChild = getNextRecyclableChild(targetVg); 2512 final AsyncApplyTask task = mNestedViews.getInternalAsyncApplyTask(context, targetVg, 2513 null /* listener */, handler, null /* size */, colorResources, 2514 null /* result */); 2515 final ViewTree tree = task.doInBackground(); 2516 2517 if (tree == null) { 2518 throw new ActionException(task.mError); 2519 } 2520 if (mStableId != NO_ID) { 2521 setStableId(task.mResult, mStableId); 2522 } 2523 2524 // Update the global view tree, so that next call to findViewTreeById 2525 // goes through the subtree as well. 2526 final int insertIndex = mIndex >= 0 ? mIndex : nextChild; 2527 target.addChild(tree, insertIndex); 2528 if (nextChild >= 0) { 2529 setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size()); 2530 } 2531 2532 return new RuntimeAction() { 2533 @Override 2534 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2535 ColorResources colorResources) throws ActionException { 2536 task.onPostExecute(tree); 2537 finalizeAction.run(); 2538 targetVg.addView(task.mResult, insertIndex); 2539 } 2540 }; 2541 } 2542 2543 @Override 2544 public int mergeBehavior() { 2545 return MERGE_APPEND; 2546 } 2547 2548 @Override 2549 public boolean prefersAsyncApply() { 2550 return mNestedViews.prefersAsyncApply(); 2551 } 2552 2553 @Override 2554 public int getActionTag() { 2555 return VIEW_GROUP_ACTION_ADD_TAG; 2556 } 2557 } 2558 2559 /** 2560 * ViewGroup methods related to removing child views. 2561 */ 2562 private class ViewGroupActionRemove extends Action { 2563 /** 2564 * Id that indicates that all child views of the affected ViewGroup should be removed. 2565 * 2566 * <p>Using -2 because the default id is -1. This avoids accidentally matching that. 2567 */ 2568 private static final int REMOVE_ALL_VIEWS_ID = -2; 2569 2570 private int mViewIdToKeep; 2571 2572 ViewGroupActionRemove(@IdRes int viewId) { 2573 this(viewId, REMOVE_ALL_VIEWS_ID); 2574 } 2575 2576 ViewGroupActionRemove(@IdRes int viewId, @IdRes int viewIdToKeep) { 2577 this.viewId = viewId; 2578 mViewIdToKeep = viewIdToKeep; 2579 } 2580 2581 ViewGroupActionRemove(Parcel parcel) { 2582 viewId = parcel.readInt(); 2583 mViewIdToKeep = parcel.readInt(); 2584 } 2585 2586 public void writeToParcel(Parcel dest, int flags) { 2587 dest.writeInt(viewId); 2588 dest.writeInt(mViewIdToKeep); 2589 } 2590 2591 @Override 2592 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2593 ColorResources colorResources) { 2594 final ViewGroup target = root.findViewById(viewId); 2595 2596 if (target == null) { 2597 return; 2598 } 2599 2600 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 2601 // Remote any view without a stable id 2602 for (int i = target.getChildCount() - 1; i >= 0; i--) { 2603 if (!hasStableId(target.getChildAt(i))) { 2604 target.removeViewAt(i); 2605 } 2606 } 2607 // In the end, only children with a stable id (i.e. recyclable) are left. 2608 setNextRecyclableChild(target, 0, target.getChildCount()); 2609 return; 2610 } 2611 2612 removeAllViewsExceptIdToKeep(target); 2613 } 2614 2615 @Override 2616 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 2617 InteractionHandler handler, ColorResources colorResources) { 2618 // In the async implementation, update the view tree so that subsequent calls to 2619 // findViewById return the current view. 2620 root.createTree(); 2621 ViewTree target = root.findViewTreeById(viewId); 2622 2623 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 2624 return ACTION_NOOP; 2625 } 2626 2627 final ViewGroup targetVg = (ViewGroup) target.mRoot; 2628 2629 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 2630 target.mChildren.removeIf(childTree -> !hasStableId(childTree.mRoot)); 2631 setNextRecyclableChild(targetVg, 0, target.mChildren.size()); 2632 } else { 2633 // Remove just the children which don't match the excepted view 2634 target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep); 2635 if (target.mChildren.isEmpty()) { 2636 target.mChildren = null; 2637 } 2638 } 2639 return new RuntimeAction() { 2640 @Override 2641 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2642 ColorResources colorResources) throws ActionException { 2643 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 2644 for (int i = targetVg.getChildCount() - 1; i >= 0; i--) { 2645 if (!hasStableId(targetVg.getChildAt(i))) { 2646 targetVg.removeViewAt(i); 2647 } 2648 } 2649 return; 2650 } 2651 2652 removeAllViewsExceptIdToKeep(targetVg); 2653 } 2654 }; 2655 } 2656 2657 /** 2658 * Iterates through the children in the given ViewGroup and removes all the views that 2659 * do not have an id of {@link #mViewIdToKeep}. 2660 */ 2661 private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) { 2662 // Otherwise, remove all the views that do not match the id to keep. 2663 int index = viewGroup.getChildCount() - 1; 2664 while (index >= 0) { 2665 if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) { 2666 viewGroup.removeViewAt(index); 2667 } 2668 index--; 2669 } 2670 } 2671 2672 @Override 2673 public int getActionTag() { 2674 return VIEW_GROUP_ACTION_REMOVE_TAG; 2675 } 2676 2677 @Override 2678 public int mergeBehavior() { 2679 return MERGE_APPEND; 2680 } 2681 } 2682 2683 /** 2684 * Action to remove a view from its parent. 2685 */ 2686 private class RemoveFromParentAction extends Action { 2687 2688 RemoveFromParentAction(@IdRes int viewId) { 2689 this.viewId = viewId; 2690 } 2691 2692 RemoveFromParentAction(Parcel parcel) { 2693 viewId = parcel.readInt(); 2694 } 2695 2696 public void writeToParcel(Parcel dest, int flags) { 2697 dest.writeInt(viewId); 2698 } 2699 2700 @Override 2701 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2702 ColorResources colorResources) { 2703 final View target = root.findViewById(viewId); 2704 2705 if (target == null || target == root) { 2706 return; 2707 } 2708 2709 ViewParent parent = target.getParent(); 2710 if (parent instanceof ViewManager) { 2711 ((ViewManager) parent).removeView(target); 2712 } 2713 } 2714 2715 @Override 2716 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 2717 InteractionHandler handler, ColorResources colorResources) { 2718 // In the async implementation, update the view tree so that subsequent calls to 2719 // findViewById return the correct view. 2720 root.createTree(); 2721 ViewTree target = root.findViewTreeById(viewId); 2722 2723 if (target == null || target == root) { 2724 return ACTION_NOOP; 2725 } 2726 2727 ViewTree parent = root.findViewTreeParentOf(target); 2728 if (parent == null || !(parent.mRoot instanceof ViewManager)) { 2729 return ACTION_NOOP; 2730 } 2731 final ViewManager parentVg = (ViewManager) parent.mRoot; 2732 2733 parent.mChildren.remove(target); 2734 return new RuntimeAction() { 2735 @Override 2736 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2737 ColorResources colorResources) throws ActionException { 2738 parentVg.removeView(target.mRoot); 2739 } 2740 }; 2741 } 2742 2743 @Override 2744 public int getActionTag() { 2745 return REMOVE_FROM_PARENT_ACTION_TAG; 2746 } 2747 2748 @Override 2749 public int mergeBehavior() { 2750 return MERGE_APPEND; 2751 } 2752 } 2753 2754 /** 2755 * Helper action to set compound drawables on a TextView. Supports relative 2756 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 2757 */ 2758 private class TextViewDrawableAction extends Action { 2759 public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, @DrawableRes int d1, 2760 @DrawableRes int d2, @DrawableRes int d3, @DrawableRes int d4) { 2761 this.viewId = viewId; 2762 this.isRelative = isRelative; 2763 this.useIcons = false; 2764 this.d1 = d1; 2765 this.d2 = d2; 2766 this.d3 = d3; 2767 this.d4 = d4; 2768 } 2769 2770 public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, 2771 Icon i1, Icon i2, Icon i3, Icon i4) { 2772 this.viewId = viewId; 2773 this.isRelative = isRelative; 2774 this.useIcons = true; 2775 this.i1 = i1; 2776 this.i2 = i2; 2777 this.i3 = i3; 2778 this.i4 = i4; 2779 } 2780 2781 public TextViewDrawableAction(Parcel parcel) { 2782 viewId = parcel.readInt(); 2783 isRelative = (parcel.readInt() != 0); 2784 useIcons = (parcel.readInt() != 0); 2785 if (useIcons) { 2786 i1 = parcel.readTypedObject(Icon.CREATOR); 2787 i2 = parcel.readTypedObject(Icon.CREATOR); 2788 i3 = parcel.readTypedObject(Icon.CREATOR); 2789 i4 = parcel.readTypedObject(Icon.CREATOR); 2790 } else { 2791 d1 = parcel.readInt(); 2792 d2 = parcel.readInt(); 2793 d3 = parcel.readInt(); 2794 d4 = parcel.readInt(); 2795 } 2796 } 2797 2798 public void writeToParcel(Parcel dest, int flags) { 2799 dest.writeInt(viewId); 2800 dest.writeInt(isRelative ? 1 : 0); 2801 dest.writeInt(useIcons ? 1 : 0); 2802 if (useIcons) { 2803 dest.writeTypedObject(i1, 0); 2804 dest.writeTypedObject(i2, 0); 2805 dest.writeTypedObject(i3, 0); 2806 dest.writeTypedObject(i4, 0); 2807 } else { 2808 dest.writeInt(d1); 2809 dest.writeInt(d2); 2810 dest.writeInt(d3); 2811 dest.writeInt(d4); 2812 } 2813 } 2814 2815 @Override 2816 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2817 ColorResources colorResources) { 2818 final TextView target = root.findViewById(viewId); 2819 if (target == null) return; 2820 if (drawablesLoaded) { 2821 if (isRelative) { 2822 target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4); 2823 } else { 2824 target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4); 2825 } 2826 } else if (useIcons) { 2827 final Context ctx = target.getContext(); 2828 final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx); 2829 final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx); 2830 final Drawable id3 = i3 == null ? null : i3.loadDrawable(ctx); 2831 final Drawable id4 = i4 == null ? null : i4.loadDrawable(ctx); 2832 if (isRelative) { 2833 target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4); 2834 } else { 2835 target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4); 2836 } 2837 } else { 2838 if (isRelative) { 2839 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4); 2840 } else { 2841 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4); 2842 } 2843 } 2844 } 2845 2846 @Override 2847 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 2848 InteractionHandler handler, ColorResources colorResources) { 2849 final TextView target = root.findViewById(viewId); 2850 if (target == null) return ACTION_NOOP; 2851 2852 TextViewDrawableAction copy = useIcons ? 2853 new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) : 2854 new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4); 2855 2856 // Load the drawables on the background thread. 2857 copy.drawablesLoaded = true; 2858 final Context ctx = target.getContext(); 2859 2860 if (useIcons) { 2861 copy.id1 = i1 == null ? null : i1.loadDrawable(ctx); 2862 copy.id2 = i2 == null ? null : i2.loadDrawable(ctx); 2863 copy.id3 = i3 == null ? null : i3.loadDrawable(ctx); 2864 copy.id4 = i4 == null ? null : i4.loadDrawable(ctx); 2865 } else { 2866 copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1); 2867 copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2); 2868 copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3); 2869 copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4); 2870 } 2871 return copy; 2872 } 2873 2874 @Override 2875 public boolean prefersAsyncApply() { 2876 return useIcons; 2877 } 2878 2879 @Override 2880 public int getActionTag() { 2881 return TEXT_VIEW_DRAWABLE_ACTION_TAG; 2882 } 2883 2884 @Override 2885 public void visitUris(@NonNull Consumer<Uri> visitor) { 2886 if (useIcons) { 2887 visitIconUri(i1, visitor); 2888 visitIconUri(i2, visitor); 2889 visitIconUri(i3, visitor); 2890 visitIconUri(i4, visitor); 2891 } 2892 } 2893 2894 boolean isRelative = false; 2895 boolean useIcons = false; 2896 int d1, d2, d3, d4; 2897 Icon i1, i2, i3, i4; 2898 2899 boolean drawablesLoaded = false; 2900 Drawable id1, id2, id3, id4; 2901 } 2902 2903 /** 2904 * Helper action to set text size on a TextView in any supported units. 2905 */ 2906 private class TextViewSizeAction extends Action { 2907 TextViewSizeAction(@IdRes int viewId, @ComplexDimensionUnit int units, float size) { 2908 this.viewId = viewId; 2909 this.units = units; 2910 this.size = size; 2911 } 2912 2913 TextViewSizeAction(Parcel parcel) { 2914 viewId = parcel.readInt(); 2915 units = parcel.readInt(); 2916 size = parcel.readFloat(); 2917 } 2918 2919 public void writeToParcel(Parcel dest, int flags) { 2920 dest.writeInt(viewId); 2921 dest.writeInt(units); 2922 dest.writeFloat(size); 2923 } 2924 2925 @Override 2926 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2927 ColorResources colorResources) { 2928 final TextView target = root.findViewById(viewId); 2929 if (target == null) return; 2930 target.setTextSize(units, size); 2931 } 2932 2933 @Override 2934 public int getActionTag() { 2935 return TEXT_VIEW_SIZE_ACTION_TAG; 2936 } 2937 2938 int units; 2939 float size; 2940 } 2941 2942 /** 2943 * Helper action to set padding on a View. 2944 */ 2945 private class ViewPaddingAction extends Action { 2946 public ViewPaddingAction(@IdRes int viewId, @Px int left, @Px int top, 2947 @Px int right, @Px int bottom) { 2948 this.viewId = viewId; 2949 this.left = left; 2950 this.top = top; 2951 this.right = right; 2952 this.bottom = bottom; 2953 } 2954 2955 public ViewPaddingAction(Parcel parcel) { 2956 viewId = parcel.readInt(); 2957 left = parcel.readInt(); 2958 top = parcel.readInt(); 2959 right = parcel.readInt(); 2960 bottom = parcel.readInt(); 2961 } 2962 2963 public void writeToParcel(Parcel dest, int flags) { 2964 dest.writeInt(viewId); 2965 dest.writeInt(left); 2966 dest.writeInt(top); 2967 dest.writeInt(right); 2968 dest.writeInt(bottom); 2969 } 2970 2971 @Override 2972 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 2973 ColorResources colorResources) { 2974 final View target = root.findViewById(viewId); 2975 if (target == null) return; 2976 target.setPadding(left, top, right, bottom); 2977 } 2978 2979 @Override 2980 public int getActionTag() { 2981 return VIEW_PADDING_ACTION_TAG; 2982 } 2983 2984 @Px int left, top, right, bottom; 2985 } 2986 2987 /** 2988 * Helper action to set layout params on a View. 2989 */ 2990 private static class LayoutParamAction extends Action { 2991 2992 static final int LAYOUT_MARGIN_LEFT = MARGIN_LEFT; 2993 static final int LAYOUT_MARGIN_TOP = MARGIN_TOP; 2994 static final int LAYOUT_MARGIN_RIGHT = MARGIN_RIGHT; 2995 static final int LAYOUT_MARGIN_BOTTOM = MARGIN_BOTTOM; 2996 static final int LAYOUT_MARGIN_START = MARGIN_START; 2997 static final int LAYOUT_MARGIN_END = MARGIN_END; 2998 static final int LAYOUT_WIDTH = 8; 2999 static final int LAYOUT_HEIGHT = 9; 3000 3001 final int mProperty; 3002 final int mValueType; 3003 final int mValue; 3004 3005 /** 3006 * @param viewId ID of the view alter 3007 * @param property which layout parameter to alter 3008 * @param value new value of the layout parameter 3009 * @param units the units of the given value 3010 */ 3011 LayoutParamAction(@IdRes int viewId, int property, float value, 3012 @ComplexDimensionUnit int units) { 3013 this.viewId = viewId; 3014 this.mProperty = property; 3015 this.mValueType = VALUE_TYPE_COMPLEX_UNIT; 3016 this.mValue = TypedValue.createComplexDimension(value, units); 3017 } 3018 3019 /** 3020 * @param viewId ID of the view alter 3021 * @param property which layout parameter to alter 3022 * @param value value to set. 3023 * @param valueType must be one of {@link #VALUE_TYPE_COMPLEX_UNIT}, 3024 * {@link #VALUE_TYPE_RESOURCE}, {@link #VALUE_TYPE_ATTRIBUTE} or 3025 * {@link #VALUE_TYPE_RAW}. 3026 */ 3027 LayoutParamAction(@IdRes int viewId, int property, int value, @ValueType int valueType) { 3028 this.viewId = viewId; 3029 this.mProperty = property; 3030 this.mValueType = valueType; 3031 this.mValue = value; 3032 } 3033 3034 public LayoutParamAction(Parcel parcel) { 3035 viewId = parcel.readInt(); 3036 mProperty = parcel.readInt(); 3037 mValueType = parcel.readInt(); 3038 mValue = parcel.readInt(); 3039 } 3040 3041 public void writeToParcel(Parcel dest, int flags) { 3042 dest.writeInt(viewId); 3043 dest.writeInt(mProperty); 3044 dest.writeInt(mValueType); 3045 dest.writeInt(mValue); 3046 } 3047 3048 @Override 3049 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 3050 ColorResources colorResources) { 3051 final View target = root.findViewById(viewId); 3052 if (target == null) { 3053 return; 3054 } 3055 ViewGroup.LayoutParams layoutParams = target.getLayoutParams(); 3056 if (layoutParams == null) { 3057 return; 3058 } 3059 switch (mProperty) { 3060 case LAYOUT_MARGIN_LEFT: 3061 if (layoutParams instanceof MarginLayoutParams) { 3062 ((MarginLayoutParams) layoutParams).leftMargin = getPixelOffset(target); 3063 target.setLayoutParams(layoutParams); 3064 } 3065 break; 3066 case LAYOUT_MARGIN_TOP: 3067 if (layoutParams instanceof MarginLayoutParams) { 3068 ((MarginLayoutParams) layoutParams).topMargin = getPixelOffset(target); 3069 target.setLayoutParams(layoutParams); 3070 } 3071 break; 3072 case LAYOUT_MARGIN_RIGHT: 3073 if (layoutParams instanceof MarginLayoutParams) { 3074 ((MarginLayoutParams) layoutParams).rightMargin = getPixelOffset(target); 3075 target.setLayoutParams(layoutParams); 3076 } 3077 break; 3078 case LAYOUT_MARGIN_BOTTOM: 3079 if (layoutParams instanceof MarginLayoutParams) { 3080 ((MarginLayoutParams) layoutParams).bottomMargin = getPixelOffset(target); 3081 target.setLayoutParams(layoutParams); 3082 } 3083 break; 3084 case LAYOUT_MARGIN_START: 3085 if (layoutParams instanceof MarginLayoutParams) { 3086 ((MarginLayoutParams) layoutParams).setMarginStart(getPixelOffset(target)); 3087 target.setLayoutParams(layoutParams); 3088 } 3089 break; 3090 case LAYOUT_MARGIN_END: 3091 if (layoutParams instanceof MarginLayoutParams) { 3092 ((MarginLayoutParams) layoutParams).setMarginEnd(getPixelOffset(target)); 3093 target.setLayoutParams(layoutParams); 3094 } 3095 break; 3096 case LAYOUT_WIDTH: 3097 layoutParams.width = getPixelSize(target); 3098 target.setLayoutParams(layoutParams); 3099 break; 3100 case LAYOUT_HEIGHT: 3101 layoutParams.height = getPixelSize(target); 3102 target.setLayoutParams(layoutParams); 3103 break; 3104 default: 3105 throw new IllegalArgumentException("Unknown property " + mProperty); 3106 } 3107 } 3108 3109 private int getPixelOffset(View target) { 3110 try { 3111 switch (mValueType) { 3112 case VALUE_TYPE_ATTRIBUTE: 3113 TypedArray typedArray = target.getContext().obtainStyledAttributes( 3114 new int[]{this.mValue}); 3115 try { 3116 return typedArray.getDimensionPixelOffset(0, 0); 3117 } finally { 3118 typedArray.recycle(); 3119 } 3120 case VALUE_TYPE_RESOURCE: 3121 if (mValue == 0) { 3122 return 0; 3123 } 3124 return target.getResources().getDimensionPixelOffset(mValue); 3125 case VALUE_TYPE_COMPLEX_UNIT: 3126 return TypedValue.complexToDimensionPixelOffset(mValue, 3127 target.getResources().getDisplayMetrics()); 3128 default: 3129 return mValue; 3130 } 3131 } catch (Throwable t) { 3132 throw new ActionException(t); 3133 } 3134 } 3135 3136 private int getPixelSize(View target) { 3137 try { 3138 switch (mValueType) { 3139 case VALUE_TYPE_ATTRIBUTE: 3140 TypedArray typedArray = target.getContext().obtainStyledAttributes( 3141 new int[]{this.mValue}); 3142 try { 3143 return typedArray.getDimensionPixelSize(0, 0); 3144 } finally { 3145 typedArray.recycle(); 3146 } 3147 case VALUE_TYPE_RESOURCE: 3148 if (mValue == 0) { 3149 return 0; 3150 } 3151 return target.getResources().getDimensionPixelSize(mValue); 3152 case VALUE_TYPE_COMPLEX_UNIT: 3153 return TypedValue.complexToDimensionPixelSize(mValue, 3154 target.getResources().getDisplayMetrics()); 3155 default: 3156 return mValue; 3157 } 3158 } catch (Throwable t) { 3159 throw new ActionException(t); 3160 } 3161 } 3162 3163 @Override 3164 public int getActionTag() { 3165 return LAYOUT_PARAM_ACTION_TAG; 3166 } 3167 3168 @Override 3169 public String getUniqueKey() { 3170 return super.getUniqueKey() + mProperty; 3171 } 3172 } 3173 3174 /** 3175 * Helper action to add a view tag with RemoteInputs. 3176 */ 3177 private class SetRemoteInputsAction extends Action { 3178 3179 public SetRemoteInputsAction(@IdRes int viewId, RemoteInput[] remoteInputs) { 3180 this.viewId = viewId; 3181 this.remoteInputs = remoteInputs; 3182 } 3183 3184 public SetRemoteInputsAction(Parcel parcel) { 3185 viewId = parcel.readInt(); 3186 remoteInputs = parcel.createTypedArray(RemoteInput.CREATOR); 3187 } 3188 3189 public void writeToParcel(Parcel dest, int flags) { 3190 dest.writeInt(viewId); 3191 dest.writeTypedArray(remoteInputs, flags); 3192 } 3193 3194 @Override 3195 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 3196 ColorResources colorResources) { 3197 final View target = root.findViewById(viewId); 3198 if (target == null) return; 3199 3200 target.setTagInternal(R.id.remote_input_tag, remoteInputs); 3201 } 3202 3203 @Override 3204 public int getActionTag() { 3205 return SET_REMOTE_INPUTS_ACTION_TAG; 3206 } 3207 3208 final Parcelable[] remoteInputs; 3209 } 3210 3211 /** 3212 * Helper action to override all textViewColors 3213 */ 3214 private class OverrideTextColorsAction extends Action { 3215 3216 private final int textColor; 3217 3218 public OverrideTextColorsAction(int textColor) { 3219 this.textColor = textColor; 3220 } 3221 3222 public OverrideTextColorsAction(Parcel parcel) { 3223 textColor = parcel.readInt(); 3224 } 3225 3226 public void writeToParcel(Parcel dest, int flags) { 3227 dest.writeInt(textColor); 3228 } 3229 3230 @Override 3231 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 3232 ColorResources colorResources) { 3233 // Let's traverse the viewtree and override all textColors! 3234 Stack<View> viewsToProcess = new Stack<>(); 3235 viewsToProcess.add(root); 3236 while (!viewsToProcess.isEmpty()) { 3237 View v = viewsToProcess.pop(); 3238 if (v instanceof TextView) { 3239 TextView textView = (TextView) v; 3240 textView.setText(ContrastColorUtil.clearColorSpans(textView.getText())); 3241 textView.setTextColor(textColor); 3242 } 3243 if (v instanceof ViewGroup) { 3244 ViewGroup viewGroup = (ViewGroup) v; 3245 for (int i = 0; i < viewGroup.getChildCount(); i++) { 3246 viewsToProcess.push(viewGroup.getChildAt(i)); 3247 } 3248 } 3249 } 3250 } 3251 3252 @Override 3253 public int getActionTag() { 3254 return OVERRIDE_TEXT_COLORS_TAG; 3255 } 3256 } 3257 3258 private class SetIntTagAction extends Action { 3259 @IdRes private final int mViewId; 3260 @IdRes private final int mKey; 3261 private final int mTag; 3262 3263 SetIntTagAction(@IdRes int viewId, @IdRes int key, int tag) { 3264 mViewId = viewId; 3265 mKey = key; 3266 mTag = tag; 3267 } 3268 3269 SetIntTagAction(Parcel parcel) { 3270 mViewId = parcel.readInt(); 3271 mKey = parcel.readInt(); 3272 mTag = parcel.readInt(); 3273 } 3274 3275 public void writeToParcel(Parcel dest, int flags) { 3276 dest.writeInt(mViewId); 3277 dest.writeInt(mKey); 3278 dest.writeInt(mTag); 3279 } 3280 3281 @Override 3282 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 3283 ColorResources colorResources) { 3284 final View target = root.findViewById(mViewId); 3285 if (target == null) return; 3286 3287 target.setTagInternal(mKey, mTag); 3288 } 3289 3290 @Override 3291 public int getActionTag() { 3292 return SET_INT_TAG_TAG; 3293 } 3294 } 3295 3296 private static class SetCompoundButtonCheckedAction extends Action { 3297 3298 private final boolean mChecked; 3299 3300 SetCompoundButtonCheckedAction(@IdRes int viewId, boolean checked) { 3301 this.viewId = viewId; 3302 mChecked = checked; 3303 } 3304 3305 SetCompoundButtonCheckedAction(Parcel in) { 3306 viewId = in.readInt(); 3307 mChecked = in.readBoolean(); 3308 } 3309 3310 @Override 3311 public void writeToParcel(Parcel dest, int flags) { 3312 dest.writeInt(viewId); 3313 dest.writeBoolean(mChecked); 3314 } 3315 3316 @Override 3317 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 3318 ColorResources colorResources) 3319 throws ActionException { 3320 final View target = root.findViewById(viewId); 3321 if (target == null) return; 3322 3323 if (!(target instanceof CompoundButton)) { 3324 Log.w(LOG_TAG, "Cannot set checked to view " 3325 + viewId + " because it is not a CompoundButton"); 3326 return; 3327 } 3328 3329 CompoundButton button = (CompoundButton) target; 3330 Object tag = button.getTag(R.id.remote_checked_change_listener_tag); 3331 // Temporarily unset the checked change listener so calling setChecked doesn't launch 3332 // the intent. 3333 if (tag instanceof OnCheckedChangeListener) { 3334 button.setOnCheckedChangeListener(null); 3335 button.setChecked(mChecked); 3336 button.setOnCheckedChangeListener((OnCheckedChangeListener) tag); 3337 } else { 3338 button.setChecked(mChecked); 3339 } 3340 } 3341 3342 @Override 3343 public int getActionTag() { 3344 return SET_COMPOUND_BUTTON_CHECKED_TAG; 3345 } 3346 } 3347 3348 private static class SetRadioGroupCheckedAction extends Action { 3349 3350 @IdRes private final int mCheckedId; 3351 3352 SetRadioGroupCheckedAction(@IdRes int viewId, @IdRes int checkedId) { 3353 this.viewId = viewId; 3354 mCheckedId = checkedId; 3355 } 3356 3357 SetRadioGroupCheckedAction(Parcel in) { 3358 viewId = in.readInt(); 3359 mCheckedId = in.readInt(); 3360 } 3361 3362 @Override 3363 public void writeToParcel(Parcel dest, int flags) { 3364 dest.writeInt(viewId); 3365 dest.writeInt(mCheckedId); 3366 } 3367 3368 @Override 3369 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 3370 ColorResources colorResources) throws ActionException { 3371 final View target = root.findViewById(viewId); 3372 if (target == null) return; 3373 3374 if (!(target instanceof RadioGroup)) { 3375 Log.w(LOG_TAG, "Cannot check " + viewId + " because it's not a RadioGroup"); 3376 return; 3377 } 3378 3379 RadioGroup group = (RadioGroup) target; 3380 3381 // Temporarily unset all the checked change listeners while we check the group. 3382 for (int i = 0; i < group.getChildCount(); i++) { 3383 View child = group.getChildAt(i); 3384 if (!(child instanceof CompoundButton)) continue; 3385 3386 Object tag = child.getTag(R.id.remote_checked_change_listener_tag); 3387 if (!(tag instanceof OnCheckedChangeListener)) continue; 3388 3389 // Clear the checked change listener, we'll restore it after the check. 3390 ((CompoundButton) child).setOnCheckedChangeListener(null); 3391 } 3392 3393 group.check(mCheckedId); 3394 3395 // Loop through the children again and restore the checked change listeners. 3396 for (int i = 0; i < group.getChildCount(); i++) { 3397 View child = group.getChildAt(i); 3398 if (!(child instanceof CompoundButton)) continue; 3399 3400 Object tag = child.getTag(R.id.remote_checked_change_listener_tag); 3401 if (!(tag instanceof OnCheckedChangeListener)) continue; 3402 3403 ((CompoundButton) child).setOnCheckedChangeListener((OnCheckedChangeListener) tag); 3404 } 3405 } 3406 3407 @Override 3408 public int getActionTag() { 3409 return SET_RADIO_GROUP_CHECKED; 3410 } 3411 } 3412 3413 private static class SetViewOutlinePreferredRadiusAction extends Action { 3414 3415 @ValueType 3416 private final int mValueType; 3417 private final int mValue; 3418 3419 SetViewOutlinePreferredRadiusAction(@IdRes int viewId, int value, 3420 @ValueType int valueType) { 3421 this.viewId = viewId; 3422 this.mValueType = valueType; 3423 this.mValue = value; 3424 } 3425 3426 SetViewOutlinePreferredRadiusAction( 3427 @IdRes int viewId, float radius, @ComplexDimensionUnit int units) { 3428 this.viewId = viewId; 3429 this.mValueType = VALUE_TYPE_COMPLEX_UNIT; 3430 this.mValue = TypedValue.createComplexDimension(radius, units); 3431 3432 } 3433 3434 SetViewOutlinePreferredRadiusAction(Parcel in) { 3435 viewId = in.readInt(); 3436 mValueType = in.readInt(); 3437 mValue = in.readInt(); 3438 } 3439 3440 @Override 3441 public void writeToParcel(Parcel dest, int flags) { 3442 dest.writeInt(viewId); 3443 dest.writeInt(mValueType); 3444 dest.writeInt(mValue); 3445 } 3446 3447 @Override 3448 public void apply(View root, ViewGroup rootParent, InteractionHandler handler, 3449 ColorResources colorResources) throws ActionException { 3450 final View target = root.findViewById(viewId); 3451 if (target == null) return; 3452 3453 try { 3454 float radius; 3455 switch (mValueType) { 3456 case VALUE_TYPE_ATTRIBUTE: 3457 TypedArray typedArray = target.getContext().obtainStyledAttributes( 3458 new int[]{mValue}); 3459 try { 3460 radius = typedArray.getDimension(0, 0); 3461 } finally { 3462 typedArray.recycle(); 3463 } 3464 break; 3465 case VALUE_TYPE_RESOURCE: 3466 radius = mValue == 0 ? 0 : target.getResources().getDimension(mValue); 3467 break; 3468 case VALUE_TYPE_COMPLEX_UNIT: 3469 radius = TypedValue.complexToDimension(mValue, 3470 target.getResources().getDisplayMetrics()); 3471 break; 3472 default: 3473 radius = mValue; 3474 } 3475 target.setOutlineProvider(new RemoteViewOutlineProvider(radius)); 3476 } catch (Throwable t) { 3477 throw new ActionException(t); 3478 } 3479 } 3480 3481 @Override 3482 public int getActionTag() { 3483 return SET_VIEW_OUTLINE_RADIUS_TAG; 3484 } 3485 } 3486 3487 /** 3488 * OutlineProvider for a view with a radius set by 3489 * {@link #setViewOutlinePreferredRadius(int, float, int)}. 3490 */ 3491 public static final class RemoteViewOutlineProvider extends ViewOutlineProvider { 3492 3493 private final float mRadius; 3494 3495 public RemoteViewOutlineProvider(float radius) { 3496 mRadius = radius; 3497 } 3498 3499 /** Returns the corner radius used when providing the view outline. */ 3500 public float getRadius() { 3501 return mRadius; 3502 } 3503 3504 @Override 3505 public void getOutline(@NonNull View view, @NonNull Outline outline) { 3506 outline.setRoundRect( 3507 0 /*left*/, 3508 0 /* top */, 3509 view.getWidth() /* right */, 3510 view.getHeight() /* bottom */, 3511 mRadius); 3512 } 3513 } 3514 3515 /** 3516 * Create a new RemoteViews object that will display the views contained 3517 * in the specified layout file. 3518 * 3519 * @param packageName Name of the package that contains the layout resource 3520 * @param layoutId The id of the layout resource 3521 */ 3522 public RemoteViews(String packageName, int layoutId) { 3523 this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId); 3524 } 3525 3526 /** 3527 * Create a new RemoteViews object that will display the views contained 3528 * in the specified layout file and change the id of the root view to the specified one. 3529 * 3530 * @param packageName Name of the package that contains the layout resource 3531 * @param layoutId The id of the layout resource 3532 */ 3533 public RemoteViews(@NonNull String packageName, @LayoutRes int layoutId, @IdRes int viewId) { 3534 this(packageName, layoutId); 3535 this.mViewId = viewId; 3536 } 3537 3538 /** 3539 * Create a new RemoteViews object that will display the views contained 3540 * in the specified layout file. 3541 * 3542 * @param application The application whose content is shown by the views. 3543 * @param layoutId The id of the layout resource. 3544 * 3545 * @hide 3546 */ 3547 protected RemoteViews(ApplicationInfo application, @LayoutRes int layoutId) { 3548 mApplication = application; 3549 mLayoutId = layoutId; 3550 mApplicationInfoCache.put(application); 3551 } 3552 3553 private boolean hasMultipleLayouts() { 3554 return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews(); 3555 } 3556 3557 private boolean hasLandscapeAndPortraitLayouts() { 3558 return (mLandscape != null) && (mPortrait != null); 3559 } 3560 3561 private boolean hasSizedRemoteViews() { 3562 return mSizedRemoteViews != null; 3563 } 3564 3565 private @Nullable SizeF getIdealSize() { 3566 return mIdealSize; 3567 } 3568 3569 private void setIdealSize(@Nullable SizeF size) { 3570 mIdealSize = size; 3571 } 3572 3573 /** 3574 * Finds the smallest view in {@code mSizedRemoteViews}. 3575 * This method must not be called if {@code mSizedRemoteViews} is null. 3576 */ 3577 private RemoteViews findSmallestRemoteView() { 3578 return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1); 3579 } 3580 3581 /** 3582 * Create a new RemoteViews object that will inflate as the specified 3583 * landspace or portrait RemoteViews, depending on the current configuration. 3584 * 3585 * @param landscape The RemoteViews to inflate in landscape configuration 3586 * @param portrait The RemoteViews to inflate in portrait configuration 3587 * @throws IllegalArgumentException if either landscape or portrait are null or if they are 3588 * not from the same application 3589 */ 3590 public RemoteViews(RemoteViews landscape, RemoteViews portrait) { 3591 if (landscape == null || portrait == null) { 3592 throw new IllegalArgumentException("Both RemoteViews must be non-null"); 3593 } 3594 if (!landscape.hasSameAppInfo(portrait.mApplication)) { 3595 throw new IllegalArgumentException( 3596 "Both RemoteViews must share the same package and user"); 3597 } 3598 mApplication = portrait.mApplication; 3599 mLayoutId = portrait.mLayoutId; 3600 mViewId = portrait.mViewId; 3601 mLightBackgroundLayoutId = portrait.mLightBackgroundLayoutId; 3602 3603 mLandscape = landscape; 3604 mPortrait = portrait; 3605 3606 mClassCookies = (portrait.mClassCookies != null) 3607 ? portrait.mClassCookies : landscape.mClassCookies; 3608 3609 configureDescendantsAsChildren(); 3610 } 3611 3612 /** 3613 * Create a new RemoteViews object that will inflate the layout with the closest size 3614 * specification. 3615 * 3616 * The default remote views in that case is always the one with the smallest area. 3617 * 3618 * If the {@link RemoteViews} host provides the size of the view, the layout with the largest 3619 * area that fits entirely in the provided size will be used (i.e. the width and height of 3620 * the layout must be less than the size of the view, with a 1dp margin to account for 3621 * rounding). If no layout fits in the view, the layout with the smallest area will be used. 3622 * 3623 * @param remoteViews Mapping of size to layout. 3624 * @throws IllegalArgumentException if the map is empty, there are more than 3625 * MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application. 3626 */ 3627 public RemoteViews(@NonNull Map<SizeF, RemoteViews> remoteViews) { 3628 if (remoteViews.isEmpty()) { 3629 throw new IllegalArgumentException("The set of RemoteViews cannot be empty"); 3630 } 3631 if (remoteViews.size() > MAX_INIT_VIEW_COUNT) { 3632 throw new IllegalArgumentException("Too many RemoteViews in constructor"); 3633 } 3634 if (remoteViews.size() == 1) { 3635 // If the map only contains a single mapping, treat this as if that RemoteViews was 3636 // passed as the top-level RemoteViews. 3637 RemoteViews single = remoteViews.values().iterator().next(); 3638 initializeFrom(single, /* hierarchyRoot= */ single); 3639 return; 3640 } 3641 mClassCookies = initializeSizedRemoteViews( 3642 remoteViews.entrySet().stream().map( 3643 entry -> { 3644 entry.getValue().setIdealSize(entry.getKey()); 3645 return entry.getValue(); 3646 } 3647 ).iterator() 3648 ); 3649 3650 RemoteViews smallestView = findSmallestRemoteView(); 3651 mApplication = smallestView.mApplication; 3652 mLayoutId = smallestView.mLayoutId; 3653 mViewId = smallestView.mViewId; 3654 mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; 3655 3656 configureDescendantsAsChildren(); 3657 } 3658 3659 // Initialize mSizedRemoteViews and return the class cookies. 3660 private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) { 3661 List<RemoteViews> sizedRemoteViews = new ArrayList<>(); 3662 Map<Class, Object> classCookies = null; 3663 float viewArea = Float.MAX_VALUE; 3664 RemoteViews smallestView = null; 3665 while (remoteViews.hasNext()) { 3666 RemoteViews view = remoteViews.next(); 3667 SizeF size = view.getIdealSize(); 3668 if (size == null) { 3669 throw new IllegalStateException("Expected RemoteViews to have ideal size"); 3670 } 3671 float newViewArea = size.getWidth() * size.getHeight(); 3672 if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) { 3673 throw new IllegalArgumentException( 3674 "All RemoteViews must share the same package and user"); 3675 } 3676 if (smallestView == null || newViewArea < viewArea) { 3677 if (smallestView != null) { 3678 sizedRemoteViews.add(smallestView); 3679 } 3680 viewArea = newViewArea; 3681 smallestView = view; 3682 } else { 3683 sizedRemoteViews.add(view); 3684 } 3685 view.setIdealSize(size); 3686 if (classCookies == null) { 3687 classCookies = view.mClassCookies; 3688 } 3689 } 3690 sizedRemoteViews.add(smallestView); 3691 mSizedRemoteViews = sizedRemoteViews; 3692 return classCookies; 3693 } 3694 3695 /** 3696 * Creates a copy of another RemoteViews. 3697 */ 3698 public RemoteViews(RemoteViews src) { 3699 initializeFrom(src, /* hierarchyRoot= */ null); 3700 } 3701 3702 /** 3703 * No-arg constructor for use with {@link #initializeFrom(RemoteViews, RemoteViews)}. A 3704 * constructor taking two RemoteViews parameters would clash with the landscape/portrait 3705 * constructor. 3706 */ 3707 private RemoteViews() {} 3708 3709 private static RemoteViews createInitializedFrom(@NonNull RemoteViews src, 3710 @Nullable RemoteViews hierarchyRoot) { 3711 RemoteViews child = new RemoteViews(); 3712 child.initializeFrom(src, hierarchyRoot); 3713 return child; 3714 } 3715 3716 private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) { 3717 if (hierarchyRoot == null) { 3718 mBitmapCache = src.mBitmapCache; 3719 mApplicationInfoCache = src.mApplicationInfoCache; 3720 } else { 3721 mBitmapCache = hierarchyRoot.mBitmapCache; 3722 mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache; 3723 } 3724 if (hierarchyRoot == null || src.mIsRoot) { 3725 // If there's no provided root, or if src was itself a root, then this RemoteViews is 3726 // the root of the new hierarchy. 3727 mIsRoot = true; 3728 hierarchyRoot = this; 3729 } else { 3730 // Otherwise, we're a descendant in the hierarchy. 3731 mIsRoot = false; 3732 } 3733 mApplication = src.mApplication; 3734 mLayoutId = src.mLayoutId; 3735 mLightBackgroundLayoutId = src.mLightBackgroundLayoutId; 3736 mApplyFlags = src.mApplyFlags; 3737 mClassCookies = src.mClassCookies; 3738 mIdealSize = src.mIdealSize; 3739 mProviderInstanceId = src.mProviderInstanceId; 3740 3741 if (src.hasLandscapeAndPortraitLayouts()) { 3742 mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot); 3743 mPortrait = createInitializedFrom(src.mPortrait, hierarchyRoot); 3744 } 3745 3746 if (src.hasSizedRemoteViews()) { 3747 mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size()); 3748 for (RemoteViews srcView : src.mSizedRemoteViews) { 3749 mSizedRemoteViews.add(createInitializedFrom(srcView, hierarchyRoot)); 3750 } 3751 } 3752 3753 if (src.mActions != null) { 3754 Parcel p = Parcel.obtain(); 3755 p.putClassCookies(mClassCookies); 3756 src.writeActionsToParcel(p, /* flags= */ 0); 3757 p.setDataPosition(0); 3758 // Since src is already in memory, we do not care about stack overflow as it has 3759 // already been read once. 3760 readActionsFromParcel(p, 0); 3761 p.recycle(); 3762 } 3763 3764 // Now that everything is initialized and duplicated, create new caches for this 3765 // RemoteViews and recursively set up all descendants. 3766 if (mIsRoot) { 3767 reconstructCaches(); 3768 } 3769 } 3770 3771 /** 3772 * Reads a RemoteViews object from a parcel. 3773 * 3774 * @param parcel 3775 */ 3776 public RemoteViews(Parcel parcel) { 3777 this(parcel, /* rootParent= */ null, /* info= */ null, /* depth= */ 0); 3778 } 3779 3780 private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData, 3781 @Nullable ApplicationInfo info, int depth) { 3782 if (depth > MAX_NESTED_VIEWS 3783 && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) { 3784 throw new IllegalArgumentException("Too many nested views."); 3785 } 3786 depth++; 3787 3788 int mode = parcel.readInt(); 3789 3790 if (rootData == null) { 3791 // We only store a bitmap cache in the root of the RemoteViews. 3792 mBitmapCache = new BitmapCache(parcel); 3793 // Store the class cookies such that they are available when we clone this RemoteView. 3794 mClassCookies = parcel.copyClassCookies(); 3795 } else { 3796 configureAsChild(rootData); 3797 } 3798 3799 if (mode == MODE_NORMAL) { 3800 mApplication = ApplicationInfo.CREATOR.createFromParcel(parcel); 3801 mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel); 3802 mLayoutId = parcel.readInt(); 3803 mViewId = parcel.readInt(); 3804 mLightBackgroundLayoutId = parcel.readInt(); 3805 3806 readActionsFromParcel(parcel, depth); 3807 } else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) { 3808 int numViews = parcel.readInt(); 3809 if (numViews > MAX_INIT_VIEW_COUNT) { 3810 throw new IllegalArgumentException( 3811 "Too many views in mapping from size to RemoteViews."); 3812 } 3813 List<RemoteViews> remoteViews = new ArrayList<>(numViews); 3814 for (int i = 0; i < numViews; i++) { 3815 RemoteViews view = new RemoteViews(parcel, getHierarchyRootData(), info, depth); 3816 info = view.mApplication; 3817 remoteViews.add(view); 3818 } 3819 initializeSizedRemoteViews(remoteViews.iterator()); 3820 RemoteViews smallestView = findSmallestRemoteView(); 3821 mApplication = smallestView.mApplication; 3822 mLayoutId = smallestView.mLayoutId; 3823 mViewId = smallestView.mViewId; 3824 mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; 3825 } else { 3826 // MODE_HAS_LANDSCAPE_AND_PORTRAIT 3827 mLandscape = new RemoteViews(parcel, getHierarchyRootData(), info, depth); 3828 mPortrait = 3829 new RemoteViews(parcel, getHierarchyRootData(), mLandscape.mApplication, depth); 3830 mApplication = mPortrait.mApplication; 3831 mLayoutId = mPortrait.mLayoutId; 3832 mViewId = mPortrait.mViewId; 3833 mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId; 3834 } 3835 mApplyFlags = parcel.readInt(); 3836 mProviderInstanceId = parcel.readLong(); 3837 3838 // Ensure that all descendants have their caches set up recursively. 3839 if (mIsRoot) { 3840 configureDescendantsAsChildren(); 3841 } 3842 } 3843 3844 private void readActionsFromParcel(Parcel parcel, int depth) { 3845 int count = parcel.readInt(); 3846 if (count > 0) { 3847 mActions = new ArrayList<>(count); 3848 for (int i = 0; i < count; i++) { 3849 mActions.add(getActionFromParcel(parcel, depth)); 3850 } 3851 } 3852 } 3853 3854 private Action getActionFromParcel(Parcel parcel, int depth) { 3855 int tag = parcel.readInt(); 3856 switch (tag) { 3857 case SET_ON_CLICK_RESPONSE_TAG: 3858 return new SetOnClickResponse(parcel); 3859 case SET_DRAWABLE_TINT_TAG: 3860 return new SetDrawableTint(parcel); 3861 case REFLECTION_ACTION_TAG: 3862 return new ReflectionAction(parcel); 3863 case VIEW_GROUP_ACTION_ADD_TAG: 3864 return new ViewGroupActionAdd(parcel, mApplication, depth); 3865 case VIEW_GROUP_ACTION_REMOVE_TAG: 3866 return new ViewGroupActionRemove(parcel); 3867 case VIEW_CONTENT_NAVIGATION_TAG: 3868 return new ViewContentNavigation(parcel); 3869 case SET_EMPTY_VIEW_ACTION_TAG: 3870 return new SetEmptyView(parcel); 3871 case SET_PENDING_INTENT_TEMPLATE_TAG: 3872 return new SetPendingIntentTemplate(parcel); 3873 case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG: 3874 return new SetRemoteViewsAdapterIntent(parcel); 3875 case TEXT_VIEW_DRAWABLE_ACTION_TAG: 3876 return new TextViewDrawableAction(parcel); 3877 case TEXT_VIEW_SIZE_ACTION_TAG: 3878 return new TextViewSizeAction(parcel); 3879 case VIEW_PADDING_ACTION_TAG: 3880 return new ViewPaddingAction(parcel); 3881 case BITMAP_REFLECTION_ACTION_TAG: 3882 return new BitmapReflectionAction(parcel); 3883 case SET_REMOTE_VIEW_ADAPTER_LIST_TAG: 3884 return new SetRemoteViewsAdapterList(parcel); 3885 case SET_REMOTE_INPUTS_ACTION_TAG: 3886 return new SetRemoteInputsAction(parcel); 3887 case LAYOUT_PARAM_ACTION_TAG: 3888 return new LayoutParamAction(parcel); 3889 case OVERRIDE_TEXT_COLORS_TAG: 3890 return new OverrideTextColorsAction(parcel); 3891 case SET_RIPPLE_DRAWABLE_COLOR_TAG: 3892 return new SetRippleDrawableColor(parcel); 3893 case SET_INT_TAG_TAG: 3894 return new SetIntTagAction(parcel); 3895 case REMOVE_FROM_PARENT_ACTION_TAG: 3896 return new RemoveFromParentAction(parcel); 3897 case RESOURCE_REFLECTION_ACTION_TAG: 3898 return new ResourceReflectionAction(parcel); 3899 case COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG: 3900 return new ComplexUnitDimensionReflectionAction(parcel); 3901 case SET_COMPOUND_BUTTON_CHECKED_TAG: 3902 return new SetCompoundButtonCheckedAction(parcel); 3903 case SET_RADIO_GROUP_CHECKED: 3904 return new SetRadioGroupCheckedAction(parcel); 3905 case SET_VIEW_OUTLINE_RADIUS_TAG: 3906 return new SetViewOutlinePreferredRadiusAction(parcel); 3907 case SET_ON_CHECKED_CHANGE_RESPONSE_TAG: 3908 return new SetOnCheckedChangeResponse(parcel); 3909 case NIGHT_MODE_REFLECTION_ACTION_TAG: 3910 return new NightModeReflectionAction(parcel); 3911 case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG: 3912 return new SetRemoteCollectionItemListAdapterAction(parcel); 3913 case ATTRIBUTE_REFLECTION_ACTION_TAG: 3914 return new AttributeReflectionAction(parcel); 3915 default: 3916 throw new ActionException("Tag " + tag + " not found"); 3917 } 3918 }; 3919 3920 /** 3921 * Returns a deep copy of the RemoteViews object. The RemoteView may not be 3922 * attached to another RemoteView -- it must be the root of a hierarchy. 3923 * 3924 * @deprecated use {@link #RemoteViews(RemoteViews)} instead. 3925 * @throws IllegalStateException if this is not the root of a RemoteView 3926 * hierarchy 3927 */ 3928 @Override 3929 @Deprecated 3930 public RemoteViews clone() { 3931 Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. " 3932 + "May only clone the root of a RemoteView hierarchy."); 3933 3934 return new RemoteViews(this); 3935 } 3936 3937 public String getPackage() { 3938 return (mApplication != null) ? mApplication.packageName : null; 3939 } 3940 3941 /** 3942 * Returns the layout id of the root layout associated with this RemoteViews. In the case 3943 * that the RemoteViews has both a landscape and portrait root, this will return the layout 3944 * id associated with the portrait layout. 3945 * 3946 * @return the layout id. 3947 */ 3948 public int getLayoutId() { 3949 return hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT) && (mLightBackgroundLayoutId != 0) 3950 ? mLightBackgroundLayoutId : mLayoutId; 3951 } 3952 3953 /** 3954 * Sets the root of the hierarchy and then recursively traverses the tree to update the root 3955 * and populate caches for all descendants. 3956 */ 3957 private void configureAsChild(@NonNull HierarchyRootData rootData) { 3958 mIsRoot = false; 3959 mBitmapCache = rootData.mBitmapCache; 3960 mApplicationInfoCache = rootData.mApplicationInfoCache; 3961 mClassCookies = rootData.mClassCookies; 3962 configureDescendantsAsChildren(); 3963 } 3964 3965 /** 3966 * Recursively traverses the tree to update the root and populate caches for all descendants. 3967 */ 3968 private void configureDescendantsAsChildren() { 3969 // Before propagating down the tree, replace our application from the root application info 3970 // cache, to ensure the same instance is present throughout the hierarchy to allow for 3971 // squashing. 3972 mApplication = mApplicationInfoCache.getOrPut(mApplication); 3973 3974 HierarchyRootData rootData = getHierarchyRootData(); 3975 if (hasSizedRemoteViews()) { 3976 for (RemoteViews remoteView : mSizedRemoteViews) { 3977 remoteView.configureAsChild(rootData); 3978 } 3979 } else if (hasLandscapeAndPortraitLayouts()) { 3980 mLandscape.configureAsChild(rootData); 3981 mPortrait.configureAsChild(rootData); 3982 } else { 3983 if (mActions != null) { 3984 for (Action action : mActions) { 3985 action.setHierarchyRootData(rootData); 3986 } 3987 } 3988 } 3989 } 3990 3991 /** 3992 * Recreates caches at the root level of the hierarchy, then recursively populates the caches 3993 * down the hierarchy. 3994 */ 3995 private void reconstructCaches() { 3996 if (!mIsRoot) return; 3997 mBitmapCache = new BitmapCache(); 3998 mApplicationInfoCache = new ApplicationInfoCache(); 3999 mApplication = mApplicationInfoCache.getOrPut(mApplication); 4000 configureDescendantsAsChildren(); 4001 } 4002 4003 /** 4004 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 4005 */ 4006 /** @hide */ 4007 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 4008 public int estimateMemoryUsage() { 4009 return mBitmapCache.getBitmapMemory(); 4010 } 4011 4012 /** 4013 * Add an action to be executed on the remote side when apply is called. 4014 * 4015 * @param a The action to add 4016 */ 4017 private void addAction(Action a) { 4018 if (hasMultipleLayouts()) { 4019 throw new RuntimeException("RemoteViews specifying separate layouts for orientation" 4020 + " or size cannot be modified. Instead, fully configure each layouts" 4021 + " individually before constructing the combined layout."); 4022 } 4023 if (mActions == null) { 4024 mActions = new ArrayList<>(); 4025 } 4026 mActions.add(a); 4027 } 4028 4029 /** 4030 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 4031 * given {@link RemoteViews}. This allows users to build "nested" 4032 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 4033 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 4034 * children. 4035 * 4036 * @param viewId The id of the parent {@link ViewGroup} to add child into. 4037 * @param nestedView {@link RemoteViews} that describes the child. 4038 */ 4039 public void addView(@IdRes int viewId, RemoteViews nestedView) { 4040 // Clear all children when nested views omitted 4041 addAction(nestedView == null 4042 ? new ViewGroupActionRemove(viewId) 4043 : new ViewGroupActionAdd(viewId, nestedView)); 4044 } 4045 4046 /** 4047 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the given 4048 * {@link RemoteViews}. If the {@link RemoteViews} may be re-inflated or updated, 4049 * {@link #removeAllViews(int)} must be called on the same {@code viewId 4050 * } before the first call to this method for the behavior of this method to be predictable. 4051 * 4052 * The {@code stableId} will be used to identify a potential view to recycled when the remote 4053 * view is inflated. Views can be re-used if inserted in the same order, potentially with 4054 * some views appearing / disappearing. To be recycled the view must not change the layout 4055 * used to inflate it or its view id (see {@link RemoteViews#RemoteViews(String, int, int)}). 4056 * 4057 * Note: if a view is re-used, all the actions will be re-applied on it. However, its properties 4058 * are not reset, so what was applied in previous round will have an effect. As a view may be 4059 * re-created at any time by the host, the RemoteViews should not rely on keeping information 4060 * from previous applications and always re-set all the properties they need. 4061 * 4062 * @param viewId The id of the parent {@link ViewGroup} to add child into. 4063 * @param nestedView {@link RemoteViews} that describes the child. 4064 * @param stableId An id that is stable across different versions of RemoteViews. 4065 */ 4066 public void addStableView(@IdRes int viewId, @NonNull RemoteViews nestedView, int stableId) { 4067 addAction(new ViewGroupActionAdd(viewId, nestedView, -1 /* index */, stableId)); 4068 } 4069 4070 /** 4071 * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the 4072 * given {@link RemoteViews}. 4073 * 4074 * @param viewId The id of the parent {@link ViewGroup} to add the child into. 4075 * @param nestedView {@link RemoteViews} of the child to add. 4076 * @param index The position at which to add the child. 4077 * 4078 * @hide 4079 */ 4080 @UnsupportedAppUsage 4081 public void addView(@IdRes int viewId, RemoteViews nestedView, int index) { 4082 addAction(new ViewGroupActionAdd(viewId, nestedView, index)); 4083 } 4084 4085 /** 4086 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 4087 * 4088 * @param viewId The id of the parent {@link ViewGroup} to remove all 4089 * children from. 4090 */ 4091 public void removeAllViews(@IdRes int viewId) { 4092 addAction(new ViewGroupActionRemove(viewId)); 4093 } 4094 4095 /** 4096 * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any 4097 * child that has the {@code viewIdToKeep} as its id. 4098 * 4099 * @param viewId The id of the parent {@link ViewGroup} to remove children from. 4100 * @param viewIdToKeep The id of a child that should not be removed. 4101 * 4102 * @hide 4103 */ 4104 public void removeAllViewsExceptId(@IdRes int viewId, @IdRes int viewIdToKeep) { 4105 addAction(new ViewGroupActionRemove(viewId, viewIdToKeep)); 4106 } 4107 4108 /** 4109 * Removes the {@link View} specified by the {@code viewId} from its parent {@link ViewManager}. 4110 * This will do nothing if the viewId specifies the root view of this RemoteViews. 4111 * 4112 * @param viewId The id of the {@link View} to remove from its parent. 4113 * 4114 * @hide 4115 */ 4116 public void removeFromParent(@IdRes int viewId) { 4117 addAction(new RemoveFromParentAction(viewId)); 4118 } 4119 4120 /** 4121 * Equivalent to calling {@link AdapterViewAnimator#showNext()} 4122 * 4123 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} 4124 */ 4125 public void showNext(@IdRes int viewId) { 4126 addAction(new ViewContentNavigation(viewId, true /* next */)); 4127 } 4128 4129 /** 4130 * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} 4131 * 4132 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} 4133 */ 4134 public void showPrevious(@IdRes int viewId) { 4135 addAction(new ViewContentNavigation(viewId, false /* next */)); 4136 } 4137 4138 /** 4139 * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} 4140 * 4141 * @param viewId The id of the view on which to call 4142 * {@link AdapterViewAnimator#setDisplayedChild(int)} 4143 */ 4144 public void setDisplayedChild(@IdRes int viewId, int childIndex) { 4145 setInt(viewId, "setDisplayedChild", childIndex); 4146 } 4147 4148 /** 4149 * Equivalent to calling {@link View#setVisibility(int)} 4150 * 4151 * @param viewId The id of the view whose visibility should change 4152 * @param visibility The new visibility for the view 4153 */ 4154 public void setViewVisibility(@IdRes int viewId, @View.Visibility int visibility) { 4155 setInt(viewId, "setVisibility", visibility); 4156 } 4157 4158 /** 4159 * Equivalent to calling {@link TextView#setText(CharSequence)} 4160 * 4161 * @param viewId The id of the view whose text should change 4162 * @param text The new text for the view 4163 */ 4164 public void setTextViewText(@IdRes int viewId, CharSequence text) { 4165 setCharSequence(viewId, "setText", text); 4166 } 4167 4168 /** 4169 * Equivalent to calling {@link TextView#setTextSize(int, float)} 4170 * 4171 * @param viewId The id of the view whose text size should change 4172 * @param units The units of size (e.g. COMPLEX_UNIT_SP) 4173 * @param size The size of the text 4174 */ 4175 public void setTextViewTextSize(@IdRes int viewId, int units, float size) { 4176 addAction(new TextViewSizeAction(viewId, units, size)); 4177 } 4178 4179 /** 4180 * Equivalent to calling 4181 * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. 4182 * 4183 * @param viewId The id of the view whose text should change 4184 * @param left The id of a drawable to place to the left of the text, or 0 4185 * @param top The id of a drawable to place above the text, or 0 4186 * @param right The id of a drawable to place to the right of the text, or 0 4187 * @param bottom The id of a drawable to place below the text, or 0 4188 */ 4189 public void setTextViewCompoundDrawables(@IdRes int viewId, @DrawableRes int left, 4190 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 4191 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 4192 } 4193 4194 /** 4195 * Equivalent to calling {@link 4196 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. 4197 * 4198 * @param viewId The id of the view whose text should change 4199 * @param start The id of a drawable to place before the text (relative to the 4200 * layout direction), or 0 4201 * @param top The id of a drawable to place above the text, or 0 4202 * @param end The id of a drawable to place after the text, or 0 4203 * @param bottom The id of a drawable to place below the text, or 0 4204 */ 4205 public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, @DrawableRes int start, 4206 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 4207 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 4208 } 4209 4210 /** 4211 * Equivalent to calling {@link 4212 * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 4213 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 4214 * 4215 * @param viewId The id of the view whose text should change 4216 * @param left an Icon to place to the left of the text, or 0 4217 * @param top an Icon to place above the text, or 0 4218 * @param right an Icon to place to the right of the text, or 0 4219 * @param bottom an Icon to place below the text, or 0 4220 * 4221 * @hide 4222 */ 4223 public void setTextViewCompoundDrawables(@IdRes int viewId, 4224 Icon left, Icon top, Icon right, Icon bottom) { 4225 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 4226 } 4227 4228 /** 4229 * Equivalent to calling {@link 4230 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 4231 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 4232 * 4233 * @param viewId The id of the view whose text should change 4234 * @param start an Icon to place before the text (relative to the 4235 * layout direction), or 0 4236 * @param top an Icon to place above the text, or 0 4237 * @param end an Icon to place after the text, or 0 4238 * @param bottom an Icon to place below the text, or 0 4239 * 4240 * @hide 4241 */ 4242 public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, 4243 Icon start, Icon top, Icon end, Icon bottom) { 4244 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 4245 } 4246 4247 /** 4248 * Equivalent to calling {@link ImageView#setImageResource(int)} 4249 * 4250 * @param viewId The id of the view whose drawable should change 4251 * @param srcId The new resource id for the drawable 4252 */ 4253 public void setImageViewResource(@IdRes int viewId, @DrawableRes int srcId) { 4254 setInt(viewId, "setImageResource", srcId); 4255 } 4256 4257 /** 4258 * Equivalent to calling {@link ImageView#setImageURI(Uri)} 4259 * 4260 * @param viewId The id of the view whose drawable should change 4261 * @param uri The Uri for the image 4262 */ 4263 public void setImageViewUri(@IdRes int viewId, Uri uri) { 4264 setUri(viewId, "setImageURI", uri); 4265 } 4266 4267 /** 4268 * Equivalent to calling {@link ImageView#setImageBitmap(Bitmap)} 4269 * 4270 * @param viewId The id of the view whose bitmap should change 4271 * @param bitmap The new Bitmap for the drawable 4272 */ 4273 public void setImageViewBitmap(@IdRes int viewId, Bitmap bitmap) { 4274 setBitmap(viewId, "setImageBitmap", bitmap); 4275 } 4276 4277 /** 4278 * Equivalent to calling {@link ImageView#setImageIcon(Icon)} 4279 * 4280 * @param viewId The id of the view whose bitmap should change 4281 * @param icon The new Icon for the ImageView 4282 */ 4283 public void setImageViewIcon(@IdRes int viewId, Icon icon) { 4284 setIcon(viewId, "setImageIcon", icon); 4285 } 4286 4287 /** 4288 * Equivalent to calling {@link AdapterView#setEmptyView(View)} 4289 * 4290 * @param viewId The id of the view on which to set the empty view 4291 * @param emptyViewId The view id of the empty view 4292 */ 4293 public void setEmptyView(@IdRes int viewId, @IdRes int emptyViewId) { 4294 addAction(new SetEmptyView(viewId, emptyViewId)); 4295 } 4296 4297 /** 4298 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 4299 * {@link Chronometer#setFormat Chronometer.setFormat}, 4300 * and {@link Chronometer#start Chronometer.start()} or 4301 * {@link Chronometer#stop Chronometer.stop()}. 4302 * 4303 * @param viewId The id of the {@link Chronometer} to change 4304 * @param base The time at which the timer would have read 0:00. This 4305 * time should be based off of 4306 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 4307 * @param format The Chronometer format string, or null to 4308 * simply display the timer value. 4309 * @param started True if you want the clock to be started, false if not. 4310 * 4311 * @see #setChronometerCountDown(int, boolean) 4312 */ 4313 public void setChronometer(@IdRes int viewId, long base, String format, boolean started) { 4314 setLong(viewId, "setBase", base); 4315 setString(viewId, "setFormat", format); 4316 setBoolean(viewId, "setStarted", started); 4317 } 4318 4319 /** 4320 * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on 4321 * the chronometer with the given viewId. 4322 * 4323 * @param viewId The id of the {@link Chronometer} to change 4324 * @param isCountDown True if you want the chronometer to count down to base instead of 4325 * counting up. 4326 */ 4327 public void setChronometerCountDown(@IdRes int viewId, boolean isCountDown) { 4328 setBoolean(viewId, "setCountDown", isCountDown); 4329 } 4330 4331 /** 4332 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 4333 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 4334 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 4335 * 4336 * If indeterminate is true, then the values for max and progress are ignored. 4337 * 4338 * @param viewId The id of the {@link ProgressBar} to change 4339 * @param max The 100% value for the progress bar 4340 * @param progress The current value of the progress bar. 4341 * @param indeterminate True if the progress bar is indeterminate, 4342 * false if not. 4343 */ 4344 public void setProgressBar(@IdRes int viewId, int max, int progress, 4345 boolean indeterminate) { 4346 setBoolean(viewId, "setIndeterminate", indeterminate); 4347 if (!indeterminate) { 4348 setInt(viewId, "setMax", max); 4349 setInt(viewId, "setProgress", progress); 4350 } 4351 } 4352 4353 /** 4354 * Equivalent to calling 4355 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 4356 * to launch the provided {@link PendingIntent}. The source bounds 4357 * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked 4358 * view in screen space. 4359 * Note that any activity options associated with the mPendingIntent may get overridden 4360 * before starting the intent. 4361 * 4362 * When setting the on-click action of items within collections (eg. {@link ListView}, 4363 * {@link StackView} etc.), this method will not work. Instead, use {@link 4364 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)} in conjunction with 4365 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 4366 * 4367 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 4368 * @param pendingIntent The {@link PendingIntent} to send when user clicks 4369 */ 4370 public void setOnClickPendingIntent(@IdRes int viewId, PendingIntent pendingIntent) { 4371 setOnClickResponse(viewId, RemoteResponse.fromPendingIntent(pendingIntent)); 4372 } 4373 4374 /** 4375 * Equivalent of calling 4376 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 4377 * to launch the provided {@link RemoteResponse}. 4378 * 4379 * @param viewId The id of the view that will trigger the {@link RemoteResponse} when clicked 4380 * @param response The {@link RemoteResponse} to send when user clicks 4381 */ 4382 public void setOnClickResponse(@IdRes int viewId, @NonNull RemoteResponse response) { 4383 addAction(new SetOnClickResponse(viewId, response)); 4384 } 4385 4386 /** 4387 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 4388 * costly to set PendingIntents on the individual items, and is hence not recommended. Instead 4389 * this method should be used to set a single PendingIntent template on the collection, and 4390 * individual items can differentiate their on-click behavior using 4391 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 4392 * 4393 * @param viewId The id of the collection who's children will use this PendingIntent template 4394 * when clicked 4395 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 4396 * by a child of viewId and executed when that child is clicked 4397 */ 4398 public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) { 4399 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 4400 } 4401 4402 /** 4403 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 4404 * costly to set PendingIntents on the individual items, and is hence not recommended. Instead 4405 * a single PendingIntent template can be set on the collection, see {@link 4406 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 4407 * action of a given item can be distinguished by setting a fillInIntent on that item. The 4408 * fillInIntent is then combined with the PendingIntent template in order to determine the final 4409 * intent which will be executed when the item is clicked. This works as follows: any fields 4410 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 4411 * will be overwritten, and the resulting PendingIntent will be used. The rest 4412 * of the PendingIntent template will then be filled in with the associated fields that are 4413 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 4414 * 4415 * @param viewId The id of the view on which to set the fillInIntent 4416 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 4417 * in order to determine the on-click behavior of the view specified by viewId 4418 */ 4419 public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) { 4420 setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent)); 4421 } 4422 4423 /** 4424 * Equivalent to calling 4425 * {@link android.widget.CompoundButton#setOnCheckedChangeListener( 4426 * android.widget.CompoundButton.OnCheckedChangeListener)} 4427 * to launch the provided {@link RemoteResponse}. 4428 * 4429 * The intent will be filled with the current checked state of the view at the key 4430 * {@link #EXTRA_CHECKED}. 4431 * 4432 * The {@link RemoteResponse} will not be launched in response to check changes arising from 4433 * {@link #setCompoundButtonChecked(int, boolean)} or {@link #setRadioGroupChecked(int, int)} 4434 * usages. 4435 * 4436 * The {@link RemoteResponse} must be created using 4437 * {@link RemoteResponse#fromFillInIntent(Intent)} in conjunction with 4438 * {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)} for items inside 4439 * collections (eg. {@link ListView}, {@link StackView} etc.). 4440 * 4441 * Otherwise, create the {@link RemoteResponse} using 4442 * {@link RemoteResponse#fromPendingIntent(PendingIntent)}. 4443 * 4444 * @param viewId The id of the view that will trigger the {@link PendingIntent} when checked 4445 * state changes. 4446 * @param response The {@link RemoteResponse} to send when the checked state changes. 4447 */ 4448 public void setOnCheckedChangeResponse( 4449 @IdRes int viewId, 4450 @NonNull RemoteResponse response) { 4451 addAction( 4452 new SetOnCheckedChangeResponse( 4453 viewId, 4454 response.setInteractionType( 4455 RemoteResponse.INTERACTION_TYPE_CHECKED_CHANGE))); 4456 } 4457 4458 /** 4459 * @hide 4460 * Equivalent to calling 4461 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 4462 * on the {@link Drawable} of a given view. 4463 * <p> 4464 * 4465 * @param viewId The id of the view that contains the target 4466 * {@link Drawable} 4467 * @param targetBackground If true, apply these parameters to the 4468 * {@link Drawable} returned by 4469 * {@link android.view.View#getBackground()}. Otherwise, assume 4470 * the target view is an {@link ImageView} and apply them to 4471 * {@link ImageView#getDrawable()}. 4472 * @param colorFilter Specify a color for a 4473 * {@link android.graphics.ColorFilter} for this drawable. This will be ignored if 4474 * {@code mode} is {@code null}. 4475 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 4476 * unchanged. 4477 */ 4478 public void setDrawableTint(@IdRes int viewId, boolean targetBackground, 4479 @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) { 4480 addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode)); 4481 } 4482 4483 /** 4484 * @hide 4485 * Equivalent to calling 4486 * {@link RippleDrawable#setColor(ColorStateList)} on the {@link Drawable} of a given view, 4487 * assuming it's a {@link RippleDrawable}. 4488 * <p> 4489 * 4490 * @param viewId The id of the view that contains the target 4491 * {@link RippleDrawable} 4492 * @param colorStateList Specify a color for a 4493 * {@link ColorStateList} for this drawable. 4494 */ 4495 public void setRippleDrawableColor(@IdRes int viewId, ColorStateList colorStateList) { 4496 addAction(new SetRippleDrawableColor(viewId, colorStateList)); 4497 } 4498 4499 /** 4500 * @hide 4501 * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}. 4502 * 4503 * @param viewId The id of the view whose tint should change 4504 * @param tint the tint to apply, may be {@code null} to clear tint 4505 */ 4506 public void setProgressTintList(@IdRes int viewId, ColorStateList tint) { 4507 addAction(new ReflectionAction(viewId, "setProgressTintList", 4508 BaseReflectionAction.COLOR_STATE_LIST, tint)); 4509 } 4510 4511 /** 4512 * @hide 4513 * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}. 4514 * 4515 * @param viewId The id of the view whose tint should change 4516 * @param tint the tint to apply, may be {@code null} to clear tint 4517 */ 4518 public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) { 4519 addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList", 4520 BaseReflectionAction.COLOR_STATE_LIST, tint)); 4521 } 4522 4523 /** 4524 * @hide 4525 * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}. 4526 * 4527 * @param viewId The id of the view whose tint should change 4528 * @param tint the tint to apply, may be {@code null} to clear tint 4529 */ 4530 public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) { 4531 addAction(new ReflectionAction(viewId, "setIndeterminateTintList", 4532 BaseReflectionAction.COLOR_STATE_LIST, tint)); 4533 } 4534 4535 /** 4536 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 4537 * 4538 * @param viewId The id of the view whose text color should change 4539 * @param color Sets the text color for all the states (normal, selected, 4540 * focused) to be this color. 4541 */ 4542 public void setTextColor(@IdRes int viewId, @ColorInt int color) { 4543 setInt(viewId, "setTextColor", color); 4544 } 4545 4546 /** 4547 * @hide 4548 * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}. 4549 * 4550 * @param viewId The id of the view whose text color should change 4551 * @param colors the text colors to set 4552 */ 4553 public void setTextColor(@IdRes int viewId, ColorStateList colors) { 4554 addAction(new ReflectionAction(viewId, "setTextColor", 4555 BaseReflectionAction.COLOR_STATE_LIST, colors)); 4556 } 4557 4558 /** 4559 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 4560 * 4561 * @param appWidgetId The id of the app widget which contains the specified view. (This 4562 * parameter is ignored in this deprecated method) 4563 * @param viewId The id of the {@link AdapterView} 4564 * @param intent The intent of the service which will be 4565 * providing data to the RemoteViewsAdapter 4566 * @deprecated This method has been deprecated. See 4567 * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} 4568 */ 4569 @Deprecated 4570 public void setRemoteAdapter(int appWidgetId, @IdRes int viewId, Intent intent) { 4571 setRemoteAdapter(viewId, intent); 4572 } 4573 4574 /** 4575 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 4576 * Can only be used for App Widgets. 4577 * 4578 * @param viewId The id of the {@link AdapterView} 4579 * @param intent The intent of the service which will be 4580 * providing data to the RemoteViewsAdapter 4581 */ 4582 public void setRemoteAdapter(@IdRes int viewId, Intent intent) { 4583 addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); 4584 } 4585 4586 /** 4587 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 4588 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 4589 * This is a simpler but less flexible approach to populating collection widgets. Its use is 4590 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 4591 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 4592 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 4593 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 4594 * 4595 * This API is supported in the compatibility library for previous API levels, see 4596 * RemoteViewsCompat. 4597 * 4598 * @param viewId The id of the {@link AdapterView} 4599 * @param list The list of RemoteViews which will populate the view specified by viewId. 4600 * @param viewTypeCount The maximum number of unique layout id's used to construct the list of 4601 * RemoteViews. This count cannot change during the life-cycle of a given widget, so this 4602 * parameter should account for the maximum possible number of types that may appear in the 4603 * See {@link Adapter#getViewTypeCount()}. 4604 * 4605 * @hide 4606 * @deprecated this appears to have no users outside of UnsupportedAppUsage? 4607 */ 4608 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 4609 @Deprecated 4610 public void setRemoteAdapter(@IdRes int viewId, ArrayList<RemoteViews> list, 4611 int viewTypeCount) { 4612 addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); 4613 } 4614 4615 /** 4616 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 4617 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 4618 * This is a simpler but less flexible approach to populating collection widgets. Its use is 4619 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 4620 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 4621 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 4622 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 4623 * 4624 * This API is supported in the compatibility library for previous API levels, see 4625 * RemoteViewsCompat. 4626 * 4627 * @param viewId The id of the {@link AdapterView}. 4628 * @param items The items to display in the {@link AdapterView}. 4629 */ 4630 public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) { 4631 addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items)); 4632 } 4633 4634 /** 4635 * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}. 4636 * 4637 * @param viewId The id of the view to change 4638 * @param position Scroll to this adapter position 4639 */ 4640 public void setScrollPosition(@IdRes int viewId, int position) { 4641 setInt(viewId, "smoothScrollToPosition", position); 4642 } 4643 4644 /** 4645 * Equivalent to calling {@link ListView#smoothScrollByOffset(int)}. 4646 * 4647 * @param viewId The id of the view to change 4648 * @param offset Scroll by this adapter position offset 4649 */ 4650 public void setRelativeScrollPosition(@IdRes int viewId, int offset) { 4651 setInt(viewId, "smoothScrollByOffset", offset); 4652 } 4653 4654 /** 4655 * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}. 4656 * 4657 * @param viewId The id of the view to change 4658 * @param left the left padding in pixels 4659 * @param top the top padding in pixels 4660 * @param right the right padding in pixels 4661 * @param bottom the bottom padding in pixels 4662 */ 4663 public void setViewPadding(@IdRes int viewId, 4664 @Px int left, @Px int top, @Px int right, @Px int bottom) { 4665 addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); 4666 } 4667 4668 /** 4669 * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. 4670 * Only works if the {@link View#getLayoutParams()} supports margins. 4671 * 4672 * @param viewId The id of the view to change 4673 * @param type The margin being set e.g. {@link #MARGIN_END} 4674 * @param dimen a dimension resource to apply to the margin, or 0 to clear the margin. 4675 */ 4676 public void setViewLayoutMarginDimen(@IdRes int viewId, @MarginType int type, 4677 @DimenRes int dimen) { 4678 addAction(new LayoutParamAction(viewId, type, dimen, VALUE_TYPE_RESOURCE)); 4679 } 4680 4681 /** 4682 * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. 4683 * Only works if the {@link View#getLayoutParams()} supports margins. 4684 * 4685 * @param viewId The id of the view to change 4686 * @param type The margin being set e.g. {@link #MARGIN_END} 4687 * @param attr a dimension attribute to apply to the margin, or 0 to clear the margin. 4688 */ 4689 public void setViewLayoutMarginAttr(@IdRes int viewId, @MarginType int type, 4690 @AttrRes int attr) { 4691 addAction(new LayoutParamAction(viewId, type, attr, VALUE_TYPE_ATTRIBUTE)); 4692 } 4693 4694 /** 4695 * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. 4696 * Only works if the {@link View#getLayoutParams()} supports margins. 4697 * 4698 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0. 4699 * Setting margins in pixels will behave poorly when the RemoteViews object is used on a 4700 * display with a different density. 4701 * 4702 * @param viewId The id of the view to change 4703 * @param type The margin being set e.g. {@link #MARGIN_END} 4704 * @param value a value for the margin the given units. 4705 * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} 4706 */ 4707 public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value, 4708 @ComplexDimensionUnit int units) { 4709 addAction(new LayoutParamAction(viewId, type, value, units)); 4710 } 4711 4712 /** 4713 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} except that you may 4714 * provide the value in any dimension units. 4715 * 4716 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, 4717 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. 4718 * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a 4719 * display with a different density. 4720 * 4721 * @param width Width of the view in the given units 4722 * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} 4723 */ 4724 public void setViewLayoutWidth(@IdRes int viewId, float width, 4725 @ComplexDimensionUnit int units) { 4726 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, width, units)); 4727 } 4728 4729 /** 4730 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with 4731 * the result of {@link Resources#getDimensionPixelSize(int)}. 4732 * 4733 * @param widthDimen the dimension resource for the view's width 4734 */ 4735 public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) { 4736 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen, 4737 VALUE_TYPE_RESOURCE)); 4738 } 4739 4740 /** 4741 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with 4742 * the value of the given attribute in the current theme. 4743 * 4744 * @param widthAttr the dimension attribute for the view's width 4745 */ 4746 public void setViewLayoutWidthAttr(@IdRes int viewId, @AttrRes int widthAttr) { 4747 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthAttr, 4748 VALUE_TYPE_ATTRIBUTE)); 4749 } 4750 4751 /** 4752 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} except that you may 4753 * provide the value in any dimension units. 4754 * 4755 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, 4756 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. 4757 * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a 4758 * display with a different density. 4759 * 4760 * @param height height of the view in the given units 4761 * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} 4762 */ 4763 public void setViewLayoutHeight(@IdRes int viewId, float height, 4764 @ComplexDimensionUnit int units) { 4765 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, height, units)); 4766 } 4767 4768 /** 4769 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with 4770 * the result of {@link Resources#getDimensionPixelSize(int)}. 4771 * 4772 * @param heightDimen a dimen resource to read the height from. 4773 */ 4774 public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) { 4775 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen, 4776 VALUE_TYPE_RESOURCE)); 4777 } 4778 4779 /** 4780 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with 4781 * the value of the given attribute in the current theme. 4782 * 4783 * @param heightAttr a dimen attribute to read the height from. 4784 */ 4785 public void setViewLayoutHeightAttr(@IdRes int viewId, @AttrRes int heightAttr) { 4786 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightAttr, 4787 VALUE_TYPE_ATTRIBUTE)); 4788 } 4789 4790 /** 4791 * Sets an OutlineProvider on the view whose corner radius is a dimension calculated using 4792 * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}. 4793 * 4794 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0. 4795 * Setting margins in pixels will behave poorly when the RemoteViews object is used on a 4796 * display with a different density. 4797 */ 4798 public void setViewOutlinePreferredRadius( 4799 @IdRes int viewId, float radius, @ComplexDimensionUnit int units) { 4800 addAction(new SetViewOutlinePreferredRadiusAction(viewId, radius, units)); 4801 } 4802 4803 /** 4804 * Sets an OutlineProvider on the view whose corner radius is a dimension resource with 4805 * {@code resId}. 4806 */ 4807 public void setViewOutlinePreferredRadiusDimen(@IdRes int viewId, @DimenRes int resId) { 4808 addAction(new SetViewOutlinePreferredRadiusAction(viewId, resId, VALUE_TYPE_RESOURCE)); 4809 } 4810 4811 /** 4812 * Sets an OutlineProvider on the view whose corner radius is a dimension attribute with 4813 * {@code attrId}. 4814 */ 4815 public void setViewOutlinePreferredRadiusAttr(@IdRes int viewId, @AttrRes int attrId) { 4816 addAction(new SetViewOutlinePreferredRadiusAction(viewId, attrId, VALUE_TYPE_ATTRIBUTE)); 4817 } 4818 4819 /** 4820 * Call a method taking one boolean on a view in the layout for this RemoteViews. 4821 * 4822 * @param viewId The id of the view on which to call the method. 4823 * @param methodName The name of the method to call. 4824 * @param value The value to pass to the method. 4825 */ 4826 public void setBoolean(@IdRes int viewId, String methodName, boolean value) { 4827 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BOOLEAN, value)); 4828 } 4829 4830 /** 4831 * Call a method taking one byte on a view in the layout for this RemoteViews. 4832 * 4833 * @param viewId The id of the view on which to call the method. 4834 * @param methodName The name of the method to call. 4835 * @param value The value to pass to the method. 4836 */ 4837 public void setByte(@IdRes int viewId, String methodName, byte value) { 4838 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BYTE, value)); 4839 } 4840 4841 /** 4842 * Call a method taking one short on a view in the layout for this RemoteViews. 4843 * 4844 * @param viewId The id of the view on which to call the method. 4845 * @param methodName The name of the method to call. 4846 * @param value The value to pass to the method. 4847 */ 4848 public void setShort(@IdRes int viewId, String methodName, short value) { 4849 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.SHORT, value)); 4850 } 4851 4852 /** 4853 * Call a method taking one int on a view in the layout for this RemoteViews. 4854 * 4855 * @param viewId The id of the view on which to call the method. 4856 * @param methodName The name of the method to call. 4857 * @param value The value to pass to the method. 4858 */ 4859 public void setInt(@IdRes int viewId, String methodName, int value) { 4860 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INT, value)); 4861 } 4862 4863 /** 4864 * Call a method taking one int, a size in pixels, on a view in the layout for this 4865 * RemoteViews. 4866 * 4867 * The dimension will be resolved from the resources at the time the {@link RemoteViews} is 4868 * (re-)applied. 4869 * 4870 * Undefined resources will result in an exception, except 0 which will resolve to 0. 4871 * 4872 * @param viewId The id of the view on which to call the method. 4873 * @param methodName The name of the method to call. 4874 * @param dimenResource The resource to resolve and pass as argument to the method. 4875 */ 4876 public void setIntDimen(@IdRes int viewId, @NonNull String methodName, 4877 @DimenRes int dimenResource) { 4878 addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT, 4879 ResourceReflectionAction.DIMEN_RESOURCE, dimenResource)); 4880 } 4881 4882 /** 4883 * Call a method taking one int, a size in pixels, on a view in the layout for this 4884 * RemoteViews. 4885 * 4886 * The dimension will be resolved from the specified dimension at the time of inflation. 4887 * 4888 * @param viewId The id of the view on which to call the method. 4889 * @param methodName The name of the method to call. 4890 * @param value The value of the dimension. 4891 * @param unit The unit in which the value is specified. 4892 */ 4893 public void setIntDimen(@IdRes int viewId, @NonNull String methodName, 4894 float value, @ComplexDimensionUnit int unit) { 4895 addAction(new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.INT, 4896 value, unit)); 4897 } 4898 4899 /** 4900 * Call a method taking one int, a size in pixels, on a view in the layout for this 4901 * RemoteViews. 4902 * 4903 * The dimension will be resolved from the theme attribute at the time the 4904 * {@link RemoteViews} is (re-)applied. 4905 * 4906 * Unresolvable attributes will result in an exception, except 0 which will resolve to 0. 4907 * 4908 * @param viewId The id of the view on which to call the method. 4909 * @param methodName The name of the method to call. 4910 * @param dimenAttr The attribute to resolve and pass as argument to the method. 4911 */ 4912 public void setIntDimenAttr(@IdRes int viewId, @NonNull String methodName, 4913 @AttrRes int dimenAttr) { 4914 addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.INT, 4915 ResourceReflectionAction.DIMEN_RESOURCE, dimenAttr)); 4916 } 4917 4918 /** 4919 * Call a method taking one int, a color, on a view in the layout for this RemoteViews. 4920 * 4921 * The Color will be resolved from the resources at the time the {@link RemoteViews} is (re-) 4922 * applied. 4923 * 4924 * Undefined resources will result in an exception, except 0 which will resolve to 0. 4925 * 4926 * @param viewId The id of the view on which to call the method. 4927 * @param methodName The name of the method to call. 4928 * @param colorResource The resource to resolve and pass as argument to the method. 4929 */ 4930 public void setColor(@IdRes int viewId, @NonNull String methodName, 4931 @ColorRes int colorResource) { 4932 addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT, 4933 ResourceReflectionAction.COLOR_RESOURCE, colorResource)); 4934 } 4935 4936 /** 4937 * Call a method taking one int, a color, on a view in the layout for this RemoteViews. 4938 * 4939 * The Color will be resolved from the theme attribute at the time the {@link RemoteViews} is 4940 * (re-)applied. 4941 * 4942 * Unresolvable attributes will result in an exception, except 0 which will resolve to 0. 4943 * 4944 * @param viewId The id of the view on which to call the method. 4945 * @param methodName The name of the method to call. 4946 * @param colorAttribute The theme attribute to resolve and pass as argument to the method. 4947 */ 4948 public void setColorAttr(@IdRes int viewId, @NonNull String methodName, 4949 @AttrRes int colorAttribute) { 4950 addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.INT, 4951 AttributeReflectionAction.COLOR_RESOURCE, colorAttribute)); 4952 } 4953 4954 /** 4955 * Call a method taking one int, a color, on a view in the layout for this RemoteViews. 4956 * 4957 * @param viewId The id of the view on which to call the method. 4958 * @param methodName The name of the method to call. 4959 * @param notNight The value to pass to the method when the view's configuration is set to 4960 * {@link Configuration#UI_MODE_NIGHT_NO} 4961 * @param night The value to pass to the method when the view's configuration is set to 4962 * {@link Configuration#UI_MODE_NIGHT_YES} 4963 */ 4964 public void setColorInt( 4965 @IdRes int viewId, 4966 @NonNull String methodName, 4967 @ColorInt int notNight, 4968 @ColorInt int night) { 4969 addAction( 4970 new NightModeReflectionAction( 4971 viewId, 4972 methodName, 4973 BaseReflectionAction.INT, 4974 notNight, 4975 night)); 4976 } 4977 4978 4979 /** 4980 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 4981 * 4982 * @param viewId The id of the view on which to call the method. 4983 * @param methodName The name of the method to call. 4984 * @param value The value to pass to the method. 4985 */ 4986 public void setColorStateList(@IdRes int viewId, @NonNull String methodName, 4987 @Nullable ColorStateList value) { 4988 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.COLOR_STATE_LIST, 4989 value)); 4990 } 4991 4992 /** 4993 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 4994 * 4995 * @param viewId The id of the view on which to call the method. 4996 * @param methodName The name of the method to call. 4997 * @param notNight The value to pass to the method when the view's configuration is set to 4998 * {@link Configuration#UI_MODE_NIGHT_NO} 4999 * @param night The value to pass to the method when the view's configuration is set to 5000 * {@link Configuration#UI_MODE_NIGHT_YES} 5001 */ 5002 public void setColorStateList( 5003 @IdRes int viewId, 5004 @NonNull String methodName, 5005 @Nullable ColorStateList notNight, 5006 @Nullable ColorStateList night) { 5007 addAction( 5008 new NightModeReflectionAction( 5009 viewId, 5010 methodName, 5011 BaseReflectionAction.COLOR_STATE_LIST, 5012 notNight, 5013 night)); 5014 } 5015 5016 /** 5017 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 5018 * 5019 * The ColorStateList will be resolved from the resources at the time the {@link RemoteViews} is 5020 * (re-)applied. 5021 * 5022 * Undefined resources will result in an exception, except 0 which will resolve to null. 5023 * 5024 * @param viewId The id of the view on which to call the method. 5025 * @param methodName The name of the method to call. 5026 * @param colorResource The resource to resolve and pass as argument to the method. 5027 */ 5028 public void setColorStateList(@IdRes int viewId, @NonNull String methodName, 5029 @ColorRes int colorResource) { 5030 addAction(new ResourceReflectionAction(viewId, methodName, 5031 BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE, 5032 colorResource)); 5033 } 5034 5035 /** 5036 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 5037 * 5038 * The ColorStateList will be resolved from the theme attribute at the time the 5039 * {@link RemoteViews} is (re-)applied. 5040 * 5041 * Unresolvable attributes will result in an exception, except 0 which will resolve to null. 5042 * 5043 * @param viewId The id of the view on which to call the method. 5044 * @param methodName The name of the method to call. 5045 * @param colorAttr The theme attribute to resolve and pass as argument to the method. 5046 */ 5047 public void setColorStateListAttr(@IdRes int viewId, @NonNull String methodName, 5048 @AttrRes int colorAttr) { 5049 addAction(new AttributeReflectionAction(viewId, methodName, 5050 BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE, 5051 colorAttr)); 5052 } 5053 5054 /** 5055 * Call a method taking one long on a view in the layout for this RemoteViews. 5056 * 5057 * @param viewId The id of the view on which to call the method. 5058 * @param methodName The name of the method to call. 5059 * @param value The value to pass to the method. 5060 */ 5061 public void setLong(@IdRes int viewId, String methodName, long value) { 5062 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.LONG, value)); 5063 } 5064 5065 /** 5066 * Call a method taking one float on a view in the layout for this RemoteViews. 5067 * 5068 * @param viewId The id of the view on which to call the method. 5069 * @param methodName The name of the method to call. 5070 * @param value The value to pass to the method. 5071 */ 5072 public void setFloat(@IdRes int viewId, String methodName, float value) { 5073 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, value)); 5074 } 5075 5076 /** 5077 * Call a method taking one float, a size in pixels, on a view in the layout for this 5078 * RemoteViews. 5079 * 5080 * The dimension will be resolved from the resources at the time the {@link RemoteViews} is 5081 * (re-)applied. 5082 * 5083 * Undefined resources will result in an exception, except 0 which will resolve to 0f. 5084 * 5085 * @param viewId The id of the view on which to call the method. 5086 * @param methodName The name of the method to call. 5087 * @param dimenResource The resource to resolve and pass as argument to the method. 5088 */ 5089 public void setFloatDimen(@IdRes int viewId, @NonNull String methodName, 5090 @DimenRes int dimenResource) { 5091 addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, 5092 ResourceReflectionAction.DIMEN_RESOURCE, dimenResource)); 5093 } 5094 5095 /** 5096 * Call a method taking one float, a size in pixels, on a view in the layout for this 5097 * RemoteViews. 5098 * 5099 * The dimension will be resolved from the resources at the time the {@link RemoteViews} is 5100 * (re-)applied. 5101 * 5102 * @param viewId The id of the view on which to call the method. 5103 * @param methodName The name of the method to call. 5104 * @param value The value of the dimension. 5105 * @param unit The unit in which the value is specified. 5106 */ 5107 public void setFloatDimen(@IdRes int viewId, @NonNull String methodName, 5108 float value, @ComplexDimensionUnit int unit) { 5109 addAction( 5110 new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.FLOAT, 5111 value, unit)); 5112 } 5113 5114 /** 5115 * Call a method taking one float, a size in pixels, on a view in the layout for this 5116 * RemoteViews. 5117 * 5118 * The dimension will be resolved from the theme attribute at the time the {@link RemoteViews} 5119 * is (re-)applied. 5120 * 5121 * Unresolvable attributes will result in an exception, except 0 which will resolve to 0f. 5122 * 5123 * @param viewId The id of the view on which to call the method. 5124 * @param methodName The name of the method to call. 5125 * @param dimenAttr The attribute to resolve and pass as argument to the method. 5126 */ 5127 public void setFloatDimenAttr(@IdRes int viewId, @NonNull String methodName, 5128 @AttrRes int dimenAttr) { 5129 addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, 5130 ResourceReflectionAction.DIMEN_RESOURCE, dimenAttr)); 5131 } 5132 5133 /** 5134 * Call a method taking one double on a view in the layout for this RemoteViews. 5135 * 5136 * @param viewId The id of the view on which to call the method. 5137 * @param methodName The name of the method to call. 5138 * @param value The value to pass to the method. 5139 */ 5140 public void setDouble(@IdRes int viewId, String methodName, double value) { 5141 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.DOUBLE, value)); 5142 } 5143 5144 /** 5145 * Call a method taking one char on a view in the layout for this RemoteViews. 5146 * 5147 * @param viewId The id of the view on which to call the method. 5148 * @param methodName The name of the method to call. 5149 * @param value The value to pass to the method. 5150 */ 5151 public void setChar(@IdRes int viewId, String methodName, char value) { 5152 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR, value)); 5153 } 5154 5155 /** 5156 * Call a method taking one String on a view in the layout for this RemoteViews. 5157 * 5158 * @param viewId The id of the view on which to call the method. 5159 * @param methodName The name of the method to call. 5160 * @param value The value to pass to the method. 5161 */ 5162 public void setString(@IdRes int viewId, String methodName, String value) { 5163 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.STRING, value)); 5164 } 5165 5166 /** 5167 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 5168 * 5169 * @param viewId The id of the view on which to call the method. 5170 * @param methodName The name of the method to call. 5171 * @param value The value to pass to the method. 5172 */ 5173 public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) { 5174 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE, 5175 value)); 5176 } 5177 5178 /** 5179 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 5180 * 5181 * The CharSequence will be resolved from the resources at the time the {@link RemoteViews} is 5182 * (re-)applied. 5183 * 5184 * Undefined resources will result in an exception, except 0 which will resolve to null. 5185 * 5186 * @param viewId The id of the view on which to call the method. 5187 * @param methodName The name of the method to call. 5188 * @param stringResource The resource to resolve and pass as argument to the method. 5189 */ 5190 public void setCharSequence(@IdRes int viewId, @NonNull String methodName, 5191 @StringRes int stringResource) { 5192 addAction( 5193 new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE, 5194 ResourceReflectionAction.STRING_RESOURCE, stringResource)); 5195 } 5196 5197 /** 5198 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 5199 * 5200 * The CharSequence will be resolved from the theme attribute at the time the 5201 * {@link RemoteViews} is (re-)applied. 5202 * 5203 * Unresolvable attributes will result in an exception, except 0 which will resolve to null. 5204 * 5205 * @param viewId The id of the view on which to call the method. 5206 * @param methodName The name of the method to call. 5207 * @param stringAttribute The attribute to resolve and pass as argument to the method. 5208 */ 5209 public void setCharSequenceAttr(@IdRes int viewId, @NonNull String methodName, 5210 @AttrRes int stringAttribute) { 5211 addAction( 5212 new AttributeReflectionAction(viewId, methodName, 5213 BaseReflectionAction.CHAR_SEQUENCE, 5214 AttributeReflectionAction.STRING_RESOURCE, stringAttribute)); 5215 } 5216 5217 /** 5218 * Call a method taking one Uri on a view in the layout for this RemoteViews. 5219 * 5220 * @param viewId The id of the view on which to call the method. 5221 * @param methodName The name of the method to call. 5222 * @param value The value to pass to the method. 5223 */ 5224 public void setUri(@IdRes int viewId, String methodName, Uri value) { 5225 if (value != null) { 5226 // Resolve any filesystem path before sending remotely 5227 value = value.getCanonicalUri(); 5228 if (StrictMode.vmFileUriExposureEnabled()) { 5229 value.checkFileUriExposed("RemoteViews.setUri()"); 5230 } 5231 } 5232 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.URI, value)); 5233 } 5234 5235 /** 5236 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 5237 * @more 5238 * <p class="note">The bitmap will be flattened into the parcel if this object is 5239 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 5240 * 5241 * @param viewId The id of the view on which to call the method. 5242 * @param methodName The name of the method to call. 5243 * @param value The value to pass to the method. 5244 */ 5245 public void setBitmap(@IdRes int viewId, String methodName, Bitmap value) { 5246 addAction(new BitmapReflectionAction(viewId, methodName, value)); 5247 } 5248 5249 /** 5250 * Call a method taking one BlendMode on a view in the layout for this RemoteViews. 5251 * 5252 * @param viewId The id of the view on which to call the method. 5253 * @param methodName The name of the method to call. 5254 * @param value The value to pass to the method. 5255 */ 5256 public void setBlendMode(@IdRes int viewId, @NonNull String methodName, 5257 @Nullable BlendMode value) { 5258 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BLEND_MODE, value)); 5259 } 5260 5261 /** 5262 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 5263 * 5264 * @param viewId The id of the view on which to call the method. 5265 * @param methodName The name of the method to call. 5266 * @param value The value to pass to the method. 5267 */ 5268 public void setBundle(@IdRes int viewId, String methodName, Bundle value) { 5269 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BUNDLE, value)); 5270 } 5271 5272 /** 5273 * Call a method taking one Intent on a view in the layout for this RemoteViews. 5274 * 5275 * @param viewId The id of the view on which to call the method. 5276 * @param methodName The name of the method to call. 5277 * @param value The {@link android.content.Intent} to pass the method. 5278 */ 5279 public void setIntent(@IdRes int viewId, String methodName, Intent value) { 5280 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INTENT, value)); 5281 } 5282 5283 /** 5284 * Call a method taking one Icon on a view in the layout for this RemoteViews. 5285 * 5286 * @param viewId The id of the view on which to call the method. 5287 * @param methodName The name of the method to call. 5288 * @param value The {@link android.graphics.drawable.Icon} to pass the method. 5289 */ 5290 public void setIcon(@IdRes int viewId, String methodName, Icon value) { 5291 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.ICON, value)); 5292 } 5293 5294 /** 5295 * Call a method taking one Icon on a view in the layout for this RemoteViews. 5296 * 5297 * @param viewId The id of the view on which to call the method. 5298 * @param methodName The name of the method to call. 5299 * @param notNight The value to pass to the method when the view's configuration is set to 5300 * {@link Configuration#UI_MODE_NIGHT_NO} 5301 * @param night The value to pass to the method when the view's configuration is set to 5302 * {@link Configuration#UI_MODE_NIGHT_YES} 5303 */ 5304 public void setIcon( 5305 @IdRes int viewId, 5306 @NonNull String methodName, 5307 @Nullable Icon notNight, 5308 @Nullable Icon night) { 5309 addAction( 5310 new NightModeReflectionAction( 5311 viewId, 5312 methodName, 5313 BaseReflectionAction.ICON, 5314 notNight, 5315 night)); 5316 } 5317 5318 /** 5319 * Equivalent to calling View.setContentDescription(CharSequence). 5320 * 5321 * @param viewId The id of the view whose content description should change. 5322 * @param contentDescription The new content description for the view. 5323 */ 5324 public void setContentDescription(@IdRes int viewId, CharSequence contentDescription) { 5325 setCharSequence(viewId, "setContentDescription", contentDescription); 5326 } 5327 5328 /** 5329 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. 5330 * 5331 * @param viewId The id of the view whose before view in accessibility traversal to set. 5332 * @param nextId The id of the next in the accessibility traversal. 5333 **/ 5334 public void setAccessibilityTraversalBefore(@IdRes int viewId, @IdRes int nextId) { 5335 setInt(viewId, "setAccessibilityTraversalBefore", nextId); 5336 } 5337 5338 /** 5339 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. 5340 * 5341 * @param viewId The id of the view whose after view in accessibility traversal to set. 5342 * @param nextId The id of the next in the accessibility traversal. 5343 **/ 5344 public void setAccessibilityTraversalAfter(@IdRes int viewId, @IdRes int nextId) { 5345 setInt(viewId, "setAccessibilityTraversalAfter", nextId); 5346 } 5347 5348 /** 5349 * Equivalent to calling {@link View#setLabelFor(int)}. 5350 * 5351 * @param viewId The id of the view whose property to set. 5352 * @param labeledId The id of a view for which this view serves as a label. 5353 */ 5354 public void setLabelFor(@IdRes int viewId, @IdRes int labeledId) { 5355 setInt(viewId, "setLabelFor", labeledId); 5356 } 5357 5358 /** 5359 * Equivalent to calling {@link android.widget.CompoundButton#setChecked(boolean)}. 5360 * 5361 * @param viewId The id of the view whose property to set. 5362 * @param checked true to check the button, false to uncheck it. 5363 */ 5364 public void setCompoundButtonChecked(@IdRes int viewId, boolean checked) { 5365 addAction(new SetCompoundButtonCheckedAction(viewId, checked)); 5366 } 5367 5368 /** 5369 * Equivalent to calling {@link android.widget.RadioGroup#check(int)}. 5370 * 5371 * @param viewId The id of the view whose property to set. 5372 * @param checkedId The unique id of the radio button to select in the group. 5373 */ 5374 public void setRadioGroupChecked(@IdRes int viewId, @IdRes int checkedId) { 5375 addAction(new SetRadioGroupCheckedAction(viewId, checkedId)); 5376 } 5377 5378 /** 5379 * Provides an alternate layout ID, which can be used to inflate this view. This layout will be 5380 * used by the host when the widgets displayed on a light-background where foreground elements 5381 * and text can safely draw using a dark color without any additional background protection. 5382 */ 5383 public void setLightBackgroundLayoutId(@LayoutRes int layoutId) { 5384 mLightBackgroundLayoutId = layoutId; 5385 } 5386 5387 /** 5388 * If this view supports dark text versions, creates a copy representing that version, 5389 * otherwise returns itself. 5390 * @hide 5391 */ 5392 public RemoteViews getDarkTextViews() { 5393 if (hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)) { 5394 return this; 5395 } 5396 5397 try { 5398 addFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT); 5399 return new RemoteViews(this); 5400 } finally { 5401 mApplyFlags &= ~FLAG_USE_LIGHT_BACKGROUND_LAYOUT; 5402 } 5403 } 5404 5405 private RemoteViews getRemoteViewsToApply(Context context) { 5406 if (hasLandscapeAndPortraitLayouts()) { 5407 int orientation = context.getResources().getConfiguration().orientation; 5408 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 5409 return mLandscape; 5410 } 5411 return mPortrait; 5412 } 5413 if (hasSizedRemoteViews()) { 5414 return findSmallestRemoteView(); 5415 } 5416 return this; 5417 } 5418 5419 /** 5420 * Returns the square distance between two points. 5421 * 5422 * This is particularly useful when we only care about the ordering of the distances. 5423 */ 5424 private static float squareDistance(SizeF p1, SizeF p2) { 5425 float dx = p1.getWidth() - p2.getWidth(); 5426 float dy = p1.getHeight() - p2.getHeight(); 5427 return dx * dx + dy * dy; 5428 } 5429 5430 /** 5431 * Returns whether the layout fits in the space available to the widget. 5432 * 5433 * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions 5434 * are smaller than the ones of the widget, adding some padding to account for rounding errors. 5435 */ 5436 private static boolean fitsIn(SizeF sizeLayout, @Nullable SizeF sizeWidget) { 5437 return sizeWidget != null && (Math.ceil(sizeWidget.getWidth()) + 1 > sizeLayout.getWidth()) 5438 && (Math.ceil(sizeWidget.getHeight()) + 1 > sizeLayout.getHeight()); 5439 } 5440 5441 private RemoteViews findBestFitLayout(@NonNull SizeF widgetSize) { 5442 // Find the better remote view 5443 RemoteViews bestFit = null; 5444 float bestSqDist = Float.MAX_VALUE; 5445 for (RemoteViews layout : mSizedRemoteViews) { 5446 SizeF layoutSize = layout.getIdealSize(); 5447 if (layoutSize == null) { 5448 throw new IllegalStateException("Expected RemoteViews to have ideal size"); 5449 } 5450 5451 if (fitsIn(layoutSize, widgetSize)) { 5452 if (bestFit == null) { 5453 bestFit = layout; 5454 bestSqDist = squareDistance(layoutSize, widgetSize); 5455 } else { 5456 float newSqDist = squareDistance(layoutSize, widgetSize); 5457 if (newSqDist < bestSqDist) { 5458 bestFit = layout; 5459 bestSqDist = newSqDist; 5460 } 5461 } 5462 } 5463 } 5464 if (bestFit == null) { 5465 Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize); 5466 return findSmallestRemoteView(); 5467 } 5468 return bestFit; 5469 } 5470 5471 /** 5472 * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the 5473 * size of the widget. 5474 * 5475 * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is 5476 * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the 5477 * diagonal the most similar to the widget. If no layout fits or the size of the widget is 5478 * not specified, the one with the smallest area will be chosen. 5479 * 5480 * @hide 5481 */ 5482 public RemoteViews getRemoteViewsToApply(@NonNull Context context, 5483 @Nullable SizeF widgetSize) { 5484 if (!hasSizedRemoteViews() || widgetSize == null) { 5485 // If there isn't multiple remote views, fall back on the previous methods. 5486 return getRemoteViewsToApply(context); 5487 } 5488 return findBestFitLayout(widgetSize); 5489 } 5490 5491 /** 5492 * Checks whether the change of size will lead to using a different {@link RemoteViews}. 5493 * 5494 * @hide 5495 */ 5496 @Nullable 5497 public RemoteViews getRemoteViewsToApplyIfDifferent(@Nullable SizeF oldSize, 5498 @NonNull SizeF newSize) { 5499 if (!hasSizedRemoteViews()) { 5500 return null; 5501 } 5502 RemoteViews oldBestFit = oldSize == null ? findSmallestRemoteView() : findBestFitLayout( 5503 oldSize); 5504 RemoteViews newBestFit = findBestFitLayout(newSize); 5505 if (oldBestFit != newBestFit) { 5506 return newBestFit; 5507 } 5508 return null; 5509 } 5510 5511 5512 /** 5513 * Inflates the view hierarchy represented by this object and applies 5514 * all of the actions. 5515 * 5516 * <p><strong>Caller beware: this may throw</strong> 5517 * 5518 * @param context Default context to use 5519 * @param parent Parent that the resulting view hierarchy will be attached to. This method 5520 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 5521 * @return The inflated view hierarchy 5522 */ 5523 public View apply(Context context, ViewGroup parent) { 5524 return apply(context, parent, null); 5525 } 5526 5527 /** @hide */ 5528 public View apply(Context context, ViewGroup parent, InteractionHandler handler) { 5529 return apply(context, parent, handler, null); 5530 } 5531 5532 /** @hide */ 5533 public View apply(@NonNull Context context, @NonNull ViewGroup parent, 5534 @Nullable InteractionHandler handler, @Nullable SizeF size) { 5535 RemoteViews rvToApply = getRemoteViewsToApply(context, size); 5536 5537 View result = inflateView(context, rvToApply, parent); 5538 rvToApply.performApply(result, parent, handler, null); 5539 return result; 5540 } 5541 5542 /** @hide */ 5543 public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, 5544 @Nullable InteractionHandler handler, @StyleRes int applyThemeResId) { 5545 return applyWithTheme(context, parent, handler, applyThemeResId, null); 5546 } 5547 5548 /** @hide */ 5549 public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, 5550 @Nullable InteractionHandler handler, @StyleRes int applyThemeResId, 5551 @Nullable SizeF size) { 5552 RemoteViews rvToApply = getRemoteViewsToApply(context, size); 5553 5554 View result = inflateView(context, rvToApply, parent, applyThemeResId, null); 5555 rvToApply.performApply(result, parent, handler, null); 5556 return result; 5557 } 5558 5559 /** @hide */ 5560 public View apply(Context context, ViewGroup parent, InteractionHandler handler, 5561 @Nullable SizeF size, @Nullable ColorResources colorResources) { 5562 RemoteViews rvToApply = getRemoteViewsToApply(context, size); 5563 5564 View result = inflateView(context, rvToApply, parent, 0, colorResources); 5565 rvToApply.performApply(result, parent, handler, colorResources); 5566 return result; 5567 } 5568 5569 private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { 5570 return inflateView(context, rv, parent, 0, null); 5571 } 5572 5573 private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent, 5574 @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) { 5575 // RemoteViews may be built by an application installed in another 5576 // user. So build a context that loads resources from that user but 5577 // still returns the current users userId so settings like data / time formats 5578 // are loaded without requiring cross user persmissions. 5579 final Context contextForResources = 5580 getContextForResourcesEnsuringCorrectCachedApkPaths(context); 5581 if (colorResources != null) { 5582 colorResources.apply(contextForResources); 5583 } 5584 Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources); 5585 5586 // If mApplyThemeResId is not given, Theme.DeviceDefault will be used. 5587 if (applyThemeResId != 0) { 5588 inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId); 5589 } 5590 LayoutInflater inflater = LayoutInflater.from(context); 5591 5592 // Clone inflater so we load resources from correct context and 5593 // we don't add a filter to the static version returned by getSystemService. 5594 inflater = inflater.cloneInContext(inflationContext); 5595 inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this); 5596 View v = inflater.inflate(rv.getLayoutId(), parent, false); 5597 if (mViewId != View.NO_ID) { 5598 v.setId(mViewId); 5599 v.setTagInternal(R.id.remote_views_override_id, mViewId); 5600 } 5601 v.setTagInternal(R.id.widget_frame, rv.getLayoutId()); 5602 return v; 5603 } 5604 5605 /** 5606 * A static filter is much lighter than RemoteViews itself. It's optimized here only for 5607 * RemoteVies class. Subclasses should always override this and return true if not overriding 5608 * {@link this#onLoadClass(Class)}. 5609 * 5610 * @hide 5611 */ 5612 protected boolean shouldUseStaticFilter() { 5613 return this.getClass().equals(RemoteViews.class); 5614 } 5615 5616 /** 5617 * Implement this interface to receive a callback when 5618 * {@link #applyAsync} or {@link #reapplyAsync} is finished. 5619 * @hide 5620 */ 5621 public interface OnViewAppliedListener { 5622 /** 5623 * Callback when the RemoteView has finished inflating, 5624 * but no actions have been applied yet. 5625 */ 5626 default void onViewInflated(View v) {}; 5627 5628 void onViewApplied(View v); 5629 5630 void onError(Exception e); 5631 } 5632 5633 /** 5634 * Applies the views asynchronously, moving as much of the task on the background 5635 * thread as possible. 5636 * 5637 * @see #apply(Context, ViewGroup) 5638 * @param context Default context to use 5639 * @param parent Parent that the resulting view hierarchy will be attached to. This method 5640 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 5641 * @param listener the callback to run when all actions have been applied. May be null. 5642 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used. 5643 * @return CancellationSignal 5644 * @hide 5645 */ 5646 public CancellationSignal applyAsync( 5647 Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) { 5648 return applyAsync(context, parent, executor, listener, null /* handler */); 5649 } 5650 5651 5652 /** @hide */ 5653 public CancellationSignal applyAsync(Context context, ViewGroup parent, 5654 Executor executor, OnViewAppliedListener listener, InteractionHandler handler) { 5655 return applyAsync(context, parent, executor, listener, handler, null /* size */); 5656 } 5657 5658 /** @hide */ 5659 public CancellationSignal applyAsync(Context context, ViewGroup parent, 5660 Executor executor, OnViewAppliedListener listener, InteractionHandler handler, 5661 SizeF size) { 5662 return applyAsync(context, parent, executor, listener, handler, size, 5663 null /* themeColors */); 5664 } 5665 5666 /** @hide */ 5667 public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, 5668 OnViewAppliedListener listener, InteractionHandler handler, SizeF size, 5669 ColorResources colorResources) { 5670 return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, 5671 handler, colorResources, null /* result */, 5672 true /* topLevel */).startTaskOnExecutor(executor); 5673 } 5674 5675 private AsyncApplyTask getInternalAsyncApplyTask(Context context, ViewGroup parent, 5676 OnViewAppliedListener listener, InteractionHandler handler, SizeF size, 5677 ColorResources colorResources, View result) { 5678 return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, 5679 handler, colorResources, result, false /* topLevel */); 5680 } 5681 5682 private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> 5683 implements CancellationSignal.OnCancelListener { 5684 final CancellationSignal mCancelSignal = new CancellationSignal(); 5685 final RemoteViews mRV; 5686 final ViewGroup mParent; 5687 final Context mContext; 5688 final OnViewAppliedListener mListener; 5689 final InteractionHandler mHandler; 5690 final ColorResources mColorResources; 5691 /** 5692 * Whether the remote view is the top-level one (i.e. not within an action). 5693 * 5694 * This is only used if the result is specified (i.e. the view is being recycled). 5695 */ 5696 final boolean mTopLevel; 5697 5698 private View mResult; 5699 private ViewTree mTree; 5700 private Action[] mActions; 5701 private Exception mError; 5702 5703 private AsyncApplyTask( 5704 RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, 5705 InteractionHandler handler, ColorResources colorResources, 5706 View result, boolean topLevel) { 5707 mRV = rv; 5708 mParent = parent; 5709 mContext = context; 5710 mListener = listener; 5711 mColorResources = colorResources; 5712 mHandler = handler; 5713 mTopLevel = topLevel; 5714 5715 mResult = result; 5716 } 5717 5718 @Nullable 5719 @Override 5720 protected ViewTree doInBackground(Void... params) { 5721 try { 5722 if (mResult == null) { 5723 mResult = inflateView(mContext, mRV, mParent, 0, mColorResources); 5724 } 5725 5726 mTree = new ViewTree(mResult); 5727 if (mRV.mActions != null) { 5728 int count = mRV.mActions.size(); 5729 mActions = new Action[count]; 5730 for (int i = 0; i < count && !isCancelled(); i++) { 5731 // TODO: check if isCancelled in nested views. 5732 mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler, 5733 mColorResources); 5734 } 5735 } else { 5736 mActions = null; 5737 } 5738 return mTree; 5739 } catch (Exception e) { 5740 mError = e; 5741 return null; 5742 } 5743 } 5744 5745 @Override 5746 protected void onPostExecute(ViewTree viewTree) { 5747 mCancelSignal.setOnCancelListener(null); 5748 if (mError == null) { 5749 if (mListener != null) { 5750 mListener.onViewInflated(viewTree.mRoot); 5751 } 5752 5753 try { 5754 if (mActions != null) { 5755 InteractionHandler handler = mHandler == null 5756 ? DEFAULT_INTERACTION_HANDLER : mHandler; 5757 for (Action a : mActions) { 5758 a.apply(viewTree.mRoot, mParent, handler, mColorResources); 5759 } 5760 } 5761 // If the parent of the view is has is a root, resolve the recycling. 5762 if (mTopLevel && mResult instanceof ViewGroup) { 5763 finalizeViewRecycling((ViewGroup) mResult); 5764 } 5765 } catch (Exception e) { 5766 mError = e; 5767 } 5768 } 5769 5770 if (mListener != null) { 5771 if (mError != null) { 5772 mListener.onError(mError); 5773 } else { 5774 mListener.onViewApplied(viewTree.mRoot); 5775 } 5776 } else if (mError != null) { 5777 if (mError instanceof ActionException) { 5778 throw (ActionException) mError; 5779 } else { 5780 throw new ActionException(mError); 5781 } 5782 } 5783 } 5784 5785 @Override 5786 public void onCancel() { 5787 cancel(true); 5788 } 5789 5790 private CancellationSignal startTaskOnExecutor(Executor executor) { 5791 mCancelSignal.setOnCancelListener(this); 5792 executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor); 5793 return mCancelSignal; 5794 } 5795 } 5796 5797 /** 5798 * Applies all of the actions to the provided view. 5799 * 5800 * <p><strong>Caller beware: this may throw</strong> 5801 * 5802 * @param v The view to apply the actions to. This should be the result of 5803 * the {@link #apply(Context,ViewGroup)} call. 5804 */ 5805 public void reapply(Context context, View v) { 5806 reapply(context, v, null /* handler */); 5807 } 5808 5809 /** @hide */ 5810 public void reapply(Context context, View v, InteractionHandler handler) { 5811 reapply(context, v, handler, null /* size */, null /* colorResources */); 5812 } 5813 5814 /** @hide */ 5815 public void reapply(Context context, View v, InteractionHandler handler, SizeF size, 5816 ColorResources colorResources) { 5817 reapply(context, v, handler, size, colorResources, true); 5818 } 5819 5820 /** @hide */ 5821 public boolean canRecycleView(@Nullable View v) { 5822 if (v == null) { 5823 return false; 5824 } 5825 Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame); 5826 if (previousLayoutId == null) { 5827 return false; 5828 } 5829 Integer overrideIdTag = (Integer) v.getTag(R.id.remote_views_override_id); 5830 int overrideId = overrideIdTag == null ? View.NO_ID : overrideIdTag; 5831 // If mViewId is View.NO_ID, we only recycle if overrideId is also View.NO_ID. 5832 // Otherwise, it might be that, on a previous iteration, the view's ID was set to 5833 // something else, and it should now be reset to the ID defined in the XML layout file, 5834 // whatever it is. 5835 return previousLayoutId == getLayoutId() && mViewId == overrideId; 5836 } 5837 5838 /** 5839 * Returns the RemoteViews that should be used in the reapply operation. 5840 * 5841 * If the current RemoteViews has multiple layout, this will select the correct one. 5842 * 5843 * @throws RuntimeException If the current RemoteViews should not be reapplied onto the provided 5844 * View. 5845 */ 5846 private RemoteViews getRemoteViewsToReapply(Context context, View v, @Nullable SizeF size) { 5847 RemoteViews rvToApply = getRemoteViewsToApply(context, size); 5848 5849 // In the case that a view has this RemoteViews applied in one orientation or size, is 5850 // persisted across change, and has the RemoteViews re-applied in a different situation 5851 // (orientation or size), we throw an exception, since the layouts may be completely 5852 // unrelated. 5853 // If the ViewID has been changed on the view, or is changed by the RemoteViews, we also 5854 // may throw an exception, as the RemoteViews will probably not apply properly. 5855 // However, we need to let potentially unrelated RemoteViews apply, as this lack of testing 5856 // is already used in production code in some apps. 5857 if (hasMultipleLayouts() 5858 || rvToApply.mViewId != View.NO_ID 5859 || v.getTag(R.id.remote_views_override_id) != null) { 5860 if (!rvToApply.canRecycleView(v)) { 5861 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 5862 " that does not share the same root layout id."); 5863 } 5864 } 5865 5866 return rvToApply; 5867 } 5868 5869 // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls 5870 // should set it to false. 5871 private void reapply(Context context, View v, InteractionHandler handler, SizeF size, 5872 ColorResources colorResources, boolean topLevel) { 5873 5874 RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); 5875 5876 rvToApply.performApply(v, (ViewGroup) v.getParent(), handler, colorResources); 5877 5878 // If the parent of the view is has is a root, resolve the recycling. 5879 if (topLevel && v instanceof ViewGroup) { 5880 finalizeViewRecycling((ViewGroup) v); 5881 } 5882 } 5883 5884 /** 5885 * Applies all the actions to the provided view, moving as much of the task on the background 5886 * thread as possible. 5887 * 5888 * @see #reapply(Context, View) 5889 * @param context Default context to use 5890 * @param v The view to apply the actions to. This should be the result of 5891 * the {@link #apply(Context,ViewGroup)} call. 5892 * @param listener the callback to run when all actions have been applied. May be null. 5893 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used 5894 * @return CancellationSignal 5895 * @hide 5896 */ 5897 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 5898 OnViewAppliedListener listener) { 5899 return reapplyAsync(context, v, executor, listener, null); 5900 } 5901 5902 /** @hide */ 5903 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 5904 OnViewAppliedListener listener, InteractionHandler handler) { 5905 return reapplyAsync(context, v, executor, listener, handler, null, null); 5906 } 5907 5908 /** @hide */ 5909 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 5910 OnViewAppliedListener listener, InteractionHandler handler, SizeF size, 5911 ColorResources colorResources) { 5912 RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); 5913 5914 return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), 5915 context, listener, handler, colorResources, v, true /* topLevel */) 5916 .startTaskOnExecutor(executor); 5917 } 5918 5919 private void performApply(View v, ViewGroup parent, InteractionHandler handler, 5920 ColorResources colorResources) { 5921 if (mActions != null) { 5922 handler = handler == null ? DEFAULT_INTERACTION_HANDLER : handler; 5923 final int count = mActions.size(); 5924 for (int i = 0; i < count; i++) { 5925 Action a = mActions.get(i); 5926 a.apply(v, parent, handler, colorResources); 5927 } 5928 } 5929 } 5930 5931 /** 5932 * Returns true if the RemoteViews contains potentially costly operations and should be 5933 * applied asynchronously. 5934 * 5935 * @hide 5936 */ 5937 public boolean prefersAsyncApply() { 5938 if (mActions != null) { 5939 final int count = mActions.size(); 5940 for (int i = 0; i < count; i++) { 5941 if (mActions.get(i).prefersAsyncApply()) { 5942 return true; 5943 } 5944 } 5945 } 5946 return false; 5947 } 5948 5949 /** @hide */ 5950 public void updateAppInfo(@NonNull ApplicationInfo info) { 5951 ApplicationInfo existing = mApplicationInfoCache.get(info); 5952 if (existing != null && !existing.sourceDir.equals(info.sourceDir)) { 5953 // Overlay paths are generated against a particular version of an application. 5954 // The overlays paths of a newly upgraded application are incompatible with the 5955 // old version of the application. 5956 return; 5957 } 5958 5959 // If we can update to the new AppInfo, put it in the cache and propagate the change 5960 // throughout the hierarchy. 5961 mApplicationInfoCache.put(info); 5962 configureDescendantsAsChildren(); 5963 } 5964 5965 private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) { 5966 if (mApplication != null) { 5967 if (context.getUserId() == UserHandle.getUserId(mApplication.uid) 5968 && context.getPackageName().equals(mApplication.packageName)) { 5969 return context; 5970 } 5971 try { 5972 LoadedApk.checkAndUpdateApkPaths(mApplication); 5973 return context.createApplicationContext(mApplication, 5974 Context.CONTEXT_RESTRICTED); 5975 } catch (NameNotFoundException e) { 5976 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); 5977 } 5978 } 5979 5980 return context; 5981 } 5982 5983 /** 5984 * Object allowing the modification of a context to overload the system's dynamic colors. 5985 * 5986 * Only colors from {@link android.R.color#system_accent1_0} to 5987 * {@link android.R.color#system_neutral2_1000} can be overloaded. 5988 * @hide 5989 */ 5990 public static final class ColorResources { 5991 // Set of valid colors resources. 5992 private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0; 5993 private static final int LAST_RESOURCE_COLOR_ID = android.R.color.system_accent3_1000; 5994 // Size, in bytes, of an entry in the array of colors in an ARSC file. 5995 private static final int ARSC_ENTRY_SIZE = 16; 5996 5997 private ResourcesLoader mLoader; 5998 5999 private ColorResources(ResourcesLoader loader) { 6000 mLoader = loader; 6001 } 6002 6003 /** 6004 * Apply the color resources to the given context. 6005 * 6006 * No resource resolution must have be done on the context given to that method. 6007 */ 6008 public void apply(Context context) { 6009 context.getResources().addLoaders(mLoader); 6010 } 6011 6012 private static ByteArrayOutputStream readFileContent(InputStream input) throws IOException { 6013 ByteArrayOutputStream content = new ByteArrayOutputStream(2048); 6014 byte[] buffer = new byte[4096]; 6015 while (input.available() > 0) { 6016 int read = input.read(buffer); 6017 content.write(buffer, 0, read); 6018 } 6019 return content; 6020 } 6021 6022 /** 6023 * Creates the compiled resources content from the asset stored in the APK. 6024 * 6025 * The asset is a compiled resource with the correct resources name and correct ids, only 6026 * the values are incorrect. The last value is at the very end of the file. The resources 6027 * are in an array, the array's entries are 16 bytes each. We use this to work out the 6028 * location of all the positions of the various resources. 6029 */ 6030 @Nullable 6031 private static byte[] createCompiledResourcesContent(Context context, 6032 SparseIntArray colorResources) throws IOException { 6033 byte[] content; 6034 try (InputStream input = context.getResources().openRawResource( 6035 com.android.internal.R.raw.remote_views_color_resources)) { 6036 ByteArrayOutputStream rawContent = readFileContent(input); 6037 content = rawContent.toByteArray(); 6038 } 6039 int valuesOffset = 6040 content.length - (LAST_RESOURCE_COLOR_ID & 0xffff) * ARSC_ENTRY_SIZE - 4; 6041 if (valuesOffset < 0) { 6042 Log.e(LOG_TAG, "ARSC file for theme colors is invalid."); 6043 return null; 6044 } 6045 for (int colorRes = FIRST_RESOURCE_COLOR_ID; colorRes <= LAST_RESOURCE_COLOR_ID; 6046 colorRes++) { 6047 // The last 2 bytes are the index in the color array. 6048 int index = colorRes & 0xffff; 6049 int offset = valuesOffset + index * ARSC_ENTRY_SIZE; 6050 int value = colorResources.get(colorRes, context.getColor(colorRes)); 6051 // Write the 32 bit integer in little endian 6052 for (int b = 0; b < 4; b++) { 6053 content[offset + b] = (byte) (value & 0xff); 6054 value >>= 8; 6055 } 6056 } 6057 return content; 6058 } 6059 6060 /** 6061 * Adds a resource loader for theme colors to the given context. 6062 * 6063 * @param context Context of the view hosting the widget. 6064 * @param colorMapping Mapping of resources to color values. 6065 * 6066 * @hide 6067 */ 6068 @Nullable 6069 public static ColorResources create(Context context, SparseIntArray colorMapping) { 6070 try { 6071 byte[] contentBytes = createCompiledResourcesContent(context, colorMapping); 6072 if (contentBytes == null) { 6073 return null; 6074 } 6075 FileDescriptor arscFile = null; 6076 try { 6077 arscFile = Os.memfd_create("remote_views_theme_colors.arsc", 0 /* flags */); 6078 // Note: This must not be closed through the OutputStream. 6079 try (OutputStream pipeWriter = new FileOutputStream(arscFile)) { 6080 pipeWriter.write(contentBytes); 6081 6082 try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) { 6083 ResourcesLoader colorsLoader = new ResourcesLoader(); 6084 colorsLoader.addProvider(ResourcesProvider 6085 .loadFromTable(pfd, null /* assetsProvider */)); 6086 return new ColorResources(colorsLoader); 6087 } 6088 } 6089 } finally { 6090 if (arscFile != null) { 6091 Os.close(arscFile); 6092 } 6093 } 6094 } catch (Exception ex) { 6095 Log.e(LOG_TAG, "Failed to setup the context for theme colors", ex); 6096 } 6097 return null; 6098 } 6099 } 6100 6101 /** 6102 * Returns the number of actions in this RemoteViews. Can be used as a sequence number. 6103 * 6104 * @hide 6105 */ 6106 public int getSequenceNumber() { 6107 return (mActions == null) ? 0 : mActions.size(); 6108 } 6109 6110 /** 6111 * Used to restrict the views which can be inflated 6112 * 6113 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 6114 * @deprecated Used by system to enforce safe inflation of {@link RemoteViews}. Apps should not 6115 * override this method. Changing of this method will NOT affect the process where RemoteViews 6116 * is rendered. 6117 */ 6118 @Deprecated 6119 public boolean onLoadClass(Class clazz) { 6120 return clazz.isAnnotationPresent(RemoteView.class); 6121 } 6122 6123 public int describeContents() { 6124 return 0; 6125 } 6126 6127 public void writeToParcel(Parcel dest, int flags) { 6128 boolean prevSquashingAllowed = dest.allowSquashing(); 6129 6130 if (!hasMultipleLayouts()) { 6131 dest.writeInt(MODE_NORMAL); 6132 // We only write the bitmap cache if we are the root RemoteViews, as this cache 6133 // is shared by all children. 6134 if (mIsRoot) { 6135 mBitmapCache.writeBitmapsToParcel(dest, flags); 6136 } 6137 mApplication.writeToParcel(dest, flags); 6138 if (mIsRoot || mIdealSize == null) { 6139 dest.writeInt(0); 6140 } else { 6141 dest.writeInt(1); 6142 mIdealSize.writeToParcel(dest, flags); 6143 } 6144 dest.writeInt(mLayoutId); 6145 dest.writeInt(mViewId); 6146 dest.writeInt(mLightBackgroundLayoutId); 6147 writeActionsToParcel(dest, flags); 6148 } else if (hasSizedRemoteViews()) { 6149 dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS); 6150 if (mIsRoot) { 6151 mBitmapCache.writeBitmapsToParcel(dest, flags); 6152 } 6153 dest.writeInt(mSizedRemoteViews.size()); 6154 for (RemoteViews view : mSizedRemoteViews) { 6155 view.writeToParcel(dest, flags); 6156 } 6157 } else { 6158 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 6159 // We only write the bitmap cache if we are the root RemoteViews, as this cache 6160 // is shared by all children. 6161 if (mIsRoot) { 6162 mBitmapCache.writeBitmapsToParcel(dest, flags); 6163 } 6164 mLandscape.writeToParcel(dest, flags); 6165 // Both RemoteViews already share the same package and user 6166 mPortrait.writeToParcel(dest, flags); 6167 } 6168 dest.writeInt(mApplyFlags); 6169 dest.writeLong(mProviderInstanceId); 6170 6171 dest.restoreAllowSquashing(prevSquashingAllowed); 6172 } 6173 6174 private void writeActionsToParcel(Parcel parcel, int flags) { 6175 int count; 6176 if (mActions != null) { 6177 count = mActions.size(); 6178 } else { 6179 count = 0; 6180 } 6181 parcel.writeInt(count); 6182 for (int i = 0; i < count; i++) { 6183 Action a = mActions.get(i); 6184 parcel.writeInt(a.getActionTag()); 6185 a.writeToParcel(parcel, flags); 6186 } 6187 } 6188 6189 @Nullable 6190 private static ApplicationInfo getApplicationInfo(@Nullable String packageName, int userId) { 6191 if (packageName == null) { 6192 return null; 6193 } 6194 6195 // Get the application for the passed in package and user. 6196 Application application = ActivityThread.currentApplication(); 6197 if (application == null) { 6198 throw new IllegalStateException("Cannot create remote views out of an aplication."); 6199 } 6200 6201 ApplicationInfo applicationInfo = application.getApplicationInfo(); 6202 if (UserHandle.getUserId(applicationInfo.uid) != userId 6203 || !applicationInfo.packageName.equals(packageName)) { 6204 try { 6205 Context context = application.getBaseContext().createPackageContextAsUser( 6206 packageName, 0, new UserHandle(userId)); 6207 applicationInfo = context.getApplicationInfo(); 6208 } catch (NameNotFoundException nnfe) { 6209 throw new IllegalArgumentException("No such package " + packageName); 6210 } 6211 } 6212 6213 return applicationInfo; 6214 } 6215 6216 /** 6217 * Returns true if the {@link #mApplication} is same as the provided info. 6218 * 6219 * @hide 6220 */ 6221 public boolean hasSameAppInfo(ApplicationInfo info) { 6222 return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid; 6223 } 6224 6225 /** 6226 * Parcelable.Creator that instantiates RemoteViews objects 6227 */ 6228 public static final @android.annotation.NonNull Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 6229 public RemoteViews createFromParcel(Parcel parcel) { 6230 return new RemoteViews(parcel); 6231 } 6232 6233 public RemoteViews[] newArray(int size) { 6234 return new RemoteViews[size]; 6235 } 6236 }; 6237 6238 /** 6239 * A representation of the view hierarchy. Only views which have a valid ID are added 6240 * and can be searched. 6241 */ 6242 private static class ViewTree { 6243 private static final int INSERT_AT_END_INDEX = -1; 6244 private View mRoot; 6245 private ArrayList<ViewTree> mChildren; 6246 6247 private ViewTree(View root) { 6248 mRoot = root; 6249 } 6250 6251 public void createTree() { 6252 if (mChildren != null) { 6253 return; 6254 } 6255 6256 mChildren = new ArrayList<>(); 6257 if (mRoot instanceof ViewGroup) { 6258 ViewGroup vg = (ViewGroup) mRoot; 6259 int count = vg.getChildCount(); 6260 for (int i = 0; i < count; i++) { 6261 addViewChild(vg.getChildAt(i)); 6262 } 6263 } 6264 } 6265 6266 @Nullable 6267 public ViewTree findViewTreeById(@IdRes int id) { 6268 if (mRoot.getId() == id) { 6269 return this; 6270 } 6271 if (mChildren == null) { 6272 return null; 6273 } 6274 for (ViewTree tree : mChildren) { 6275 ViewTree result = tree.findViewTreeById(id); 6276 if (result != null) { 6277 return result; 6278 } 6279 } 6280 return null; 6281 } 6282 6283 @Nullable 6284 public ViewTree findViewTreeParentOf(ViewTree child) { 6285 if (mChildren == null) { 6286 return null; 6287 } 6288 for (ViewTree tree : mChildren) { 6289 if (tree == child) { 6290 return this; 6291 } 6292 ViewTree result = tree.findViewTreeParentOf(child); 6293 if (result != null) { 6294 return result; 6295 } 6296 } 6297 return null; 6298 } 6299 6300 public void replaceView(View v) { 6301 mRoot = v; 6302 mChildren = null; 6303 createTree(); 6304 } 6305 6306 @Nullable 6307 public <T extends View> T findViewById(@IdRes int id) { 6308 if (mChildren == null) { 6309 return mRoot.findViewById(id); 6310 } 6311 ViewTree tree = findViewTreeById(id); 6312 return tree == null ? null : (T) tree.mRoot; 6313 } 6314 6315 public void addChild(ViewTree child) { 6316 addChild(child, INSERT_AT_END_INDEX); 6317 } 6318 6319 /** 6320 * Adds the given {@link ViewTree} as a child at the given index. 6321 * 6322 * @param index The position at which to add the child or -1 to add last. 6323 */ 6324 public void addChild(ViewTree child, int index) { 6325 if (mChildren == null) { 6326 mChildren = new ArrayList<>(); 6327 } 6328 child.createTree(); 6329 6330 if (index == INSERT_AT_END_INDEX) { 6331 mChildren.add(child); 6332 return; 6333 } 6334 6335 mChildren.add(index, child); 6336 } 6337 6338 public void removeChildren(int start, int count) { 6339 if (mChildren != null) { 6340 for (int i = 0; i < count; i++) { 6341 mChildren.remove(start); 6342 } 6343 } 6344 } 6345 6346 private void addViewChild(View v) { 6347 // ViewTree only contains Views which can be found using findViewById. 6348 // If isRootNamespace is true, this view is skipped. 6349 // @see ViewGroup#findViewTraversal(int) 6350 if (v.isRootNamespace()) { 6351 return; 6352 } 6353 final ViewTree target; 6354 6355 // If the view has a valid id, i.e., if can be found using findViewById, add it to the 6356 // tree, otherwise skip this view and add its children instead. 6357 if (v.getId() != 0) { 6358 ViewTree tree = new ViewTree(v); 6359 mChildren.add(tree); 6360 target = tree; 6361 } else { 6362 target = this; 6363 } 6364 6365 if (v instanceof ViewGroup) { 6366 if (target.mChildren == null) { 6367 target.mChildren = new ArrayList<>(); 6368 ViewGroup vg = (ViewGroup) v; 6369 int count = vg.getChildCount(); 6370 for (int i = 0; i < count; i++) { 6371 target.addViewChild(vg.getChildAt(i)); 6372 } 6373 } 6374 } 6375 } 6376 6377 /** Find the first child for which the condition is true and return its index. */ 6378 public int findChildIndex(Predicate<View> condition) { 6379 return findChildIndex(0, condition); 6380 } 6381 6382 /** 6383 * Find the first child, starting at {@code startIndex}, for which the condition is true and 6384 * return its index. 6385 */ 6386 public int findChildIndex(int startIndex, Predicate<View> condition) { 6387 if (mChildren == null) { 6388 return -1; 6389 } 6390 6391 for (int i = startIndex; i < mChildren.size(); i++) { 6392 if (condition.test(mChildren.get(i).mRoot)) { 6393 return i; 6394 } 6395 } 6396 return -1; 6397 } 6398 } 6399 6400 /** 6401 * Class representing a response to an action performed on any element of a RemoteViews. 6402 */ 6403 public static class RemoteResponse { 6404 6405 /** @hide **/ 6406 @IntDef(prefix = "INTERACTION_TYPE_", value = { 6407 INTERACTION_TYPE_CLICK, 6408 INTERACTION_TYPE_CHECKED_CHANGE, 6409 }) 6410 @Retention(RetentionPolicy.SOURCE) 6411 @interface InteractionType {} 6412 /** @hide */ 6413 public static final int INTERACTION_TYPE_CLICK = 0; 6414 /** @hide */ 6415 public static final int INTERACTION_TYPE_CHECKED_CHANGE = 1; 6416 6417 private PendingIntent mPendingIntent; 6418 private Intent mFillIntent; 6419 6420 private int mInteractionType = INTERACTION_TYPE_CLICK; 6421 private IntArray mViewIds; 6422 private ArrayList<String> mElementNames; 6423 6424 /** 6425 * Creates a response which sends a pending intent as part of the response. The source 6426 * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the 6427 * target view in screen space. 6428 * Note that any activity options associated with the mPendingIntent may get overridden 6429 * before starting the intent. 6430 * 6431 * @param pendingIntent The {@link PendingIntent} to send as part of the response 6432 */ 6433 @NonNull 6434 public static RemoteResponse fromPendingIntent(@NonNull PendingIntent pendingIntent) { 6435 RemoteResponse response = new RemoteResponse(); 6436 response.mPendingIntent = pendingIntent; 6437 return response; 6438 } 6439 6440 /** 6441 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is 6442 * very costly to set PendingIntents on the individual items, and is hence not recommended. 6443 * Instead a single PendingIntent template can be set on the collection, see {@link 6444 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 6445 * action of a given item can be distinguished by setting a fillInIntent on that item. The 6446 * fillInIntent is then combined with the PendingIntent template in order to determine the 6447 * final intent which will be executed when the item is clicked. This works as follows: any 6448 * fields which are left blank in the PendingIntent template, but are provided by the 6449 * fillInIntent will be overwritten, and the resulting PendingIntent will be used. The rest 6450 * of the PendingIntent template will then be filled in with the associated fields that are 6451 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 6452 * Creates a response which sends a pending intent as part of the response. The source 6453 * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the 6454 * target view in screen space. 6455 * Note that any activity options associated with the mPendingIntent may get overridden 6456 * before starting the intent. 6457 * 6458 * @param fillIntent The intent which will be combined with the parent's PendingIntent in 6459 * order to determine the behavior of the response 6460 * @see RemoteViews#setPendingIntentTemplate(int, PendingIntent) 6461 * @see RemoteViews#setOnClickFillInIntent(int, Intent) 6462 */ 6463 @NonNull 6464 public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) { 6465 RemoteResponse response = new RemoteResponse(); 6466 response.mFillIntent = fillIntent; 6467 return response; 6468 } 6469 6470 /** 6471 * Adds a shared element to be transferred as part of the transition between Activities 6472 * using cross-Activity scene animations. The position of the first element will be used as 6473 * the epicenter for the exit Transition. The position of the associated shared element in 6474 * the launched Activity will be the epicenter of its entering Transition. 6475 * 6476 * @param viewId The id of the view to be shared as part of the transition 6477 * @param sharedElementName The shared element name for this view 6478 * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[]) 6479 */ 6480 @NonNull 6481 public RemoteResponse addSharedElement(@IdRes int viewId, 6482 @NonNull String sharedElementName) { 6483 if (mViewIds == null) { 6484 mViewIds = new IntArray(); 6485 mElementNames = new ArrayList<>(); 6486 } 6487 mViewIds.add(viewId); 6488 mElementNames.add(sharedElementName); 6489 return this; 6490 } 6491 6492 /** 6493 * Sets the interaction type for which this RemoteResponse responds. 6494 * 6495 * @param type the type of interaction for which this is a response, such as clicking or 6496 * checked state changing 6497 * 6498 * @hide 6499 */ 6500 @NonNull 6501 public RemoteResponse setInteractionType(@InteractionType int type) { 6502 mInteractionType = type; 6503 return this; 6504 } 6505 6506 private void writeToParcel(Parcel dest, int flags) { 6507 PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest); 6508 if (mPendingIntent == null) { 6509 // Only write the intent if pending intent is null 6510 dest.writeTypedObject(mFillIntent, flags); 6511 } 6512 dest.writeInt(mInteractionType); 6513 dest.writeIntArray(mViewIds == null ? null : mViewIds.toArray()); 6514 dest.writeStringList(mElementNames); 6515 } 6516 6517 private void readFromParcel(Parcel parcel) { 6518 mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 6519 if (mPendingIntent == null) { 6520 mFillIntent = parcel.readTypedObject(Intent.CREATOR); 6521 } 6522 mInteractionType = parcel.readInt(); 6523 int[] viewIds = parcel.createIntArray(); 6524 mViewIds = viewIds == null ? null : IntArray.wrap(viewIds); 6525 mElementNames = parcel.createStringArrayList(); 6526 } 6527 6528 private void handleViewInteraction( 6529 View v, 6530 InteractionHandler handler) { 6531 final PendingIntent pi; 6532 if (mPendingIntent != null) { 6533 pi = mPendingIntent; 6534 } else if (mFillIntent != null) { 6535 AdapterView<?> ancestor = getAdapterViewAncestor(v); 6536 if (ancestor == null) { 6537 Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent"); 6538 return; 6539 } 6540 6541 // Ensure that a template pending intent has been set on the ancestor 6542 if (!(ancestor.getTag() instanceof PendingIntent)) { 6543 Log.e(LOG_TAG, "Attempting setOnClickFillInIntent or " 6544 + "setOnCheckedChangeFillInIntent without calling " 6545 + "setPendingIntentTemplate on parent."); 6546 return; 6547 } 6548 6549 pi = (PendingIntent) ancestor.getTag(); 6550 } else { 6551 Log.e(LOG_TAG, "Response has neither pendingIntent nor fillInIntent"); 6552 return; 6553 } 6554 6555 handler.onInteraction(v, pi, this); 6556 } 6557 6558 /** 6559 * Returns the closest ancestor of the view that is an AdapterView or null if none could be 6560 * found. 6561 */ 6562 @Nullable 6563 private static AdapterView<?> getAdapterViewAncestor(@Nullable View view) { 6564 if (view == null) return null; 6565 6566 View parent = (View) view.getParent(); 6567 // Break the for loop on the first encounter of: 6568 // 1) an AdapterView, 6569 // 2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or 6570 // 3) a null parent. 6571 // 2) and 3) are unexpected and catch the case where a child is not 6572 // correctly parented in an AdapterView. 6573 while (parent != null && !(parent instanceof AdapterView<?>) 6574 && !((parent instanceof AppWidgetHostView) 6575 && !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) { 6576 parent = (View) parent.getParent(); 6577 } 6578 6579 return parent instanceof AdapterView<?> ? (AdapterView<?>) parent : null; 6580 } 6581 6582 /** @hide */ 6583 public Pair<Intent, ActivityOptions> getLaunchOptions(View view) { 6584 Intent intent = mPendingIntent != null ? new Intent() : new Intent(mFillIntent); 6585 intent.setSourceBounds(getSourceBounds(view)); 6586 6587 if (view instanceof CompoundButton 6588 && mInteractionType == INTERACTION_TYPE_CHECKED_CHANGE) { 6589 intent.putExtra(EXTRA_CHECKED, ((CompoundButton) view).isChecked()); 6590 } 6591 6592 ActivityOptions opts = null; 6593 6594 Context context = view.getContext(); 6595 if (context.getResources().getBoolean( 6596 com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { 6597 TypedArray windowStyle = context.getTheme().obtainStyledAttributes( 6598 com.android.internal.R.styleable.Window); 6599 int windowAnimations = windowStyle.getResourceId( 6600 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 6601 TypedArray windowAnimationStyle = context.obtainStyledAttributes( 6602 windowAnimations, com.android.internal.R.styleable.WindowAnimation); 6603 int enterAnimationId = windowAnimationStyle.getResourceId(com.android.internal.R 6604 .styleable.WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0); 6605 windowStyle.recycle(); 6606 windowAnimationStyle.recycle(); 6607 6608 if (enterAnimationId != 0) { 6609 opts = ActivityOptions.makeCustomAnimation(context, 6610 enterAnimationId, 0); 6611 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 6612 } 6613 } 6614 6615 if (opts == null && mViewIds != null && mElementNames != null) { 6616 View parent = (View) view.getParent(); 6617 while (parent != null && !(parent instanceof AppWidgetHostView)) { 6618 parent = (View) parent.getParent(); 6619 } 6620 if (parent instanceof AppWidgetHostView) { 6621 opts = ((AppWidgetHostView) parent).createSharedElementActivityOptions( 6622 mViewIds.toArray(), 6623 mElementNames.toArray(new String[mElementNames.size()]), intent); 6624 } 6625 } 6626 6627 if (opts == null) { 6628 opts = ActivityOptions.makeBasic(); 6629 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 6630 } 6631 return Pair.create(intent, opts); 6632 } 6633 } 6634 6635 /** @hide */ 6636 public static boolean startPendingIntent(View view, PendingIntent pendingIntent, 6637 Pair<Intent, ActivityOptions> options) { 6638 try { 6639 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 6640 Context context = view.getContext(); 6641 // The NEW_TASK flags are applied through the activity options and not as a part of 6642 // the call to startIntentSender() to ensure that they are consistently applied to 6643 // both mutable and immutable PendingIntents. 6644 context.startIntentSender( 6645 pendingIntent.getIntentSender(), options.first, 6646 0, 0, 0, options.second.toBundle()); 6647 } catch (IntentSender.SendIntentException e) { 6648 Log.e(LOG_TAG, "Cannot send pending intent: ", e); 6649 return false; 6650 } catch (Exception e) { 6651 Log.e(LOG_TAG, "Cannot send pending intent due to unknown exception: ", e); 6652 return false; 6653 } 6654 return true; 6655 } 6656 6657 /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */ 6658 public static final class RemoteCollectionItems implements Parcelable { 6659 private final long[] mIds; 6660 private final RemoteViews[] mViews; 6661 private final boolean mHasStableIds; 6662 private final int mViewTypeCount; 6663 6664 private HierarchyRootData mHierarchyRootData; 6665 6666 RemoteCollectionItems( 6667 long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) { 6668 mIds = ids; 6669 mViews = views; 6670 mHasStableIds = hasStableIds; 6671 mViewTypeCount = viewTypeCount; 6672 if (ids.length != views.length) { 6673 throw new IllegalArgumentException( 6674 "RemoteCollectionItems has different number of ids and views"); 6675 } 6676 if (viewTypeCount < 1) { 6677 throw new IllegalArgumentException("View type count must be >= 1"); 6678 } 6679 int layoutIdCount = (int) Arrays.stream(views) 6680 .mapToInt(RemoteViews::getLayoutId) 6681 .distinct() 6682 .count(); 6683 if (layoutIdCount > viewTypeCount) { 6684 throw new IllegalArgumentException( 6685 "View type count is set to " + viewTypeCount + ", but the collection " 6686 + "contains " + layoutIdCount + " different layout ids"); 6687 } 6688 6689 // Until the collection items are attached to a parent, we configure the first item 6690 // to be the root of the others to share caches and save space during serialization. 6691 if (views.length > 0) { 6692 setHierarchyRootData(views[0].getHierarchyRootData()); 6693 views[0].mIsRoot = true; 6694 } 6695 } 6696 6697 RemoteCollectionItems(@NonNull Parcel in, @Nullable HierarchyRootData hierarchyRootData) { 6698 mHasStableIds = in.readBoolean(); 6699 mViewTypeCount = in.readInt(); 6700 int length = in.readInt(); 6701 mIds = new long[length]; 6702 in.readLongArray(mIds); 6703 6704 boolean attached = in.readBoolean(); 6705 mViews = new RemoteViews[length]; 6706 int firstChildIndex; 6707 if (attached) { 6708 if (hierarchyRootData == null) { 6709 throw new IllegalStateException("Cannot unparcel a RemoteCollectionItems that " 6710 + "was parceled as attached without providing data for a root " 6711 + "RemoteViews"); 6712 } 6713 mHierarchyRootData = hierarchyRootData; 6714 firstChildIndex = 0; 6715 } else { 6716 mViews[0] = new RemoteViews(in); 6717 mHierarchyRootData = mViews[0].getHierarchyRootData(); 6718 firstChildIndex = 1; 6719 } 6720 6721 for (int i = firstChildIndex; i < length; i++) { 6722 mViews[i] = new RemoteViews( 6723 in, 6724 mHierarchyRootData, 6725 /* info= */ null, 6726 /* depth= */ 0); 6727 } 6728 } 6729 6730 void setHierarchyRootData(@NonNull HierarchyRootData rootData) { 6731 mHierarchyRootData = rootData; 6732 for (RemoteViews view : mViews) { 6733 view.configureAsChild(rootData); 6734 } 6735 } 6736 6737 @Override 6738 public int describeContents() { 6739 return 0; 6740 } 6741 6742 @Override 6743 public void writeToParcel(@NonNull Parcel dest, int flags) { 6744 writeToParcel(dest, flags, /* attached= */ false); 6745 } 6746 6747 private void writeToParcel(@NonNull Parcel dest, int flags, boolean attached) { 6748 boolean prevAllowSquashing = dest.allowSquashing(); 6749 6750 dest.writeBoolean(mHasStableIds); 6751 dest.writeInt(mViewTypeCount); 6752 dest.writeInt(mIds.length); 6753 dest.writeLongArray(mIds); 6754 6755 if (attached && mHierarchyRootData == null) { 6756 throw new IllegalStateException("Cannot call writeToParcelAttached for a " 6757 + "RemoteCollectionItems without first calling setHierarchyRootData()"); 6758 } 6759 6760 // Write whether we parceled as attached or not. This allows cleaner validation and 6761 // proper error messaging when unparceling later. 6762 dest.writeBoolean(attached); 6763 boolean restoreRoot = false; 6764 if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) { 6765 // If we're writing unattached, temporarily set the first item as the root so that 6766 // the bitmap cache is written to the parcel. 6767 restoreRoot = true; 6768 mViews[0].mIsRoot = true; 6769 } 6770 6771 for (RemoteViews view : mViews) { 6772 view.writeToParcel(dest, flags); 6773 } 6774 6775 if (restoreRoot) mViews[0].mIsRoot = false; 6776 dest.restoreAllowSquashing(prevAllowSquashing); 6777 } 6778 6779 /** 6780 * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id 6781 * should be considered meaningful across collection updates. 6782 * 6783 * @return Id for the position. 6784 */ 6785 public long getItemId(int position) { 6786 return mIds[position]; 6787 } 6788 6789 /** 6790 * Returns the {@link RemoteViews} to display at {@code position}. 6791 * 6792 * @return RemoteViews for the position. 6793 */ 6794 @NonNull 6795 public RemoteViews getItemView(int position) { 6796 return mViews[position]; 6797 } 6798 6799 /** 6800 * Returns the number of elements in the collection. 6801 * 6802 * @return Count of items. 6803 */ 6804 public int getItemCount() { 6805 return mIds.length; 6806 } 6807 6808 /** 6809 * Returns the view type count for the collection when used in an adapter 6810 * 6811 * @return Count of view types for the collection when used in an adapter. 6812 * @see android.widget.Adapter#getViewTypeCount() 6813 */ 6814 public int getViewTypeCount() { 6815 return mViewTypeCount; 6816 } 6817 6818 /** 6819 * Indicates whether the item ids are stable across changes to the underlying data. 6820 * 6821 * @return True if the same id always refers to the same object. 6822 * @see android.widget.Adapter#hasStableIds() 6823 */ 6824 public boolean hasStableIds() { 6825 return mHasStableIds; 6826 } 6827 6828 @NonNull 6829 public static final Creator<RemoteCollectionItems> CREATOR = 6830 new Creator<RemoteCollectionItems>() { 6831 @NonNull 6832 @Override 6833 public RemoteCollectionItems createFromParcel(@NonNull Parcel source) { 6834 return new RemoteCollectionItems(source, /* hierarchyRoot= */ null); 6835 } 6836 6837 @NonNull 6838 @Override 6839 public RemoteCollectionItems[] newArray(int size) { 6840 return new RemoteCollectionItems[size]; 6841 } 6842 }; 6843 6844 /** Builder class for {@link RemoteCollectionItems} objects.*/ 6845 public static final class Builder { 6846 private final LongArray mIds = new LongArray(); 6847 private final List<RemoteViews> mViews = new ArrayList<>(); 6848 private boolean mHasStableIds; 6849 private int mViewTypeCount; 6850 6851 /** 6852 * Adds a {@link RemoteViews} to the collection. 6853 * 6854 * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to 6855 * indicate that ids are stable across changes to the collection. 6856 * @param view RemoteViews to display for the row. 6857 */ 6858 @NonNull 6859 // Covered by getItemId, getItemView, getItemCount. 6860 @SuppressLint("MissingGetterMatchingBuilder") 6861 public Builder addItem(long id, @NonNull RemoteViews view) { 6862 if (view == null) throw new NullPointerException(); 6863 if (view.hasMultipleLayouts()) { 6864 throw new IllegalArgumentException( 6865 "RemoteViews used in a RemoteCollectionItems cannot specify separate " 6866 + "layouts for orientations or sizes."); 6867 } 6868 mIds.add(id); 6869 mViews.add(view); 6870 return this; 6871 } 6872 6873 /** 6874 * Sets whether the item ids are stable across changes to the underlying data. 6875 * 6876 * @see android.widget.Adapter#hasStableIds() 6877 */ 6878 @NonNull 6879 public Builder setHasStableIds(boolean hasStableIds) { 6880 mHasStableIds = hasStableIds; 6881 return this; 6882 } 6883 6884 /** 6885 * Sets the view type count for the collection when used in an adapter. This can be set 6886 * to the maximum number of different layout ids that will be used by RemoteViews in 6887 * this collection. 6888 * 6889 * If this value is not set, then a value will be inferred from the provided items. As 6890 * a result, the adapter may need to be recreated when the list is updated with 6891 * previously unseen RemoteViews layouts for new items. 6892 * 6893 * @see android.widget.Adapter#getViewTypeCount() 6894 */ 6895 @NonNull 6896 public Builder setViewTypeCount(int viewTypeCount) { 6897 mViewTypeCount = viewTypeCount; 6898 return this; 6899 } 6900 6901 /** Creates the {@link RemoteCollectionItems} defined by this builder. */ 6902 @NonNull 6903 public RemoteCollectionItems build() { 6904 if (mViewTypeCount < 1) { 6905 // If a view type count wasn't specified, set it to be the number of distinct 6906 // layout ids used in the items. 6907 mViewTypeCount = (int) mViews.stream() 6908 .mapToInt(RemoteViews::getLayoutId) 6909 .distinct() 6910 .count(); 6911 } 6912 return new RemoteCollectionItems( 6913 mIds.toArray(), 6914 mViews.toArray(new RemoteViews[0]), 6915 mHasStableIds, 6916 Math.max(mViewTypeCount, 1)); 6917 } 6918 } 6919 } 6920 6921 /** 6922 * Get the ID of the top-level view of the XML layout, if set using 6923 * {@link RemoteViews#RemoteViews(String, int, int)}. 6924 */ 6925 public @IdRes int getViewId() { 6926 return mViewId; 6927 } 6928 6929 /** 6930 * Set the provider instance ID. 6931 * 6932 * This should only be used by {@link com.android.server.appwidget.AppWidgetService}. 6933 * @hide 6934 */ 6935 public void setProviderInstanceId(long id) { 6936 mProviderInstanceId = id; 6937 } 6938 6939 /** 6940 * Get the provider instance id. 6941 * 6942 * This should uniquely identifies {@link RemoteViews} coming from a given App Widget 6943 * Provider. This changes each time the App Widget provider update the {@link RemoteViews} of 6944 * its widget. Returns -1 if the {@link RemoteViews} doesn't come from an App Widget provider. 6945 * @hide 6946 */ 6947 public long getProviderInstanceId() { 6948 return mProviderInstanceId; 6949 } 6950 6951 /** 6952 * Identify the child of this {@link RemoteViews}, or 0 if this is not a child. 6953 * 6954 * The returned value is always a small integer, currently between 0 and 17. 6955 */ 6956 private int getChildId(@NonNull RemoteViews child) { 6957 if (child == this) { 6958 return 0; 6959 } 6960 if (hasSizedRemoteViews()) { 6961 for (int i = 0; i < mSizedRemoteViews.size(); i++) { 6962 if (mSizedRemoteViews.get(i) == child) { 6963 return i + 1; 6964 } 6965 } 6966 } 6967 if (hasLandscapeAndPortraitLayouts()) { 6968 if (mLandscape == child) { 6969 return 1; 6970 } else if (mPortrait == child) { 6971 return 2; 6972 } 6973 } 6974 // This is not a child of this RemoteViews. 6975 return 0; 6976 } 6977 6978 /** 6979 * Identify uniquely this RemoteViews, or returns -1 if not possible. 6980 * 6981 * @param parent If the {@link RemoteViews} is not a root {@link RemoteViews}, this should be 6982 * the parent that contains it. 6983 * 6984 * @hide 6985 */ 6986 public long computeUniqueId(@Nullable RemoteViews parent) { 6987 if (mIsRoot) { 6988 long viewId = getProviderInstanceId(); 6989 if (viewId != -1) { 6990 viewId <<= 8; 6991 } 6992 return viewId; 6993 } 6994 if (parent == null) { 6995 return -1; 6996 } 6997 long viewId = parent.getProviderInstanceId(); 6998 if (viewId == -1) { 6999 return -1; 7000 } 7001 int childId = parent.getChildId(this); 7002 if (childId == -1) { 7003 return -1; 7004 } 7005 viewId <<= 8; 7006 viewId |= childId; 7007 return viewId; 7008 } 7009 7010 @Nullable 7011 private static Pair<String, Integer> getPackageUserKey(@Nullable ApplicationInfo info) { 7012 if (info == null || info.packageName == null) return null; 7013 return Pair.create(info.packageName, info.uid); 7014 } 7015 7016 private HierarchyRootData getHierarchyRootData() { 7017 return new HierarchyRootData(mBitmapCache, mApplicationInfoCache, mClassCookies); 7018 } 7019 7020 private static final class HierarchyRootData { 7021 final BitmapCache mBitmapCache; 7022 final ApplicationInfoCache mApplicationInfoCache; 7023 final Map<Class, Object> mClassCookies; 7024 7025 HierarchyRootData( 7026 BitmapCache bitmapCache, 7027 ApplicationInfoCache applicationInfoCache, 7028 Map<Class, Object> classCookies) { 7029 mBitmapCache = bitmapCache; 7030 mApplicationInfoCache = applicationInfoCache; 7031 mClassCookies = classCookies; 7032 } 7033 } 7034 } 7035