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