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