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