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