1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import static android.appwidget.flags.Flags.FLAG_DRAW_DATA_PARCEL; 20 import static android.appwidget.flags.Flags.FLAG_REMOTE_VIEWS_PROTO; 21 import static android.appwidget.flags.Flags.drawDataParcel; 22 import static android.appwidget.flags.Flags.remoteAdapterConversion; 23 import static android.content.res.Flags.FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO; 24 import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8; 25 import static android.util.proto.ProtoInputStream.NO_MORE_FIELDS; 26 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR; 27 28 import android.annotation.AttrRes; 29 import android.annotation.ColorInt; 30 import android.annotation.ColorRes; 31 import android.annotation.DimenRes; 32 import android.annotation.DrawableRes; 33 import android.annotation.FlaggedApi; 34 import android.annotation.IdRes; 35 import android.annotation.IntDef; 36 import android.annotation.LayoutRes; 37 import android.annotation.NonNull; 38 import android.annotation.Nullable; 39 import android.annotation.Px; 40 import android.annotation.StringRes; 41 import android.annotation.StyleRes; 42 import android.annotation.SuppressLint; 43 import android.app.Activity; 44 import android.app.ActivityOptions; 45 import android.app.ActivityThread; 46 import android.app.Application; 47 import android.app.PendingIntent; 48 import android.app.RemoteInput; 49 import android.appwidget.AppWidgetHostView; 50 import android.appwidget.AppWidgetManager.ServiceCollectionCache; 51 import android.appwidget.flags.Flags; 52 import android.compat.annotation.UnsupportedAppUsage; 53 import android.content.ComponentName; 54 import android.content.Context; 55 import android.content.ContextWrapper; 56 import android.content.Intent; 57 import android.content.IntentSender; 58 import android.content.om.FabricatedOverlay; 59 import android.content.om.OverlayInfo; 60 import android.content.om.OverlayManager; 61 import android.content.om.OverlayManagerTransaction; 62 import android.content.pm.ApplicationInfo; 63 import android.content.pm.PackageManager.NameNotFoundException; 64 import android.content.res.ColorStateList; 65 import android.content.res.Configuration; 66 import android.content.res.Resources; 67 import android.content.res.TypedArray; 68 import android.content.res.loader.ResourcesLoader; 69 import android.content.res.loader.ResourcesProvider; 70 import android.graphics.Bitmap; 71 import android.graphics.BitmapFactory; 72 import android.graphics.BlendMode; 73 import android.graphics.Outline; 74 import android.graphics.PorterDuff; 75 import android.graphics.Rect; 76 import android.graphics.drawable.Drawable; 77 import android.graphics.drawable.Icon; 78 import android.graphics.drawable.RippleDrawable; 79 import android.net.Uri; 80 import android.os.AsyncTask; 81 import android.os.Binder; 82 import android.os.Build; 83 import android.os.Bundle; 84 import android.os.CancellationSignal; 85 import android.os.Parcel; 86 import android.os.ParcelFileDescriptor; 87 import android.os.Parcelable; 88 import android.os.Process; 89 import android.os.RemoteException; 90 import android.os.StrictMode; 91 import android.os.Trace; 92 import android.os.UserHandle; 93 import android.system.Os; 94 import android.text.TextUtils; 95 import android.util.ArrayMap; 96 import android.util.DisplayMetrics; 97 import android.util.IntArray; 98 import android.util.Log; 99 import android.util.LongArray; 100 import android.util.LongSparseArray; 101 import android.util.Pair; 102 import android.util.SizeF; 103 import android.util.SparseArray; 104 import android.util.SparseIntArray; 105 import android.util.TypedValue; 106 import android.util.TypedValue.ComplexDimensionUnit; 107 import android.util.proto.ProtoInputStream; 108 import android.util.proto.ProtoOutputStream; 109 import android.util.proto.ProtoStream; 110 import android.util.proto.ProtoUtils; 111 import android.view.ContextThemeWrapper; 112 import android.view.LayoutInflater; 113 import android.view.LayoutInflater.Filter; 114 import android.view.MotionEvent; 115 import android.view.RemotableViewMethod; 116 import android.view.View; 117 import android.view.ViewGroup; 118 import android.view.ViewGroup.MarginLayoutParams; 119 import android.view.ViewManager; 120 import android.view.ViewOutlineProvider; 121 import android.view.ViewParent; 122 import android.view.ViewStub; 123 import android.widget.AdapterView.OnItemClickListener; 124 import android.widget.CompoundButton.OnCheckedChangeListener; 125 126 import com.android.internal.R; 127 import com.android.internal.util.Preconditions; 128 import com.android.internal.widget.IRemoteViewsFactory; 129 import com.android.internal.widget.remotecompose.core.CoreDocument; 130 import com.android.internal.widget.remotecompose.core.operations.Theme; 131 import com.android.internal.widget.remotecompose.player.RemoteComposeDocument; 132 import com.android.internal.widget.remotecompose.player.RemoteComposePlayer; 133 134 import java.io.ByteArrayInputStream; 135 import java.io.ByteArrayOutputStream; 136 import java.io.FileDescriptor; 137 import java.io.FileOutputStream; 138 import java.io.IOException; 139 import java.io.InputStream; 140 import java.io.OutputStream; 141 import java.lang.annotation.ElementType; 142 import java.lang.annotation.Retention; 143 import java.lang.annotation.RetentionPolicy; 144 import java.lang.annotation.Target; 145 import java.lang.invoke.MethodHandle; 146 import java.lang.invoke.MethodHandles; 147 import java.lang.invoke.MethodType; 148 import java.lang.reflect.Method; 149 import java.util.ArrayDeque; 150 import java.util.ArrayList; 151 import java.util.Arrays; 152 import java.util.HashMap; 153 import java.util.HashSet; 154 import java.util.Iterator; 155 import java.util.List; 156 import java.util.Map; 157 import java.util.Objects; 158 import java.util.Set; 159 import java.util.concurrent.CompletableFuture; 160 import java.util.concurrent.Executor; 161 import java.util.concurrent.TimeUnit; 162 import java.util.concurrent.atomic.AtomicLong; 163 import java.util.function.Consumer; 164 import java.util.function.Function; 165 import java.util.function.Predicate; 166 167 /** 168 * A class that describes a view hierarchy that can be displayed in 169 * another process. The hierarchy is inflated from a layout resource 170 * file, and this class provides some basic operations for modifying 171 * the content of the inflated hierarchy. 172 * 173 * <p>{@code RemoteViews} is limited to support for the following layouts:</p> 174 * <ul> 175 * <li>{@link android.widget.AdapterViewFlipper}</li> 176 * <li>{@link android.widget.FrameLayout}</li> 177 * <li>{@link android.widget.GridLayout}</li> 178 * <li>{@link android.widget.GridView}</li> 179 * <li>{@link android.widget.LinearLayout}</li> 180 * <li>{@link android.widget.ListView}</li> 181 * <li>{@link android.widget.RelativeLayout}</li> 182 * <li>{@link android.widget.StackView}</li> 183 * <li>{@link android.widget.ViewFlipper}</li> 184 * </ul> 185 * <p>And the following widgets:</p> 186 * <ul> 187 * <li>{@link android.widget.AnalogClock}</li> 188 * <li>{@link android.widget.Button}</li> 189 * <li>{@link android.widget.Chronometer}</li> 190 * <li>{@link android.widget.ImageButton}</li> 191 * <li>{@link android.widget.ImageView}</li> 192 * <li>{@link android.widget.ProgressBar}</li> 193 * <li>{@link android.widget.TextClock}</li> 194 * <li>{@link android.widget.TextView}</li> 195 * </ul> 196 * <p>As of API 31, the following widgets and layouts may also be used:</p> 197 * <ul> 198 * <li>{@link android.widget.CheckBox}</li> 199 * <li>{@link android.widget.RadioButton}</li> 200 * <li>{@link android.widget.RadioGroup}</li> 201 * <li>{@link android.widget.Switch}</li> 202 * </ul> 203 * <p>Descendants of these classes are not supported.</p> 204 */ 205 public class RemoteViews implements Parcelable, Filter { 206 207 private static final String LOG_TAG = "RemoteViews"; 208 209 /** The intent extra for whether the view whose checked state changed is currently checked. */ 210 public static final String EXTRA_CHECKED = "android.widget.extra.CHECKED"; 211 212 /** 213 * The intent extra that contains the appWidgetId. 214 * @hide 215 */ 216 static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; 217 218 /** 219 * The intent extra that contains {@code true} if inflating as dak text theme. 220 * @hide 221 */ 222 static final String EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND = "remoteAdapterOnLightBackground"; 223 224 /** 225 * The intent extra that contains the bounds for all shared elements. 226 */ 227 public static final String EXTRA_SHARED_ELEMENT_BOUNDS = 228 "android.widget.extra.SHARED_ELEMENT_BOUNDS"; 229 230 /** 231 * Maximum depth of nested views calls from {@link #addView(int, RemoteViews)} and 232 * {@link #RemoteViews(RemoteViews, RemoteViews)}. 233 */ 234 private static final int MAX_NESTED_VIEWS = 10; 235 236 /** 237 * Maximum number of RemoteViews that can be specified in constructor. 238 */ 239 private static final int MAX_INIT_VIEW_COUNT = 16; 240 241 // The unique identifiers for each custom {@link Action}. 242 private static final int SET_ON_CLICK_RESPONSE_TAG = 1; 243 private static final int REFLECTION_ACTION_TAG = 2; 244 private static final int SET_DRAWABLE_TINT_TAG = 3; 245 private static final int VIEW_GROUP_ACTION_ADD_TAG = 4; 246 private static final int VIEW_CONTENT_NAVIGATION_TAG = 5; 247 private static final int SET_EMPTY_VIEW_ACTION_TAG = 6; 248 private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7; 249 private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8; 250 private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10; 251 private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11; 252 private static final int BITMAP_REFLECTION_ACTION_TAG = 12; 253 private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13; 254 private static final int VIEW_PADDING_ACTION_TAG = 14; 255 private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18; 256 private static final int LAYOUT_PARAM_ACTION_TAG = 19; 257 private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21; 258 private static final int SET_INT_TAG_TAG = 22; 259 private static final int REMOVE_FROM_PARENT_ACTION_TAG = 23; 260 private static final int RESOURCE_REFLECTION_ACTION_TAG = 24; 261 private static final int COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG = 25; 262 private static final int SET_COMPOUND_BUTTON_CHECKED_TAG = 26; 263 private static final int SET_RADIO_GROUP_CHECKED = 27; 264 private static final int SET_VIEW_OUTLINE_RADIUS_TAG = 28; 265 private static final int SET_ON_CHECKED_CHANGE_RESPONSE_TAG = 29; 266 private static final int NIGHT_MODE_REFLECTION_ACTION_TAG = 30; 267 private static final int SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG = 31; 268 private static final int ATTRIBUTE_REFLECTION_ACTION_TAG = 32; 269 private static final int SET_REMOTE_ADAPTER_TAG = 33; 270 private static final int SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG = 34; 271 private static final int SET_DRAW_INSTRUCTION_TAG = 35; 272 273 /** @hide **/ 274 @IntDef(prefix = "MARGIN_", value = { 275 MARGIN_LEFT, 276 MARGIN_TOP, 277 MARGIN_RIGHT, 278 MARGIN_BOTTOM, 279 MARGIN_START, 280 MARGIN_END 281 }) 282 @Retention(RetentionPolicy.SOURCE) 283 public @interface MarginType {} 284 /** The value will apply to the marginLeft. */ 285 public static final int MARGIN_LEFT = 0; 286 /** The value will apply to the marginTop. */ 287 public static final int MARGIN_TOP = 1; 288 /** The value will apply to the marginRight. */ 289 public static final int MARGIN_RIGHT = 2; 290 /** The value will apply to the marginBottom. */ 291 public static final int MARGIN_BOTTOM = 3; 292 /** The value will apply to the marginStart. */ 293 public static final int MARGIN_START = 4; 294 /** The value will apply to the marginEnd. */ 295 public static final int MARGIN_END = 5; 296 297 @IntDef(prefix = "VALUE_TYPE_", value = { 298 VALUE_TYPE_RAW, 299 VALUE_TYPE_COMPLEX_UNIT, 300 VALUE_TYPE_RESOURCE, 301 VALUE_TYPE_ATTRIBUTE 302 }) 303 @Retention(RetentionPolicy.SOURCE) 304 @interface ValueType {} 305 static final int VALUE_TYPE_RAW = 1; 306 static final int VALUE_TYPE_COMPLEX_UNIT = 2; 307 static final int VALUE_TYPE_RESOURCE = 3; 308 static final int VALUE_TYPE_ATTRIBUTE = 4; 309 310 /** @hide **/ 311 @IntDef(flag = true, value = { 312 FLAG_REAPPLY_DISALLOWED, 313 FLAG_WIDGET_IS_COLLECTION_CHILD, 314 FLAG_USE_LIGHT_BACKGROUND_LAYOUT 315 }) 316 @Retention(RetentionPolicy.SOURCE) 317 public @interface ApplyFlags {} 318 /** 319 * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify 320 * the layout in a way that isn't recoverable, since views are being removed. 321 * @hide 322 */ 323 public static final int FLAG_REAPPLY_DISALLOWED = 1; 324 /** 325 * This flag indicates whether this RemoteViews object is being created from a 326 * RemoteViewsService for use as a child of a widget collection. This flag is used 327 * to determine whether or not certain features are available, in particular, 328 * setting on click extras and setting on click pending intents. The former is enabled, 329 * and the latter disabled when this flag is true. 330 * @hide 331 */ 332 public static final int FLAG_WIDGET_IS_COLLECTION_CHILD = 2; 333 /** 334 * When this flag is set, the views is inflated with {@link #mLightBackgroundLayoutId} instead 335 * of {link #mLayoutId} 336 * @hide 337 */ 338 public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4; 339 340 /** 341 * This mask determines which flags are propagated to nested RemoteViews (either added by 342 * addView, or set as portrait/landscape/sized RemoteViews). 343 */ 344 static final int FLAG_MASK_TO_PROPAGATE = 345 FLAG_WIDGET_IS_COLLECTION_CHILD | FLAG_USE_LIGHT_BACKGROUND_LAYOUT; 346 347 /** 348 * A ReadWriteHelper which has the same behavior as ReadWriteHelper.DEFAULT, but which is 349 * intentionally a different instance in order to trick Bundle reader so that it doesn't allow 350 * lazy initialization. 351 */ 352 private static final Parcel.ReadWriteHelper ALTERNATIVE_DEFAULT = new Parcel.ReadWriteHelper(); 353 354 /** 355 * Used to restrict the views which can be inflated 356 * 357 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 358 */ 359 private static final LayoutInflater.Filter INFLATER_FILTER = 360 (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); 361 362 /** 363 * The maximum waiting time for remote adapter conversion in milliseconds 364 * 365 * @hide 366 */ 367 private static final int MAX_ADAPTER_CONVERSION_WAITING_TIME_MS = 20_000; 368 369 /** 370 * Application that hosts the remote views. 371 * 372 * @hide 373 */ 374 @UnsupportedAppUsage 375 public ApplicationInfo mApplication; 376 377 /** 378 * The resource ID of the layout file. (Added to the parcel) 379 */ 380 @UnsupportedAppUsage 381 private int mLayoutId; 382 383 /** 384 * The resource ID of the layout file in dark text mode. (Added to the parcel) 385 */ 386 private int mLightBackgroundLayoutId = 0; 387 388 /** 389 * An array of actions to perform on the view tree once it has been 390 * inflated 391 */ 392 @UnsupportedAppUsage 393 private ArrayList<Action> mActions; 394 395 /** 396 * Maps bitmaps to unique indicies to avoid Bitmap duplication. 397 */ 398 @UnsupportedAppUsage 399 private BitmapCache mBitmapCache = new BitmapCache(); 400 401 /** 402 * Maps Intent ID to RemoteCollectionItems to avoid duplicate items 403 */ 404 private @NonNull RemoteCollectionCache mCollectionCache = new RemoteCollectionCache(); 405 406 /** Cache of ApplicationInfos used by collection items. */ 407 private ApplicationInfoCache mApplicationInfoCache = new ApplicationInfoCache(); 408 409 /** 410 * Indicates whether or not this RemoteViews object is contained as a child of any other 411 * RemoteViews. 412 */ 413 private boolean mIsRoot = true; 414 415 /** 416 * Constants to whether or not this RemoteViews is composed of a landscape and portrait 417 * RemoteViews. 418 */ 419 private static final int MODE_NORMAL = 0; 420 private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; 421 private static final int MODE_HAS_SIZED_REMOTEVIEWS = 2; 422 423 /** 424 * Used in conjunction with the special constructor 425 * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait 426 * RemoteViews. 427 */ 428 private RemoteViews mLandscape = null; 429 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 430 private RemoteViews mPortrait = null; 431 /** 432 * List of RemoteViews with their ideal size. There must be at least two if the map is not null. 433 * 434 * The smallest remote view is always the last element in the list. 435 */ 436 private List<RemoteViews> mSizedRemoteViews = null; 437 438 /** 439 * Ideal size for this RemoteViews. 440 * 441 * Only to be used on children views used in a {@link RemoteViews} with 442 * {@link RemoteViews#hasSizedRemoteViews()}. 443 */ 444 private SizeF mIdealSize = null; 445 446 @ApplyFlags 447 private int mApplyFlags = 0; 448 449 /** 450 * Id to use to override the ID of the top-level view in this RemoteViews. 451 * 452 * Only used if this RemoteViews is defined from a XML layout value. 453 */ 454 private int mViewId = View.NO_ID; 455 456 /** 457 * Id used to uniquely identify a {@link RemoteViews} instance coming from a given provider. 458 */ 459 private long mProviderInstanceId = -1; 460 461 /** Class cookies of the Parcel this instance was read from. */ 462 private Map<Class, Object> mClassCookies; 463 464 /** 465 * {@link LayoutInflater.Factory2} which will be passed into a {@link LayoutInflater} instance 466 * used by this class. 467 */ 468 @Nullable 469 private LayoutInflater.Factory2 mLayoutInflaterFactory2; 470 471 /** 472 * Indicates whether this {@link RemoteViews} was instantiated with a {@link DrawInstructions} 473 * object. {@link DrawInstructions} serves as an alternative protocol for the host process 474 * to render. 475 */ 476 private boolean mHasDrawInstructions; 477 478 @Nullable 479 private SparseArray<PendingIntent> mPendingIntentTemplate; 480 481 @Nullable 482 private SparseArray<Intent> mFillInIntent; 483 484 private static final InteractionHandler DEFAULT_INTERACTION_HANDLER = 485 (view, pendingIntent, response) -> 486 startPendingIntent(view, pendingIntent, response.getLaunchOptions(view)); 487 488 private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); 489 490 /** 491 * This key is used to perform lookups in sMethods without causing allocations. 492 */ 493 private static final MethodKey sLookupKey = new MethodKey(); 494 495 /** 496 * @hide 497 */ setRemoteInputs(@dRes int viewId, RemoteInput[] remoteInputs)498 public void setRemoteInputs(@IdRes int viewId, RemoteInput[] remoteInputs) { 499 mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); 500 } 501 502 /** 503 * Sets {@link LayoutInflater.Factory2} to be passed into {@link LayoutInflater} used 504 * by this class instance. It has to be set before the views are inflated to have any effect. 505 * 506 * The factory callbacks will be called on the background thread so the implementation needs 507 * to be thread safe. 508 * 509 * @hide 510 */ setLayoutInflaterFactory(@ullable LayoutInflater.Factory2 factory)511 public void setLayoutInflaterFactory(@Nullable LayoutInflater.Factory2 factory) { 512 mLayoutInflaterFactory2 = factory; 513 } 514 515 /** 516 * Returns currently set {@link LayoutInflater.Factory2}. 517 * 518 * @hide 519 */ 520 @Nullable getLayoutInflaterFactory()521 public LayoutInflater.Factory2 getLayoutInflaterFactory() { 522 return mLayoutInflaterFactory2; 523 } 524 525 /** 526 * Reduces all images and ensures that they are all below the given sizes. 527 * 528 * @param maxWidth the maximum width allowed 529 * @param maxHeight the maximum height allowed 530 * 531 * @hide 532 */ reduceImageSizes(int maxWidth, int maxHeight)533 public void reduceImageSizes(int maxWidth, int maxHeight) { 534 ArrayList<Bitmap> cache = mBitmapCache.mBitmaps; 535 for (int i = 0; i < cache.size(); i++) { 536 Bitmap bitmap = cache.get(i); 537 cache.set(i, Icon.scaleDownIfNecessary(bitmap, maxWidth, maxHeight)); 538 } 539 } 540 541 /** 542 * Sets an integer tag to the view. 543 * 544 * @hide 545 */ setIntTag(@dRes int viewId, @IdRes int key, int tag)546 public void setIntTag(@IdRes int viewId, @IdRes int key, int tag) { 547 addAction(new SetIntTagAction(viewId, key, tag)); 548 } 549 550 /** 551 * Set a view tag associating a View with an ID to be used for widget interaction usage events 552 * ({@link android.app.usage.UsageEvents.Event}). When this RemoteViews is applied to a bound 553 * widget, any clicks or scrolls on the tagged view will be reported to 554 * {@link android.app.usage.UsageStatsManager} using this tag. 555 * 556 * @param viewId ID of the View whose tag will be set 557 * @param tag The integer tag to use for the event 558 * 559 * @see android.appwidget.AppWidgetManager#EVENT_TYPE_WIDGET_INTERACTION 560 * @see android.appwidget.AppWidgetManager#EXTRA_EVENT_CLICKED_VIEWS 561 * @see android.appwidget.AppWidgetManager#EXTRA_EVENT_SCROLLED_VIEWS 562 * @see android.app.usage.UsageStatsManager#queryEventsForSelf 563 */ 564 @FlaggedApi(Flags.FLAG_ENGAGEMENT_METRICS) setUsageEventTag(@dRes int viewId, int tag)565 public void setUsageEventTag(@IdRes int viewId, int tag) { 566 addAction(new SetIntTagAction(viewId, com.android.internal.R.id.remoteViewsMetricsId, tag)); 567 } 568 569 /** 570 * Set that it is disallowed to reapply another remoteview with the same layout as this view. 571 * This should be done if an action is destroying the view tree of the base layout. 572 * 573 * @hide 574 */ addFlags(@pplyFlags int flags)575 public void addFlags(@ApplyFlags int flags) { 576 mApplyFlags = mApplyFlags | flags; 577 578 int flagsToPropagate = flags & FLAG_MASK_TO_PROPAGATE; 579 if (flagsToPropagate != 0) { 580 if (hasSizedRemoteViews()) { 581 for (RemoteViews remoteView : mSizedRemoteViews) { 582 remoteView.addFlags(flagsToPropagate); 583 } 584 } else if (hasLandscapeAndPortraitLayouts()) { 585 mLandscape.addFlags(flagsToPropagate); 586 mPortrait.addFlags(flagsToPropagate); 587 } 588 } 589 } 590 591 /** 592 * @hide 593 */ hasFlags(@pplyFlags int flag)594 public boolean hasFlags(@ApplyFlags int flag) { 595 return (mApplyFlags & flag) == flag; 596 } 597 598 /** 599 * Stores information related to reflection method lookup. 600 */ 601 static class MethodKey { 602 public Class targetClass; 603 public Class paramClass; 604 public String methodName; 605 606 @Override equals(@ullable Object o)607 public boolean equals(@Nullable Object o) { 608 if (!(o instanceof MethodKey)) { 609 return false; 610 } 611 MethodKey p = (MethodKey) o; 612 return Objects.equals(p.targetClass, targetClass) 613 && Objects.equals(p.paramClass, paramClass) 614 && Objects.equals(p.methodName, methodName); 615 } 616 617 @Override hashCode()618 public int hashCode() { 619 return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) 620 ^ Objects.hashCode(methodName); 621 } 622 set(Class targetClass, Class paramClass, String methodName)623 public void set(Class targetClass, Class paramClass, String methodName) { 624 this.targetClass = targetClass; 625 this.paramClass = paramClass; 626 this.methodName = methodName; 627 } 628 } 629 630 631 /** 632 * Stores information related to reflection method lookup result. 633 */ 634 static class MethodArgs { 635 public MethodHandle syncMethod; 636 public MethodHandle asyncMethod; 637 public String asyncMethodName; 638 } 639 640 /** 641 * This annotation indicates that a subclass of View is allowed to be used 642 * with the {@link RemoteViews} mechanism. 643 */ 644 @Target({ ElementType.TYPE }) 645 @Retention(RetentionPolicy.RUNTIME) 646 public @interface RemoteView { 647 } 648 649 /** 650 * Exception to send when something goes wrong executing an action 651 * 652 */ 653 public static class ActionException extends RuntimeException { ActionException(Exception ex)654 public ActionException(Exception ex) { 655 super(ex); 656 } ActionException(String message)657 public ActionException(String message) { 658 super(message); 659 } 660 /** 661 * @hide 662 */ ActionException(Throwable t)663 public ActionException(Throwable t) { 664 super(t); 665 } 666 } 667 668 /** 669 * Handler for view interactions (such as clicks) within a RemoteViews. 670 * 671 * @hide 672 */ 673 public interface InteractionHandler { 674 /** 675 * Invoked when the user performs an interaction on the View. 676 * 677 * @param view the View with which the user interacted 678 * @param pendingIntent the base PendingIntent associated with the view 679 * @param response the response to the interaction, which knows how to fill in the 680 * attached PendingIntent 681 * 682 * @hide 683 */ onInteraction( View view, PendingIntent pendingIntent, RemoteResponse response)684 boolean onInteraction( 685 View view, 686 PendingIntent pendingIntent, 687 RemoteResponse response); 688 689 /** 690 * Invoked when an AbsListView is scrolled. 691 * @param view view that was scrolled 692 * 693 * @hide 694 */ onScroll(@onNull AbsListView view)695 default void onScroll(@NonNull AbsListView view) {} 696 } 697 698 /** 699 * Base class for all actions that can be performed on an 700 * inflated view. 701 * 702 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 703 */ 704 private abstract static class Action { 705 @IdRes 706 @UnsupportedAppUsage 707 int mViewId; 708 apply(View root, ViewGroup rootParent, ActionApplyParams params)709 public abstract void apply(View root, ViewGroup rootParent, ActionApplyParams params) 710 throws ActionException; 711 712 public static final int MERGE_REPLACE = 0; 713 public static final int MERGE_APPEND = 1; 714 public static final int MERGE_IGNORE = 2; 715 setHierarchyRootData(HierarchyRootData root)716 public void setHierarchyRootData(HierarchyRootData root) { 717 // Do nothing 718 } 719 720 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) mergeBehavior()721 public int mergeBehavior() { 722 return MERGE_REPLACE; 723 } 724 getActionTag()725 public abstract int getActionTag(); 726 getUniqueKey()727 public String getUniqueKey() { 728 return (getActionTag() + "_" + mViewId); 729 } 730 731 /** 732 * This is called on the background thread. It should perform any non-ui computations 733 * and return the final action which will run on the UI thread. 734 * Override this if some of the tasks can be performed async. 735 */ initActionAsync(ViewTree root, ViewGroup rootParent, ActionApplyParams params)736 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 737 ActionApplyParams params) { 738 return this; 739 } 740 prefersAsyncApply()741 public boolean prefersAsyncApply() { 742 return false; 743 } 744 745 /** See {@link RemoteViews#visitUris(Consumer)}. **/ visitUris(@onNull Consumer<Uri> visitor)746 public void visitUris(@NonNull Consumer<Uri> visitor) { 747 // Nothing to visit by default. 748 } 749 750 /** See {@link RemoteViews#visitIcons(Consumer)}. **/ visitIcons(@onNull Consumer<Icon> visitor)751 public void visitIcons(@NonNull Consumer<Icon> visitor) { 752 // Nothing to visit by default. 753 } 754 writeToParcel(Parcel dest, int flags)755 public abstract void writeToParcel(Parcel dest, int flags); 756 757 /** 758 * Override to return true if this Action can be serialized to Protobuf, and implement 759 * writeToProto / createFromProto. 760 * 761 * If this returns false, then the action will be omitted from RemoteViews previews created 762 * with createPreviewFromProto / writePreviewToProto. 763 * 764 * Because Parcelables should not be serialized to disk, any action that contains an Intent, 765 * PendingIntent, or Bundle should return false here. 766 */ canWriteToProto()767 public boolean canWriteToProto() { 768 return false; 769 } 770 writeToProto(ProtoOutputStream out, Context context, Resources appResources)771 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 772 throw new UnsupportedOperationException(); 773 } 774 } 775 776 /** 777 * Action class used during async inflation of RemoteViews. Subclasses are not parcelable. 778 */ 779 private abstract static class RuntimeAction extends Action { 780 @Override getActionTag()781 public final int getActionTag() { 782 return 0; 783 } 784 785 @Override writeToParcel(Parcel dest, int flags)786 public final void writeToParcel(Parcel dest, int flags) { 787 throw new UnsupportedOperationException(); 788 } 789 } 790 791 // Constant used during async execution. It is not parcelable. 792 private static final Action ACTION_NOOP = new RuntimeAction() { 793 @Override 794 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { } 795 }; 796 797 /** 798 * Merges the passed RemoteViews actions with this RemoteViews actions according to 799 * action-specific merge rules. 800 * 801 * @param newRv 802 * 803 * @hide 804 */ 805 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) mergeRemoteViews(RemoteViews newRv)806 public void mergeRemoteViews(RemoteViews newRv) { 807 if (newRv == null) return; 808 // We first copy the new RemoteViews, as the process of merging modifies the way the actions 809 // reference the bitmap cache. We don't want to modify the object as it may need to 810 // be merged and applied multiple times. 811 RemoteViews copy = new RemoteViews(newRv); 812 813 HashMap<String, Action> map = new HashMap<String, Action>(); 814 if (mActions == null) { 815 mActions = new ArrayList<Action>(); 816 } 817 818 int count = mActions.size(); 819 for (int i = 0; i < count; i++) { 820 Action a = mActions.get(i); 821 map.put(a.getUniqueKey(), a); 822 } 823 824 ArrayList<Action> newActions = copy.mActions; 825 if (newActions == null) return; 826 count = newActions.size(); 827 for (int i = 0; i < count; i++) { 828 Action a = newActions.get(i); 829 String key = newActions.get(i).getUniqueKey(); 830 int mergeBehavior = newActions.get(i).mergeBehavior(); 831 if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) { 832 mActions.remove(map.get(key)); 833 map.remove(key); 834 } 835 836 // If the merge behavior is ignore, we don't bother keeping the extra action 837 if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) { 838 mActions.add(a); 839 } 840 } 841 842 // Because pruning can remove the need for bitmaps, we reconstruct the caches. 843 reconstructCaches(); 844 } 845 846 /** 847 * Return {@code true} only if this {@code RemoteViews} is a legacy list widget that uses 848 * {@code Intent} for inflating child entries. 849 * 850 * @hide 851 */ isLegacyListRemoteViews()852 public boolean isLegacyListRemoteViews() { 853 return mCollectionCache.mIdToUriMapping.size() > 0; 854 } 855 856 /** 857 * Note all {@link Uri} that are referenced internally, with the expectation that Uri permission 858 * grants will need to be issued to ensure the recipient of this object is able to render its 859 * contents. 860 * See b/281044385 for more context and examples about what happens when this isn't done 861 * correctly. 862 * 863 * @hide 864 */ visitUris(@onNull Consumer<Uri> visitor)865 public void visitUris(@NonNull Consumer<Uri> visitor) { 866 if (mActions != null) { 867 for (int i = 0; i < mActions.size(); i++) { 868 mActions.get(i).visitUris(visitor); 869 } 870 } 871 if (mSizedRemoteViews != null) { 872 for (int i = 0; i < mSizedRemoteViews.size(); i++) { 873 mSizedRemoteViews.get(i).visitUris(visitor); 874 } 875 } 876 if (mLandscape != null) { 877 mLandscape.visitUris(visitor); 878 } 879 if (mPortrait != null) { 880 mPortrait.visitUris(visitor); 881 } 882 } 883 884 /** 885 * Note all {@link Icon} that are referenced internally. 886 * @hide 887 */ visitIcons(@onNull Consumer<Icon> visitor)888 public void visitIcons(@NonNull Consumer<Icon> visitor) { 889 if (mActions != null) { 890 for (int i = 0; i < mActions.size(); i++) { 891 mActions.get(i).visitIcons(visitor); 892 } 893 } 894 if (mSizedRemoteViews != null) { 895 for (int i = 0; i < mSizedRemoteViews.size(); i++) { 896 mSizedRemoteViews.get(i).visitIcons(visitor); 897 } 898 } 899 if (mLandscape != null) { 900 mLandscape.visitIcons(visitor); 901 } 902 if (mPortrait != null) { 903 mPortrait.visitIcons(visitor); 904 } 905 } 906 907 /** 908 * @hide 909 * @return True if there is a change 910 */ replaceRemoteCollections(int viewId)911 public boolean replaceRemoteCollections(int viewId) { 912 boolean isActionReplaced = false; 913 if (mActions != null) { 914 for (int i = 0; i < mActions.size(); i++) { 915 Action action = mActions.get(i); 916 if (action instanceof SetRemoteCollectionItemListAdapterAction itemsAction 917 && itemsAction.mViewId == viewId 918 && itemsAction.mServiceIntent != null) { 919 SetRemoteCollectionItemListAdapterAction newCollectionAction = 920 new SetRemoteCollectionItemListAdapterAction( 921 itemsAction.mViewId, itemsAction.mServiceIntent); 922 newCollectionAction.mIntentId = itemsAction.mIntentId; 923 newCollectionAction.mIsReplacedIntoAction = true; 924 mActions.set(i, newCollectionAction); 925 isActionReplaced = true; 926 } else if (action instanceof SetRemoteViewsAdapterIntent intentAction 927 && intentAction.mViewId == viewId) { 928 mActions.set(i, new SetRemoteCollectionItemListAdapterAction( 929 intentAction.mViewId, intentAction.mIntent)); 930 isActionReplaced = true; 931 } else if (action instanceof ViewGroupActionAdd groupAction 932 && groupAction.mNestedViews != null) { 933 isActionReplaced |= groupAction.mNestedViews.replaceRemoteCollections(viewId); 934 } 935 } 936 } 937 if (mSizedRemoteViews != null) { 938 for (int i = 0; i < mSizedRemoteViews.size(); i++) { 939 isActionReplaced |= mSizedRemoteViews.get(i).replaceRemoteCollections(viewId); 940 } 941 } 942 if (mLandscape != null) { 943 isActionReplaced |= mLandscape.replaceRemoteCollections(viewId); 944 } 945 if (mPortrait != null) { 946 isActionReplaced |= mPortrait.replaceRemoteCollections(viewId); 947 } 948 949 return isActionReplaced; 950 } 951 952 /** 953 * @return True if has set remote adapter using service intent 954 * @hide 955 */ hasLegacyLists()956 public boolean hasLegacyLists() { 957 if (mActions != null) { 958 for (int i = 0; i < mActions.size(); i++) { 959 Action action = mActions.get(i); 960 if ((action instanceof SetRemoteCollectionItemListAdapterAction itemsAction 961 && itemsAction.mServiceIntent != null) 962 || (action instanceof SetRemoteViewsAdapterIntent intentAction 963 && intentAction.mIntent != null) 964 || (action instanceof ViewGroupActionAdd groupAction 965 && groupAction.mNestedViews != null 966 && groupAction.mNestedViews.hasLegacyLists())) { 967 return true; 968 } 969 } 970 } 971 if (mSizedRemoteViews != null) { 972 for (int i = 0; i < mSizedRemoteViews.size(); i++) { 973 if (mSizedRemoteViews.get(i).hasLegacyLists()) { 974 return true; 975 } 976 } 977 } 978 if (mLandscape != null && mLandscape.hasLegacyLists()) { 979 return true; 980 } 981 return mPortrait != null && mPortrait.hasLegacyLists(); 982 } 983 visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor)984 private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { 985 if (icon != null && (icon.getType() == Icon.TYPE_URI 986 || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { 987 visitor.accept(icon.getUri()); 988 } 989 } 990 991 private static class RemoteViewsContextWrapper extends ContextWrapper { 992 private final Context mContextForResources; 993 RemoteViewsContextWrapper(Context context, Context contextForResources)994 RemoteViewsContextWrapper(Context context, Context contextForResources) { 995 super(context); 996 mContextForResources = contextForResources; 997 } 998 999 @Override getResources()1000 public Resources getResources() { 1001 return mContextForResources.getResources(); 1002 } 1003 1004 @Override getTheme()1005 public Resources.Theme getTheme() { 1006 return mContextForResources.getTheme(); 1007 } 1008 1009 @Override getPackageName()1010 public String getPackageName() { 1011 return mContextForResources.getPackageName(); 1012 } 1013 1014 @Override getUser()1015 public UserHandle getUser() { 1016 return mContextForResources.getUser(); 1017 } 1018 1019 @Override getUserId()1020 public int getUserId() { 1021 return mContextForResources.getUserId(); 1022 } 1023 1024 @Override isRestricted()1025 public boolean isRestricted() { 1026 // Override isRestricted and direct to resource's implementation. The isRestricted is 1027 // used for determining the risky resources loading, e.g. fonts, thus direct to context 1028 // for resource. 1029 return mContextForResources.isRestricted(); 1030 } 1031 } 1032 1033 private static class SetEmptyView extends Action { 1034 int mEmptyViewId; 1035 SetEmptyView(@dRes int viewId, @IdRes int emptyViewId)1036 SetEmptyView(@IdRes int viewId, @IdRes int emptyViewId) { 1037 this.mViewId = viewId; 1038 this.mEmptyViewId = emptyViewId; 1039 } 1040 SetEmptyView(Parcel in)1041 SetEmptyView(Parcel in) { 1042 this.mViewId = in.readInt(); 1043 this.mEmptyViewId = in.readInt(); 1044 } 1045 writeToParcel(Parcel out, int flags)1046 public void writeToParcel(Parcel out, int flags) { 1047 out.writeInt(this.mViewId); 1048 out.writeInt(this.mEmptyViewId); 1049 } 1050 1051 @Override apply(View root, ViewGroup rootParent, ActionApplyParams params)1052 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 1053 final View view = root.findViewById(mViewId); 1054 if (!(view instanceof AdapterView<?>)) return; 1055 1056 AdapterView<?> adapterView = (AdapterView<?>) view; 1057 1058 final View emptyView = root.findViewById(mEmptyViewId); 1059 if (emptyView == null) return; 1060 1061 adapterView.setEmptyView(emptyView); 1062 } 1063 1064 @Override getActionTag()1065 public int getActionTag() { 1066 return SET_EMPTY_VIEW_ACTION_TAG; 1067 } 1068 1069 @Override canWriteToProto()1070 public boolean canWriteToProto() { 1071 return true; 1072 } 1073 1074 @Override writeToProto(ProtoOutputStream out, Context context, Resources appResources)1075 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 1076 final long token = out.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); 1077 out.write(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, 1078 appResources.getResourceName(mViewId)); 1079 out.write(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, 1080 appResources.getResourceName(mViewId)); 1081 out.end(token); 1082 } 1083 createFromProto(ProtoInputStream in)1084 public static PendingResources<Action> createFromProto(ProtoInputStream in) 1085 throws Exception { 1086 final LongSparseArray<Object> values = new LongSparseArray<>(); 1087 1088 final long token = in.start(RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION); 1089 while (in.nextField() != NO_MORE_FIELDS) { 1090 switch (in.getFieldNumber()) { 1091 case (int) RemoteViewsProto.SetEmptyViewAction.VIEW_ID: 1092 values.put(RemoteViewsProto.SetEmptyViewAction.VIEW_ID, 1093 in.readString(RemoteViewsProto.SetEmptyViewAction.VIEW_ID)); 1094 break; 1095 case (int) RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID: 1096 values.put(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID, 1097 in.readString(RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID)); 1098 break; 1099 default: 1100 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 1101 + ProtoUtils.currentFieldToString(in)); 1102 } 1103 } 1104 in.end(token); 1105 1106 checkContainsKeys(values, new long[]{RemoteViewsProto.SetEmptyViewAction.VIEW_ID, 1107 RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID}); 1108 1109 return (context, resources, rootData, depth) -> { 1110 int viewId = getAsIdentifier(resources, values, 1111 RemoteViewsProto.SetEmptyViewAction.VIEW_ID); 1112 int emptyViewId = getAsIdentifier(resources, values, 1113 RemoteViewsProto.SetEmptyViewAction.EMPTY_VIEW_ID); 1114 return new SetEmptyView(viewId, emptyViewId); 1115 }; 1116 } 1117 } 1118 1119 private static class SetPendingIntentTemplate extends Action { 1120 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 1121 PendingIntent mPendingIntentTemplate; 1122 SetPendingIntentTemplate(@dRes int id, PendingIntent pendingIntentTemplate)1123 public SetPendingIntentTemplate(@IdRes int id, PendingIntent pendingIntentTemplate) { 1124 this.mViewId = id; 1125 this.mPendingIntentTemplate = pendingIntentTemplate; 1126 } 1127 SetPendingIntentTemplate(Parcel parcel)1128 public SetPendingIntentTemplate(Parcel parcel) { 1129 mViewId = parcel.readInt(); 1130 mPendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 1131 } 1132 writeToParcel(Parcel dest, int flags)1133 public void writeToParcel(Parcel dest, int flags) { 1134 dest.writeInt(mViewId); 1135 PendingIntent.writePendingIntentOrNullToParcel(mPendingIntentTemplate, dest); 1136 } 1137 1138 @Override apply(View root, ViewGroup rootParent, ActionApplyParams params)1139 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 1140 final View target = root.findViewById(mViewId); 1141 if (target == null) return; 1142 1143 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 1144 if (target instanceof AdapterView<?>) { 1145 AdapterView<?> av = (AdapterView<?>) target; 1146 // The PendingIntent template is stored in the view's tag. 1147 OnItemClickListener listener = (parent, view, position, id) -> { 1148 RemoteResponse response = findRemoteResponseTag(view); 1149 if (response != null) { 1150 response.handleViewInteraction(view, params.handler); 1151 } 1152 }; 1153 av.setOnItemClickListener(listener); 1154 av.setTag(mPendingIntentTemplate); 1155 } else { 1156 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" + 1157 "an AdapterView (id: " + mViewId + ")"); 1158 return; 1159 } 1160 } 1161 1162 @Nullable findRemoteResponseTag(@ullable View rootView)1163 private RemoteResponse findRemoteResponseTag(@Nullable View rootView) { 1164 if (rootView == null) return null; 1165 1166 ArrayDeque<View> viewsToCheck = new ArrayDeque<>(); 1167 viewsToCheck.addLast(rootView); 1168 1169 while (!viewsToCheck.isEmpty()) { 1170 View view = viewsToCheck.removeFirst(); 1171 Object tag = view.getTag(R.id.fillInIntent); 1172 if (tag instanceof RemoteResponse) return (RemoteResponse) tag; 1173 if (!(view instanceof ViewGroup)) continue; 1174 1175 ViewGroup viewGroup = (ViewGroup) view; 1176 for (int i = 0; i < viewGroup.getChildCount(); i++) { 1177 viewsToCheck.addLast(viewGroup.getChildAt(i)); 1178 } 1179 } 1180 1181 return null; 1182 } 1183 1184 @Override getActionTag()1185 public int getActionTag() { 1186 return SET_PENDING_INTENT_TEMPLATE_TAG; 1187 } 1188 } 1189 1190 /** 1191 * Cache of {@link ApplicationInfo}s that can be used to ensure that the same 1192 * {@link ApplicationInfo} instance is used throughout the RemoteViews. 1193 */ 1194 private static class ApplicationInfoCache { 1195 private final Map<Pair<String, Integer>, ApplicationInfo> mPackageUserToApplicationInfo; 1196 ApplicationInfoCache()1197 ApplicationInfoCache() { 1198 mPackageUserToApplicationInfo = new ArrayMap<>(); 1199 } 1200 1201 /** 1202 * Adds the {@link ApplicationInfo} to the cache if it's not present. Returns either the 1203 * provided {@code applicationInfo} or a previously added value with the same package name 1204 * and uid. 1205 */ 1206 @Nullable getOrPut(@ullable ApplicationInfo applicationInfo)1207 ApplicationInfo getOrPut(@Nullable ApplicationInfo applicationInfo) { 1208 Pair<String, Integer> key = getPackageUserKey(applicationInfo); 1209 if (key == null) return null; 1210 return mPackageUserToApplicationInfo.computeIfAbsent(key, ignored -> applicationInfo); 1211 } 1212 1213 /** Puts the {@link ApplicationInfo} in the cache, replacing any previously stored value. */ put(@ullable ApplicationInfo applicationInfo)1214 void put(@Nullable ApplicationInfo applicationInfo) { 1215 Pair<String, Integer> key = getPackageUserKey(applicationInfo); 1216 if (key == null) return; 1217 mPackageUserToApplicationInfo.put(key, applicationInfo); 1218 } 1219 1220 /** 1221 * Returns the currently stored {@link ApplicationInfo} from the cache matching 1222 * {@code applicationInfo}, or null if there wasn't any. 1223 */ get(@ullable ApplicationInfo applicationInfo)1224 @Nullable ApplicationInfo get(@Nullable ApplicationInfo applicationInfo) { 1225 Pair<String, Integer> key = getPackageUserKey(applicationInfo); 1226 if (key == null) return null; 1227 return mPackageUserToApplicationInfo.get(key); 1228 } 1229 } 1230 1231 private class SetRemoteCollectionItemListAdapterAction extends Action { 1232 private @Nullable RemoteCollectionItems mItems; 1233 final Intent mServiceIntent; 1234 int mIntentId = -1; 1235 boolean mIsReplacedIntoAction = false; 1236 SetRemoteCollectionItemListAdapterAction(@dRes int id, @NonNull RemoteCollectionItems items)1237 SetRemoteCollectionItemListAdapterAction(@IdRes int id, 1238 @NonNull RemoteCollectionItems items) { 1239 mViewId = id; 1240 items.setHierarchyRootData(getHierarchyRootData()); 1241 mItems = items; 1242 mServiceIntent = null; 1243 } 1244 SetRemoteCollectionItemListAdapterAction(@dRes int id, Intent intent)1245 SetRemoteCollectionItemListAdapterAction(@IdRes int id, Intent intent) { 1246 mViewId = id; 1247 mItems = null; 1248 mServiceIntent = intent; 1249 } 1250 SetRemoteCollectionItemListAdapterAction(Parcel parcel)1251 SetRemoteCollectionItemListAdapterAction(Parcel parcel) { 1252 mViewId = parcel.readInt(); 1253 mIntentId = parcel.readInt(); 1254 mIsReplacedIntoAction = parcel.readBoolean(); 1255 mServiceIntent = parcel.readTypedObject(Intent.CREATOR); 1256 mItems = mServiceIntent != null 1257 ? null 1258 : new RemoteCollectionItems(parcel, getHierarchyRootData()); 1259 } 1260 1261 @Override setHierarchyRootData(HierarchyRootData rootData)1262 public void setHierarchyRootData(HierarchyRootData rootData) { 1263 if (mItems != null) { 1264 mItems.setHierarchyRootData(rootData); 1265 return; 1266 } 1267 1268 if (mIntentId != -1) { 1269 // Set the root data for items in the cache instead 1270 mCollectionCache.setHierarchyDataForId(mIntentId, rootData); 1271 } 1272 } 1273 1274 @Override writeToParcel(Parcel dest, int flags)1275 public void writeToParcel(Parcel dest, int flags) { 1276 dest.writeInt(mViewId); 1277 dest.writeInt(mIntentId); 1278 dest.writeBoolean(mIsReplacedIntoAction); 1279 dest.writeTypedObject(mServiceIntent, flags); 1280 if (mItems != null) { 1281 mItems.writeToParcel(dest, flags, /* attached= */ true); 1282 } 1283 } 1284 1285 @Override apply(View root, ViewGroup rootParent, ActionApplyParams params)1286 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) 1287 throws ActionException { 1288 View target = root.findViewById(mViewId); 1289 if (target == null) return; 1290 1291 RemoteCollectionItems items = mIntentId == -1 1292 ? mItems == null 1293 ? new RemoteCollectionItems.Builder().build() 1294 : mItems 1295 : mCollectionCache.getItemsForId(mIntentId); 1296 1297 // Ensure that we are applying to an AppWidget root 1298 if (!(rootParent instanceof AppWidgetHostView)) { 1299 Log.e(LOG_TAG, "setRemoteAdapter can only be used for " 1300 + "AppWidgets (root id: " + mViewId + ")"); 1301 return; 1302 } 1303 1304 if (!(target instanceof AdapterView)) { 1305 Log.e(LOG_TAG, "Cannot call setRemoteAdapter on a view which is not " 1306 + "an AdapterView (id: " + mViewId + ")"); 1307 return; 1308 } 1309 1310 AdapterView adapterView = (AdapterView) target; 1311 Adapter adapter = adapterView.getAdapter(); 1312 boolean onLightBackground = hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT); 1313 // We can reuse the adapter if it's a RemoteCollectionItemsAdapter and the view type 1314 // count hasn't increased. Note that AbsListView allocates a fixed size array for view 1315 // recycling in setAdapter, so we must call setAdapter again if the number of view types 1316 // increases. 1317 if (adapter instanceof RemoteCollectionItemsAdapter 1318 && adapter.getViewTypeCount() >= items.getViewTypeCount()) { 1319 try { 1320 ((RemoteCollectionItemsAdapter) adapter) 1321 .setData( 1322 items, 1323 params.handler, 1324 params.colorResources, 1325 onLightBackground); 1326 } catch (Throwable throwable) { 1327 // setData should never failed with the validation in the items builder, but if 1328 // it does, catch and rethrow. 1329 throw new ActionException(throwable); 1330 } 1331 return; 1332 } 1333 1334 try { 1335 adapterView.setAdapter( 1336 new RemoteCollectionItemsAdapter( 1337 items, params.handler, params.colorResources, onLightBackground)); 1338 } catch (Throwable throwable) { 1339 // This could throw if the AdapterView somehow doesn't accept BaseAdapter due to 1340 // a type error. 1341 throw new ActionException(throwable); 1342 } 1343 if (adapterView instanceof AbsListView listView) { 1344 listView.setOnScrollListener(new AbsListView.OnScrollListener() { 1345 @Override 1346 public void onScrollStateChanged(AbsListView view, int scrollState) { 1347 if (scrollState != SCROLL_STATE_IDLE) { 1348 params.handler.onScroll(view); 1349 } 1350 } 1351 1352 @Override 1353 public void onScroll(AbsListView view, int firstVisibleItem, 1354 int visibleItemCount, int totalItemCount) { 1355 } 1356 }); 1357 } 1358 } 1359 1360 @Override getActionTag()1361 public int getActionTag() { 1362 return SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG; 1363 } 1364 1365 @Override getUniqueKey()1366 public String getUniqueKey() { 1367 return (SET_REMOTE_ADAPTER_TAG + "_" + mViewId); 1368 } 1369 1370 @Override visitUris(@onNull Consumer<Uri> visitor)1371 public void visitUris(@NonNull Consumer<Uri> visitor) { 1372 if (mItems == null) { 1373 // Null item indicates adapter conversion took place, so the URIs in cached items 1374 // need to be validated. 1375 RemoteCollectionItems cachedItems = mCollectionCache.getItemsForId(mIntentId); 1376 if (cachedItems != null) { 1377 cachedItems.visitUris(visitor); 1378 } 1379 return; 1380 } 1381 1382 mItems.visitUris(visitor); 1383 } 1384 1385 @Override visitIcons(Consumer<Icon> visitor)1386 public void visitIcons(Consumer<Icon> visitor) { 1387 if (mItems == null) { 1388 RemoteCollectionItems cachedItems = mCollectionCache.getItemsForId(mIntentId); 1389 if (cachedItems != null) { 1390 cachedItems.visitIcons(visitor); 1391 } 1392 return; 1393 } 1394 1395 mItems.visitIcons(visitor); 1396 } 1397 1398 @Override canWriteToProto()1399 public boolean canWriteToProto() { 1400 // Skip actions that do not contain items (intent only actions) 1401 return mItems != null; 1402 } 1403 1404 @Override writeToProto(ProtoOutputStream out, Context context, Resources appResources)1405 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 1406 if (mItems == null) return; 1407 final long token = out.start( 1408 RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); 1409 out.write(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, 1410 appResources.getResourceName(mViewId)); 1411 final long itemsToken = out.start( 1412 RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); 1413 mItems.writeToProto(context, out, /* attached= */ true); 1414 out.end(itemsToken); 1415 out.end(token); 1416 } 1417 } 1418 createSetRemoteCollectionItemListAdapterActionFromProto( ProtoInputStream in)1419 private PendingResources<Action> createSetRemoteCollectionItemListAdapterActionFromProto( 1420 ProtoInputStream in) throws Exception { 1421 final LongSparseArray<Object> values = new LongSparseArray<>(); 1422 1423 final long token = in.start( 1424 RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION); 1425 while (in.nextField() != NO_MORE_FIELDS) { 1426 switch (in.getFieldNumber()) { 1427 case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID: 1428 values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, 1429 in.readString( 1430 RemoteViewsProto 1431 .SetRemoteCollectionItemListAdapterAction.VIEW_ID)); 1432 break; 1433 case (int) RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS: 1434 final long itemsToken = in.start( 1435 RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS); 1436 values.put(RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS, 1437 RemoteCollectionItems.createFromProto(in)); 1438 in.end(itemsToken); 1439 break; 1440 default: 1441 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 1442 + ProtoUtils.currentFieldToString(in)); 1443 } 1444 } 1445 in.end(token); 1446 1447 checkContainsKeys(values, 1448 new long[]{RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID, 1449 RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS}); 1450 1451 return (context, resources, rootData, depth) -> { 1452 int viewId = getAsIdentifier(resources, values, 1453 RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.VIEW_ID); 1454 return new SetRemoteCollectionItemListAdapterAction(viewId, 1455 ((PendingResources<RemoteCollectionItems>) values.get( 1456 RemoteViewsProto.SetRemoteCollectionItemListAdapterAction.ITEMS)) 1457 .create(context, resources, rootData, depth)); 1458 }; 1459 } 1460 1461 /** 1462 * The maximum size for RemoteViews with converted RemoteCollectionItemsAdapter. 1463 * When converting RemoteViewsAdapter to RemoteCollectionItemsAdapter, we want to put size 1464 * limits on each unique RemoteCollectionItems in order to not exceed the transaction size limit 1465 * for each parcel (typically 1 MB). We leave a certain ratio of the maximum size as a buffer 1466 * for missing calculations of certain parameters (e.g. writing a RemoteCollectionItems to the 1467 * parcel will write its Id array as well, but that is missing when writing itschild RemoteViews 1468 * directly to the parcel as we did in RemoteViewsService) 1469 * 1470 * @hide 1471 */ 1472 private static final int MAX_SINGLE_PARCEL_SIZE = (int) (1_000_000 * 0.8); 1473 1474 /** 1475 * @hide 1476 */ 1477 public CompletableFuture<Void> collectAllIntents(int bitmapSizeLimit, 1478 @NonNull ServiceCollectionCache collectionCache) { 1479 return mCollectionCache.collectAllIntentsNoComplete(this, bitmapSizeLimit, 1480 collectionCache); 1481 } 1482 1483 private class RemoteCollectionCache { 1484 private final SparseArray<String> mIdToUriMapping = new SparseArray<>(); 1485 private final Map<String, RemoteCollectionItems> mUriToCollectionMapping = new HashMap<>(); 1486 1487 RemoteCollectionCache() { } 1488 1489 RemoteCollectionCache(RemoteCollectionCache src) { 1490 for (int i = 0; i < src.mIdToUriMapping.size(); i++) { 1491 String uri = src.mIdToUriMapping.valueAt(i); 1492 mIdToUriMapping.put(src.mIdToUriMapping.keyAt(i), uri); 1493 mUriToCollectionMapping.put(uri, src.mUriToCollectionMapping.get(uri)); 1494 } 1495 } 1496 1497 RemoteCollectionCache(Parcel in) { 1498 int cacheSize = in.readInt(); 1499 HierarchyRootData currentRootData = new HierarchyRootData(mBitmapCache, 1500 this, 1501 mApplicationInfoCache, 1502 mClassCookies); 1503 for (int i = 0; i < cacheSize; i++) { 1504 int intentId = in.readInt(); 1505 String intentUri = in.readString8(); 1506 RemoteCollectionItems items = new RemoteCollectionItems(in, currentRootData); 1507 addMapping(intentId, intentUri, items); 1508 } 1509 } 1510 1511 void addMapping(int intentId, String intentUri, RemoteCollectionItems items) { 1512 mIdToUriMapping.put(intentId, intentUri); 1513 mUriToCollectionMapping.put(intentUri, items); 1514 } 1515 1516 1517 void setHierarchyDataForId(int intentId, HierarchyRootData data) { 1518 String uri = mIdToUriMapping.get(intentId); 1519 if (mUriToCollectionMapping.get(uri) == null) { 1520 Log.e(LOG_TAG, "Error setting hierarchy data for id=" + intentId); 1521 return; 1522 } 1523 1524 RemoteCollectionItems items = mUriToCollectionMapping.get(uri); 1525 items.setHierarchyRootData(data); 1526 } 1527 1528 RemoteCollectionItems getItemsForId(int intentId) { 1529 String uri = mIdToUriMapping.get(intentId); 1530 return mUriToCollectionMapping.get(uri); 1531 } 1532 1533 public @NonNull CompletableFuture<Void> collectAllIntentsNoComplete( 1534 @NonNull RemoteViews inViews, int bitmapSizeLimit, 1535 @NonNull ServiceCollectionCache collectionCache) { 1536 SparseArray<Intent> idToIntentMapping = new SparseArray<>(); 1537 // Collect the number of uinque Intent (which is equal to the number of new connections 1538 // to make) for size allocation and exclude certain collections from being written to 1539 // the parcel to better estimate the space left for reallocation. 1540 collectAllIntentsInternal(inViews, idToIntentMapping); 1541 1542 // Calculate the individual size here 1543 int numOfIntents = idToIntentMapping.size(); 1544 if (numOfIntents == 0) { 1545 Log.e(LOG_TAG, "Possibly notifying updates for nonexistent view Id"); 1546 return CompletableFuture.completedFuture(null); 1547 } 1548 1549 Parcel sizeTestParcel = Parcel.obtain(); 1550 // Write self RemoteViews to the parcel, which includes the actions/bitmaps/collection 1551 // cache to see how much space is left for the RemoteCollectionItems that are to be 1552 // updated. 1553 RemoteViews.this.writeToParcel(sizeTestParcel, 1554 /* flags= */ 0, 1555 /* intentsToIgnore= */ idToIntentMapping); 1556 int remainingSize = MAX_SINGLE_PARCEL_SIZE - sizeTestParcel.dataSize(); 1557 sizeTestParcel.recycle(); 1558 1559 int individualSize = remainingSize < 0 1560 ? 0 1561 : remainingSize / numOfIntents; 1562 1563 int individualBitmapSizeLimit = (bitmapSizeLimit - getBitmapMemoryUsedByActions()) 1564 / numOfIntents; 1565 1566 return connectAllUniqueIntents(individualSize, individualBitmapSizeLimit, 1567 idToIntentMapping, collectionCache); 1568 } 1569 1570 private void collectAllIntentsInternal(@NonNull RemoteViews inViews, 1571 @NonNull SparseArray<Intent> idToIntentMapping) { 1572 if (inViews.hasSizedRemoteViews()) { 1573 for (RemoteViews remoteViews : inViews.mSizedRemoteViews) { 1574 collectAllIntentsInternal(remoteViews, idToIntentMapping); 1575 } 1576 } else if (inViews.hasLandscapeAndPortraitLayouts()) { 1577 collectAllIntentsInternal(inViews.mLandscape, idToIntentMapping); 1578 collectAllIntentsInternal(inViews.mPortrait, idToIntentMapping); 1579 } else if (inViews.mActions != null) { 1580 for (Action action : inViews.mActions) { 1581 if (action instanceof SetRemoteCollectionItemListAdapterAction rca) { 1582 // Deal with the case where the intent is replaced into the action list 1583 if (rca.mIntentId != -1 && !rca.mIsReplacedIntoAction) { 1584 continue; 1585 } 1586 1587 if (rca.mIntentId != -1 && rca.mIsReplacedIntoAction) { 1588 rca.mIsReplacedIntoAction = false; 1589 1590 // Avoid redundant connections for the same intent. Also making sure 1591 // that the number of connections we are making is always equal to the 1592 // nmuber of unique intents that are being used for the updates. 1593 if (idToIntentMapping.contains(rca.mIntentId)) { 1594 continue; 1595 } 1596 1597 idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); 1598 rca.mItems = null; 1599 continue; 1600 } 1601 1602 // Differentiate between the normal collection actions and the ones with 1603 // intents. 1604 if (rca.mServiceIntent != null) { 1605 final String uri = rca.mServiceIntent.toUri(0); 1606 int index = mIdToUriMapping.indexOfValueByValue(uri); 1607 if (index == -1) { 1608 int newIntentId = mIdToUriMapping.size(); 1609 rca.mIntentId = newIntentId; 1610 mIdToUriMapping.put(newIntentId, uri); 1611 } else { 1612 rca.mIntentId = mIdToUriMapping.keyAt(index); 1613 rca.mItems = null; 1614 continue; 1615 } 1616 1617 idToIntentMapping.put(rca.mIntentId, rca.mServiceIntent); 1618 rca.mItems = null; 1619 } else { 1620 for (RemoteViews views : rca.mItems.mViews) { 1621 collectAllIntentsInternal(views, idToIntentMapping); 1622 } 1623 } 1624 } else if (action instanceof ViewGroupActionAdd vgaa 1625 && vgaa.mNestedViews != null) { 1626 collectAllIntentsInternal(vgaa.mNestedViews, idToIntentMapping); 1627 } 1628 } 1629 } 1630 } 1631 1632 private @NonNull CompletableFuture<Void> connectAllUniqueIntents(int individualSize, 1633 int individualBitmapSize, @NonNull SparseArray<Intent> idToIntentMapping, 1634 @NonNull ServiceCollectionCache collectionCache) { 1635 List<CompletableFuture<Void>> intentFutureList = new ArrayList<>(); 1636 for (int i = 0; i < idToIntentMapping.size(); i++) { 1637 String currentIntentUri = mIdToUriMapping.get(idToIntentMapping.keyAt(i)); 1638 Intent currentIntent = idToIntentMapping.valueAt(i); 1639 intentFutureList.add(getItemsFutureFromIntentWithTimeout(currentIntent, 1640 individualSize, individualBitmapSize, collectionCache) 1641 .thenAccept(items -> { 1642 items.setHierarchyRootData(getHierarchyRootData()); 1643 mUriToCollectionMapping.put(currentIntentUri, items); 1644 })); 1645 } 1646 1647 return CompletableFuture.allOf(intentFutureList.toArray(CompletableFuture[]::new)); 1648 } 1649 1650 private static CompletableFuture<RemoteCollectionItems> getItemsFutureFromIntentWithTimeout( 1651 Intent intent, int individualSize, int individualBitmapSize, 1652 @NonNull ServiceCollectionCache collectionCache) { 1653 if (intent == null) { 1654 Log.e(LOG_TAG, "Null intent received when generating adapter future"); 1655 return CompletableFuture.completedFuture(new RemoteCollectionItems 1656 .Builder().build()); 1657 } 1658 1659 final Context context = ActivityThread.currentApplication(); 1660 1661 final CompletableFuture<RemoteCollectionItems> result = new CompletableFuture<>(); 1662 String contextPackageName = context.getPackageName(); 1663 ComponentName intentComponent = intent.getComponent(); 1664 if (contextPackageName != null 1665 && intentComponent != null 1666 && (!contextPackageName.equals(intentComponent.getPackageName()))) { 1667 // We shouldn't allow for connections to other packages 1668 result.complete(new RemoteCollectionItems.Builder().build()); 1669 return result; 1670 } 1671 1672 collectionCache.connectAndConsume(intent, iBinder -> { 1673 RemoteCollectionItems items; 1674 try { 1675 items = IRemoteViewsFactory.Stub.asInterface(iBinder) 1676 .getRemoteCollectionItems(individualSize, 1677 individualBitmapSize); 1678 } catch (RemoteException re) { 1679 items = new RemoteCollectionItems.Builder().build(); 1680 Log.e(LOG_TAG, "Error getting collection items from the" 1681 + " factory", re); 1682 } 1683 1684 if (items == null) { 1685 items = new RemoteCollectionItems.Builder().build(); 1686 } 1687 1688 result.complete(items); 1689 }, result.defaultExecutor()); 1690 1691 result.completeOnTimeout( 1692 new RemoteCollectionItems.Builder().build(), 1693 MAX_ADAPTER_CONVERSION_WAITING_TIME_MS, TimeUnit.MILLISECONDS); 1694 1695 return result; 1696 } 1697 1698 public void writeToParcel(Parcel out, int flags, 1699 @Nullable SparseArray<Intent> intentsToIgnore) { 1700 out.writeInt(mIdToUriMapping.size()); 1701 for (int i = 0; i < mIdToUriMapping.size(); i++) { 1702 int currentIntentId = mIdToUriMapping.keyAt(i); 1703 if (intentsToIgnore != null && intentsToIgnore.contains(currentIntentId)) { 1704 // Skip writing collections that are to be updated in the following steps to 1705 // better estimate the RemoteViews size. 1706 continue; 1707 } 1708 out.writeInt(currentIntentId); 1709 String intentUri = mIdToUriMapping.valueAt(i); 1710 out.writeString8(intentUri); 1711 mUriToCollectionMapping.get(intentUri).writeToParcel(out, flags, true); 1712 } 1713 } 1714 1715 public void writeToProto(Context context, ProtoOutputStream out) { 1716 final long token = out.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE); 1717 for (int i = 0; i < mIdToUriMapping.size(); i++) { 1718 final long entryToken = out.start(RemoteViewsProto.RemoteCollectionCache.ENTRIES); 1719 out.write(RemoteViewsProto.RemoteCollectionCache.Entry.ID, 1720 mIdToUriMapping.keyAt(i)); 1721 String intentUri = mIdToUriMapping.valueAt(i); 1722 out.write(RemoteViewsProto.RemoteCollectionCache.Entry.URI, intentUri); 1723 final long itemsToken = out.start( 1724 RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS); 1725 mUriToCollectionMapping.get(intentUri).writeToProto(context, out, /* attached= */ 1726 true); 1727 out.end(itemsToken); 1728 out.end(entryToken); 1729 } 1730 out.end(token); 1731 } 1732 } 1733 1734 private PendingResources<RemoteCollectionCache> populateRemoteCollectionCacheFromProto( 1735 ProtoInputStream in) throws Exception { 1736 final ArrayList<LongSparseArray<Object>> entries = new ArrayList<>(); 1737 final long token = in.start(RemoteViewsProto.REMOTE_COLLECTION_CACHE); 1738 while (in.nextField() != NO_MORE_FIELDS) { 1739 switch (in.getFieldNumber()) { 1740 case (int) RemoteViewsProto.RemoteCollectionCache.ENTRIES: 1741 final LongSparseArray<Object> entry = new LongSparseArray<>(); 1742 1743 final long entryToken = in.start( 1744 RemoteViewsProto.RemoteCollectionCache.ENTRIES); 1745 while (in.nextField() != NO_MORE_FIELDS) { 1746 switch (in.getFieldNumber()) { 1747 case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ID: 1748 entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ID, 1749 in.readInt( 1750 RemoteViewsProto.RemoteCollectionCache.Entry.ID)); 1751 break; 1752 case (int) RemoteViewsProto.RemoteCollectionCache.Entry.URI: 1753 entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.URI, 1754 in.readString( 1755 RemoteViewsProto.RemoteCollectionCache.Entry.URI)); 1756 break; 1757 case (int) RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS: 1758 final long itemsToken = in.start( 1759 RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS); 1760 entry.put(RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS, 1761 RemoteCollectionItems.createFromProto(in)); 1762 in.end(itemsToken); 1763 break; 1764 default: 1765 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 1766 + ProtoUtils.currentFieldToString(in)); 1767 } 1768 } 1769 in.end(entryToken); 1770 1771 checkContainsKeys(entry, 1772 new long[]{RemoteViewsProto.RemoteCollectionCache.Entry.ID, 1773 RemoteViewsProto.RemoteCollectionCache.Entry.URI, 1774 RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS}); 1775 1776 entries.add(entry); 1777 break; 1778 default: 1779 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 1780 + ProtoUtils.currentFieldToString(in)); 1781 } 1782 } 1783 in.end(token); 1784 1785 return (context, resources, rootData, depth) -> { 1786 for (LongSparseArray<Object> entry : entries) { 1787 int id = (int) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.ID); 1788 String uri = (String) entry.get(RemoteViewsProto.RemoteCollectionCache.Entry.URI); 1789 // Depth resets to 0 for RemoteCollectionItems 1790 RemoteCollectionItems items = ((PendingResources<RemoteCollectionItems>) entry.get( 1791 RemoteViewsProto.RemoteCollectionCache.Entry.ITEMS)).create(context, 1792 resources, rootData, depth); 1793 rootData.mRemoteCollectionCache.addMapping(id, uri, items); 1794 } 1795 // Redundant return, but type signature requires we return something. 1796 return rootData.mRemoteCollectionCache; 1797 }; 1798 } 1799 1800 private class SetRemoteViewsAdapterIntent extends Action { 1801 Intent mIntent; 1802 boolean mIsAsync = false; 1803 1804 public SetRemoteViewsAdapterIntent(@IdRes int id, Intent intent) { 1805 this.mViewId = id; 1806 this.mIntent = intent; 1807 } 1808 1809 public SetRemoteViewsAdapterIntent(Parcel parcel) { 1810 mViewId = parcel.readInt(); 1811 mIntent = parcel.readTypedObject(Intent.CREATOR); 1812 } 1813 1814 public void writeToParcel(Parcel dest, int flags) { 1815 dest.writeInt(mViewId); 1816 dest.writeTypedObject(mIntent, flags); 1817 } 1818 1819 @Override 1820 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 1821 final View target = root.findViewById(mViewId); 1822 if (target == null) return; 1823 1824 // Ensure that we are applying to an AppWidget root 1825 if (!(rootParent instanceof AppWidgetHostView)) { 1826 Log.e(LOG_TAG, "setRemoteAdapter can only be used for " 1827 + "AppWidgets (root id: " + mViewId + ")"); 1828 return; 1829 } 1830 1831 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 1832 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 1833 Log.e(LOG_TAG, "Cannot setRemoteAdapter on a view which is not " 1834 + "an AbsListView or AdapterViewAnimator (id: " + mViewId + ")"); 1835 return; 1836 } 1837 1838 // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent 1839 // RemoteViewsService 1840 AppWidgetHostView host = (AppWidgetHostView) rootParent; 1841 mIntent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()) 1842 .putExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND, 1843 hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)); 1844 1845 if (target instanceof AbsListView) { 1846 AbsListView v = (AbsListView) target; 1847 v.setRemoteViewsAdapter(mIntent, mIsAsync); 1848 v.setRemoteViewsInteractionHandler(params.handler); 1849 v.setOnScrollListener(new AbsListView.OnScrollListener() { 1850 @Override 1851 public void onScrollStateChanged(AbsListView view, int scrollState) { 1852 if (scrollState != SCROLL_STATE_IDLE) { 1853 params.handler.onScroll(view); 1854 } 1855 } 1856 1857 @Override 1858 public void onScroll(AbsListView view, int firstVisibleItem, 1859 int visibleItemCount, int totalItemCount) { 1860 } 1861 }); 1862 } else if (target instanceof AdapterViewAnimator) { 1863 AdapterViewAnimator v = (AdapterViewAnimator) target; 1864 v.setRemoteViewsAdapter(mIntent, mIsAsync); 1865 v.setRemoteViewsOnClickHandler(params.handler); 1866 } 1867 } 1868 1869 @Override 1870 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 1871 ActionApplyParams params) { 1872 SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(mViewId, mIntent); 1873 copy.mIsAsync = true; 1874 return copy; 1875 } 1876 1877 @Override 1878 public int getActionTag() { 1879 return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG; 1880 } 1881 } 1882 1883 /** 1884 * Equivalent to calling 1885 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 1886 * to launch the provided {@link PendingIntent}. 1887 */ 1888 private class SetOnClickResponse extends Action { 1889 final RemoteResponse mResponse; 1890 1891 SetOnClickResponse(@IdRes int id, RemoteResponse response) { 1892 this.mViewId = id; 1893 this.mResponse = response; 1894 } 1895 1896 SetOnClickResponse(Parcel parcel) { 1897 mViewId = parcel.readInt(); 1898 mResponse = new RemoteResponse(); 1899 mResponse.readFromParcel(parcel); 1900 } 1901 1902 public void writeToParcel(Parcel dest, int flags) { 1903 dest.writeInt(mViewId); 1904 mResponse.writeToParcel(dest, flags); 1905 } 1906 1907 @Override 1908 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 1909 if (hasDrawInstructions() && root instanceof RemoteComposePlayer) { 1910 return; 1911 } 1912 final View target = root.findViewById(mViewId); 1913 if (target == null) return; 1914 1915 if (mResponse.mPendingIntent != null) { 1916 // If the view is an AdapterView, setting a PendingIntent on click doesn't make 1917 // much sense, do they mean to set a PendingIntent template for the 1918 // AdapterView's children? 1919 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 1920 Log.w(LOG_TAG, "Cannot SetOnClickResponse for collection item " 1921 + "(id: " + mViewId + ")"); 1922 ApplicationInfo appInfo = root.getContext().getApplicationInfo(); 1923 1924 // We let this slide for HC and ICS so as to not break compatibility. It should 1925 // have been disabled from the outset, but was left open by accident. 1926 if (appInfo != null 1927 && appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { 1928 return; 1929 } 1930 } 1931 target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent); 1932 } else if (mResponse.mFillIntent != null) { 1933 if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 1934 Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " 1935 + "only from RemoteViewsFactory (ie. on collection items)."); 1936 return; 1937 } 1938 if (target == root) { 1939 // Target is a root node of an AdapterView child. Set the response in the tag. 1940 // Actual click handling is done by OnItemClickListener in 1941 // SetPendingIntentTemplate, which uses this tag information. 1942 target.setTagInternal(com.android.internal.R.id.fillInIntent, mResponse); 1943 return; 1944 } 1945 } else { 1946 // No intent to apply, clear the listener and any tags that were previously set. 1947 target.setOnClickListener(null); 1948 target.setTagInternal(R.id.pending_intent_tag, null); 1949 target.setTagInternal(com.android.internal.R.id.fillInIntent, null); 1950 return; 1951 } 1952 target.setOnClickListener(v -> 1953 mResponse.handleViewInteraction(v, params.handler)); 1954 } 1955 1956 @Override 1957 public int getActionTag() { 1958 return SET_ON_CLICK_RESPONSE_TAG; 1959 } 1960 } 1961 1962 /** Helper action to configure handwriting delegation via {@link PendingIntent}. */ 1963 private class SetOnStylusHandwritingResponse extends Action { 1964 final PendingIntent mPendingIntent; 1965 1966 SetOnStylusHandwritingResponse(@IdRes int id, @Nullable PendingIntent pendingIntent) { 1967 this.mViewId = id; 1968 this.mPendingIntent = pendingIntent; 1969 } 1970 1971 SetOnStylusHandwritingResponse(@NonNull Parcel parcel) { 1972 mViewId = parcel.readInt(); 1973 mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 1974 } 1975 1976 public void writeToParcel(@NonNull Parcel dest, int flags) { 1977 dest.writeInt(mViewId); 1978 PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest); 1979 } 1980 1981 @Override 1982 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 1983 final View target = root.findViewById(mViewId); 1984 if (target == null) return; 1985 1986 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 1987 Log.w(LOG_TAG, "Cannot use setOnStylusHandwritingPendingIntent for collection item " 1988 + "(id: " + mViewId + ")"); 1989 return; 1990 } 1991 1992 if (mPendingIntent != null) { 1993 RemoteResponse response = RemoteResponse.fromPendingIntent(mPendingIntent); 1994 target.setHandwritingDelegatorCallback( 1995 () -> response.handleViewInteraction(target, params.handler)); 1996 target.setAllowedHandwritingDelegatePackage(mPendingIntent.getCreatorPackage()); 1997 } else { 1998 target.setHandwritingDelegatorCallback(null); 1999 target.setAllowedHandwritingDelegatePackage(null); 2000 } 2001 } 2002 2003 @Override 2004 public int getActionTag() { 2005 return SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG; 2006 } 2007 } 2008 2009 /** 2010 * Equivalent to calling 2011 * {@link android.widget.CompoundButton#setOnCheckedChangeListener( 2012 * android.widget.CompoundButton.OnCheckedChangeListener)} 2013 * to launch the provided {@link PendingIntent}. 2014 */ 2015 private class SetOnCheckedChangeResponse extends Action { 2016 private final RemoteResponse mResponse; 2017 2018 SetOnCheckedChangeResponse(@IdRes int id, RemoteResponse response) { 2019 this.mViewId = id; 2020 this.mResponse = response; 2021 } 2022 2023 SetOnCheckedChangeResponse(Parcel parcel) { 2024 mViewId = parcel.readInt(); 2025 mResponse = new RemoteResponse(); 2026 mResponse.readFromParcel(parcel); 2027 } 2028 2029 public void writeToParcel(Parcel dest, int flags) { 2030 dest.writeInt(mViewId); 2031 mResponse.writeToParcel(dest, flags); 2032 } 2033 2034 @Override 2035 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 2036 final View target = root.findViewById(mViewId); 2037 if (target == null) return; 2038 if (!(target instanceof CompoundButton)) { 2039 Log.w(LOG_TAG, "setOnCheckedChange methods cannot be used on " 2040 + "non-CompoundButton child (id: " + mViewId + ")"); 2041 return; 2042 } 2043 CompoundButton button = (CompoundButton) target; 2044 2045 if (mResponse.mPendingIntent != null) { 2046 // setOnCheckedChangePendingIntent cannot be used with collection children, which 2047 // must use setOnCheckedChangeFillInIntent instead. 2048 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 2049 Log.w(LOG_TAG, "Cannot setOnCheckedChangePendingIntent for collection item " 2050 + "(id: " + mViewId + ")"); 2051 return; 2052 } 2053 target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent); 2054 } else if (mResponse.mFillIntent != null) { 2055 if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 2056 Log.e(LOG_TAG, "The method setOnCheckedChangeFillInIntent is available " 2057 + "only from RemoteViewsFactory (ie. on collection items)."); 2058 return; 2059 } 2060 } else { 2061 // No intent to apply, clear any existing listener or tag. 2062 button.setOnCheckedChangeListener(null); 2063 button.setTagInternal(R.id.remote_checked_change_listener_tag, null); 2064 return; 2065 } 2066 2067 OnCheckedChangeListener onCheckedChangeListener = 2068 (v, isChecked) -> mResponse.handleViewInteraction(v, params.handler); 2069 button.setTagInternal(R.id.remote_checked_change_listener_tag, onCheckedChangeListener); 2070 button.setOnCheckedChangeListener(onCheckedChangeListener); 2071 } 2072 2073 @Override 2074 public int getActionTag() { 2075 return SET_ON_CHECKED_CHANGE_RESPONSE_TAG; 2076 } 2077 } 2078 2079 /** @hide **/ 2080 public static Rect getSourceBounds(View v) { 2081 final float appScale = v.getContext().getResources() 2082 .getCompatibilityInfo().applicationScale; 2083 final int[] pos = new int[2]; 2084 v.getLocationOnScreen(pos); 2085 2086 final Rect rect = new Rect(); 2087 rect.left = (int) (pos[0] * appScale + 0.5f); 2088 rect.top = (int) (pos[1] * appScale + 0.5f); 2089 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 2090 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 2091 return rect; 2092 } 2093 2094 @Nullable 2095 private static Class<?> getParameterType(int type) { 2096 switch (type) { 2097 case BaseReflectionAction.BOOLEAN: 2098 return boolean.class; 2099 case BaseReflectionAction.BYTE: 2100 return byte.class; 2101 case BaseReflectionAction.SHORT: 2102 return short.class; 2103 case BaseReflectionAction.INT: 2104 return int.class; 2105 case BaseReflectionAction.LONG: 2106 return long.class; 2107 case BaseReflectionAction.FLOAT: 2108 return float.class; 2109 case BaseReflectionAction.DOUBLE: 2110 return double.class; 2111 case BaseReflectionAction.CHAR: 2112 return char.class; 2113 case BaseReflectionAction.STRING: 2114 return String.class; 2115 case BaseReflectionAction.CHAR_SEQUENCE: 2116 return CharSequence.class; 2117 case BaseReflectionAction.URI: 2118 return Uri.class; 2119 case BaseReflectionAction.BITMAP: 2120 return Bitmap.class; 2121 case BaseReflectionAction.BUNDLE: 2122 return Bundle.class; 2123 case BaseReflectionAction.INTENT: 2124 return Intent.class; 2125 case BaseReflectionAction.COLOR_STATE_LIST: 2126 return ColorStateList.class; 2127 case BaseReflectionAction.ICON: 2128 return Icon.class; 2129 case BaseReflectionAction.BLEND_MODE: 2130 return BlendMode.class; 2131 default: 2132 return null; 2133 } 2134 } 2135 2136 @Nullable 2137 private static MethodHandle getMethod(View view, String methodName, Class<?> paramType, 2138 boolean async) { 2139 MethodArgs result; 2140 Class<? extends View> klass = view.getClass(); 2141 2142 synchronized (sMethods) { 2143 // The key is defined by the view class, param class and method name. 2144 sLookupKey.set(klass, paramType, methodName); 2145 result = sMethods.get(sLookupKey); 2146 2147 if (result == null) { 2148 Method method; 2149 try { 2150 if (paramType == null) { 2151 method = klass.getMethod(methodName); 2152 } else { 2153 method = klass.getMethod(methodName, paramType); 2154 } 2155 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 2156 throw new ActionException("view: " + klass.getName() 2157 + " can't use method with RemoteViews: " 2158 + methodName + getParameters(paramType)); 2159 } 2160 2161 result = new MethodArgs(); 2162 result.syncMethod = MethodHandles.publicLookup().unreflect(method); 2163 result.asyncMethodName = 2164 method.getAnnotation(RemotableViewMethod.class).asyncImpl(); 2165 } catch (NoSuchMethodException | IllegalAccessException ex) { 2166 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 2167 + methodName + getParameters(paramType)); 2168 } 2169 2170 MethodKey key = new MethodKey(); 2171 key.set(klass, paramType, methodName); 2172 sMethods.put(key, result); 2173 } 2174 2175 if (!async) { 2176 return result.syncMethod; 2177 } 2178 // Check this so see if async method is implemented or not. 2179 if (result.asyncMethodName.isEmpty()) { 2180 return null; 2181 } 2182 // Async method is lazily loaded. If it is not yet loaded, load now. 2183 if (result.asyncMethod == null) { 2184 MethodType asyncType = result.syncMethod.type() 2185 .dropParameterTypes(0, 1).changeReturnType(Runnable.class); 2186 try { 2187 result.asyncMethod = MethodHandles.publicLookup().findVirtual( 2188 klass, result.asyncMethodName, asyncType); 2189 } catch (NoSuchMethodException | IllegalAccessException ex) { 2190 throw new ActionException("Async implementation declared as " 2191 + result.asyncMethodName + " but not defined for " + methodName 2192 + ": public Runnable " + result.asyncMethodName + " (" 2193 + TextUtils.join(",", asyncType.parameterArray()) + ")"); 2194 } 2195 } 2196 return result.asyncMethod; 2197 } 2198 } 2199 2200 private static String getParameters(Class<?> paramType) { 2201 if (paramType == null) return "()"; 2202 return "(" + paramType + ")"; 2203 } 2204 2205 /** 2206 * Equivalent to calling 2207 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 2208 * on the {@link Drawable} of a given view. 2209 * <p> 2210 * The operation will be performed on the {@link Drawable} returned by the 2211 * target {@link View#getBackground()} by default. If targetBackground is false, 2212 * we assume the target is an {@link ImageView} and try applying the operations 2213 * to {@link ImageView#getDrawable()}. 2214 * <p> 2215 */ 2216 private static class SetDrawableTint extends Action { 2217 boolean mTargetBackground; 2218 @ColorInt int mColorFilter; 2219 PorterDuff.Mode mFilterMode; 2220 2221 SetDrawableTint(@IdRes int id, boolean targetBackground, 2222 @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) { 2223 this.mViewId = id; 2224 this.mTargetBackground = targetBackground; 2225 this.mColorFilter = colorFilter; 2226 this.mFilterMode = mode; 2227 } 2228 2229 SetDrawableTint(Parcel parcel) { 2230 mViewId = parcel.readInt(); 2231 mTargetBackground = parcel.readInt() != 0; 2232 mColorFilter = parcel.readInt(); 2233 mFilterMode = PorterDuff.intToMode(parcel.readInt()); 2234 } 2235 2236 public void writeToParcel(Parcel dest, int flags) { 2237 dest.writeInt(mViewId); 2238 dest.writeInt(mTargetBackground ? 1 : 0); 2239 dest.writeInt(mColorFilter); 2240 dest.writeInt(PorterDuff.modeToInt(mFilterMode)); 2241 } 2242 2243 @Override 2244 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 2245 final View target = root.findViewById(mViewId); 2246 if (target == null) return; 2247 2248 // Pick the correct drawable to modify for this view 2249 Drawable targetDrawable = null; 2250 if (mTargetBackground) { 2251 targetDrawable = target.getBackground(); 2252 } else if (target instanceof ImageView) { 2253 ImageView imageView = (ImageView) target; 2254 targetDrawable = imageView.getDrawable(); 2255 } 2256 2257 if (targetDrawable != null) { 2258 targetDrawable.mutate().setColorFilter(mColorFilter, mFilterMode); 2259 } 2260 } 2261 2262 @Override 2263 public int getActionTag() { 2264 return SET_DRAWABLE_TINT_TAG; 2265 } 2266 2267 @Override 2268 public boolean canWriteToProto() { 2269 return true; 2270 } 2271 2272 @Override 2273 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 2274 final long token = out.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); 2275 out.write(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, 2276 appResources.getResourceName(mViewId)); 2277 out.write(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, mColorFilter); 2278 out.write(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, 2279 PorterDuff.modeToInt(mFilterMode)); 2280 out.write(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, mTargetBackground); 2281 out.end(token); 2282 } 2283 2284 public static PendingResources<Action> createFromProto(ProtoInputStream in) 2285 throws Exception { 2286 final LongSparseArray<Object> values = new LongSparseArray<>(); 2287 2288 final long token = in.start(RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION); 2289 while (in.nextField() != NO_MORE_FIELDS) { 2290 switch (in.getFieldNumber()) { 2291 case (int) RemoteViewsProto.SetDrawableTintAction.VIEW_ID: 2292 values.put(RemoteViewsProto.SetDrawableTintAction.VIEW_ID, 2293 in.readString(RemoteViewsProto.SetDrawableTintAction.VIEW_ID)); 2294 break; 2295 case (int) RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND: 2296 values.put(RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, 2297 in.readBoolean( 2298 RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND)); 2299 break; 2300 case (int) RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER: 2301 values.put(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 2302 in.readInt(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER)); 2303 break; 2304 case (int) RemoteViewsProto.SetDrawableTintAction.FILTER_MODE: 2305 values.put(RemoteViewsProto.SetDrawableTintAction.FILTER_MODE, 2306 PorterDuff.intToMode(in.readInt( 2307 RemoteViewsProto.SetDrawableTintAction.FILTER_MODE))); 2308 break; 2309 default: 2310 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 2311 + ProtoUtils.currentFieldToString(in)); 2312 } 2313 } 2314 in.end(token); 2315 2316 checkContainsKeys(values, new long[]{RemoteViewsProto.SetDrawableTintAction.VIEW_ID}); 2317 2318 return (context, resources, rootData, depth) -> { 2319 int viewId = getAsIdentifier(resources, values, 2320 RemoteViewsProto.SetDrawableTintAction.VIEW_ID); 2321 return new SetDrawableTint(viewId, (boolean) values.get( 2322 RemoteViewsProto.SetDrawableTintAction.TARGET_BACKGROUND, false), 2323 (int) values.get(RemoteViewsProto.SetDrawableTintAction.COLOR_FILTER, 0), 2324 (PorterDuff.Mode) values.get( 2325 RemoteViewsProto.SetDrawableTintAction.FILTER_MODE)); 2326 }; 2327 } 2328 } 2329 2330 /** 2331 * Equivalent to calling 2332 * {@link RippleDrawable#setColor(ColorStateList)}, 2333 * on the {@link Drawable} of a given view. 2334 * <p> 2335 * The operation will be performed on the {@link Drawable} returned by the 2336 * target {@link View#getBackground()}. 2337 * <p> 2338 */ 2339 private static class SetRippleDrawableColor extends Action { 2340 ColorStateList mColorStateList; 2341 2342 SetRippleDrawableColor(@IdRes int id, ColorStateList colorStateList) { 2343 this.mViewId = id; 2344 this.mColorStateList = colorStateList; 2345 } 2346 2347 SetRippleDrawableColor(Parcel parcel) { 2348 mViewId = parcel.readInt(); 2349 mColorStateList = parcel.readParcelable(null, android.content.res.ColorStateList.class); 2350 } 2351 2352 public void writeToParcel(Parcel dest, int flags) { 2353 dest.writeInt(mViewId); 2354 dest.writeParcelable(mColorStateList, 0); 2355 } 2356 2357 @Override 2358 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 2359 final View target = root.findViewById(mViewId); 2360 if (target == null) return; 2361 2362 // Pick the correct drawable to modify for this view 2363 Drawable targetDrawable = target.getBackground(); 2364 2365 if (targetDrawable instanceof RippleDrawable) { 2366 ((RippleDrawable) targetDrawable.mutate()).setColor(mColorStateList); 2367 } 2368 } 2369 2370 @Override 2371 public int getActionTag() { 2372 return SET_RIPPLE_DRAWABLE_COLOR_TAG; 2373 } 2374 2375 @Override 2376 public boolean canWriteToProto() { 2377 return true; 2378 } 2379 2380 @Override 2381 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 2382 final long token = out.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); 2383 out.write(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, 2384 appResources.getResourceName(mViewId)); 2385 writeColorStateListToProto(out, mColorStateList, 2386 RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST); 2387 out.end(token); 2388 } 2389 2390 public static PendingResources<Action> createFromProto(ProtoInputStream in) 2391 throws Exception { 2392 final LongSparseArray<Object> values = new LongSparseArray<>(); 2393 2394 final long token = in.start(RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION); 2395 while (in.nextField() != NO_MORE_FIELDS) { 2396 switch (in.getFieldNumber()) { 2397 case (int) RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID: 2398 values.put(RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, 2399 in.readString( 2400 RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID)); 2401 break; 2402 case (int) RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST: 2403 values.put(RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST, 2404 createColorStateListFromProto(in, 2405 RemoteViewsProto 2406 .SetRippleDrawableColorAction.COLOR_STATE_LIST)); 2407 break; 2408 default: 2409 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 2410 + ProtoUtils.currentFieldToString(in)); 2411 } 2412 } 2413 in.end(token); 2414 2415 checkContainsKeys(values, 2416 new long[]{RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID, 2417 RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST}); 2418 2419 return (context, resources, rootData, depth) -> { 2420 int viewId = getAsIdentifier(resources, values, 2421 RemoteViewsProto.SetRippleDrawableColorAction.VIEW_ID); 2422 return new SetRippleDrawableColor(viewId, (ColorStateList) values.get( 2423 RemoteViewsProto.SetRippleDrawableColorAction.COLOR_STATE_LIST)); 2424 }; 2425 } 2426 } 2427 2428 /** 2429 * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call 2430 * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change 2431 * unexpectedly. 2432 */ 2433 @Deprecated 2434 private final class ViewContentNavigation extends Action { 2435 final boolean mNext; 2436 2437 ViewContentNavigation(@IdRes int viewId, boolean next) { 2438 this.mViewId = viewId; 2439 this.mNext = next; 2440 } 2441 2442 ViewContentNavigation(Parcel in) { 2443 this.mViewId = in.readInt(); 2444 this.mNext = in.readBoolean(); 2445 } 2446 2447 public void writeToParcel(Parcel out, int flags) { 2448 out.writeInt(this.mViewId); 2449 out.writeBoolean(this.mNext); 2450 } 2451 2452 @Override 2453 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 2454 final View view = root.findViewById(mViewId); 2455 if (view == null) return; 2456 2457 try { 2458 getMethod(view, 2459 mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view); 2460 } catch (Throwable ex) { 2461 throw new ActionException(ex); 2462 } 2463 } 2464 2465 public int mergeBehavior() { 2466 return MERGE_IGNORE; 2467 } 2468 2469 @Override 2470 public int getActionTag() { 2471 return VIEW_CONTENT_NAVIGATION_TAG; 2472 } 2473 } 2474 2475 /** 2476 * @hide 2477 */ 2478 @NonNull BitmapCache getBitmapCache() { 2479 return mBitmapCache; 2480 } 2481 2482 static class BitmapCache { 2483 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 2484 ArrayList<Bitmap> mBitmaps; 2485 SparseIntArray mBitmapHashes; 2486 long mBitmapMemory = -1; 2487 2488 public BitmapCache() { 2489 mBitmaps = new ArrayList<>(); 2490 mBitmapHashes = new SparseIntArray(); 2491 } 2492 2493 public BitmapCache(Parcel source) { 2494 mBitmaps = source.createTypedArrayList(Bitmap.CREATOR); 2495 mBitmapHashes = new SparseIntArray(); 2496 for (int i = 0; i < mBitmaps.size(); i++) { 2497 Bitmap b = mBitmaps.get(i); 2498 if (b != null) { 2499 mBitmapHashes.put(b.hashCode(), i); 2500 } 2501 } 2502 } 2503 2504 BitmapCache(BitmapCache other) { 2505 mBitmaps = new ArrayList<>(other.mBitmaps); 2506 mBitmapHashes = other.mBitmapHashes.clone(); 2507 } 2508 2509 public int getBitmapId(Bitmap b) { 2510 if (b == null) { 2511 return -1; 2512 } else { 2513 int hash = b.hashCode(); 2514 int hashId = mBitmapHashes.get(hash, -1); 2515 if (hashId != -1) { 2516 return hashId; 2517 } else { 2518 if (b.isMutable()) { 2519 b = b.asShared(); 2520 } 2521 mBitmaps.add(b); 2522 mBitmapHashes.put(hash, mBitmaps.size() - 1); 2523 mBitmapMemory = -1; 2524 return (mBitmaps.size() - 1); 2525 } 2526 } 2527 } 2528 2529 @Nullable 2530 public Bitmap getBitmapForId(int id) { 2531 if (id == -1 || id >= mBitmaps.size()) { 2532 return null; 2533 } 2534 return mBitmaps.get(id); 2535 } 2536 2537 public void writeBitmapsToParcel(Parcel dest, int flags) { 2538 dest.writeTypedList(mBitmaps, flags); 2539 } 2540 2541 public void writeBitmapsToProto(ProtoOutputStream out) { 2542 for (int i = 0; i < mBitmaps.size(); i++) { 2543 final Bitmap bitmap = mBitmaps.get(i); 2544 final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 2545 bitmap.compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, bytes); 2546 out.write(RemoteViewsProto.BITMAP_CACHE, bytes.toByteArray()); 2547 } 2548 } 2549 2550 public long getBitmapMemory() { 2551 if (mBitmapMemory < 0) { 2552 mBitmapMemory = 0; 2553 int count = mBitmaps.size(); 2554 for (int i = 0; i < count; i++) { 2555 mBitmapMemory += mBitmaps.get(i).getAllocationByteCount(); 2556 } 2557 } 2558 return mBitmapMemory; 2559 } 2560 2561 public void mergeWithCache(BitmapCache other) { 2562 for (int i = 0; i < other.mBitmaps.size(); i++) { 2563 getBitmapId(other.mBitmaps.get(i)); 2564 } 2565 } 2566 } 2567 2568 private class BitmapReflectionAction extends Action { 2569 int mBitmapId; 2570 @UnsupportedAppUsage 2571 Bitmap mBitmap; 2572 @UnsupportedAppUsage 2573 String mMethodName; 2574 2575 BitmapReflectionAction(@IdRes int viewId, String methodName, Bitmap bitmap) { 2576 this.mBitmap = bitmap; 2577 this.mViewId = viewId; 2578 this.mMethodName = methodName; 2579 mBitmapId = mBitmapCache.getBitmapId(bitmap); 2580 } 2581 2582 BitmapReflectionAction(Parcel in) { 2583 mViewId = in.readInt(); 2584 mMethodName = in.readString8(); 2585 mBitmapId = in.readInt(); 2586 mBitmap = mBitmapCache.getBitmapForId(mBitmapId); 2587 } 2588 2589 @Override 2590 public void writeToParcel(Parcel dest, int flags) { 2591 dest.writeInt(mViewId); 2592 dest.writeString8(mMethodName); 2593 dest.writeInt(mBitmapId); 2594 } 2595 2596 @Override 2597 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) 2598 throws ActionException { 2599 ReflectionAction ra = new ReflectionAction(mViewId, mMethodName, 2600 BaseReflectionAction.BITMAP, 2601 mBitmap); 2602 ra.apply(root, rootParent, params); 2603 } 2604 2605 @Override 2606 public void setHierarchyRootData(HierarchyRootData rootData) { 2607 mBitmapId = rootData.mBitmapCache.getBitmapId(mBitmap); 2608 } 2609 2610 @Override 2611 public int getActionTag() { 2612 return BITMAP_REFLECTION_ACTION_TAG; 2613 } 2614 2615 @Override 2616 public boolean canWriteToProto() { 2617 return true; 2618 } 2619 2620 @Override 2621 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 2622 final long token = out.start(RemoteViewsProto.Action.BITMAP_REFLECTION_ACTION); 2623 out.write(RemoteViewsProto.BitmapReflectionAction.VIEW_ID, 2624 appResources.getResourceName(mViewId)); 2625 out.write(RemoteViewsProto.BitmapReflectionAction.METHOD_NAME, mMethodName); 2626 out.write(RemoteViewsProto.BitmapReflectionAction.BITMAP_ID, mBitmapId); 2627 out.end(token); 2628 } 2629 } 2630 2631 private PendingResources<Action> createFromBitmapReflectionActionFromProto(ProtoInputStream in) 2632 throws Exception { 2633 final LongSparseArray<Object> values = new LongSparseArray<>(); 2634 2635 final long token = in.start(RemoteViewsProto.Action.BITMAP_REFLECTION_ACTION); 2636 while (in.nextField() != NO_MORE_FIELDS) { 2637 switch (in.getFieldNumber()) { 2638 case (int) RemoteViewsProto.BitmapReflectionAction.VIEW_ID: 2639 values.put(RemoteViewsProto.BitmapReflectionAction.VIEW_ID, 2640 in.readString(RemoteViewsProto.BitmapReflectionAction.VIEW_ID)); 2641 break; 2642 case (int) RemoteViewsProto.BitmapReflectionAction.METHOD_NAME: 2643 values.put(RemoteViewsProto.BitmapReflectionAction.METHOD_NAME, 2644 in.readString(RemoteViewsProto.BitmapReflectionAction.METHOD_NAME)); 2645 break; 2646 case (int) RemoteViewsProto.BitmapReflectionAction.BITMAP_ID: 2647 values.put(RemoteViewsProto.BitmapReflectionAction.BITMAP_ID, 2648 in.readInt(RemoteViewsProto.BitmapReflectionAction.BITMAP_ID)); 2649 break; 2650 default: 2651 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 2652 + ProtoUtils.currentFieldToString(in)); 2653 } 2654 } 2655 in.end(token); 2656 2657 checkContainsKeys(values, new long[]{RemoteViewsProto.BitmapReflectionAction.VIEW_ID, 2658 RemoteViewsProto.BitmapReflectionAction.METHOD_NAME}); 2659 2660 return (context, resources, rootData, depth) -> { 2661 int viewId = getAsIdentifier(resources, values, 2662 RemoteViewsProto.BitmapReflectionAction.VIEW_ID); 2663 return new BitmapReflectionAction(viewId, 2664 (String) values.get(RemoteViewsProto.BitmapReflectionAction.METHOD_NAME), 2665 rootData.mBitmapCache.getBitmapForId( 2666 (int) values.get(RemoteViewsProto.BitmapReflectionAction.BITMAP_ID, 2667 0))); 2668 }; 2669 2670 } 2671 2672 /** 2673 * Base class for the reflection actions. 2674 */ 2675 private abstract static class BaseReflectionAction extends Action { 2676 static final int BOOLEAN = 1; 2677 static final int BYTE = 2; 2678 static final int SHORT = 3; 2679 static final int INT = 4; 2680 static final int LONG = 5; 2681 static final int FLOAT = 6; 2682 static final int DOUBLE = 7; 2683 static final int CHAR = 8; 2684 static final int STRING = 9; 2685 static final int CHAR_SEQUENCE = 10; 2686 static final int URI = 11; 2687 // BITMAP actions are never stored in the list of actions. They are only used locally 2688 // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache. 2689 static final int BITMAP = 12; 2690 static final int BUNDLE = 13; 2691 static final int INTENT = 14; 2692 static final int COLOR_STATE_LIST = 15; 2693 static final int ICON = 16; 2694 static final int BLEND_MODE = 17; 2695 2696 @UnsupportedAppUsage 2697 String mMethodName; 2698 int mType; 2699 2700 BaseReflectionAction(@IdRes int viewId, String methodName, int type) { 2701 this.mViewId = viewId; 2702 this.mMethodName = methodName; 2703 this.mType = type; 2704 } 2705 2706 BaseReflectionAction(Parcel in) { 2707 this.mViewId = in.readInt(); 2708 this.mMethodName = in.readString8(); 2709 this.mType = in.readInt(); 2710 //noinspection ConstantIfStatement 2711 if (false) { 2712 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.mViewId) 2713 + " methodName=" + this.mMethodName + " type=" + this.mType); 2714 } 2715 } 2716 2717 public void writeToParcel(Parcel out, int flags) { 2718 out.writeInt(this.mViewId); 2719 out.writeString8(this.mMethodName); 2720 out.writeInt(this.mType); 2721 } 2722 2723 /** 2724 * Returns the value to use as parameter for the method. 2725 * 2726 * The view might be passed as {@code null} if the parameter value is requested outside of 2727 * inflation. If the parameter cannot be determined at that time, the method should return 2728 * {@code null} but not raise any exception. 2729 */ 2730 @Nullable 2731 protected abstract Object getParameterValue(@Nullable View view) throws ActionException; 2732 2733 @Override 2734 public final void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 2735 final View view = root.findViewById(mViewId); 2736 if (view == null) return; 2737 2738 Class<?> param = getParameterType(this.mType); 2739 if (param == null) { 2740 throw new ActionException("bad type: " + this.mType); 2741 } 2742 Object value = getParameterValue(view); 2743 try { 2744 getMethod(view, this.mMethodName, param, false /* async */).invoke(view, value); 2745 } catch (Throwable ex) { 2746 throw new ActionException(ex); 2747 } 2748 } 2749 2750 @Override 2751 public final Action initActionAsync(ViewTree root, ViewGroup rootParent, 2752 ActionApplyParams params) { 2753 final View view = root.findViewById(mViewId); 2754 if (view == null) return ACTION_NOOP; 2755 2756 Class<?> param = getParameterType(this.mType); 2757 if (param == null) { 2758 throw new ActionException("bad type: " + this.mType); 2759 } 2760 2761 Object value = getParameterValue(view); 2762 try { 2763 MethodHandle method = getMethod(view, this.mMethodName, param, true /* async */); 2764 // Upload the bitmap to GPU if the parameter is of type Bitmap or Icon. 2765 // Since bitmaps in framework are seldomly modified, this is supposed to accelerate 2766 // the operations. 2767 if (value instanceof Bitmap bitmap) { 2768 bitmap.prepareToDraw(); 2769 } 2770 2771 if (value instanceof Icon icon 2772 && (icon.getType() == Icon.TYPE_BITMAP 2773 || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP)) { 2774 Bitmap bitmap = icon.getBitmap(); 2775 if (bitmap != null) { 2776 bitmap.prepareToDraw(); 2777 } 2778 } 2779 2780 if (method != null) { 2781 Runnable endAction = (Runnable) method.invoke(view, value); 2782 if (endAction == null) { 2783 return ACTION_NOOP; 2784 } 2785 // Special case view stub 2786 if (endAction instanceof ViewStub.ViewReplaceRunnable) { 2787 root.createTree(); 2788 // Replace child tree 2789 root.findViewTreeById(mViewId).replaceView( 2790 ((ViewStub.ViewReplaceRunnable) endAction).view); 2791 } 2792 return new RunnableAction(endAction); 2793 } 2794 } catch (Throwable ex) { 2795 throw new ActionException(ex); 2796 } 2797 2798 return this; 2799 } 2800 2801 public final int mergeBehavior() { 2802 // smoothScrollBy is cumulative, everything else overwites. 2803 if (mMethodName.equals("smoothScrollBy")) { 2804 return MERGE_APPEND; 2805 } else { 2806 return MERGE_REPLACE; 2807 } 2808 } 2809 2810 @Override 2811 public final String getUniqueKey() { 2812 // Each type of reflection action corresponds to a setter, so each should be seen as 2813 // unique from the standpoint of merging. 2814 return super.getUniqueKey() + this.mMethodName + this.mType; 2815 } 2816 2817 @Override 2818 public final boolean prefersAsyncApply() { 2819 return this.mType == URI || this.mType == ICON; 2820 } 2821 2822 @Override 2823 public void visitUris(@NonNull Consumer<Uri> visitor) { 2824 switch (this.mType) { 2825 case URI: 2826 final Uri uri = (Uri) getParameterValue(null); 2827 if (uri != null) visitor.accept(uri); 2828 break; 2829 case ICON: 2830 final Icon icon = (Icon) getParameterValue(null); 2831 if (icon != null) visitIconUri(icon, visitor); 2832 break; 2833 // TODO(b/281044385): Should we do anything about type BUNDLE? 2834 } 2835 } 2836 2837 @Override 2838 public void visitIcons(@NonNull Consumer<Icon> visitor) { 2839 if (mType == ICON && getParameterValue(null) instanceof Icon icon) { 2840 visitor.accept(icon); 2841 } 2842 } 2843 } 2844 2845 /** Class for the reflection actions. */ 2846 private static final class ReflectionAction extends BaseReflectionAction { 2847 @UnsupportedAppUsage 2848 Object mValue; 2849 2850 ReflectionAction(@IdRes int viewId, String methodName, int type, Object value) { 2851 super(viewId, methodName, type); 2852 this.mValue = value; 2853 } 2854 2855 ReflectionAction(Parcel in) { 2856 super(in); 2857 // For some values that may have been null, we first check a flag to see if they were 2858 // written to the parcel. 2859 switch (this.mType) { 2860 case BOOLEAN: 2861 this.mValue = in.readBoolean(); 2862 break; 2863 case BYTE: 2864 this.mValue = in.readByte(); 2865 break; 2866 case SHORT: 2867 this.mValue = (short) in.readInt(); 2868 break; 2869 case INT: 2870 this.mValue = in.readInt(); 2871 break; 2872 case LONG: 2873 this.mValue = in.readLong(); 2874 break; 2875 case FLOAT: 2876 this.mValue = in.readFloat(); 2877 break; 2878 case DOUBLE: 2879 this.mValue = in.readDouble(); 2880 break; 2881 case CHAR: 2882 this.mValue = (char) in.readInt(); 2883 break; 2884 case STRING: 2885 this.mValue = in.readString8(); 2886 break; 2887 case CHAR_SEQUENCE: 2888 this.mValue = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 2889 break; 2890 case URI: 2891 this.mValue = in.readTypedObject(Uri.CREATOR); 2892 break; 2893 case BITMAP: 2894 this.mValue = in.readTypedObject(Bitmap.CREATOR); 2895 break; 2896 case BUNDLE: 2897 // Because we use Parcel.allowSquashing() when writing, and that affects 2898 // how the contents of Bundles are written, we need to ensure the bundle is 2899 // unparceled immediately, not lazily. Setting a custom ReadWriteHelper 2900 // just happens to have that effect on Bundle.readFromParcel(). 2901 // TODO(b/212731590): build this state tracking into Bundle 2902 if (in.hasReadWriteHelper()) { 2903 this.mValue = in.readBundle(); 2904 } else { 2905 in.setReadWriteHelper(ALTERNATIVE_DEFAULT); 2906 this.mValue = in.readBundle(); 2907 in.setReadWriteHelper(null); 2908 } 2909 break; 2910 case INTENT: 2911 this.mValue = in.readTypedObject(Intent.CREATOR); 2912 break; 2913 case COLOR_STATE_LIST: 2914 this.mValue = in.readTypedObject(ColorStateList.CREATOR); 2915 break; 2916 case ICON: 2917 this.mValue = in.readTypedObject(Icon.CREATOR); 2918 break; 2919 case BLEND_MODE: 2920 this.mValue = BlendMode.fromValue(in.readInt()); 2921 break; 2922 default: 2923 break; 2924 } 2925 } 2926 2927 public void writeToParcel(Parcel out, int flags) { 2928 super.writeToParcel(out, flags); 2929 // For some values which are null, we record an integer flag to indicate whether 2930 // we have written a valid value to the parcel. 2931 switch (this.mType) { 2932 case BOOLEAN: 2933 out.writeBoolean((Boolean) this.mValue); 2934 break; 2935 case BYTE: 2936 out.writeByte((Byte) this.mValue); 2937 break; 2938 case SHORT: 2939 out.writeInt((Short) this.mValue); 2940 break; 2941 case INT: 2942 out.writeInt((Integer) this.mValue); 2943 break; 2944 case LONG: 2945 out.writeLong((Long) this.mValue); 2946 break; 2947 case FLOAT: 2948 out.writeFloat((Float) this.mValue); 2949 break; 2950 case DOUBLE: 2951 out.writeDouble((Double) this.mValue); 2952 break; 2953 case CHAR: 2954 out.writeInt((int) ((Character) this.mValue).charValue()); 2955 break; 2956 case STRING: 2957 out.writeString8((String) this.mValue); 2958 break; 2959 case CHAR_SEQUENCE: 2960 TextUtils.writeToParcel((CharSequence) this.mValue, out, flags); 2961 break; 2962 case BUNDLE: 2963 out.writeBundle((Bundle) this.mValue); 2964 break; 2965 case BLEND_MODE: 2966 out.writeInt(BlendMode.toValue((BlendMode) this.mValue)); 2967 break; 2968 case URI: 2969 case BITMAP: 2970 case INTENT: 2971 case COLOR_STATE_LIST: 2972 case ICON: 2973 out.writeTypedObject((Parcelable) this.mValue, flags); 2974 break; 2975 default: 2976 break; 2977 } 2978 } 2979 2980 @Nullable 2981 @Override 2982 protected Object getParameterValue(@Nullable View view) throws ActionException { 2983 return this.mValue; 2984 } 2985 2986 @Override 2987 public int getActionTag() { 2988 return REFLECTION_ACTION_TAG; 2989 } 2990 2991 @Override 2992 public boolean canWriteToProto() { 2993 return true; 2994 } 2995 2996 @Override 2997 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 2998 final long token = out.start(RemoteViewsProto.Action.REFLECTION_ACTION); 2999 out.write(RemoteViewsProto.ReflectionAction.VIEW_ID, 3000 appResources.getResourceName(mViewId)); 3001 out.write(RemoteViewsProto.ReflectionAction.METHOD_NAME, mMethodName); 3002 out.write(RemoteViewsProto.ReflectionAction.PARAMETER_TYPE, mType); 3003 if (this.mValue != null) { 3004 switch (this.mType) { 3005 case BOOLEAN: 3006 // ProtoOutputStream will omit this write if the value is false 3007 out.write(RemoteViewsProto.ReflectionAction.BOOLEAN_VALUE, 3008 (boolean) this.mValue); 3009 break; 3010 case BYTE: 3011 out.write(RemoteViewsProto.ReflectionAction.BYTE_VALUE, 3012 new byte[]{(byte) this.mValue}); 3013 break; 3014 case SHORT: 3015 out.write(RemoteViewsProto.ReflectionAction.SHORT_VALUE, 3016 (short) this.mValue); 3017 break; 3018 case INT: 3019 out.write(RemoteViewsProto.ReflectionAction.INT_VALUE, (int) this.mValue); 3020 break; 3021 case LONG: 3022 out.write(RemoteViewsProto.ReflectionAction.LONG_VALUE, (long) this.mValue); 3023 break; 3024 case FLOAT: 3025 out.write(RemoteViewsProto.ReflectionAction.FLOAT_VALUE, 3026 (float) this.mValue); 3027 break; 3028 case DOUBLE: 3029 out.write(RemoteViewsProto.ReflectionAction.DOUBLE_VALUE, 3030 (double) this.mValue); 3031 break; 3032 case CHAR: 3033 out.write(RemoteViewsProto.ReflectionAction.CHAR_VALUE, 3034 (Character) this.mValue); 3035 break; 3036 case STRING: 3037 out.write(RemoteViewsProto.ReflectionAction.STRING_VALUE, 3038 (String) this.mValue); 3039 break; 3040 case CHAR_SEQUENCE: 3041 long csToken = out.start( 3042 RemoteViewsProto.ReflectionAction.CHAR_SEQUENCE_VALUE); 3043 RemoteViewsSerializers.writeCharSequenceToProto(out, 3044 (CharSequence) this.mValue); 3045 out.end(csToken); 3046 break; 3047 case URI: 3048 out.write(RemoteViewsProto.ReflectionAction.URI_VALUE, 3049 ((Uri) this.mValue).toString()); 3050 break; 3051 case BITMAP: 3052 final ByteArrayOutputStream bytes = new ByteArrayOutputStream(); 3053 ((Bitmap) this.mValue).compress(Bitmap.CompressFormat.WEBP_LOSSLESS, 100, 3054 bytes); 3055 out.write(RemoteViewsProto.ReflectionAction.BITMAP_VALUE, 3056 bytes.toByteArray()); 3057 break; 3058 case BLEND_MODE: 3059 out.write(RemoteViewsProto.ReflectionAction.BLEND_MODE_VALUE, 3060 BlendMode.toValue((BlendMode) this.mValue)); 3061 break; 3062 case COLOR_STATE_LIST: 3063 writeColorStateListToProto(out, (ColorStateList) this.mValue, 3064 RemoteViewsProto.ReflectionAction.COLOR_STATE_LIST_VALUE); 3065 break; 3066 case ICON: 3067 writeIconToProto(out, appResources, (Icon) this.mValue, 3068 RemoteViewsProto.ReflectionAction.ICON_VALUE); 3069 break; 3070 case BUNDLE: 3071 case INTENT: 3072 default: 3073 break; 3074 } 3075 } 3076 out.end(token); 3077 } 3078 3079 public static PendingResources<Action> createFromProto(ProtoInputStream in) 3080 throws Exception { 3081 final LongSparseArray<Object> values = new LongSparseArray<>(); 3082 3083 final long token = in.start(RemoteViewsProto.Action.REFLECTION_ACTION); 3084 while (in.nextField() != NO_MORE_FIELDS) { 3085 switch (in.getFieldNumber()) { 3086 case (int) RemoteViewsProto.ReflectionAction.VIEW_ID: 3087 values.put(RemoteViewsProto.ReflectionAction.VIEW_ID, 3088 in.readString(RemoteViewsProto.ReflectionAction.VIEW_ID)); 3089 break; 3090 case (int) RemoteViewsProto.ReflectionAction.METHOD_NAME: 3091 values.put(RemoteViewsProto.ReflectionAction.METHOD_NAME, 3092 in.readString(RemoteViewsProto.ReflectionAction.METHOD_NAME)); 3093 break; 3094 case (int) RemoteViewsProto.ReflectionAction.PARAMETER_TYPE: 3095 values.put(RemoteViewsProto.ReflectionAction.PARAMETER_TYPE, 3096 in.readInt(RemoteViewsProto.ReflectionAction.PARAMETER_TYPE)); 3097 break; 3098 case (int) RemoteViewsProto.ReflectionAction.BOOLEAN_VALUE: 3099 values.put(RemoteViewsProto.ReflectionAction.BOOLEAN_VALUE, 3100 in.readBoolean(RemoteViewsProto.ReflectionAction.BOOLEAN_VALUE)); 3101 break; 3102 case (int) RemoteViewsProto.ReflectionAction.BYTE_VALUE: 3103 values.put(RemoteViewsProto.ReflectionAction.BYTE_VALUE, 3104 in.readBytes(RemoteViewsProto.ReflectionAction.BYTE_VALUE)); 3105 break; 3106 case (int) RemoteViewsProto.ReflectionAction.SHORT_VALUE: 3107 values.put(RemoteViewsProto.ReflectionAction.SHORT_VALUE, 3108 (short) in.readInt(RemoteViewsProto.ReflectionAction.SHORT_VALUE)); 3109 break; 3110 case (int) RemoteViewsProto.ReflectionAction.INT_VALUE: 3111 values.put(RemoteViewsProto.ReflectionAction.INT_VALUE, 3112 in.readInt(RemoteViewsProto.ReflectionAction.INT_VALUE)); 3113 break; 3114 case (int) RemoteViewsProto.ReflectionAction.LONG_VALUE: 3115 values.put(RemoteViewsProto.ReflectionAction.LONG_VALUE, 3116 in.readLong(RemoteViewsProto.ReflectionAction.LONG_VALUE)); 3117 break; 3118 case (int) RemoteViewsProto.ReflectionAction.FLOAT_VALUE: 3119 values.put(RemoteViewsProto.ReflectionAction.FLOAT_VALUE, 3120 in.readFloat(RemoteViewsProto.ReflectionAction.FLOAT_VALUE)); 3121 break; 3122 case (int) RemoteViewsProto.ReflectionAction.DOUBLE_VALUE: 3123 values.put(RemoteViewsProto.ReflectionAction.DOUBLE_VALUE, 3124 in.readDouble(RemoteViewsProto.ReflectionAction.DOUBLE_VALUE)); 3125 break; 3126 case (int) RemoteViewsProto.ReflectionAction.CHAR_VALUE: 3127 values.put(RemoteViewsProto.ReflectionAction.CHAR_VALUE, 3128 (char) in.readInt(RemoteViewsProto.ReflectionAction.CHAR_VALUE)); 3129 break; 3130 case (int) RemoteViewsProto.ReflectionAction.STRING_VALUE: 3131 values.put(RemoteViewsProto.ReflectionAction.STRING_VALUE, 3132 in.readString(RemoteViewsProto.ReflectionAction.STRING_VALUE)); 3133 break; 3134 case (int) RemoteViewsProto.ReflectionAction.CHAR_SEQUENCE_VALUE: 3135 values.put(RemoteViewsProto.ReflectionAction.CHAR_SEQUENCE_VALUE, 3136 createCharSequenceFromProto(in, 3137 RemoteViewsProto.ReflectionAction.CHAR_SEQUENCE_VALUE)); 3138 break; 3139 case (int) RemoteViewsProto.ReflectionAction.URI_VALUE: 3140 values.put(RemoteViewsProto.ReflectionAction.URI_VALUE, 3141 in.readString(RemoteViewsProto.ReflectionAction.URI_VALUE)); 3142 break; 3143 case (int) RemoteViewsProto.ReflectionAction.BITMAP_VALUE: 3144 byte[] bitmapData = in.readBytes( 3145 RemoteViewsProto.ReflectionAction.BITMAP_VALUE); 3146 values.put(RemoteViewsProto.ReflectionAction.BITMAP_VALUE, 3147 BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length)); 3148 break; 3149 case (int) RemoteViewsProto.ReflectionAction.COLOR_STATE_LIST_VALUE: 3150 values.put(RemoteViewsProto.ReflectionAction.COLOR_STATE_LIST_VALUE, 3151 createColorStateListFromProto(in, 3152 RemoteViewsProto.ReflectionAction.COLOR_STATE_LIST_VALUE)); 3153 break; 3154 case (int) RemoteViewsProto.ReflectionAction.ICON_VALUE: 3155 values.put(RemoteViewsProto.ReflectionAction.ICON_VALUE, 3156 createIconFromProto(in, 3157 RemoteViewsProto.ReflectionAction.ICON_VALUE)); 3158 break; 3159 case (int) RemoteViewsProto.ReflectionAction.BLEND_MODE_VALUE: 3160 values.put(RemoteViewsProto.ReflectionAction.BLEND_MODE_VALUE, 3161 BlendMode.fromValue(in.readInt( 3162 RemoteViewsProto.ReflectionAction.BLEND_MODE_VALUE))); 3163 break; 3164 default: 3165 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 3166 + ProtoUtils.currentFieldToString(in)); 3167 } 3168 } 3169 in.end(token); 3170 3171 checkContainsKeys(values, new long[]{RemoteViewsProto.ReflectionAction.VIEW_ID, 3172 RemoteViewsProto.ReflectionAction.METHOD_NAME, 3173 RemoteViewsProto.ReflectionAction.PARAMETER_TYPE}); 3174 3175 return (context, resources, rootData, depth) -> { 3176 int viewId = getAsIdentifier(resources, values, 3177 RemoteViewsProto.ReflectionAction.VIEW_ID); 3178 Object value = null; 3179 int parameterType = (int) values.get( 3180 RemoteViewsProto.ReflectionAction.PARAMETER_TYPE); 3181 switch (parameterType) { 3182 case BOOLEAN: 3183 value = (boolean) values.get( 3184 RemoteViewsProto.ReflectionAction.BOOLEAN_VALUE, false); 3185 break; 3186 case BYTE: 3187 byte[] bytes = (byte[]) values.get( 3188 RemoteViewsProto.ReflectionAction.BYTE_VALUE); 3189 if (bytes != null && bytes.length > 0) { 3190 value = bytes[0]; 3191 } 3192 break; 3193 case SHORT: 3194 value = (short) values.get(RemoteViewsProto.ReflectionAction.SHORT_VALUE, 3195 0); 3196 break; 3197 case INT: 3198 value = (int) values.get(RemoteViewsProto.ReflectionAction.INT_VALUE, 0); 3199 break; 3200 case LONG: 3201 value = (long) values.get(RemoteViewsProto.ReflectionAction.LONG_VALUE, 0); 3202 break; 3203 case FLOAT: 3204 value = (float) values.get(RemoteViewsProto.ReflectionAction.FLOAT_VALUE, 3205 0); 3206 break; 3207 case DOUBLE: 3208 value = (double) values.get(RemoteViewsProto.ReflectionAction.DOUBLE_VALUE, 3209 0); 3210 break; 3211 case CHAR: 3212 value = (char) values.get(RemoteViewsProto.ReflectionAction.CHAR_VALUE, 0); 3213 break; 3214 case STRING: 3215 value = (String) values.get(RemoteViewsProto.ReflectionAction.STRING_VALUE); 3216 break; 3217 case CHAR_SEQUENCE: 3218 value = (CharSequence) values.get( 3219 RemoteViewsProto.ReflectionAction.CHAR_SEQUENCE_VALUE); 3220 break; 3221 case URI: 3222 value = Uri.parse( 3223 (String) values.get(RemoteViewsProto.ReflectionAction.URI_VALUE)); 3224 break; 3225 case BITMAP: 3226 value = (Bitmap) values.get(RemoteViewsProto.ReflectionAction.BITMAP_VALUE); 3227 break; 3228 case BLEND_MODE: 3229 value = (BlendMode) values.get( 3230 RemoteViewsProto.ReflectionAction.BLEND_MODE_VALUE); 3231 break; 3232 case COLOR_STATE_LIST: 3233 value = (ColorStateList) values.get( 3234 RemoteViewsProto.ReflectionAction.COLOR_STATE_LIST_VALUE); 3235 break; 3236 case ICON: 3237 value = ((PendingResources<Icon>) values.get( 3238 RemoteViewsProto.ReflectionAction.ICON_VALUE)).create(context, 3239 resources, rootData, depth); 3240 break; 3241 case BUNDLE: 3242 case INTENT: 3243 default: 3244 // omit the action for unsupported parameter types 3245 return null; 3246 } 3247 return new ReflectionAction(viewId, 3248 (String) values.get(RemoteViewsProto.ReflectionAction.METHOD_NAME), 3249 parameterType, value); 3250 }; 3251 } 3252 } 3253 3254 private static final class ResourceReflectionAction extends BaseReflectionAction { 3255 static final int DIMEN_RESOURCE = 1; 3256 static final int COLOR_RESOURCE = 2; 3257 static final int STRING_RESOURCE = 3; 3258 3259 private final int mResourceType; 3260 private final int mResId; 3261 3262 ResourceReflectionAction(@IdRes int viewId, String methodName, int parameterType, 3263 int resourceType, int resId) { 3264 super(viewId, methodName, parameterType); 3265 this.mResourceType = resourceType; 3266 this.mResId = resId; 3267 } 3268 3269 ResourceReflectionAction(Parcel in) { 3270 super(in); 3271 this.mResourceType = in.readInt(); 3272 this.mResId = in.readInt(); 3273 } 3274 3275 @Override 3276 public void writeToParcel(Parcel dest, int flags) { 3277 super.writeToParcel(dest, flags); 3278 dest.writeInt(this.mResourceType); 3279 dest.writeInt(this.mResId); 3280 } 3281 3282 @Nullable 3283 @Override 3284 protected Object getParameterValue(@Nullable View view) throws ActionException { 3285 if (view == null) return null; 3286 3287 Resources resources = view.getContext().getResources(); 3288 try { 3289 switch (this.mResourceType) { 3290 case DIMEN_RESOURCE: 3291 switch (this.mType) { 3292 case BaseReflectionAction.INT: 3293 return mResId == 0 ? 0 : resources.getDimensionPixelSize(mResId); 3294 case BaseReflectionAction.FLOAT: 3295 return mResId == 0 ? 0f : resources.getDimension(mResId); 3296 default: 3297 throw new ActionException( 3298 "dimen resources must be used as INT or FLOAT, " 3299 + "not " + this.mType); 3300 } 3301 case COLOR_RESOURCE: 3302 switch (this.mType) { 3303 case BaseReflectionAction.INT: 3304 return mResId == 0 ? 0 : view.getContext().getColor(mResId); 3305 case BaseReflectionAction.COLOR_STATE_LIST: 3306 return mResId == 0 3307 ? null : view.getContext().getColorStateList(mResId); 3308 default: 3309 throw new ActionException( 3310 "color resources must be used as INT or COLOR_STATE_LIST," 3311 + " not " + this.mType); 3312 } 3313 case STRING_RESOURCE: 3314 switch (this.mType) { 3315 case BaseReflectionAction.CHAR_SEQUENCE: 3316 return mResId == 0 ? null : resources.getText(mResId); 3317 case BaseReflectionAction.STRING: 3318 return mResId == 0 ? null : resources.getString(mResId); 3319 default: 3320 throw new ActionException( 3321 "string resources must be used as STRING or CHAR_SEQUENCE," 3322 + " not " + this.mType); 3323 } 3324 default: 3325 throw new ActionException("unknown resource type: " + this.mResourceType); 3326 } 3327 } catch (ActionException ex) { 3328 throw ex; 3329 } catch (Throwable t) { 3330 throw new ActionException(t); 3331 } 3332 } 3333 3334 @Override 3335 public int getActionTag() { 3336 return RESOURCE_REFLECTION_ACTION_TAG; 3337 } 3338 3339 @Override 3340 public boolean canWriteToProto() { 3341 return true; 3342 } 3343 3344 @Override 3345 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 3346 final long token = out.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); 3347 out.write(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, 3348 appResources.getResourceName(mViewId)); 3349 out.write(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, mMethodName); 3350 out.write(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, mType); 3351 out.write(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, mResourceType); 3352 if (mResId != 0) { 3353 out.write(RemoteViewsProto.ResourceReflectionAction.RES_ID, 3354 appResources.getResourceName(mResId)); 3355 } 3356 out.end(token); 3357 } 3358 3359 public static PendingResources<Action> createFromProto(ProtoInputStream in) 3360 throws Exception { 3361 final LongSparseArray<Object> values = new LongSparseArray<>(); 3362 3363 final long token = in.start(RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION); 3364 while (in.nextField() != NO_MORE_FIELDS) { 3365 switch (in.getFieldNumber()) { 3366 case (int) RemoteViewsProto.ResourceReflectionAction.VIEW_ID: 3367 values.put(RemoteViewsProto.ResourceReflectionAction.VIEW_ID, 3368 in.readString(RemoteViewsProto.ResourceReflectionAction.VIEW_ID)); 3369 break; 3370 case (int) RemoteViewsProto.ResourceReflectionAction.METHOD_NAME: 3371 values.put(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, 3372 in.readString( 3373 RemoteViewsProto.ResourceReflectionAction.METHOD_NAME)); 3374 break; 3375 case (int) RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE: 3376 values.put(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, 3377 in.readInt( 3378 RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE)); 3379 break; 3380 case (int) RemoteViewsProto.ResourceReflectionAction.RES_ID: 3381 values.put(RemoteViewsProto.ResourceReflectionAction.RES_ID, 3382 in.readString(RemoteViewsProto.ResourceReflectionAction.RES_ID)); 3383 break; 3384 case (int) RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE: 3385 values.put(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE, 3386 in.readInt( 3387 RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE)); 3388 break; 3389 default: 3390 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 3391 + ProtoUtils.currentFieldToString(in)); 3392 } 3393 } 3394 in.end(token); 3395 3396 checkContainsKeys(values, new long[]{RemoteViewsProto.ResourceReflectionAction.VIEW_ID, 3397 RemoteViewsProto.ResourceReflectionAction.METHOD_NAME, 3398 RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE}); 3399 3400 return (context, resources, rootData, depth) -> { 3401 int viewId = getAsIdentifier(resources, values, 3402 RemoteViewsProto.ResourceReflectionAction.VIEW_ID); 3403 3404 int resId = (values.indexOfKey(RemoteViewsProto.ResourceReflectionAction.RES_ID) 3405 >= 0) ? getAsIdentifier(resources, values, 3406 RemoteViewsProto.ResourceReflectionAction.RES_ID) : 0; 3407 return new ResourceReflectionAction(viewId, 3408 (String) values.get(RemoteViewsProto.ResourceReflectionAction.METHOD_NAME), 3409 (int) values.get(RemoteViewsProto.ResourceReflectionAction.PARAMETER_TYPE), 3410 (int) values.get(RemoteViewsProto.ResourceReflectionAction.RESOURCE_TYPE, 3411 0), resId); 3412 }; 3413 } 3414 } 3415 3416 private static final class AttributeReflectionAction extends BaseReflectionAction { 3417 static final int DIMEN_RESOURCE = 1; 3418 static final int COLOR_RESOURCE = 2; 3419 static final int STRING_RESOURCE = 3; 3420 3421 private final int mResourceType; 3422 private final int mAttrId; 3423 3424 AttributeReflectionAction(@IdRes int viewId, String methodName, int parameterType, 3425 int resourceType, int attrId) { 3426 super(viewId, methodName, parameterType); 3427 this.mResourceType = resourceType; 3428 this.mAttrId = attrId; 3429 } 3430 3431 AttributeReflectionAction(Parcel in) { 3432 super(in); 3433 this.mResourceType = in.readInt(); 3434 this.mAttrId = in.readInt(); 3435 } 3436 3437 @Override 3438 public void writeToParcel(Parcel dest, int flags) { 3439 super.writeToParcel(dest, flags); 3440 dest.writeInt(this.mResourceType); 3441 dest.writeInt(this.mAttrId); 3442 } 3443 3444 @Override 3445 protected Object getParameterValue(View view) throws ActionException { 3446 TypedArray typedArray = view.getContext().obtainStyledAttributes(new int[]{mAttrId}); 3447 try { 3448 // When mAttrId == 0, we will depend on the default values below 3449 if (mAttrId != 0 && typedArray.getType(0) == TypedValue.TYPE_NULL) { 3450 throw new ActionException("Attribute 0x" + Integer.toHexString(this.mAttrId) 3451 + " is not defined"); 3452 } 3453 switch (this.mResourceType) { 3454 case DIMEN_RESOURCE: 3455 switch (this.mType) { 3456 case BaseReflectionAction.INT: 3457 return typedArray.getDimensionPixelSize(0, 0); 3458 case BaseReflectionAction.FLOAT: 3459 return typedArray.getDimension(0, 0); 3460 default: 3461 throw new ActionException( 3462 "dimen attribute 0x" + Integer.toHexString(this.mAttrId) 3463 + " must be used as INT or FLOAT," 3464 + " not " + this.mType); 3465 } 3466 case COLOR_RESOURCE: 3467 switch (this.mType) { 3468 case BaseReflectionAction.INT: 3469 return typedArray.getColor(0, 0); 3470 case BaseReflectionAction.COLOR_STATE_LIST: 3471 return typedArray.getColorStateList(0); 3472 default: 3473 throw new ActionException( 3474 "color attribute 0x" + Integer.toHexString(this.mAttrId) 3475 + " must be used as INT or COLOR_STATE_LIST," 3476 + " not " + this.mType); 3477 } 3478 case STRING_RESOURCE: 3479 switch (this.mType) { 3480 case BaseReflectionAction.CHAR_SEQUENCE: 3481 return typedArray.getText(0); 3482 case BaseReflectionAction.STRING: 3483 return typedArray.getString(0); 3484 default: 3485 throw new ActionException( 3486 "string attribute 0x" + Integer.toHexString(this.mAttrId) 3487 + " must be used as STRING or CHAR_SEQUENCE," 3488 + " not " + this.mType); 3489 } 3490 default: 3491 // Note: This can only be an implementation error. 3492 throw new ActionException( 3493 "Unknown resource type: " + this.mResourceType); 3494 } 3495 } catch (ActionException ex) { 3496 throw ex; 3497 } catch (Throwable t) { 3498 throw new ActionException(t); 3499 } finally { 3500 typedArray.recycle(); 3501 } 3502 } 3503 3504 @Override 3505 public int getActionTag() { 3506 return ATTRIBUTE_REFLECTION_ACTION_TAG; 3507 } 3508 3509 @Override 3510 public boolean canWriteToProto() { 3511 return true; 3512 } 3513 3514 @Override 3515 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 3516 final long token = out.start(RemoteViewsProto.Action.ATTRIBUTE_REFLECTION_ACTION); 3517 out.write(RemoteViewsProto.AttributeReflectionAction.VIEW_ID, 3518 appResources.getResourceName(mViewId)); 3519 out.write(RemoteViewsProto.AttributeReflectionAction.METHOD_NAME, mMethodName); 3520 out.write(RemoteViewsProto.AttributeReflectionAction.PARAMETER_TYPE, mType); 3521 out.write(RemoteViewsProto.AttributeReflectionAction.RESOURCE_TYPE, mResourceType); 3522 if (mAttrId != 0) { 3523 out.write(RemoteViewsProto.AttributeReflectionAction.ATTRIBUTE_ID, 3524 appResources.getResourceName(mAttrId)); 3525 } 3526 out.end(token); 3527 } 3528 3529 public static PendingResources<Action> createFromProto(ProtoInputStream in) 3530 throws Exception { 3531 final LongSparseArray<Object> values = new LongSparseArray<>(); 3532 3533 final long token = in.start(RemoteViewsProto.Action.ATTRIBUTE_REFLECTION_ACTION); 3534 while (in.nextField() != NO_MORE_FIELDS) { 3535 switch (in.getFieldNumber()) { 3536 case (int) RemoteViewsProto.AttributeReflectionAction.VIEW_ID: { 3537 values.put(RemoteViewsProto.AttributeReflectionAction.VIEW_ID, 3538 in.readString(RemoteViewsProto.AttributeReflectionAction.VIEW_ID)); 3539 break; 3540 } 3541 case (int) RemoteViewsProto.AttributeReflectionAction.METHOD_NAME: 3542 values.put(RemoteViewsProto.AttributeReflectionAction.METHOD_NAME, 3543 in.readString( 3544 RemoteViewsProto.AttributeReflectionAction.METHOD_NAME)); 3545 break; 3546 case (int) RemoteViewsProto.AttributeReflectionAction.ATTRIBUTE_ID: 3547 values.put(RemoteViewsProto.AttributeReflectionAction.ATTRIBUTE_ID, 3548 in.readString( 3549 RemoteViewsProto.AttributeReflectionAction.ATTRIBUTE_ID)); 3550 break; 3551 case (int) RemoteViewsProto.AttributeReflectionAction.PARAMETER_TYPE: 3552 values.put(RemoteViewsProto.AttributeReflectionAction.PARAMETER_TYPE, 3553 in.readInt( 3554 RemoteViewsProto.AttributeReflectionAction.PARAMETER_TYPE)); 3555 break; 3556 case (int) RemoteViewsProto.AttributeReflectionAction.RESOURCE_TYPE: 3557 values.put(RemoteViewsProto.AttributeReflectionAction.RESOURCE_TYPE, 3558 in.readInt( 3559 RemoteViewsProto.AttributeReflectionAction.RESOURCE_TYPE)); 3560 break; 3561 default: 3562 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 3563 + ProtoUtils.currentFieldToString(in)); 3564 } 3565 } 3566 in.end(token); 3567 3568 checkContainsKeys(values, new long[]{RemoteViewsProto.AttributeReflectionAction.VIEW_ID, 3569 RemoteViewsProto.AttributeReflectionAction.METHOD_NAME, 3570 RemoteViewsProto.AttributeReflectionAction.PARAMETER_TYPE, 3571 RemoteViewsProto.AttributeReflectionAction.RESOURCE_TYPE}); 3572 3573 return (context, resources, rootData, depth) -> { 3574 int viewId = getAsIdentifier(resources, values, 3575 RemoteViewsProto.AttributeReflectionAction.VIEW_ID); 3576 int attributeId = (values.indexOfKey( 3577 RemoteViewsProto.AttributeReflectionAction.ATTRIBUTE_ID) >= 0) 3578 ? getAsIdentifier(resources, values, 3579 RemoteViewsProto.AttributeReflectionAction.ATTRIBUTE_ID) : 0; 3580 return new AttributeReflectionAction(viewId, 3581 (String) values.get(RemoteViewsProto.AttributeReflectionAction.METHOD_NAME), 3582 (int) values.get(RemoteViewsProto.AttributeReflectionAction.PARAMETER_TYPE), 3583 (int) values.get(RemoteViewsProto.AttributeReflectionAction.RESOURCE_TYPE), 3584 attributeId); 3585 }; 3586 } 3587 } 3588 3589 private static final class ComplexUnitDimensionReflectionAction extends BaseReflectionAction { 3590 private final float mValue; 3591 @ComplexDimensionUnit 3592 private final int mUnit; 3593 3594 ComplexUnitDimensionReflectionAction(int viewId, String methodName, int parameterType, 3595 float value, @ComplexDimensionUnit int unit) { 3596 super(viewId, methodName, parameterType); 3597 this.mValue = value; 3598 this.mUnit = unit; 3599 } 3600 3601 ComplexUnitDimensionReflectionAction(Parcel in) { 3602 super(in); 3603 this.mValue = in.readFloat(); 3604 this.mUnit = in.readInt(); 3605 } 3606 3607 @Override 3608 public void writeToParcel(Parcel dest, int flags) { 3609 super.writeToParcel(dest, flags); 3610 dest.writeFloat(this.mValue); 3611 dest.writeInt(this.mUnit); 3612 } 3613 3614 @Nullable 3615 @Override 3616 protected Object getParameterValue(@Nullable View view) throws ActionException { 3617 if (view == null) return null; 3618 3619 DisplayMetrics dm = view.getContext().getResources().getDisplayMetrics(); 3620 try { 3621 int data = TypedValue.createComplexDimension(this.mValue, this.mUnit); 3622 switch (this.mType) { 3623 case ReflectionAction.INT: 3624 return TypedValue.complexToDimensionPixelSize(data, dm); 3625 case ReflectionAction.FLOAT: 3626 return TypedValue.complexToDimension(data, dm); 3627 default: 3628 throw new ActionException( 3629 "parameter type must be INT or FLOAT, not " + this.mType); 3630 } 3631 } catch (ActionException ex) { 3632 throw ex; 3633 } catch (Throwable t) { 3634 throw new ActionException(t); 3635 } 3636 } 3637 3638 @Override 3639 public int getActionTag() { 3640 return COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG; 3641 } 3642 3643 @Override 3644 public boolean canWriteToProto() { 3645 return true; 3646 } 3647 3648 @Override 3649 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 3650 final long token = out.start( 3651 RemoteViewsProto.Action.COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION); 3652 out.write(RemoteViewsProto.ComplexUnitDimensionReflectionAction.VIEW_ID, 3653 appResources.getResourceName(mViewId)); 3654 out.write(RemoteViewsProto.ComplexUnitDimensionReflectionAction.METHOD_NAME, 3655 mMethodName); 3656 out.write(RemoteViewsProto.ComplexUnitDimensionReflectionAction.PARAMETER_TYPE, mType); 3657 out.write(RemoteViewsProto.ComplexUnitDimensionReflectionAction.DIMENSION_VALUE, 3658 mValue); 3659 out.write(RemoteViewsProto.ComplexUnitDimensionReflectionAction.UNIT, mUnit); 3660 out.end(token); 3661 } 3662 3663 public static PendingResources<Action> createFromProto(ProtoInputStream in) 3664 throws Exception { 3665 final LongSparseArray<Object> values = new LongSparseArray<>(); 3666 3667 final long token = in.start( 3668 RemoteViewsProto.Action.COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION); 3669 while (in.nextField() != NO_MORE_FIELDS) { 3670 switch (in.getFieldNumber()) { 3671 case (int) RemoteViewsProto.ComplexUnitDimensionReflectionAction.VIEW_ID: 3672 values.put(RemoteViewsProto.ComplexUnitDimensionReflectionAction.VIEW_ID, 3673 in.readString( 3674 RemoteViewsProto 3675 .ComplexUnitDimensionReflectionAction.VIEW_ID)); 3676 break; 3677 case (int) RemoteViewsProto.ComplexUnitDimensionReflectionAction.METHOD_NAME: 3678 values.put( 3679 RemoteViewsProto.ComplexUnitDimensionReflectionAction.METHOD_NAME, 3680 in.readString( 3681 RemoteViewsProto 3682 .ComplexUnitDimensionReflectionAction.METHOD_NAME)); 3683 break; 3684 case (int) RemoteViewsProto.ComplexUnitDimensionReflectionAction.PARAMETER_TYPE: 3685 values.put( 3686 RemoteViewsProto 3687 .ComplexUnitDimensionReflectionAction.PARAMETER_TYPE, 3688 in.readInt( 3689 RemoteViewsProto 3690 .ComplexUnitDimensionReflectionAction 3691 .PARAMETER_TYPE)); 3692 break; 3693 case (int) RemoteViewsProto 3694 .ComplexUnitDimensionReflectionAction.DIMENSION_VALUE: 3695 values.put( 3696 RemoteViewsProto 3697 .ComplexUnitDimensionReflectionAction.DIMENSION_VALUE, 3698 in.readFloat( 3699 RemoteViewsProto 3700 .ComplexUnitDimensionReflectionAction 3701 .DIMENSION_VALUE)); 3702 break; 3703 case (int) RemoteViewsProto.ComplexUnitDimensionReflectionAction.UNIT: 3704 values.put(RemoteViewsProto.ComplexUnitDimensionReflectionAction.UNIT, 3705 in.readInt( 3706 RemoteViewsProto 3707 .ComplexUnitDimensionReflectionAction.UNIT)); 3708 break; 3709 default: 3710 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 3711 + ProtoUtils.currentFieldToString(in)); 3712 } 3713 } 3714 in.end(token); 3715 3716 checkContainsKeys(values, 3717 new long[]{RemoteViewsProto.ComplexUnitDimensionReflectionAction.VIEW_ID, 3718 RemoteViewsProto.ComplexUnitDimensionReflectionAction.METHOD_NAME, 3719 RemoteViewsProto.ComplexUnitDimensionReflectionAction.PARAMETER_TYPE}); 3720 3721 return (context, resources, rootData, depth) -> { 3722 int viewId = getAsIdentifier(resources, values, 3723 RemoteViewsProto.ComplexUnitDimensionReflectionAction.VIEW_ID); 3724 return new ComplexUnitDimensionReflectionAction(viewId, (String) values.get( 3725 RemoteViewsProto.ComplexUnitDimensionReflectionAction.METHOD_NAME), 3726 (int) values.get( 3727 RemoteViewsProto 3728 .ComplexUnitDimensionReflectionAction.PARAMETER_TYPE), 3729 (float) values.get( 3730 RemoteViewsProto 3731 .ComplexUnitDimensionReflectionAction.DIMENSION_VALUE, 3732 0), 3733 (int) values.get(RemoteViewsProto.ComplexUnitDimensionReflectionAction.UNIT, 3734 0)); 3735 }; 3736 } 3737 } 3738 3739 private static final class NightModeReflectionAction extends BaseReflectionAction { 3740 private final Object mLightValue; 3741 private final Object mDarkValue; 3742 3743 NightModeReflectionAction( 3744 @IdRes int viewId, 3745 String methodName, 3746 int type, 3747 Object lightValue, 3748 Object darkValue) { 3749 super(viewId, methodName, type); 3750 mLightValue = lightValue; 3751 mDarkValue = darkValue; 3752 } 3753 3754 NightModeReflectionAction(Parcel in) { 3755 super(in); 3756 switch (this.mType) { 3757 case ICON: 3758 mLightValue = in.readTypedObject(Icon.CREATOR); 3759 mDarkValue = in.readTypedObject(Icon.CREATOR); 3760 break; 3761 case COLOR_STATE_LIST: 3762 mLightValue = in.readTypedObject(ColorStateList.CREATOR); 3763 mDarkValue = in.readTypedObject(ColorStateList.CREATOR); 3764 break; 3765 case INT: 3766 mLightValue = in.readInt(); 3767 mDarkValue = in.readInt(); 3768 break; 3769 default: 3770 throw new ActionException("Unexpected night mode action type: " + this.mType); 3771 } 3772 } 3773 3774 @Override 3775 public void writeToParcel(Parcel out, int flags) { 3776 super.writeToParcel(out, flags); 3777 switch (this.mType) { 3778 case ICON: 3779 case COLOR_STATE_LIST: 3780 out.writeTypedObject((Parcelable) mLightValue, flags); 3781 out.writeTypedObject((Parcelable) mDarkValue, flags); 3782 break; 3783 case INT: 3784 out.writeInt((int) mLightValue); 3785 out.writeInt((int) mDarkValue); 3786 break; 3787 } 3788 } 3789 3790 @Nullable 3791 @Override 3792 protected Object getParameterValue(@Nullable View view) throws ActionException { 3793 if (view == null) return null; 3794 3795 Configuration configuration = view.getResources().getConfiguration(); 3796 return configuration.isNightModeActive() ? mDarkValue : mLightValue; 3797 } 3798 3799 @Override 3800 public int getActionTag() { 3801 return NIGHT_MODE_REFLECTION_ACTION_TAG; 3802 } 3803 3804 @Override 3805 public void visitUris(@NonNull Consumer<Uri> visitor) { 3806 if (this.mType == ICON) { 3807 visitIconUri((Icon) mDarkValue, visitor); 3808 visitIconUri((Icon) mLightValue, visitor); 3809 } 3810 } 3811 3812 @Override 3813 public boolean canWriteToProto() { 3814 return true; 3815 } 3816 3817 @Override 3818 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 3819 final long token = out.start(RemoteViewsProto.Action.NIGHT_MODE_REFLECTION_ACTION); 3820 out.write(RemoteViewsProto.NightModeReflectionAction.VIEW_ID, 3821 appResources.getResourceName(mViewId)); 3822 out.write(RemoteViewsProto.NightModeReflectionAction.METHOD_NAME, mMethodName); 3823 out.write(RemoteViewsProto.NightModeReflectionAction.PARAMETER_TYPE, mType); 3824 switch (this.mType) { 3825 case ICON: 3826 writeIconToProto(out, appResources, (Icon) mLightValue, 3827 RemoteViewsProto.NightModeReflectionAction.LIGHT_ICON); 3828 writeIconToProto(out, appResources, (Icon) mDarkValue, 3829 RemoteViewsProto.NightModeReflectionAction.DARK_ICON); 3830 break; 3831 case COLOR_STATE_LIST: 3832 writeColorStateListToProto(out, (ColorStateList) mLightValue, 3833 RemoteViewsProto.NightModeReflectionAction.LIGHT_COLOR_STATE_LIST); 3834 writeColorStateListToProto(out, (ColorStateList) mDarkValue, 3835 RemoteViewsProto.NightModeReflectionAction.DARK_COLOR_STATE_LIST); 3836 break; 3837 case INT: 3838 out.write(RemoteViewsProto.NightModeReflectionAction.LIGHT_INT, 3839 (int) mLightValue); 3840 out.write(RemoteViewsProto.NightModeReflectionAction.DARK_INT, 3841 (int) mDarkValue); 3842 break; 3843 } 3844 out.end(token); 3845 } 3846 3847 public static PendingResources<Action> createFromProto(ProtoInputStream in) 3848 throws Exception { 3849 final LongSparseArray<Object> values = new LongSparseArray<>(); 3850 3851 final long token = in.start(RemoteViewsProto.Action.NIGHT_MODE_REFLECTION_ACTION); 3852 while (in.nextField() != NO_MORE_FIELDS) { 3853 switch (in.getFieldNumber()) { 3854 case (int) RemoteViewsProto.NightModeReflectionAction.VIEW_ID: 3855 values.put(RemoteViewsProto.NightModeReflectionAction.VIEW_ID, 3856 in.readString(RemoteViewsProto.NightModeReflectionAction.VIEW_ID)); 3857 break; 3858 case (int) RemoteViewsProto.NightModeReflectionAction.METHOD_NAME: 3859 values.put(RemoteViewsProto.NightModeReflectionAction.METHOD_NAME, 3860 in.readString( 3861 RemoteViewsProto.NightModeReflectionAction.METHOD_NAME)); 3862 break; 3863 case (int) RemoteViewsProto.NightModeReflectionAction.PARAMETER_TYPE: 3864 values.put(RemoteViewsProto.NightModeReflectionAction.PARAMETER_TYPE, 3865 in.readInt( 3866 RemoteViewsProto.NightModeReflectionAction.PARAMETER_TYPE)); 3867 break; 3868 case (int) RemoteViewsProto.NightModeReflectionAction.LIGHT_ICON: 3869 values.put(RemoteViewsProto.NightModeReflectionAction.LIGHT_ICON, 3870 createIconFromProto(in, 3871 RemoteViewsProto.NightModeReflectionAction.LIGHT_ICON)); 3872 break; 3873 case (int) RemoteViewsProto.NightModeReflectionAction.LIGHT_COLOR_STATE_LIST: 3874 values.put( 3875 RemoteViewsProto.NightModeReflectionAction.LIGHT_COLOR_STATE_LIST, 3876 createColorStateListFromProto(in, 3877 RemoteViewsProto 3878 .NightModeReflectionAction.LIGHT_COLOR_STATE_LIST)); 3879 break; 3880 case (int) RemoteViewsProto.NightModeReflectionAction.LIGHT_INT: 3881 values.put(RemoteViewsProto.NightModeReflectionAction.LIGHT_INT, 3882 in.readInt(RemoteViewsProto.NightModeReflectionAction.LIGHT_INT)); 3883 break; 3884 case (int) RemoteViewsProto.NightModeReflectionAction.DARK_ICON: 3885 values.put(RemoteViewsProto.NightModeReflectionAction.DARK_ICON, 3886 createIconFromProto(in, 3887 RemoteViewsProto.NightModeReflectionAction.DARK_ICON)); 3888 break; 3889 case (int) RemoteViewsProto.NightModeReflectionAction.DARK_COLOR_STATE_LIST: 3890 values.put(RemoteViewsProto.NightModeReflectionAction.DARK_COLOR_STATE_LIST, 3891 createColorStateListFromProto(in, 3892 RemoteViewsProto 3893 .NightModeReflectionAction.DARK_COLOR_STATE_LIST)); 3894 break; 3895 case (int) RemoteViewsProto.NightModeReflectionAction.DARK_INT: 3896 values.put(RemoteViewsProto.NightModeReflectionAction.DARK_INT, 3897 in.readInt(RemoteViewsProto.NightModeReflectionAction.DARK_INT)); 3898 break; 3899 default: 3900 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 3901 + ProtoUtils.currentFieldToString(in)); 3902 } 3903 } 3904 in.end(token); 3905 3906 checkContainsKeys(values, new long[]{RemoteViewsProto.NightModeReflectionAction.VIEW_ID, 3907 RemoteViewsProto.NightModeReflectionAction.METHOD_NAME, 3908 RemoteViewsProto.NightModeReflectionAction.PARAMETER_TYPE}); 3909 3910 return (context, resources, rootData, depth) -> { 3911 int viewId = getAsIdentifier(resources, values, 3912 RemoteViewsProto.NightModeReflectionAction.VIEW_ID); 3913 String methodName = (String) values.get( 3914 RemoteViewsProto.NightModeReflectionAction.METHOD_NAME); 3915 int parameterType = (int) values.get( 3916 RemoteViewsProto.NightModeReflectionAction.PARAMETER_TYPE); 3917 switch (parameterType) { 3918 case ICON: 3919 PendingResources<Icon> pendingLightIcon = 3920 (PendingResources<Icon>) values.get( 3921 RemoteViewsProto.NightModeReflectionAction.LIGHT_ICON); 3922 PendingResources<Icon> pendingDarkIcon = 3923 (PendingResources<Icon>) values.get( 3924 RemoteViewsProto.NightModeReflectionAction.DARK_ICON); 3925 Icon lightIcon = pendingLightIcon != null ? pendingLightIcon.create(context, 3926 resources, rootData, depth) : null; 3927 Icon darkIcon = pendingDarkIcon != null ? pendingDarkIcon.create(context, 3928 resources, rootData, depth) : null; 3929 return new NightModeReflectionAction(viewId, methodName, parameterType, 3930 lightIcon, darkIcon); 3931 case COLOR_STATE_LIST: 3932 return new NightModeReflectionAction(viewId, methodName, parameterType, 3933 (ColorStateList) values.get( 3934 RemoteViewsProto 3935 .NightModeReflectionAction.LIGHT_COLOR_STATE_LIST), 3936 (ColorStateList) values.get( 3937 RemoteViewsProto 3938 .NightModeReflectionAction.DARK_COLOR_STATE_LIST)); 3939 case INT: 3940 return new NightModeReflectionAction(viewId, methodName, parameterType, 3941 (int) values.get( 3942 RemoteViewsProto.NightModeReflectionAction.LIGHT_INT, 0), 3943 (int) values.get( 3944 RemoteViewsProto.NightModeReflectionAction.DARK_INT, 0)); 3945 default: 3946 throw new RuntimeException("Unknown parameterType: " + parameterType); 3947 } 3948 }; 3949 } 3950 } 3951 3952 /** 3953 * This is only used for async execution of actions and it not parcelable. 3954 */ 3955 private static final class RunnableAction extends RuntimeAction { 3956 private final Runnable mRunnable; 3957 3958 RunnableAction(Runnable r) { 3959 mRunnable = r; 3960 } 3961 3962 @Override 3963 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 3964 mRunnable.run(); 3965 } 3966 } 3967 3968 private static boolean hasStableId(View view) { 3969 Object tag = view.getTag(com.android.internal.R.id.remote_views_stable_id); 3970 return tag != null; 3971 } 3972 3973 private static int getStableId(View view) { 3974 Integer id = (Integer) view.getTag(com.android.internal.R.id.remote_views_stable_id); 3975 return id == null ? ViewGroupActionAdd.NO_ID : id; 3976 } 3977 3978 private static void setStableId(View view, int stableId) { 3979 view.setTagInternal(com.android.internal.R.id.remote_views_stable_id, stableId); 3980 } 3981 3982 // Returns the next recyclable child of the view group, or -1 if there are none. 3983 private static int getNextRecyclableChild(ViewGroup vg) { 3984 Integer tag = (Integer) vg.getTag(com.android.internal.R.id.remote_views_next_child); 3985 return tag == null ? -1 : tag; 3986 } 3987 3988 private static int getViewLayoutId(View v) { 3989 return (Integer) v.getTag(R.id.widget_frame); 3990 } 3991 3992 private static void setNextRecyclableChild(ViewGroup vg, int nextChild, int numChildren) { 3993 if (nextChild < 0 || nextChild >= numChildren) { 3994 vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, -1); 3995 } else { 3996 vg.setTagInternal(com.android.internal.R.id.remote_views_next_child, nextChild); 3997 } 3998 } 3999 4000 private void finalizeViewRecycling(ViewGroup root) { 4001 // Remove any recyclable children that were not used. nextChild should either be -1 or point 4002 // to the next recyclable child that hasn't been recycled. 4003 int nextChild = getNextRecyclableChild(root); 4004 if (nextChild >= 0 && nextChild < root.getChildCount()) { 4005 root.removeViews(nextChild, root.getChildCount() - nextChild); 4006 } 4007 // Make sure on the next round, we don't try to recycle if removeAllViews is not called. 4008 setNextRecyclableChild(root, -1, 0); 4009 // Traverse the view tree. 4010 for (int i = 0; i < root.getChildCount(); i++) { 4011 View child = root.getChildAt(i); 4012 if (child instanceof ViewGroup && !child.isRootNamespace()) { 4013 finalizeViewRecycling((ViewGroup) child); 4014 } 4015 } 4016 } 4017 4018 /** 4019 * ViewGroup methods that are related to adding Views. 4020 */ 4021 private class ViewGroupActionAdd extends Action { 4022 static final int NO_ID = -1; 4023 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 4024 private RemoteViews mNestedViews; 4025 private int mIndex; 4026 private int mStableId; 4027 4028 ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews) { 4029 this(viewId, nestedViews, -1 /* index */, NO_ID /* nestedViewId */); 4030 } 4031 4032 ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index) { 4033 this(viewId, nestedViews, index, NO_ID /* nestedViewId */); 4034 } 4035 4036 ViewGroupActionAdd(@IdRes int viewId, RemoteViews nestedViews, int index, int stableId) { 4037 this.mViewId = viewId; 4038 mNestedViews = nestedViews; 4039 mIndex = index; 4040 mStableId = stableId; 4041 nestedViews.configureAsChild(getHierarchyRootData()); 4042 } 4043 4044 ViewGroupActionAdd(Parcel parcel, ApplicationInfo info, int depth) { 4045 mViewId = parcel.readInt(); 4046 mIndex = parcel.readInt(); 4047 mStableId = parcel.readInt(); 4048 mNestedViews = new RemoteViews(parcel, getHierarchyRootData(), info, depth); 4049 mNestedViews.addFlags(mApplyFlags); 4050 } 4051 4052 public void writeToParcel(Parcel dest, int flags) { 4053 dest.writeInt(mViewId); 4054 dest.writeInt(mIndex); 4055 dest.writeInt(mStableId); 4056 mNestedViews.writeToParcel(dest, flags); 4057 } 4058 4059 @Override 4060 public void setHierarchyRootData(HierarchyRootData root) { 4061 mNestedViews.configureAsChild(root); 4062 } 4063 4064 private int findViewIndexToRecycle(ViewGroup target, RemoteViews newContent) { 4065 for (int nextChild = getNextRecyclableChild(target); nextChild < target.getChildCount(); 4066 nextChild++) { 4067 View child = target.getChildAt(nextChild); 4068 if (getStableId(child) == mStableId) { 4069 return nextChild; 4070 } 4071 } 4072 return -1; 4073 } 4074 4075 @Override 4076 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4077 final Context context = root.getContext(); 4078 final ViewGroup target = root.findViewById(mViewId); 4079 4080 if (target == null) { 4081 return; 4082 } 4083 4084 // If removeAllViews was called, this returns the next potential recycled view. 4085 // If there are no more views to recycle (or removeAllViews was not called), this 4086 // will return -1. 4087 final int nextChild = getNextRecyclableChild(target); 4088 RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); 4089 4090 int flagsToPropagate = mApplyFlags & FLAG_MASK_TO_PROPAGATE; 4091 if (flagsToPropagate != 0) rvToApply.addFlags(flagsToPropagate); 4092 4093 if (nextChild >= 0 && mStableId != NO_ID) { 4094 // At that point, the views starting at index nextChild are the ones recyclable but 4095 // not yet recycled. All views added on that round of application are placed before. 4096 // Find the next view with the same stable id, or -1. 4097 int recycledViewIndex = findViewIndexToRecycle(target, rvToApply); 4098 if (recycledViewIndex >= 0) { 4099 View child = target.getChildAt(recycledViewIndex); 4100 if (rvToApply.canRecycleView(child)) { 4101 if (nextChild < recycledViewIndex) { 4102 target.removeViews(nextChild, recycledViewIndex - nextChild); 4103 } 4104 setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); 4105 rvToApply.reapplyNestedViews(context, child, rootParent, params); 4106 return; 4107 } 4108 // If we cannot recycle the views, we still remove all views in between to 4109 // avoid weird behaviors and insert the new view in place of the old one. 4110 target.removeViews(nextChild, recycledViewIndex - nextChild + 1); 4111 } 4112 } 4113 // If we cannot recycle, insert the new view before the next recyclable child. 4114 4115 // Inflate nested views and add as children 4116 View nestedView = rvToApply.apply(context, target, rootParent, null /* size */, params); 4117 if (mStableId != NO_ID) { 4118 setStableId(nestedView, mStableId); 4119 } 4120 target.addView(nestedView, mIndex >= 0 ? mIndex : nextChild); 4121 if (nextChild >= 0) { 4122 // If we are at the end, there is no reason to try to recycle anymore 4123 setNextRecyclableChild(target, nextChild + 1, target.getChildCount()); 4124 } 4125 } 4126 4127 @Override 4128 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 4129 ActionApplyParams params) { 4130 // In the async implementation, update the view tree so that subsequent calls to 4131 // findViewById return the current view. 4132 root.createTree(); 4133 ViewTree target = root.findViewTreeById(mViewId); 4134 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 4135 return ACTION_NOOP; 4136 } 4137 final ViewGroup targetVg = (ViewGroup) target.mRoot; 4138 4139 // Inflate nested views and perform all the async tasks for the child remoteView. 4140 final Context context = root.mRoot.getContext(); 4141 4142 // If removeAllViews was called, this returns the next potential recycled view. 4143 // If there are no more views to recycle (or removeAllViews was not called), this 4144 // will return -1. 4145 final int nextChild = getNextRecyclableChild(targetVg); 4146 if (nextChild >= 0 && mStableId != NO_ID) { 4147 RemoteViews rvToApply = mNestedViews.getRemoteViewsToApply(context); 4148 final int recycledViewIndex = target.findChildIndex(nextChild, 4149 view -> getStableId(view) == mStableId); 4150 if (recycledViewIndex >= 0) { 4151 // At that point, the views starting at index nextChild are the ones 4152 // recyclable but not yet recycled. All views added on that round of 4153 // application are placed before. 4154 ViewTree recycled = target.mChildren.get(recycledViewIndex); 4155 // We can only recycle the view if the layout id is the same. 4156 if (rvToApply.canRecycleView(recycled.mRoot)) { 4157 if (recycledViewIndex > nextChild) { 4158 target.removeChildren(nextChild, recycledViewIndex - nextChild); 4159 } 4160 setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size()); 4161 final AsyncApplyTask reapplyTask = rvToApply.getInternalAsyncApplyTask( 4162 context, 4163 targetVg, null /* listener */, params, null /* size */, 4164 recycled.mRoot); 4165 final ViewTree tree = reapplyTask.doInBackground(); 4166 if (tree == null) { 4167 throw new ActionException(reapplyTask.mError); 4168 } 4169 return new RuntimeAction() { 4170 @Override 4171 public void apply(View root, ViewGroup rootParent, 4172 ActionApplyParams params) throws ActionException { 4173 reapplyTask.onPostExecute(tree); 4174 if (recycledViewIndex > nextChild) { 4175 targetVg.removeViews(nextChild, recycledViewIndex - nextChild); 4176 } 4177 } 4178 }; 4179 } 4180 // If the layout id is different, still remove the children as if we recycled 4181 // the view, to insert at the same place. 4182 target.removeChildren(nextChild, recycledViewIndex - nextChild + 1); 4183 return insertNewView(context, target, params, 4184 () -> targetVg.removeViews(nextChild, 4185 recycledViewIndex - nextChild + 1)); 4186 4187 } 4188 } 4189 // If we cannot recycle, simply add the view at the same available slot. 4190 return insertNewView(context, target, params, () -> {}); 4191 } 4192 4193 private Action insertNewView(Context context, ViewTree target, 4194 ActionApplyParams params, Runnable finalizeAction) { 4195 ViewGroup targetVg = (ViewGroup) target.mRoot; 4196 int nextChild = getNextRecyclableChild(targetVg); 4197 final AsyncApplyTask task = mNestedViews.getInternalAsyncApplyTask(context, targetVg, 4198 null /* listener */, params, null /* size */, null /* result */); 4199 final ViewTree tree = task.doInBackground(); 4200 4201 if (tree == null) { 4202 throw new ActionException(task.mError); 4203 } 4204 if (mStableId != NO_ID) { 4205 setStableId(task.mResult, mStableId); 4206 } 4207 4208 // Update the global view tree, so that next call to findViewTreeById 4209 // goes through the subtree as well. 4210 final int insertIndex = mIndex >= 0 ? mIndex : nextChild; 4211 target.addChild(tree, insertIndex); 4212 if (nextChild >= 0) { 4213 setNextRecyclableChild(targetVg, nextChild + 1, target.mChildren.size()); 4214 } 4215 4216 return new RuntimeAction() { 4217 @Override 4218 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4219 task.onPostExecute(tree); 4220 finalizeAction.run(); 4221 targetVg.addView(task.mResult, insertIndex); 4222 } 4223 }; 4224 } 4225 4226 @Override 4227 public int mergeBehavior() { 4228 return MERGE_APPEND; 4229 } 4230 4231 @Override 4232 public boolean prefersAsyncApply() { 4233 return mNestedViews.prefersAsyncApply(); 4234 } 4235 4236 @Override 4237 public int getActionTag() { 4238 return VIEW_GROUP_ACTION_ADD_TAG; 4239 } 4240 4241 @Override 4242 public void visitUris(@NonNull Consumer<Uri> visitor) { 4243 mNestedViews.visitUris(visitor); 4244 } 4245 4246 @Override 4247 public void visitIcons(@NonNull Consumer<Icon> visitor) { 4248 mNestedViews.visitIcons(visitor); 4249 } 4250 4251 @Override 4252 public boolean canWriteToProto() { 4253 return true; 4254 } 4255 4256 @Override 4257 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 4258 if (!Flags.remoteViewsProto()) return; 4259 final long token = out.start(RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION); 4260 out.write(RemoteViewsProto.ViewGroupAddAction.VIEW_ID, 4261 appResources.getResourceName(mViewId)); 4262 out.write(RemoteViewsProto.ViewGroupAddAction.INDEX, mIndex); 4263 out.write(RemoteViewsProto.ViewGroupAddAction.STABLE_ID, mStableId); 4264 long rvToken = out.start(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS); 4265 mNestedViews.writePreviewToProto(context, out); 4266 out.end(rvToken); 4267 out.end(token); 4268 } 4269 } 4270 4271 private PendingResources<Action> createViewGroupActionAddFromProto(ProtoInputStream in) 4272 throws Exception { 4273 final LongSparseArray<Object> values = new LongSparseArray<>(); 4274 4275 final long token = in.start(RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION); 4276 while (in.nextField() != NO_MORE_FIELDS) { 4277 switch (in.getFieldNumber()) { 4278 case (int) RemoteViewsProto.ViewGroupAddAction.VIEW_ID: 4279 values.put(RemoteViewsProto.ViewGroupAddAction.VIEW_ID, 4280 in.readString(RemoteViewsProto.ViewGroupAddAction.VIEW_ID)); 4281 break; 4282 case (int) RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS: 4283 final long nvToken = in.start(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS); 4284 values.put(RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS, 4285 createFromProto(in)); 4286 in.end(nvToken); 4287 break; 4288 case (int) RemoteViewsProto.ViewGroupAddAction.INDEX: 4289 values.put(RemoteViewsProto.ViewGroupAddAction.INDEX, 4290 in.readInt(RemoteViewsProto.ViewGroupAddAction.INDEX)); 4291 break; 4292 case (int) RemoteViewsProto.ViewGroupAddAction.STABLE_ID: 4293 values.put(RemoteViewsProto.ViewGroupAddAction.STABLE_ID, 4294 in.readInt(RemoteViewsProto.ViewGroupAddAction.STABLE_ID)); 4295 break; 4296 default: 4297 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 4298 + ProtoUtils.currentFieldToString(in)); 4299 } 4300 } 4301 in.end(token); 4302 4303 checkContainsKeys(values, new long[]{RemoteViewsProto.ViewGroupAddAction.VIEW_ID, 4304 RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS}); 4305 4306 return (context, resources, rootData, depth) -> { 4307 int viewId = getAsIdentifier(resources, values, 4308 RemoteViewsProto.ViewGroupAddAction.VIEW_ID); 4309 return new ViewGroupActionAdd(viewId, ((PendingResources<RemoteViews>) values.get( 4310 RemoteViewsProto.ViewGroupAddAction.NESTED_VIEWS)).create(context, resources, 4311 rootData, depth), 4312 (int) values.get(RemoteViewsProto.ViewGroupAddAction.INDEX, 0), 4313 (int) values.get(RemoteViewsProto.ViewGroupAddAction.STABLE_ID, 0)); 4314 }; 4315 } 4316 4317 /** 4318 * ViewGroup methods related to removing child views. 4319 */ 4320 private static class ViewGroupActionRemove extends Action { 4321 /** 4322 * Id that indicates that all child views of the affected ViewGroup should be removed. 4323 * 4324 * <p>Using -2 because the default id is -1. This avoids accidentally matching that. 4325 */ 4326 private static final int REMOVE_ALL_VIEWS_ID = -2; 4327 4328 private int mViewIdToKeep; 4329 4330 ViewGroupActionRemove(@IdRes int viewId) { 4331 this(viewId, REMOVE_ALL_VIEWS_ID); 4332 } 4333 4334 ViewGroupActionRemove(@IdRes int viewId, @IdRes int viewIdToKeep) { 4335 this.mViewId = viewId; 4336 mViewIdToKeep = viewIdToKeep; 4337 } 4338 4339 ViewGroupActionRemove(Parcel parcel) { 4340 mViewId = parcel.readInt(); 4341 mViewIdToKeep = parcel.readInt(); 4342 } 4343 4344 public void writeToParcel(Parcel dest, int flags) { 4345 dest.writeInt(mViewId); 4346 dest.writeInt(mViewIdToKeep); 4347 } 4348 4349 @Override 4350 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4351 final ViewGroup target = root.findViewById(mViewId); 4352 4353 if (target == null) { 4354 return; 4355 } 4356 4357 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 4358 // Remote any view without a stable id 4359 for (int i = target.getChildCount() - 1; i >= 0; i--) { 4360 if (!hasStableId(target.getChildAt(i))) { 4361 target.removeViewAt(i); 4362 } 4363 } 4364 // In the end, only children with a stable id (i.e. recyclable) are left. 4365 setNextRecyclableChild(target, 0, target.getChildCount()); 4366 return; 4367 } 4368 4369 removeAllViewsExceptIdToKeep(target); 4370 } 4371 4372 @Override 4373 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 4374 ActionApplyParams params) { 4375 // In the async implementation, update the view tree so that subsequent calls to 4376 // findViewById return the current view. 4377 root.createTree(); 4378 ViewTree target = root.findViewTreeById(mViewId); 4379 4380 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 4381 return ACTION_NOOP; 4382 } 4383 4384 final ViewGroup targetVg = (ViewGroup) target.mRoot; 4385 4386 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 4387 target.mChildren.removeIf(childTree -> !hasStableId(childTree.mRoot)); 4388 setNextRecyclableChild(targetVg, 0, target.mChildren.size()); 4389 } else { 4390 // Remove just the children which don't match the excepted view 4391 target.mChildren.removeIf(childTree -> childTree.mRoot.getId() != mViewIdToKeep); 4392 if (target.mChildren.isEmpty()) { 4393 target.mChildren = null; 4394 } 4395 } 4396 return new RuntimeAction() { 4397 @Override 4398 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4399 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 4400 for (int i = targetVg.getChildCount() - 1; i >= 0; i--) { 4401 if (!hasStableId(targetVg.getChildAt(i))) { 4402 targetVg.removeViewAt(i); 4403 } 4404 } 4405 return; 4406 } 4407 4408 removeAllViewsExceptIdToKeep(targetVg); 4409 } 4410 }; 4411 } 4412 4413 /** 4414 * Iterates through the children in the given ViewGroup and removes all the views that 4415 * do not have an id of {@link #mViewIdToKeep}. 4416 */ 4417 private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) { 4418 // Otherwise, remove all the views that do not match the id to keep. 4419 int index = viewGroup.getChildCount() - 1; 4420 while (index >= 0) { 4421 if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) { 4422 viewGroup.removeViewAt(index); 4423 } 4424 index--; 4425 } 4426 } 4427 4428 @Override 4429 public int getActionTag() { 4430 return VIEW_GROUP_ACTION_REMOVE_TAG; 4431 } 4432 4433 @Override 4434 public int mergeBehavior() { 4435 return MERGE_APPEND; 4436 } 4437 4438 @Override 4439 public boolean canWriteToProto() { 4440 return true; 4441 } 4442 4443 @Override 4444 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 4445 final long token = out.start(RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION); 4446 out.write(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID, 4447 appResources.getResourceName(mViewId)); 4448 if (mViewIdToKeep != REMOVE_ALL_VIEWS_ID) { 4449 out.write(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP, 4450 appResources.getResourceName(mViewIdToKeep)); 4451 } 4452 out.end(token); 4453 } 4454 4455 public static PendingResources<Action> createFromProto(ProtoInputStream in) 4456 throws Exception { 4457 final LongSparseArray<Object> values = new LongSparseArray<>(); 4458 4459 final long token = in.start(RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION); 4460 while (in.nextField() != NO_MORE_FIELDS) { 4461 switch (in.getFieldNumber()) { 4462 case (int) RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID: 4463 values.put(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID, 4464 in.readString(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID)); 4465 break; 4466 case (int) RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP: 4467 values.put(RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP, 4468 in.readString( 4469 RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP)); 4470 break; 4471 default: 4472 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 4473 + ProtoUtils.currentFieldToString(in)); 4474 } 4475 } 4476 in.end(token); 4477 4478 checkContainsKeys(values, new long[]{RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID}); 4479 4480 return (context, resources, rootData, depth) -> { 4481 int viewId = getAsIdentifier(resources, values, 4482 RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID); 4483 int viewIdToKeep = (values.indexOfKey( 4484 RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP) >= 0) 4485 ? getAsIdentifier(resources, values, 4486 RemoteViewsProto.ViewGroupRemoveAction.VIEW_ID_TO_KEEP) 4487 : REMOVE_ALL_VIEWS_ID; 4488 return new ViewGroupActionRemove(viewId, viewIdToKeep); 4489 }; 4490 } 4491 } 4492 4493 /** 4494 * Action to remove a view from its parent. 4495 */ 4496 private static class RemoveFromParentAction extends Action { 4497 RemoveFromParentAction(@IdRes int viewId) { 4498 this.mViewId = viewId; 4499 } 4500 4501 RemoveFromParentAction(Parcel parcel) { 4502 mViewId = parcel.readInt(); 4503 } 4504 4505 public void writeToParcel(Parcel dest, int flags) { 4506 dest.writeInt(mViewId); 4507 } 4508 4509 @Override 4510 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4511 final View target = root.findViewById(mViewId); 4512 4513 if (target == null || target == root) { 4514 return; 4515 } 4516 4517 ViewParent parent = target.getParent(); 4518 if (parent instanceof ViewManager) { 4519 ((ViewManager) parent).removeView(target); 4520 } 4521 } 4522 4523 @Override 4524 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 4525 ActionApplyParams params) { 4526 // In the async implementation, update the view tree so that subsequent calls to 4527 // findViewById return the correct view. 4528 root.createTree(); 4529 ViewTree target = root.findViewTreeById(mViewId); 4530 4531 if (target == null || target == root) { 4532 return ACTION_NOOP; 4533 } 4534 4535 ViewTree parent = root.findViewTreeParentOf(target); 4536 if (parent == null || !(parent.mRoot instanceof ViewManager)) { 4537 return ACTION_NOOP; 4538 } 4539 final ViewManager parentVg = (ViewManager) parent.mRoot; 4540 4541 parent.mChildren.remove(target); 4542 return new RuntimeAction() { 4543 @Override 4544 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4545 parentVg.removeView(target.mRoot); 4546 } 4547 }; 4548 } 4549 4550 @Override 4551 public int getActionTag() { 4552 return REMOVE_FROM_PARENT_ACTION_TAG; 4553 } 4554 4555 @Override 4556 public int mergeBehavior() { 4557 return MERGE_APPEND; 4558 } 4559 4560 @Override 4561 public boolean canWriteToProto() { 4562 return true; 4563 } 4564 4565 @Override 4566 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 4567 final long token = out.start(RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION); 4568 out.write(RemoteViewsProto.RemoveFromParentAction.VIEW_ID, 4569 appResources.getResourceName(mViewId)); 4570 out.end(token); 4571 } 4572 4573 public static PendingResources<Action> createFromProto(ProtoInputStream in) 4574 throws Exception { 4575 final LongSparseArray<Object> values = new LongSparseArray<>(); 4576 4577 final long token = in.start(RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION); 4578 while (in.nextField() != NO_MORE_FIELDS) { 4579 switch (in.getFieldNumber()) { 4580 case (int) RemoteViewsProto.RemoveFromParentAction.VIEW_ID: 4581 values.put(RemoteViewsProto.RemoveFromParentAction.VIEW_ID, 4582 in.readString(RemoteViewsProto.RemoveFromParentAction.VIEW_ID)); 4583 break; 4584 default: 4585 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 4586 + ProtoUtils.currentFieldToString(in)); 4587 } 4588 } 4589 in.end(token); 4590 4591 checkContainsKeys(values, new long[]{RemoteViewsProto.RemoveFromParentAction.VIEW_ID}); 4592 4593 return (context, resources, rootData, depth) -> { 4594 int viewId = getAsIdentifier(resources, values, 4595 RemoteViewsProto.RemoveFromParentAction.VIEW_ID); 4596 return new RemoveFromParentAction(viewId); 4597 }; 4598 } 4599 } 4600 4601 /** 4602 * Helper action to set compound drawables on a TextView. Supports relative 4603 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 4604 */ 4605 private static class TextViewDrawableAction extends Action { 4606 boolean mIsRelative = false; 4607 boolean mUseIcons = false; 4608 int mD1, mD2, mD3, mD4; 4609 Icon mI1, mI2, mI3, mI4; 4610 4611 boolean mDrawablesLoaded = false; 4612 Drawable mId1, mId2, mId3, mId4; 4613 4614 public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, @DrawableRes int d1, 4615 @DrawableRes int d2, @DrawableRes int d3, @DrawableRes int d4) { 4616 this.mViewId = viewId; 4617 this.mIsRelative = isRelative; 4618 this.mUseIcons = false; 4619 this.mD1 = d1; 4620 this.mD2 = d2; 4621 this.mD3 = d3; 4622 this.mD4 = d4; 4623 } 4624 4625 public TextViewDrawableAction(@IdRes int viewId, boolean isRelative, 4626 Icon i1, Icon i2, Icon i3, Icon i4) { 4627 this.mViewId = viewId; 4628 this.mIsRelative = isRelative; 4629 this.mUseIcons = true; 4630 this.mI1 = i1; 4631 this.mI2 = i2; 4632 this.mI3 = i3; 4633 this.mI4 = i4; 4634 } 4635 4636 public TextViewDrawableAction(Parcel parcel) { 4637 mViewId = parcel.readInt(); 4638 mIsRelative = (parcel.readInt() != 0); 4639 mUseIcons = (parcel.readInt() != 0); 4640 if (mUseIcons) { 4641 mI1 = parcel.readTypedObject(Icon.CREATOR); 4642 mI2 = parcel.readTypedObject(Icon.CREATOR); 4643 mI3 = parcel.readTypedObject(Icon.CREATOR); 4644 mI4 = parcel.readTypedObject(Icon.CREATOR); 4645 } else { 4646 mD1 = parcel.readInt(); 4647 mD2 = parcel.readInt(); 4648 mD3 = parcel.readInt(); 4649 mD4 = parcel.readInt(); 4650 } 4651 } 4652 4653 public void writeToParcel(Parcel dest, int flags) { 4654 dest.writeInt(mViewId); 4655 dest.writeInt(mIsRelative ? 1 : 0); 4656 dest.writeInt(mUseIcons ? 1 : 0); 4657 if (mUseIcons) { 4658 dest.writeTypedObject(mI1, 0); 4659 dest.writeTypedObject(mI2, 0); 4660 dest.writeTypedObject(mI3, 0); 4661 dest.writeTypedObject(mI4, 0); 4662 } else { 4663 dest.writeInt(mD1); 4664 dest.writeInt(mD2); 4665 dest.writeInt(mD3); 4666 dest.writeInt(mD4); 4667 } 4668 } 4669 4670 @Override 4671 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4672 final TextView target = root.findViewById(mViewId); 4673 if (target == null) return; 4674 if (mDrawablesLoaded) { 4675 if (mIsRelative) { 4676 target.setCompoundDrawablesRelativeWithIntrinsicBounds(mId1, mId2, mId3, mId4); 4677 } else { 4678 target.setCompoundDrawablesWithIntrinsicBounds(mId1, mId2, mId3, mId4); 4679 } 4680 } else if (mUseIcons) { 4681 final Context ctx = target.getContext(); 4682 final Drawable id1 = mI1 == null ? null : mI1.loadDrawable(ctx); 4683 final Drawable id2 = mI2 == null ? null : mI2.loadDrawable(ctx); 4684 final Drawable id3 = mI3 == null ? null : mI3.loadDrawable(ctx); 4685 final Drawable id4 = mI4 == null ? null : mI4.loadDrawable(ctx); 4686 if (mIsRelative) { 4687 target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4); 4688 } else { 4689 target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4); 4690 } 4691 } else { 4692 if (mIsRelative) { 4693 target.setCompoundDrawablesRelativeWithIntrinsicBounds(mD1, mD2, mD3, mD4); 4694 } else { 4695 target.setCompoundDrawablesWithIntrinsicBounds(mD1, mD2, mD3, mD4); 4696 } 4697 } 4698 } 4699 4700 @Override 4701 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 4702 ActionApplyParams params) { 4703 final TextView target = root.findViewById(mViewId); 4704 if (target == null) return ACTION_NOOP; 4705 4706 TextViewDrawableAction copy = mUseIcons 4707 ? new TextViewDrawableAction(mViewId, mIsRelative, mI1, mI2, mI3, mI4) 4708 : new TextViewDrawableAction(mViewId, mIsRelative, mD1, mD2, mD3, mD4); 4709 4710 // Load the drawables on the background thread. 4711 copy.mDrawablesLoaded = true; 4712 final Context ctx = target.getContext(); 4713 4714 if (mUseIcons) { 4715 copy.mId1 = mI1 == null ? null : mI1.loadDrawable(ctx); 4716 copy.mId2 = mI2 == null ? null : mI2.loadDrawable(ctx); 4717 copy.mId3 = mI3 == null ? null : mI3.loadDrawable(ctx); 4718 copy.mId4 = mI4 == null ? null : mI4.loadDrawable(ctx); 4719 } else { 4720 copy.mId1 = mD1 == 0 ? null : ctx.getDrawable(mD1); 4721 copy.mId2 = mD2 == 0 ? null : ctx.getDrawable(mD2); 4722 copy.mId3 = mD3 == 0 ? null : ctx.getDrawable(mD3); 4723 copy.mId4 = mD4 == 0 ? null : ctx.getDrawable(mD4); 4724 } 4725 return copy; 4726 } 4727 4728 @Override 4729 public boolean prefersAsyncApply() { 4730 return mUseIcons; 4731 } 4732 4733 @Override 4734 public int getActionTag() { 4735 return TEXT_VIEW_DRAWABLE_ACTION_TAG; 4736 } 4737 4738 @Override 4739 public void visitUris(@NonNull Consumer<Uri> visitor) { 4740 if (mUseIcons) { 4741 visitIconUri(mI1, visitor); 4742 visitIconUri(mI2, visitor); 4743 visitIconUri(mI3, visitor); 4744 visitIconUri(mI4, visitor); 4745 } 4746 } 4747 4748 @Override 4749 public boolean canWriteToProto() { 4750 return true; 4751 } 4752 4753 @Override 4754 public void writeToProto(ProtoOutputStream out, Context context, 4755 Resources appResources) { // rebase 4756 final long token = out.start(RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION); 4757 out.write(RemoteViewsProto.TextViewDrawableAction.VIEW_ID, 4758 appResources.getResourceName(mViewId)); 4759 out.write(RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE, mIsRelative); 4760 if (mUseIcons) { 4761 long iconsToken = out.start(RemoteViewsProto.TextViewDrawableAction.ICONS); 4762 if (mI1 != null) { 4763 writeIconToProto(out, appResources, mI1, 4764 RemoteViewsProto.TextViewDrawableAction.Icons.ONE); 4765 } 4766 if (mI2 != null) { 4767 writeIconToProto(out, appResources, mI2, 4768 RemoteViewsProto.TextViewDrawableAction.Icons.TWO); 4769 } 4770 if (mI3 != null) { 4771 writeIconToProto(out, appResources, mI3, 4772 RemoteViewsProto.TextViewDrawableAction.Icons.THREE); 4773 } 4774 if (mI4 != null) { 4775 writeIconToProto(out, appResources, mI4, 4776 RemoteViewsProto.TextViewDrawableAction.Icons.FOUR); 4777 } 4778 out.end(iconsToken); 4779 } else { 4780 long resourcesToken = out.start(RemoteViewsProto.TextViewDrawableAction.RESOURCES); 4781 if (mD1 != 0) { 4782 out.write(RemoteViewsProto.TextViewDrawableAction.Resources.ONE, 4783 appResources.getResourceName(mD1)); 4784 } 4785 if (mD2 != 0) { 4786 out.write(RemoteViewsProto.TextViewDrawableAction.Resources.TWO, 4787 appResources.getResourceName(mD2)); 4788 } 4789 if (mD3 != 0) { 4790 out.write(RemoteViewsProto.TextViewDrawableAction.Resources.THREE, 4791 appResources.getResourceName(mD3)); 4792 } 4793 if (mD4 != 0) { 4794 out.write(RemoteViewsProto.TextViewDrawableAction.Resources.FOUR, 4795 appResources.getResourceName(mD4)); 4796 } 4797 out.end(resourcesToken); 4798 } 4799 out.end(token); 4800 } 4801 4802 public static PendingResources<Action> createFromProto(ProtoInputStream in) 4803 throws Exception { 4804 final LongSparseArray<Object> values = new LongSparseArray<>(); 4805 4806 values.put(RemoteViewsProto.TextViewDrawableAction.ICONS, 4807 new SparseArray<PendingResources<Icon>>()); 4808 values.put(RemoteViewsProto.TextViewDrawableAction.RESOURCES, 4809 new SparseArray<String>()); 4810 final long token = in.start(RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION); 4811 while (in.nextField() != NO_MORE_FIELDS) { 4812 switch (in.getFieldNumber()) { 4813 case (int) RemoteViewsProto.TextViewDrawableAction.VIEW_ID: 4814 values.put(RemoteViewsProto.TextViewDrawableAction.VIEW_ID, 4815 in.readString(RemoteViewsProto.TextViewDrawableAction.VIEW_ID)); 4816 break; 4817 case (int) RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE: 4818 values.put(RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE, 4819 in.readBoolean( 4820 RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE)); 4821 break; 4822 case (int) RemoteViewsProto.TextViewDrawableAction.RESOURCES: 4823 final long resourcesToken = in.start( 4824 RemoteViewsProto.TextViewDrawableAction.RESOURCES); 4825 while (in.nextField() != NO_MORE_FIELDS) { 4826 switch (in.getFieldNumber()) { 4827 case (int) RemoteViewsProto.TextViewDrawableAction.Resources.ONE: 4828 ((SparseArray<String>) values.get( 4829 RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put( 4830 1, in.readString( 4831 RemoteViewsProto 4832 .TextViewDrawableAction.Resources.ONE)); 4833 break; 4834 case (int) RemoteViewsProto.TextViewDrawableAction.Resources.TWO: 4835 ((SparseArray<String>) values.get( 4836 RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put( 4837 2, in.readString( 4838 RemoteViewsProto 4839 .TextViewDrawableAction.Resources.TWO)); 4840 break; 4841 case (int) RemoteViewsProto.TextViewDrawableAction.Resources.THREE: 4842 ((SparseArray<String>) values.get( 4843 RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put( 4844 3, in.readString( 4845 RemoteViewsProto 4846 .TextViewDrawableAction 4847 .Resources.THREE)); 4848 break; 4849 case (int) RemoteViewsProto.TextViewDrawableAction.Resources.FOUR: 4850 ((SparseArray<String>) values.get( 4851 RemoteViewsProto.TextViewDrawableAction.RESOURCES)).put( 4852 4, in.readString( 4853 RemoteViewsProto 4854 .TextViewDrawableAction 4855 .Resources.FOUR)); 4856 break; 4857 default: 4858 Log.w(LOG_TAG, 4859 "Unhandled field while reading RemoteViews proto!\n" 4860 + ProtoUtils.currentFieldToString(in)); 4861 } 4862 } 4863 in.end(resourcesToken); 4864 break; 4865 case (int) RemoteViewsProto.TextViewDrawableAction.ICONS: 4866 final long iconsToken = in.start( 4867 RemoteViewsProto.TextViewDrawableAction.ICONS); 4868 while (in.nextField() != NO_MORE_FIELDS) { 4869 switch (in.getFieldNumber()) { 4870 case (int) RemoteViewsProto.TextViewDrawableAction.Icons.ONE: 4871 ((SparseArray<PendingResources<Icon>>) values.get( 4872 RemoteViewsProto.TextViewDrawableAction.ICONS)).put(1, 4873 createIconFromProto(in, 4874 RemoteViewsProto 4875 .TextViewDrawableAction.Icons.ONE)); 4876 break; 4877 case (int) RemoteViewsProto.TextViewDrawableAction.Icons.TWO: 4878 ((SparseArray<PendingResources<Icon>>) values.get( 4879 RemoteViewsProto.TextViewDrawableAction.ICONS)).put(2, 4880 createIconFromProto(in, 4881 RemoteViewsProto 4882 .TextViewDrawableAction.Icons.TWO)); 4883 break; 4884 case (int) RemoteViewsProto.TextViewDrawableAction.Icons.THREE: 4885 ((SparseArray<PendingResources<Icon>>) values.get( 4886 RemoteViewsProto.TextViewDrawableAction.ICONS)).put(3, 4887 createIconFromProto(in, 4888 RemoteViewsProto 4889 .TextViewDrawableAction.Icons.THREE)); 4890 break; 4891 case (int) RemoteViewsProto.TextViewDrawableAction.Icons.FOUR: 4892 ((SparseArray<PendingResources<Icon>>) values.get( 4893 RemoteViewsProto.TextViewDrawableAction.ICONS)).put(4, 4894 createIconFromProto(in, 4895 RemoteViewsProto 4896 .TextViewDrawableAction.Icons.FOUR)); 4897 break; 4898 default: 4899 Log.w(LOG_TAG, 4900 "Unhandled field while reading RemoteViews proto!\n" 4901 + ProtoUtils.currentFieldToString(in)); 4902 } 4903 } 4904 in.end(iconsToken); 4905 break; 4906 default: 4907 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 4908 + ProtoUtils.currentFieldToString(in)); 4909 } 4910 } 4911 in.end(token); 4912 4913 checkContainsKeys(values, new long[]{RemoteViewsProto.TextViewDrawableAction.VIEW_ID}); 4914 4915 return (context, resources, rootData, depth) -> { 4916 int viewId = getAsIdentifier(resources, values, 4917 RemoteViewsProto.TextViewDrawableAction.VIEW_ID); 4918 SparseArray<PendingResources<Icon>> icons = 4919 (SparseArray<PendingResources<Icon>>) values.get( 4920 RemoteViewsProto.TextViewDrawableAction.ICONS); 4921 SparseArray<String> resArray = (SparseArray<String>) values.get( 4922 RemoteViewsProto.TextViewDrawableAction.RESOURCES); 4923 boolean isRelative = (boolean) values.get( 4924 RemoteViewsProto.TextViewDrawableAction.IS_RELATIVE, false); 4925 if (icons.size() > 0) { 4926 return new TextViewDrawableAction(viewId, isRelative, 4927 icons.get(1).create(context, resources, rootData, depth), 4928 icons.get(2).create(context, resources, rootData, depth), 4929 icons.get(3).create(context, resources, rootData, depth), 4930 icons.get(4).create(context, resources, rootData, depth)); 4931 } else { 4932 int first = resArray.contains(1) ? getAsIdentifier(resources, resArray, 1) : 0; 4933 int second = resArray.contains(2) ? getAsIdentifier(resources, resArray, 2) : 0; 4934 int third = resArray.contains(3) ? getAsIdentifier(resources, resArray, 3) : 0; 4935 int fourth = resArray.contains(4) ? getAsIdentifier(resources, resArray, 4) : 0; 4936 return new TextViewDrawableAction(viewId, isRelative, first, second, third, 4937 fourth); 4938 } 4939 }; 4940 } 4941 } 4942 4943 /** 4944 * Helper action to set text size on a TextView in any supported units. 4945 */ 4946 private static class TextViewSizeAction extends Action { 4947 int mUnits; 4948 float mSize; 4949 4950 TextViewSizeAction(@IdRes int viewId, @ComplexDimensionUnit int units, float size) { 4951 this.mViewId = viewId; 4952 this.mUnits = units; 4953 this.mSize = size; 4954 } 4955 4956 TextViewSizeAction(Parcel parcel) { 4957 mViewId = parcel.readInt(); 4958 mUnits = parcel.readInt(); 4959 mSize = parcel.readFloat(); 4960 } 4961 4962 public void writeToParcel(Parcel dest, int flags) { 4963 dest.writeInt(mViewId); 4964 dest.writeInt(mUnits); 4965 dest.writeFloat(mSize); 4966 } 4967 4968 @Override 4969 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 4970 final TextView target = root.findViewById(mViewId); 4971 if (target == null) return; 4972 target.setTextSize(mUnits, mSize); 4973 } 4974 4975 @Override 4976 public int getActionTag() { 4977 return TEXT_VIEW_SIZE_ACTION_TAG; 4978 } 4979 4980 @Override 4981 public boolean canWriteToProto() { 4982 return true; 4983 } 4984 4985 @Override 4986 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 4987 final long token = out.start(RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION); 4988 out.write(RemoteViewsProto.TextViewSizeAction.VIEW_ID, 4989 appResources.getResourceName(mViewId)); 4990 out.write(RemoteViewsProto.TextViewSizeAction.UNITS, mUnits); 4991 out.write(RemoteViewsProto.TextViewSizeAction.SIZE, mSize); 4992 out.end(token); 4993 } 4994 4995 public static PendingResources<Action> createFromProto(ProtoInputStream in) 4996 throws Exception { 4997 final LongSparseArray<Object> values = new LongSparseArray<>(); 4998 4999 final long token = in.start(RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION); 5000 while (in.nextField() != NO_MORE_FIELDS) { 5001 switch (in.getFieldNumber()) { 5002 case (int) RemoteViewsProto.TextViewSizeAction.VIEW_ID: 5003 values.put(RemoteViewsProto.TextViewSizeAction.VIEW_ID, 5004 in.readString(RemoteViewsProto.TextViewSizeAction.VIEW_ID)); 5005 break; 5006 case (int) RemoteViewsProto.TextViewSizeAction.UNITS: 5007 values.put(RemoteViewsProto.TextViewSizeAction.UNITS, 5008 in.readInt(RemoteViewsProto.TextViewSizeAction.UNITS)); 5009 break; 5010 case (int) RemoteViewsProto.TextViewSizeAction.SIZE: 5011 values.put(RemoteViewsProto.TextViewSizeAction.SIZE, 5012 in.readFloat(RemoteViewsProto.TextViewSizeAction.SIZE)); 5013 break; 5014 default: 5015 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5016 + ProtoUtils.currentFieldToString(in)); 5017 } 5018 } 5019 in.end(token); 5020 5021 checkContainsKeys(values, new long[]{RemoteViewsProto.TextViewSizeAction.VIEW_ID}); 5022 5023 return (context, resources, rootData, depth) -> { 5024 int viewId = getAsIdentifier(resources, values, 5025 RemoteViewsProto.TextViewSizeAction.VIEW_ID); 5026 return new TextViewSizeAction(viewId, 5027 (int) values.get(RemoteViewsProto.TextViewSizeAction.UNITS, 0), 5028 (float) values.get(RemoteViewsProto.TextViewSizeAction.SIZE, 0)); 5029 }; 5030 } 5031 } 5032 5033 /** 5034 * Helper action to set padding on a View. 5035 */ 5036 private static class ViewPaddingAction extends Action { 5037 @Px int mLeft, mTop, mRight, mBottom; 5038 5039 public ViewPaddingAction(@IdRes int viewId, @Px int left, @Px int top, 5040 @Px int right, @Px int bottom) { 5041 this.mViewId = viewId; 5042 this.mLeft = left; 5043 this.mTop = top; 5044 this.mRight = right; 5045 this.mBottom = bottom; 5046 } 5047 5048 public ViewPaddingAction(Parcel parcel) { 5049 mViewId = parcel.readInt(); 5050 mLeft = parcel.readInt(); 5051 mTop = parcel.readInt(); 5052 mRight = parcel.readInt(); 5053 mBottom = parcel.readInt(); 5054 } 5055 5056 public void writeToParcel(Parcel dest, int flags) { 5057 dest.writeInt(mViewId); 5058 dest.writeInt(mLeft); 5059 dest.writeInt(mTop); 5060 dest.writeInt(mRight); 5061 dest.writeInt(mBottom); 5062 } 5063 5064 @Override 5065 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 5066 final View target = root.findViewById(mViewId); 5067 if (target == null) return; 5068 target.setPadding(mLeft, mTop, mRight, mBottom); 5069 } 5070 5071 @Override 5072 public int getActionTag() { 5073 return VIEW_PADDING_ACTION_TAG; 5074 } 5075 5076 @Override 5077 public boolean canWriteToProto() { 5078 return true; 5079 } 5080 5081 @Override 5082 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 5083 final long token = out.start(RemoteViewsProto.Action.VIEW_PADDING_ACTION); 5084 out.write(RemoteViewsProto.ViewPaddingAction.VIEW_ID, 5085 appResources.getResourceName(mViewId)); 5086 out.write(RemoteViewsProto.ViewPaddingAction.LEFT, mLeft); 5087 out.write(RemoteViewsProto.ViewPaddingAction.RIGHT, mRight); 5088 out.write(RemoteViewsProto.ViewPaddingAction.TOP, mTop); 5089 out.write(RemoteViewsProto.ViewPaddingAction.BOTTOM, mBottom); 5090 out.end(token); 5091 } 5092 5093 public static PendingResources<Action> createFromProto(ProtoInputStream in) 5094 throws Exception { 5095 final LongSparseArray<Object> values = new LongSparseArray<>(); 5096 5097 final long token = in.start(RemoteViewsProto.Action.VIEW_PADDING_ACTION); 5098 while (in.nextField() != NO_MORE_FIELDS) { 5099 switch (in.getFieldNumber()) { 5100 case (int) RemoteViewsProto.ViewPaddingAction.VIEW_ID: 5101 values.put(RemoteViewsProto.ViewPaddingAction.VIEW_ID, 5102 in.readString(RemoteViewsProto.ViewPaddingAction.VIEW_ID)); 5103 break; 5104 case (int) RemoteViewsProto.ViewPaddingAction.LEFT: 5105 values.put(RemoteViewsProto.ViewPaddingAction.LEFT, 5106 in.readInt(RemoteViewsProto.ViewPaddingAction.LEFT)); 5107 break; 5108 case (int) RemoteViewsProto.ViewPaddingAction.RIGHT: 5109 values.put(RemoteViewsProto.ViewPaddingAction.RIGHT, 5110 in.readInt(RemoteViewsProto.ViewPaddingAction.RIGHT)); 5111 break; 5112 case (int) RemoteViewsProto.ViewPaddingAction.TOP: 5113 values.put(RemoteViewsProto.ViewPaddingAction.TOP, 5114 in.readInt(RemoteViewsProto.ViewPaddingAction.TOP)); 5115 break; 5116 case (int) RemoteViewsProto.ViewPaddingAction.BOTTOM: 5117 values.put(RemoteViewsProto.ViewPaddingAction.BOTTOM, 5118 in.readInt(RemoteViewsProto.ViewPaddingAction.BOTTOM)); 5119 break; 5120 default: 5121 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5122 + ProtoUtils.currentFieldToString(in)); 5123 } 5124 } 5125 in.end(token); 5126 5127 checkContainsKeys(values, new long[]{RemoteViewsProto.ViewPaddingAction.VIEW_ID}); 5128 5129 return (context, resources, rootData, depth) -> { 5130 int viewId = getAsIdentifier(resources, values, 5131 RemoteViewsProto.ViewPaddingAction.VIEW_ID); 5132 return new ViewPaddingAction(viewId, 5133 (int) values.get(RemoteViewsProto.ViewPaddingAction.LEFT, 0), 5134 (int) values.get(RemoteViewsProto.ViewPaddingAction.TOP, 0), 5135 (int) values.get(RemoteViewsProto.ViewPaddingAction.RIGHT, 0), 5136 (int) values.get(RemoteViewsProto.ViewPaddingAction.BOTTOM, 0)); 5137 }; 5138 } 5139 } 5140 5141 /** 5142 * Helper action to set layout params on a View. 5143 */ 5144 private static class LayoutParamAction extends Action { 5145 static final int LAYOUT_MARGIN_LEFT = MARGIN_LEFT; 5146 static final int LAYOUT_MARGIN_TOP = MARGIN_TOP; 5147 static final int LAYOUT_MARGIN_RIGHT = MARGIN_RIGHT; 5148 static final int LAYOUT_MARGIN_BOTTOM = MARGIN_BOTTOM; 5149 static final int LAYOUT_MARGIN_START = MARGIN_START; 5150 static final int LAYOUT_MARGIN_END = MARGIN_END; 5151 static final int LAYOUT_WIDTH = 8; 5152 static final int LAYOUT_HEIGHT = 9; 5153 5154 final int mProperty; 5155 final int mValueType; 5156 final int mValue; 5157 5158 /** 5159 * @param viewId ID of the view alter 5160 * @param property which layout parameter to alter 5161 * @param value new value of the layout parameter 5162 * @param units the units of the given value 5163 */ 5164 LayoutParamAction(@IdRes int viewId, int property, float value, 5165 @ComplexDimensionUnit int units) { 5166 this.mViewId = viewId; 5167 this.mProperty = property; 5168 this.mValueType = VALUE_TYPE_COMPLEX_UNIT; 5169 this.mValue = TypedValue.createComplexDimension(value, units); 5170 } 5171 5172 /** 5173 * @param viewId ID of the view alter 5174 * @param property which layout parameter to alter 5175 * @param value value to set. 5176 * @param valueType must be one of {@link #VALUE_TYPE_COMPLEX_UNIT}, 5177 * {@link #VALUE_TYPE_RESOURCE}, {@link #VALUE_TYPE_ATTRIBUTE} or 5178 * {@link #VALUE_TYPE_RAW}. 5179 */ 5180 LayoutParamAction(@IdRes int viewId, int property, int value, @ValueType int valueType) { 5181 this.mViewId = viewId; 5182 this.mProperty = property; 5183 this.mValueType = valueType; 5184 this.mValue = value; 5185 } 5186 5187 public LayoutParamAction(Parcel parcel) { 5188 mViewId = parcel.readInt(); 5189 mProperty = parcel.readInt(); 5190 mValueType = parcel.readInt(); 5191 mValue = parcel.readInt(); 5192 } 5193 5194 public void writeToParcel(Parcel dest, int flags) { 5195 dest.writeInt(mViewId); 5196 dest.writeInt(mProperty); 5197 dest.writeInt(mValueType); 5198 dest.writeInt(mValue); 5199 } 5200 5201 @Override 5202 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 5203 final View target = root.findViewById(mViewId); 5204 if (target == null) { 5205 return; 5206 } 5207 ViewGroup.LayoutParams layoutParams = target.getLayoutParams(); 5208 if (layoutParams == null) { 5209 return; 5210 } 5211 switch (mProperty) { 5212 case LAYOUT_MARGIN_LEFT: 5213 if (layoutParams instanceof MarginLayoutParams) { 5214 ((MarginLayoutParams) layoutParams).leftMargin = getPixelOffset(target); 5215 target.setLayoutParams(layoutParams); 5216 } 5217 break; 5218 case LAYOUT_MARGIN_TOP: 5219 if (layoutParams instanceof MarginLayoutParams) { 5220 ((MarginLayoutParams) layoutParams).topMargin = getPixelOffset(target); 5221 target.setLayoutParams(layoutParams); 5222 } 5223 break; 5224 case LAYOUT_MARGIN_RIGHT: 5225 if (layoutParams instanceof MarginLayoutParams) { 5226 ((MarginLayoutParams) layoutParams).rightMargin = getPixelOffset(target); 5227 target.setLayoutParams(layoutParams); 5228 } 5229 break; 5230 case LAYOUT_MARGIN_BOTTOM: 5231 if (layoutParams instanceof MarginLayoutParams) { 5232 ((MarginLayoutParams) layoutParams).bottomMargin = getPixelOffset(target); 5233 target.setLayoutParams(layoutParams); 5234 } 5235 break; 5236 case LAYOUT_MARGIN_START: 5237 if (layoutParams instanceof MarginLayoutParams) { 5238 ((MarginLayoutParams) layoutParams).setMarginStart(getPixelOffset(target)); 5239 target.setLayoutParams(layoutParams); 5240 } 5241 break; 5242 case LAYOUT_MARGIN_END: 5243 if (layoutParams instanceof MarginLayoutParams) { 5244 ((MarginLayoutParams) layoutParams).setMarginEnd(getPixelOffset(target)); 5245 target.setLayoutParams(layoutParams); 5246 } 5247 break; 5248 case LAYOUT_WIDTH: 5249 layoutParams.width = getPixelSize(target); 5250 target.setLayoutParams(layoutParams); 5251 break; 5252 case LAYOUT_HEIGHT: 5253 layoutParams.height = getPixelSize(target); 5254 target.setLayoutParams(layoutParams); 5255 break; 5256 default: 5257 throw new IllegalArgumentException("Unknown property " + mProperty); 5258 } 5259 } 5260 5261 private int getPixelOffset(View target) { 5262 try { 5263 switch (mValueType) { 5264 case VALUE_TYPE_ATTRIBUTE: 5265 TypedArray typedArray = target.getContext().obtainStyledAttributes( 5266 new int[]{this.mValue}); 5267 try { 5268 return typedArray.getDimensionPixelOffset(0, 0); 5269 } finally { 5270 typedArray.recycle(); 5271 } 5272 case VALUE_TYPE_RESOURCE: 5273 if (mValue == 0) { 5274 return 0; 5275 } 5276 return target.getResources().getDimensionPixelOffset(mValue); 5277 case VALUE_TYPE_COMPLEX_UNIT: 5278 return TypedValue.complexToDimensionPixelOffset(mValue, 5279 target.getResources().getDisplayMetrics()); 5280 default: 5281 return mValue; 5282 } 5283 } catch (Throwable t) { 5284 throw new ActionException(t); 5285 } 5286 } 5287 5288 private int getPixelSize(View target) { 5289 try { 5290 switch (mValueType) { 5291 case VALUE_TYPE_ATTRIBUTE: 5292 TypedArray typedArray = target.getContext().obtainStyledAttributes( 5293 new int[]{this.mValue}); 5294 try { 5295 return typedArray.getDimensionPixelSize(0, 0); 5296 } finally { 5297 typedArray.recycle(); 5298 } 5299 case VALUE_TYPE_RESOURCE: 5300 if (mValue == 0) { 5301 return 0; 5302 } 5303 return target.getResources().getDimensionPixelSize(mValue); 5304 case VALUE_TYPE_COMPLEX_UNIT: 5305 return TypedValue.complexToDimensionPixelSize(mValue, 5306 target.getResources().getDisplayMetrics()); 5307 default: 5308 return mValue; 5309 } 5310 } catch (Throwable t) { 5311 throw new ActionException(t); 5312 } 5313 } 5314 5315 @Override 5316 public int getActionTag() { 5317 return LAYOUT_PARAM_ACTION_TAG; 5318 } 5319 5320 @Override 5321 public String getUniqueKey() { 5322 return super.getUniqueKey() + mProperty; 5323 } 5324 5325 @Override 5326 public boolean canWriteToProto() { 5327 return true; 5328 } 5329 5330 @Override 5331 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 5332 final long token = out.start(RemoteViewsProto.Action.LAYOUT_PARAM_ACTION); 5333 out.write(RemoteViewsProto.LayoutParamAction.VIEW_ID, 5334 appResources.getResourceName(mViewId)); 5335 out.write(RemoteViewsProto.LayoutParamAction.PROPERTY, mProperty); 5336 out.write(RemoteViewsProto.LayoutParamAction.LAYOUT_VALUE, mValue); 5337 out.write(RemoteViewsProto.LayoutParamAction.VALUE_TYPE, mValueType); 5338 out.end(token); 5339 } 5340 5341 public static PendingResources<Action> createFromProto(ProtoInputStream in) 5342 throws Exception { 5343 final LongSparseArray<Object> values = new LongSparseArray<>(); 5344 5345 final long token = in.start(RemoteViewsProto.Action.LAYOUT_PARAM_ACTION); 5346 while (in.nextField() != NO_MORE_FIELDS) { 5347 switch (in.getFieldNumber()) { 5348 case (int) RemoteViewsProto.LayoutParamAction.VIEW_ID: 5349 values.put(RemoteViewsProto.LayoutParamAction.VIEW_ID, 5350 in.readString(RemoteViewsProto.LayoutParamAction.VIEW_ID)); 5351 break; 5352 case (int) RemoteViewsProto.LayoutParamAction.PROPERTY: 5353 values.put(RemoteViewsProto.LayoutParamAction.PROPERTY, 5354 in.readInt(RemoteViewsProto.LayoutParamAction.PROPERTY)); 5355 break; 5356 case (int) RemoteViewsProto.LayoutParamAction.LAYOUT_VALUE: 5357 values.put(RemoteViewsProto.LayoutParamAction.LAYOUT_VALUE, 5358 in.readInt(RemoteViewsProto.LayoutParamAction.LAYOUT_VALUE)); 5359 break; 5360 case (int) RemoteViewsProto.LayoutParamAction.VALUE_TYPE: 5361 values.put(RemoteViewsProto.LayoutParamAction.VALUE_TYPE, 5362 in.readInt(RemoteViewsProto.LayoutParamAction.VALUE_TYPE)); 5363 break; 5364 default: 5365 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5366 + ProtoUtils.currentFieldToString(in)); 5367 } 5368 } 5369 in.end(token); 5370 5371 checkContainsKeys(values, new long[]{RemoteViewsProto.LayoutParamAction.VIEW_ID}); 5372 5373 return (context, resources, rootData, depth) -> { 5374 int viewId = getAsIdentifier(resources, values, 5375 RemoteViewsProto.LayoutParamAction.VIEW_ID); 5376 return new LayoutParamAction(viewId, 5377 (int) values.get(RemoteViewsProto.LayoutParamAction.PROPERTY, 0), 5378 (int) values.get(RemoteViewsProto.LayoutParamAction.LAYOUT_VALUE, 0), 5379 (int) values.get(RemoteViewsProto.LayoutParamAction.VALUE_TYPE, 0)); 5380 }; 5381 } 5382 } 5383 5384 /** 5385 * Helper action to add a view tag with RemoteInputs. 5386 */ 5387 private static class SetRemoteInputsAction extends Action { 5388 final Parcelable[] mRemoteInputs; 5389 5390 public SetRemoteInputsAction(@IdRes int viewId, RemoteInput[] remoteInputs) { 5391 this.mViewId = viewId; 5392 this.mRemoteInputs = remoteInputs; 5393 } 5394 5395 public SetRemoteInputsAction(Parcel parcel) { 5396 mViewId = parcel.readInt(); 5397 mRemoteInputs = parcel.createTypedArray(RemoteInput.CREATOR); 5398 } 5399 5400 public void writeToParcel(Parcel dest, int flags) { 5401 dest.writeInt(mViewId); 5402 dest.writeTypedArray(mRemoteInputs, flags); 5403 } 5404 5405 @Override 5406 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 5407 final View target = root.findViewById(mViewId); 5408 if (target == null) return; 5409 5410 target.setTagInternal(R.id.remote_input_tag, mRemoteInputs); 5411 } 5412 5413 @Override 5414 public int getActionTag() { 5415 return SET_REMOTE_INPUTS_ACTION_TAG; 5416 } 5417 } 5418 5419 private static class SetIntTagAction extends Action { 5420 @IdRes private final int mViewId; 5421 @IdRes private final int mKey; 5422 private final int mTag; 5423 5424 SetIntTagAction(@IdRes int viewId, @IdRes int key, int tag) { 5425 mViewId = viewId; 5426 mKey = key; 5427 mTag = tag; 5428 } 5429 5430 SetIntTagAction(Parcel parcel) { 5431 mViewId = parcel.readInt(); 5432 mKey = parcel.readInt(); 5433 mTag = parcel.readInt(); 5434 } 5435 5436 public void writeToParcel(Parcel dest, int flags) { 5437 dest.writeInt(mViewId); 5438 dest.writeInt(mKey); 5439 dest.writeInt(mTag); 5440 } 5441 5442 @Override 5443 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) { 5444 final View target = root.findViewById(mViewId); 5445 if (target == null) return; 5446 5447 target.setTagInternal(mKey, mTag); 5448 } 5449 5450 @Override 5451 public int getActionTag() { 5452 return SET_INT_TAG_TAG; 5453 } 5454 5455 @Override 5456 public boolean canWriteToProto() { 5457 return true; 5458 } 5459 5460 @Override 5461 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 5462 final long token = out.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); 5463 out.write(RemoteViewsProto.SetIntTagAction.VIEW_ID, 5464 appResources.getResourceName(mViewId)); 5465 out.write(RemoteViewsProto.SetIntTagAction.KEY, 5466 appResources.getResourceName(mKey)); // rebase 5467 out.write(RemoteViewsProto.SetIntTagAction.TAG, mTag); 5468 out.end(token); 5469 } 5470 5471 public static PendingResources<Action> createFromProto(ProtoInputStream in) 5472 throws Exception { 5473 final LongSparseArray<Object> values = new LongSparseArray<>(); 5474 5475 final long token = in.start(RemoteViewsProto.Action.SET_INT_TAG_ACTION); 5476 while (in.nextField() != NO_MORE_FIELDS) { 5477 switch (in.getFieldNumber()) { 5478 case (int) RemoteViewsProto.SetIntTagAction.VIEW_ID: 5479 values.put(RemoteViewsProto.SetIntTagAction.VIEW_ID, 5480 in.readString(RemoteViewsProto.SetIntTagAction.VIEW_ID)); 5481 break; 5482 case (int) RemoteViewsProto.SetIntTagAction.KEY: 5483 values.put(RemoteViewsProto.SetIntTagAction.KEY, 5484 in.readString(RemoteViewsProto.SetIntTagAction.KEY)); 5485 break; 5486 case (int) RemoteViewsProto.SetIntTagAction.TAG: 5487 values.put(RemoteViewsProto.SetIntTagAction.TAG, 5488 in.readInt(RemoteViewsProto.SetIntTagAction.TAG)); 5489 break; 5490 default: 5491 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5492 + ProtoUtils.currentFieldToString(in)); 5493 } 5494 } 5495 in.end(token); 5496 5497 checkContainsKeys(values, new long[]{RemoteViewsProto.SetIntTagAction.VIEW_ID, 5498 RemoteViewsProto.SetIntTagAction.KEY}); 5499 5500 return (context, resources, rootData, depth) -> { 5501 int viewId = getAsIdentifier(resources, values, 5502 RemoteViewsProto.SetIntTagAction.VIEW_ID); 5503 int keyId = getAsIdentifier(resources, values, 5504 RemoteViewsProto.SetIntTagAction.KEY); 5505 return new SetIntTagAction(viewId, keyId, 5506 (int) values.get(RemoteViewsProto.SetIntTagAction.TAG, 0)); 5507 }; 5508 } 5509 } 5510 5511 private static class SetCompoundButtonCheckedAction extends Action { 5512 private final boolean mChecked; 5513 5514 SetCompoundButtonCheckedAction(@IdRes int viewId, boolean checked) { 5515 this.mViewId = viewId; 5516 mChecked = checked; 5517 } 5518 5519 SetCompoundButtonCheckedAction(Parcel in) { 5520 mViewId = in.readInt(); 5521 mChecked = in.readBoolean(); 5522 } 5523 5524 @Override 5525 public void writeToParcel(Parcel dest, int flags) { 5526 dest.writeInt(mViewId); 5527 dest.writeBoolean(mChecked); 5528 } 5529 5530 @Override 5531 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) 5532 throws ActionException { 5533 final View target = root.findViewById(mViewId); 5534 if (target == null) return; 5535 5536 if (!(target instanceof CompoundButton)) { 5537 Log.w(LOG_TAG, "Cannot set checked to view " 5538 + mViewId + " because it is not a CompoundButton"); 5539 return; 5540 } 5541 5542 CompoundButton button = (CompoundButton) target; 5543 Object tag = button.getTag(R.id.remote_checked_change_listener_tag); 5544 // Temporarily unset the checked change listener so calling setChecked doesn't launch 5545 // the intent. 5546 if (tag instanceof OnCheckedChangeListener) { 5547 button.setOnCheckedChangeListener(null); 5548 button.setChecked(mChecked); 5549 button.setOnCheckedChangeListener((OnCheckedChangeListener) tag); 5550 } else { 5551 button.setChecked(mChecked); 5552 } 5553 } 5554 5555 @Override 5556 public int getActionTag() { 5557 return SET_COMPOUND_BUTTON_CHECKED_TAG; 5558 } 5559 5560 @Override 5561 public boolean canWriteToProto() { 5562 return true; 5563 } 5564 5565 @Override 5566 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 5567 final long token = out.start( 5568 RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); 5569 out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, 5570 appResources.getResourceName(mViewId)); 5571 out.write(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, mChecked); 5572 out.end(token); 5573 } 5574 5575 public static PendingResources<Action> createFromProto(ProtoInputStream in) 5576 throws Exception { 5577 final LongSparseArray<Object> values = new LongSparseArray<>(); 5578 5579 final long token = in.start(RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION); 5580 while (in.nextField() != NO_MORE_FIELDS) { 5581 switch (in.getFieldNumber()) { 5582 case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID: 5583 values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID, 5584 in.readString( 5585 RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID)); 5586 break; 5587 case (int) RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED: 5588 values.put(RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, 5589 in.readBoolean( 5590 RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED)); 5591 break; 5592 default: 5593 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5594 + ProtoUtils.currentFieldToString(in)); 5595 } 5596 } 5597 in.end(token); 5598 5599 checkContainsKeys(values, 5600 new long[]{RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID}); 5601 5602 return (context, resources, rootData, depth) -> { 5603 int viewId = getAsIdentifier(resources, values, 5604 RemoteViewsProto.SetCompoundButtonCheckedAction.VIEW_ID); 5605 return new SetCompoundButtonCheckedAction(viewId, (boolean) values.get( 5606 RemoteViewsProto.SetCompoundButtonCheckedAction.CHECKED, false)); 5607 }; 5608 } 5609 } 5610 5611 private static class SetRadioGroupCheckedAction extends Action { 5612 @IdRes private final int mCheckedId; 5613 5614 SetRadioGroupCheckedAction(@IdRes int viewId, @IdRes int checkedId) { 5615 this.mViewId = viewId; 5616 mCheckedId = checkedId; 5617 } 5618 5619 SetRadioGroupCheckedAction(Parcel in) { 5620 mViewId = in.readInt(); 5621 mCheckedId = in.readInt(); 5622 } 5623 5624 @Override 5625 public void writeToParcel(Parcel dest, int flags) { 5626 dest.writeInt(mViewId); 5627 dest.writeInt(mCheckedId); 5628 } 5629 5630 @Override 5631 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) 5632 throws ActionException { 5633 final View target = root.findViewById(mViewId); 5634 if (target == null) return; 5635 5636 if (!(target instanceof RadioGroup)) { 5637 Log.w(LOG_TAG, "Cannot check " + mViewId + " because it's not a RadioGroup"); 5638 return; 5639 } 5640 5641 RadioGroup group = (RadioGroup) target; 5642 5643 // Temporarily unset all the checked change listeners while we check the group. 5644 for (int i = 0; i < group.getChildCount(); i++) { 5645 View child = group.getChildAt(i); 5646 if (!(child instanceof CompoundButton)) continue; 5647 5648 Object tag = child.getTag(R.id.remote_checked_change_listener_tag); 5649 if (!(tag instanceof OnCheckedChangeListener)) continue; 5650 5651 // Clear the checked change listener, we'll restore it after the check. 5652 ((CompoundButton) child).setOnCheckedChangeListener(null); 5653 } 5654 5655 group.check(mCheckedId); 5656 5657 // Loop through the children again and restore the checked change listeners. 5658 for (int i = 0; i < group.getChildCount(); i++) { 5659 View child = group.getChildAt(i); 5660 if (!(child instanceof CompoundButton)) continue; 5661 5662 Object tag = child.getTag(R.id.remote_checked_change_listener_tag); 5663 if (!(tag instanceof OnCheckedChangeListener)) continue; 5664 5665 ((CompoundButton) child).setOnCheckedChangeListener((OnCheckedChangeListener) tag); 5666 } 5667 } 5668 5669 @Override 5670 public int getActionTag() { 5671 return SET_RADIO_GROUP_CHECKED; 5672 } 5673 5674 @Override 5675 public boolean canWriteToProto() { 5676 return true; 5677 } 5678 5679 @Override 5680 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 5681 final long token = out.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); 5682 out.write(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, 5683 appResources.getResourceName(mViewId)); 5684 if (mCheckedId != -1) { 5685 out.write(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, 5686 appResources.getResourceName(mCheckedId)); 5687 } 5688 out.end(token); 5689 } 5690 5691 public static PendingResources<Action> createFromProto(ProtoInputStream in) 5692 throws Exception { 5693 final LongSparseArray<Object> values = new LongSparseArray<>(); 5694 5695 final long token = in.start(RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION); 5696 while (in.nextField() != NO_MORE_FIELDS) { 5697 switch (in.getFieldNumber()) { 5698 case (int) RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID: 5699 values.put(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID, 5700 in.readString(RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID)); 5701 break; 5702 case (int) RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID: 5703 values.put(RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID, 5704 in.readString( 5705 RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID)); 5706 break; 5707 default: 5708 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5709 + ProtoUtils.currentFieldToString(in)); 5710 } 5711 } 5712 in.end(token); 5713 5714 checkContainsKeys(values, 5715 new long[]{RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID}); 5716 5717 return (context, resources, rootData, depth) -> { 5718 int viewId = getAsIdentifier(resources, values, 5719 RemoteViewsProto.SetRadioGroupCheckedAction.VIEW_ID); 5720 5721 int checkedId = (values.indexOfKey( 5722 RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) >= 0) 5723 ? getAsIdentifier(resources, values, 5724 RemoteViewsProto.SetRadioGroupCheckedAction.CHECKED_ID) : -1; 5725 return new SetRadioGroupCheckedAction(viewId, checkedId); 5726 }; 5727 } 5728 } 5729 5730 private static class SetViewOutlinePreferredRadiusAction extends Action { 5731 @ValueType 5732 private final int mValueType; 5733 private final int mValue; 5734 5735 SetViewOutlinePreferredRadiusAction(@IdRes int viewId, int value, 5736 @ValueType int valueType) { 5737 this.mViewId = viewId; 5738 this.mValueType = valueType; 5739 this.mValue = value; 5740 } 5741 5742 SetViewOutlinePreferredRadiusAction( 5743 @IdRes int viewId, float radius, @ComplexDimensionUnit int units) { 5744 this.mViewId = viewId; 5745 this.mValueType = VALUE_TYPE_COMPLEX_UNIT; 5746 this.mValue = TypedValue.createComplexDimension(radius, units); 5747 5748 } 5749 5750 SetViewOutlinePreferredRadiusAction(Parcel in) { 5751 mViewId = in.readInt(); 5752 mValueType = in.readInt(); 5753 mValue = in.readInt(); 5754 } 5755 5756 @Override 5757 public void writeToParcel(Parcel dest, int flags) { 5758 dest.writeInt(mViewId); 5759 dest.writeInt(mValueType); 5760 dest.writeInt(mValue); 5761 } 5762 5763 @Override 5764 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) 5765 throws ActionException { 5766 final View target = root.findViewById(mViewId); 5767 if (target == null) return; 5768 5769 try { 5770 float radius; 5771 switch (mValueType) { 5772 case VALUE_TYPE_ATTRIBUTE: 5773 TypedArray typedArray = target.getContext().obtainStyledAttributes( 5774 new int[]{mValue}); 5775 try { 5776 radius = typedArray.getDimension(0, 0); 5777 } finally { 5778 typedArray.recycle(); 5779 } 5780 break; 5781 case VALUE_TYPE_RESOURCE: 5782 radius = mValue == 0 ? 0 : target.getResources().getDimension(mValue); 5783 break; 5784 case VALUE_TYPE_COMPLEX_UNIT: 5785 radius = TypedValue.complexToDimension(mValue, 5786 target.getResources().getDisplayMetrics()); 5787 break; 5788 default: 5789 radius = mValue; 5790 } 5791 target.setOutlineProvider(new RemoteViewOutlineProvider(radius)); 5792 } catch (Throwable t) { 5793 throw new ActionException(t); 5794 } 5795 } 5796 5797 @Override 5798 public int getActionTag() { 5799 return SET_VIEW_OUTLINE_RADIUS_TAG; 5800 } 5801 5802 @Override 5803 public boolean canWriteToProto() { 5804 return true; 5805 } 5806 5807 @Override 5808 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 5809 final long token = out.start( 5810 RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION); 5811 out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID, 5812 appResources.getResourceName(mViewId)); 5813 out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE, mValueType); 5814 out.write(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE, mValue); 5815 out.end(token); 5816 } 5817 5818 public static PendingResources<Action> createFromProto(ProtoInputStream in) 5819 throws Exception { 5820 final LongSparseArray<Object> values = new LongSparseArray<>(); 5821 5822 final long token = in.start( 5823 RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION); 5824 while (in.nextField() != NO_MORE_FIELDS) { 5825 switch (in.getFieldNumber()) { 5826 case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID: 5827 values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID, 5828 in.readString( 5829 RemoteViewsProto 5830 .SetViewOutlinePreferredRadiusAction.VIEW_ID)); 5831 break; 5832 case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE: 5833 values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE, 5834 in.readInt( 5835 RemoteViewsProto 5836 .SetViewOutlinePreferredRadiusAction.VALUE_TYPE)); 5837 break; 5838 case (int) RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE: 5839 values.put(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE, 5840 in.readInt( 5841 RemoteViewsProto 5842 .SetViewOutlinePreferredRadiusAction.VALUE)); 5843 break; 5844 default: 5845 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5846 + ProtoUtils.currentFieldToString(in)); 5847 } 5848 } 5849 in.end(token); 5850 5851 checkContainsKeys(values, 5852 new long[]{RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID, 5853 RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE}); 5854 5855 return (context, resources, rootData, depth) -> { 5856 int viewId = getAsIdentifier(resources, values, 5857 RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VIEW_ID); 5858 return new SetViewOutlinePreferredRadiusAction(viewId, 5859 (int) values.get(RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE, 5860 0), (int) values.get( 5861 RemoteViewsProto.SetViewOutlinePreferredRadiusAction.VALUE_TYPE)); 5862 }; 5863 } 5864 } 5865 5866 /** 5867 * OutlineProvider for a view with a radius set by 5868 * {@link #setViewOutlinePreferredRadius(int, float, int)}. 5869 */ 5870 public static final class RemoteViewOutlineProvider extends ViewOutlineProvider { 5871 private final float mRadius; 5872 5873 public RemoteViewOutlineProvider(float radius) { 5874 mRadius = radius; 5875 } 5876 5877 /** Returns the corner radius used when providing the view outline. */ 5878 public float getRadius() { 5879 return mRadius; 5880 } 5881 5882 @Override 5883 public void getOutline(@NonNull View view, @NonNull Outline outline) { 5884 outline.setRoundRect( 5885 0 /*left*/, 5886 0 /* top */, 5887 view.getWidth() /* right */, 5888 view.getHeight() /* bottom */, 5889 mRadius); 5890 } 5891 } 5892 5893 private class SetDrawInstructionAction extends Action { 5894 5895 @Nullable 5896 private final DrawInstructions mInstructions; 5897 5898 SetDrawInstructionAction(@NonNull final DrawInstructions instructions) { 5899 mInstructions = instructions; 5900 } 5901 5902 SetDrawInstructionAction(@NonNull final Parcel in) { 5903 if (drawDataParcel()) { 5904 mInstructions = DrawInstructions.readFromParcel(in); 5905 } else { 5906 mInstructions = null; 5907 } 5908 } 5909 5910 @Override 5911 public void writeToParcel(Parcel dest, int flags) { 5912 if (drawDataParcel()) { 5913 DrawInstructions.writeToParcel(mInstructions, dest, flags); 5914 } 5915 } 5916 5917 @Override 5918 public void apply(View root, ViewGroup rootParent, ActionApplyParams params) 5919 throws ActionException { 5920 if (drawDataParcel() && mInstructions != null 5921 && root instanceof RemoteComposePlayer player) { 5922 final List<byte[]> bytes = mInstructions.mInstructions; 5923 if (bytes.isEmpty()) { 5924 return; 5925 } 5926 try (ByteArrayInputStream is = new ByteArrayInputStream(bytes.get(0))) { 5927 player.setDocument(new RemoteComposeDocument(is)); 5928 player.addIdActionListener((viewId, metadata) -> { 5929 mActions.forEach(action -> { 5930 if (viewId == action.mViewId 5931 && action instanceof SetOnClickResponse setOnClickResponse) { 5932 final RemoteResponse response = setOnClickResponse.mResponse; 5933 if (response.mFillIntent == null) { 5934 response.mFillIntent = new Intent(); 5935 } 5936 response.mFillIntent.putExtra( 5937 "remotecompose_metadata", metadata); 5938 response.handleViewInteraction(player, params.handler); 5939 } 5940 }); 5941 }); 5942 } catch (IOException e) { 5943 Log.e(LOG_TAG, "Failed to render draw instructions", e); 5944 } 5945 } 5946 } 5947 5948 @Override 5949 public int getActionTag() { 5950 return SET_DRAW_INSTRUCTION_TAG; 5951 } 5952 5953 @Override 5954 public boolean canWriteToProto() { 5955 return drawDataParcel(); 5956 } 5957 5958 @Override 5959 public void writeToProto(ProtoOutputStream out, Context context, Resources appResources) { 5960 if (!drawDataParcel()) return; 5961 final long token = out.start(RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION); 5962 if (mInstructions != null) { 5963 for (byte[] bytes : mInstructions.mInstructions) { 5964 out.write(RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS, bytes); 5965 } 5966 } 5967 out.end(token); 5968 } 5969 } 5970 5971 @FlaggedApi(FLAG_DRAW_DATA_PARCEL) 5972 private PendingResources<Action> createSetDrawInstructionActionFromProto(ProtoInputStream in) 5973 throws Exception { 5974 List<byte[]> instructions = new ArrayList<byte[]>(); 5975 5976 final long token = in.start(RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION); 5977 while (in.nextField() != NO_MORE_FIELDS) { 5978 switch (in.getFieldNumber()) { 5979 case (int) RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS: 5980 instructions.add( 5981 in.readBytes(RemoteViewsProto.SetDrawInstructionAction.INSTRUCTIONS)); 5982 break; 5983 default: 5984 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 5985 + ProtoUtils.currentFieldToString(in)); 5986 } 5987 } 5988 in.end(token); 5989 5990 return (context, resources, rootData, depth) -> new SetDrawInstructionAction( 5991 new DrawInstructions.Builder(instructions).build()); 5992 } 5993 5994 /** 5995 * Create a new RemoteViews object that will display the views contained 5996 * in the specified layout file. 5997 * 5998 * @param packageName Name of the package that contains the layout resource 5999 * @param layoutId The id of the layout resource 6000 */ 6001 public RemoteViews(String packageName, int layoutId) { 6002 this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId); 6003 } 6004 6005 /** 6006 * Create a new RemoteViews object that will display the views contained 6007 * in the specified layout file and change the id of the root view to the specified one. 6008 * 6009 * @param packageName Name of the package that contains the layout resource 6010 * @param layoutId The id of the layout resource 6011 */ 6012 public RemoteViews(@NonNull String packageName, @LayoutRes int layoutId, @IdRes int viewId) { 6013 this(packageName, layoutId); 6014 this.mViewId = viewId; 6015 } 6016 6017 /** 6018 * Create a new RemoteViews object that will display the views contained 6019 * in the specified layout file. 6020 * 6021 * @param application The application whose content is shown by the views. 6022 * @param layoutId The id of the layout resource. 6023 * 6024 * @hide 6025 */ 6026 protected RemoteViews(ApplicationInfo application, @LayoutRes int layoutId) { 6027 mApplication = application; 6028 mLayoutId = layoutId; 6029 mApplicationInfoCache.put(application); 6030 } 6031 6032 private boolean hasMultipleLayouts() { 6033 return hasLandscapeAndPortraitLayouts() || hasSizedRemoteViews(); 6034 } 6035 6036 private boolean hasLandscapeAndPortraitLayouts() { 6037 return (mLandscape != null) && (mPortrait != null); 6038 } 6039 6040 private boolean hasSizedRemoteViews() { 6041 return mSizedRemoteViews != null; 6042 } 6043 6044 @Nullable 6045 private SizeF getIdealSize() { 6046 return mIdealSize; 6047 } 6048 6049 private void setIdealSize(@Nullable SizeF size) { 6050 mIdealSize = size; 6051 } 6052 6053 /** 6054 * Finds the smallest view in {@code mSizedRemoteViews}. 6055 * This method must not be called if {@code mSizedRemoteViews} is null. 6056 */ 6057 private RemoteViews findSmallestRemoteView() { 6058 return mSizedRemoteViews.get(mSizedRemoteViews.size() - 1); 6059 } 6060 6061 /** 6062 * Create a new RemoteViews object that will inflate as the specified 6063 * landspace or portrait RemoteViews, depending on the current configuration. 6064 * 6065 * @param landscape The RemoteViews to inflate in landscape configuration 6066 * @param portrait The RemoteViews to inflate in portrait configuration 6067 * @throws IllegalArgumentException if either landscape or portrait are null or if they are 6068 * not from the same application 6069 */ 6070 public RemoteViews(RemoteViews landscape, RemoteViews portrait) { 6071 if (landscape == null || portrait == null) { 6072 throw new IllegalArgumentException("Both RemoteViews must be non-null"); 6073 } 6074 if (!landscape.hasSameAppInfo(portrait.mApplication)) { 6075 throw new IllegalArgumentException( 6076 "Both RemoteViews must share the same package and user"); 6077 } 6078 mApplication = portrait.mApplication; 6079 mLayoutId = portrait.mLayoutId; 6080 mViewId = portrait.mViewId; 6081 mLightBackgroundLayoutId = portrait.mLightBackgroundLayoutId; 6082 6083 mLandscape = landscape; 6084 mPortrait = portrait; 6085 6086 mClassCookies = (portrait.mClassCookies != null) 6087 ? portrait.mClassCookies : landscape.mClassCookies; 6088 6089 configureDescendantsAsChildren(); 6090 } 6091 6092 /** 6093 * Create a new RemoteViews object that will inflate the layout with the closest size 6094 * specification. 6095 * 6096 * The default remote views in that case is always the one with the smallest area. 6097 * 6098 * If the {@link RemoteViews} host provides the size of the view, the layout with the largest 6099 * area that fits entirely in the provided size will be used (i.e. the width and height of 6100 * the layout must be less than the size of the view, with a 1dp margin to account for 6101 * rounding). If no layout fits in the view, the layout with the smallest area will be used. 6102 * 6103 * @param remoteViews Mapping of size to layout. 6104 * @throws IllegalArgumentException if the map is empty, there are more than 6105 * MAX_INIT_VIEW_COUNT layouts or the remote views are not all from the same application. 6106 */ 6107 public RemoteViews(@NonNull Map<SizeF, RemoteViews> remoteViews) { 6108 if (remoteViews.isEmpty()) { 6109 throw new IllegalArgumentException("The set of RemoteViews cannot be empty"); 6110 } 6111 if (remoteViews.size() > MAX_INIT_VIEW_COUNT) { 6112 throw new IllegalArgumentException("Too many RemoteViews in constructor"); 6113 } 6114 if (remoteViews.size() == 1) { 6115 // If the map only contains a single mapping, treat this as if that RemoteViews was 6116 // passed as the top-level RemoteViews. 6117 RemoteViews single = remoteViews.values().iterator().next(); 6118 initializeFrom(single, /* hierarchyRoot= */ single); 6119 return; 6120 } 6121 mClassCookies = initializeSizedRemoteViews( 6122 remoteViews.entrySet().stream().map( 6123 entry -> { 6124 entry.getValue().setIdealSize(entry.getKey()); 6125 return entry.getValue(); 6126 } 6127 ).iterator() 6128 ); 6129 6130 RemoteViews smallestView = findSmallestRemoteView(); 6131 mApplication = smallestView.mApplication; 6132 mLayoutId = smallestView.mLayoutId; 6133 mViewId = smallestView.mViewId; 6134 mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; 6135 6136 configureDescendantsAsChildren(); 6137 } 6138 6139 // Initialize mSizedRemoteViews and return the class cookies. 6140 private Map<Class, Object> initializeSizedRemoteViews(Iterator<RemoteViews> remoteViews) { 6141 List<RemoteViews> sizedRemoteViews = new ArrayList<>(); 6142 Map<Class, Object> classCookies = null; 6143 float viewArea = Float.MAX_VALUE; 6144 RemoteViews smallestView = null; 6145 while (remoteViews.hasNext()) { 6146 RemoteViews view = remoteViews.next(); 6147 SizeF size = view.getIdealSize(); 6148 if (size == null) { 6149 throw new IllegalStateException("Expected RemoteViews to have ideal size"); 6150 } 6151 float newViewArea = size.getWidth() * size.getHeight(); 6152 if (smallestView != null && !view.hasSameAppInfo(smallestView.mApplication)) { 6153 throw new IllegalArgumentException( 6154 "All RemoteViews must share the same package and user"); 6155 } 6156 if (smallestView == null || newViewArea < viewArea) { 6157 if (smallestView != null) { 6158 sizedRemoteViews.add(smallestView); 6159 } 6160 viewArea = newViewArea; 6161 smallestView = view; 6162 } else { 6163 sizedRemoteViews.add(view); 6164 } 6165 view.setIdealSize(size); 6166 if (classCookies == null) { 6167 classCookies = view.mClassCookies; 6168 } 6169 } 6170 sizedRemoteViews.add(smallestView); 6171 mSizedRemoteViews = sizedRemoteViews; 6172 return classCookies; 6173 } 6174 6175 /** 6176 * Creates a copy of another RemoteViews. 6177 */ 6178 public RemoteViews(RemoteViews src) { 6179 initializeFrom(src, /* hierarchyRoot= */ null); 6180 } 6181 6182 /** 6183 * No-arg constructor for use with {@link #initializeFrom(RemoteViews, RemoteViews)}. A 6184 * constructor taking two RemoteViews parameters would clash with the landscape/portrait 6185 * constructor. 6186 */ 6187 private RemoteViews() {} 6188 6189 private static RemoteViews createInitializedFrom(@NonNull RemoteViews src, 6190 @Nullable RemoteViews hierarchyRoot) { 6191 RemoteViews child = new RemoteViews(); 6192 child.initializeFrom(src, hierarchyRoot); 6193 return child; 6194 } 6195 6196 private void initializeFrom(@NonNull RemoteViews src, @Nullable RemoteViews hierarchyRoot) { 6197 if (hierarchyRoot == null) { 6198 mBitmapCache = src.mBitmapCache; 6199 // We need to create a new instance because we don't reconstruct collection cache 6200 mCollectionCache = new RemoteCollectionCache(src.mCollectionCache); 6201 mApplicationInfoCache = src.mApplicationInfoCache; 6202 } else { 6203 mBitmapCache = hierarchyRoot.mBitmapCache; 6204 mCollectionCache = hierarchyRoot.mCollectionCache; 6205 mApplicationInfoCache = hierarchyRoot.mApplicationInfoCache; 6206 } 6207 if (hierarchyRoot == null || src.mIsRoot) { 6208 // If there's no provided root, or if src was itself a root, then this RemoteViews is 6209 // the root of the new hierarchy. 6210 mIsRoot = true; 6211 hierarchyRoot = this; 6212 } else { 6213 // Otherwise, we're a descendant in the hierarchy. 6214 mIsRoot = false; 6215 } 6216 mApplication = src.mApplication; 6217 mLayoutId = src.mLayoutId; 6218 mLightBackgroundLayoutId = src.mLightBackgroundLayoutId; 6219 mApplyFlags = src.mApplyFlags; 6220 mClassCookies = src.mClassCookies; 6221 mIdealSize = src.mIdealSize; 6222 mProviderInstanceId = src.mProviderInstanceId; 6223 mHasDrawInstructions = src.mHasDrawInstructions; 6224 6225 if (src.hasLandscapeAndPortraitLayouts()) { 6226 mLandscape = createInitializedFrom(src.mLandscape, hierarchyRoot); 6227 mPortrait = createInitializedFrom(src.mPortrait, hierarchyRoot); 6228 } 6229 6230 if (src.hasSizedRemoteViews()) { 6231 mSizedRemoteViews = new ArrayList<>(src.mSizedRemoteViews.size()); 6232 for (RemoteViews srcView : src.mSizedRemoteViews) { 6233 mSizedRemoteViews.add(createInitializedFrom(srcView, hierarchyRoot)); 6234 } 6235 } 6236 6237 if (src.mActions != null) { 6238 Parcel p = Parcel.obtain(); 6239 p.putClassCookies(mClassCookies); 6240 src.writeActionsToParcel(p, /* flags= */ 0); 6241 p.setDataPosition(0); 6242 // Since src is already in memory, we do not care about stack overflow as it has 6243 // already been read once. 6244 readActionsFromParcel(p, 0); 6245 p.recycle(); 6246 } 6247 6248 // Now that everything is initialized and duplicated, create new caches for this 6249 // RemoteViews and recursively set up all descendants. 6250 if (mIsRoot) { 6251 reconstructCaches(); 6252 } 6253 } 6254 6255 /** 6256 * Reads a RemoteViews object from a parcel. 6257 * 6258 * @param parcel the parcel object 6259 */ 6260 public RemoteViews(Parcel parcel) { 6261 this(parcel, /* rootData= */ null, /* info= */ null, /* depth= */ 0); 6262 } 6263 6264 /** 6265 * Instantiates a RemoteViews object using {@link DrawInstructions}, which serves as an 6266 * alternative to XML layout. {@link DrawInstructions} objects contains the instructions which 6267 * can be interpreted and rendered accordingly in the host process. 6268 * 6269 * @param drawInstructions The {@link DrawInstructions} object 6270 */ 6271 @FlaggedApi(FLAG_DRAW_DATA_PARCEL) 6272 public RemoteViews(@NonNull final DrawInstructions drawInstructions) { 6273 Objects.requireNonNull(drawInstructions); 6274 mHasDrawInstructions = true; 6275 addAction(new SetDrawInstructionAction(drawInstructions)); 6276 } 6277 6278 private RemoteViews(@NonNull Parcel parcel, @Nullable HierarchyRootData rootData, 6279 @Nullable ApplicationInfo info, int depth) { 6280 if (depth > MAX_NESTED_VIEWS 6281 && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) { 6282 throw new IllegalArgumentException("Too many nested views."); 6283 } 6284 depth++; 6285 6286 int mode = parcel.readInt(); 6287 6288 if (rootData == null) { 6289 // We only store a bitmap cache in the root of the RemoteViews. 6290 mBitmapCache = new BitmapCache(parcel); 6291 // Store the class cookies such that they are available when we clone this RemoteView. 6292 mClassCookies = parcel.copyClassCookies(); 6293 mCollectionCache = new RemoteCollectionCache(parcel); 6294 } else { 6295 configureAsChild(rootData); 6296 } 6297 6298 if (mode == MODE_NORMAL) { 6299 mApplication = parcel.readTypedObject(ApplicationInfo.CREATOR); 6300 mIdealSize = parcel.readInt() == 0 ? null : SizeF.CREATOR.createFromParcel(parcel); 6301 mLayoutId = parcel.readInt(); 6302 mViewId = parcel.readInt(); 6303 mLightBackgroundLayoutId = parcel.readInt(); 6304 6305 readActionsFromParcel(parcel, depth); 6306 } else if (mode == MODE_HAS_SIZED_REMOTEVIEWS) { 6307 int numViews = parcel.readInt(); 6308 if (numViews > MAX_INIT_VIEW_COUNT) { 6309 throw new IllegalArgumentException( 6310 "Too many views in mapping from size to RemoteViews."); 6311 } 6312 List<RemoteViews> remoteViews = new ArrayList<>(numViews); 6313 for (int i = 0; i < numViews; i++) { 6314 RemoteViews view = new RemoteViews(parcel, getHierarchyRootData(), info, depth); 6315 info = view.mApplication; 6316 remoteViews.add(view); 6317 } 6318 initializeSizedRemoteViews(remoteViews.iterator()); 6319 RemoteViews smallestView = findSmallestRemoteView(); 6320 mApplication = smallestView.mApplication; 6321 mLayoutId = smallestView.mLayoutId; 6322 mViewId = smallestView.mViewId; 6323 mLightBackgroundLayoutId = smallestView.mLightBackgroundLayoutId; 6324 } else { 6325 // MODE_HAS_LANDSCAPE_AND_PORTRAIT 6326 mLandscape = new RemoteViews(parcel, getHierarchyRootData(), info, depth); 6327 mPortrait = 6328 new RemoteViews(parcel, getHierarchyRootData(), mLandscape.mApplication, depth); 6329 mApplication = mPortrait.mApplication; 6330 mLayoutId = mPortrait.mLayoutId; 6331 mViewId = mPortrait.mViewId; 6332 mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId; 6333 } 6334 mApplyFlags = parcel.readInt(); 6335 mProviderInstanceId = parcel.readLong(); 6336 mHasDrawInstructions = parcel.readBoolean(); 6337 6338 // Ensure that all descendants have their caches set up recursively. 6339 if (mIsRoot) { 6340 configureDescendantsAsChildren(); 6341 } 6342 } 6343 6344 private void readActionsFromParcel(Parcel parcel, int depth) { 6345 int count = parcel.readInt(); 6346 if (count > 0) { 6347 mActions = new ArrayList<>(count); 6348 for (int i = 0; i < count; i++) { 6349 mActions.add(getActionFromParcel(parcel, depth)); 6350 } 6351 } 6352 } 6353 6354 private Action getActionFromParcel(Parcel parcel, int depth) { 6355 int tag = parcel.readInt(); 6356 switch (tag) { 6357 case SET_ON_CLICK_RESPONSE_TAG: 6358 return new SetOnClickResponse(parcel); 6359 case SET_DRAWABLE_TINT_TAG: 6360 return new SetDrawableTint(parcel); 6361 case REFLECTION_ACTION_TAG: 6362 return new ReflectionAction(parcel); 6363 case VIEW_GROUP_ACTION_ADD_TAG: 6364 return new ViewGroupActionAdd(parcel, mApplication, depth); 6365 case VIEW_GROUP_ACTION_REMOVE_TAG: 6366 return new ViewGroupActionRemove(parcel); 6367 case VIEW_CONTENT_NAVIGATION_TAG: 6368 return new ViewContentNavigation(parcel); 6369 case SET_EMPTY_VIEW_ACTION_TAG: 6370 return new SetEmptyView(parcel); 6371 case SET_PENDING_INTENT_TEMPLATE_TAG: 6372 return new SetPendingIntentTemplate(parcel); 6373 case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG: 6374 return new SetRemoteViewsAdapterIntent(parcel); 6375 case TEXT_VIEW_DRAWABLE_ACTION_TAG: 6376 return new TextViewDrawableAction(parcel); 6377 case TEXT_VIEW_SIZE_ACTION_TAG: 6378 return new TextViewSizeAction(parcel); 6379 case VIEW_PADDING_ACTION_TAG: 6380 return new ViewPaddingAction(parcel); 6381 case BITMAP_REFLECTION_ACTION_TAG: 6382 return new BitmapReflectionAction(parcel); 6383 case SET_REMOTE_INPUTS_ACTION_TAG: 6384 return new SetRemoteInputsAction(parcel); 6385 case LAYOUT_PARAM_ACTION_TAG: 6386 return new LayoutParamAction(parcel); 6387 case SET_RIPPLE_DRAWABLE_COLOR_TAG: 6388 return new SetRippleDrawableColor(parcel); 6389 case SET_INT_TAG_TAG: 6390 return new SetIntTagAction(parcel); 6391 case REMOVE_FROM_PARENT_ACTION_TAG: 6392 return new RemoveFromParentAction(parcel); 6393 case RESOURCE_REFLECTION_ACTION_TAG: 6394 return new ResourceReflectionAction(parcel); 6395 case COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION_TAG: 6396 return new ComplexUnitDimensionReflectionAction(parcel); 6397 case SET_COMPOUND_BUTTON_CHECKED_TAG: 6398 return new SetCompoundButtonCheckedAction(parcel); 6399 case SET_RADIO_GROUP_CHECKED: 6400 return new SetRadioGroupCheckedAction(parcel); 6401 case SET_VIEW_OUTLINE_RADIUS_TAG: 6402 return new SetViewOutlinePreferredRadiusAction(parcel); 6403 case SET_ON_CHECKED_CHANGE_RESPONSE_TAG: 6404 return new SetOnCheckedChangeResponse(parcel); 6405 case NIGHT_MODE_REFLECTION_ACTION_TAG: 6406 return new NightModeReflectionAction(parcel); 6407 case SET_REMOTE_COLLECTION_ITEMS_ADAPTER_TAG: 6408 return new SetRemoteCollectionItemListAdapterAction(parcel); 6409 case ATTRIBUTE_REFLECTION_ACTION_TAG: 6410 return new AttributeReflectionAction(parcel); 6411 case SET_ON_STYLUS_HANDWRITING_RESPONSE_TAG: 6412 return new SetOnStylusHandwritingResponse(parcel); 6413 case SET_DRAW_INSTRUCTION_TAG: 6414 return new SetDrawInstructionAction(parcel); 6415 default: 6416 throw new ActionException("Tag " + tag + " not found"); 6417 } 6418 } 6419 6420 /** 6421 * Returns a deep copy of the RemoteViews object. The RemoteView may not be 6422 * attached to another RemoteView -- it must be the root of a hierarchy. 6423 * 6424 * @deprecated use {@link #RemoteViews(RemoteViews)} instead. 6425 * @throws IllegalStateException if this is not the root of a RemoteView 6426 * hierarchy 6427 */ 6428 @Override 6429 @Deprecated 6430 public RemoteViews clone() { 6431 Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. " 6432 + "May only clone the root of a RemoteView hierarchy."); 6433 6434 return new RemoteViews(this); 6435 } 6436 6437 public String getPackage() { 6438 return (mApplication != null) ? mApplication.packageName : null; 6439 } 6440 6441 /** 6442 * Returns the layout id of the root layout associated with this RemoteViews. In the case 6443 * that the RemoteViews has both a landscape and portrait root, this will return the layout 6444 * id associated with the portrait layout. 6445 * 6446 * @return the layout id. 6447 */ 6448 public int getLayoutId() { 6449 return hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT) && (mLightBackgroundLayoutId != 0) 6450 ? mLightBackgroundLayoutId : mLayoutId; 6451 } 6452 6453 /** 6454 * Sets the root of the hierarchy and then recursively traverses the tree to update the root 6455 * and populate caches for all descendants. 6456 */ 6457 private void configureAsChild(@NonNull HierarchyRootData rootData) { 6458 mIsRoot = false; 6459 mBitmapCache = rootData.mBitmapCache; 6460 mCollectionCache = rootData.mRemoteCollectionCache; 6461 mApplicationInfoCache = rootData.mApplicationInfoCache; 6462 mClassCookies = rootData.mClassCookies; 6463 configureDescendantsAsChildren(); 6464 } 6465 6466 /** 6467 * Recursively traverses the tree to update the root and populate caches for all descendants. 6468 */ 6469 private void configureDescendantsAsChildren() { 6470 // Before propagating down the tree, replace our application from the root application info 6471 // cache, to ensure the same instance is present throughout the hierarchy to allow for 6472 // squashing. 6473 mApplication = mApplicationInfoCache.getOrPut(mApplication); 6474 6475 HierarchyRootData rootData = getHierarchyRootData(); 6476 if (hasSizedRemoteViews()) { 6477 for (RemoteViews remoteView : mSizedRemoteViews) { 6478 remoteView.configureAsChild(rootData); 6479 } 6480 } else if (hasLandscapeAndPortraitLayouts()) { 6481 mLandscape.configureAsChild(rootData); 6482 mPortrait.configureAsChild(rootData); 6483 } else { 6484 if (mActions != null) { 6485 for (Action action : mActions) { 6486 action.setHierarchyRootData(rootData); 6487 } 6488 } 6489 } 6490 } 6491 6492 /** 6493 * Recreates caches at the root level of the hierarchy, then recursively populates the caches 6494 * down the hierarchy. 6495 */ 6496 private void reconstructCaches() { 6497 if (!mIsRoot) return; 6498 mBitmapCache = new BitmapCache(); 6499 mApplicationInfoCache = new ApplicationInfoCache(); 6500 mApplication = mApplicationInfoCache.getOrPut(mApplication); 6501 configureDescendantsAsChildren(); 6502 } 6503 6504 /** 6505 * Returns an estimate of the bitmap heap memory usage by setBitmap and setImageViewBitmap in 6506 * this RemoteViews. 6507 * 6508 * @hide 6509 */ 6510 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 6511 public long estimateMemoryUsage() { 6512 return mBitmapCache.getBitmapMemory(); 6513 } 6514 6515 /** 6516 * Returns an estimate of bitmap heap memory usage by setIcon and setImageViewIcon in this 6517 * RemoteViews. Note that this function will count duplicate Icons in its estimate. 6518 * 6519 * @hide 6520 */ 6521 public long estimateIconMemoryUsage() { 6522 AtomicLong total = new AtomicLong(0); 6523 visitIcons(icon -> { 6524 if (icon.getType() == Icon.TYPE_BITMAP || icon.getType() == Icon.TYPE_ADAPTIVE_BITMAP) { 6525 total.addAndGet(icon.getBitmap().getAllocationByteCount()); 6526 } 6527 }); 6528 return total.get(); 6529 } 6530 6531 /** 6532 * Returns an estimate of the bitmap heap memory usage for all Icon and Bitmap actions in this 6533 * RemoteViews. 6534 * 6535 * @hide 6536 */ 6537 public long estimateTotalBitmapMemoryUsage() { 6538 return estimateMemoryUsage() + estimateIconMemoryUsage(); 6539 } 6540 6541 /** 6542 * Add an action to be executed on the remote side when apply is called. 6543 * 6544 * @param a The action to add 6545 */ 6546 private void addAction(Action a) { 6547 if (hasMultipleLayouts()) { 6548 throw new RuntimeException("RemoteViews specifying separate layouts for orientation" 6549 + " or size cannot be modified. Instead, fully configure each layouts" 6550 + " individually before constructing the combined layout."); 6551 } 6552 if (mActions == null) { 6553 mActions = new ArrayList<>(); 6554 } 6555 mActions.add(a); 6556 } 6557 6558 /** 6559 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 6560 * given {@link RemoteViews}. This allows users to build "nested" 6561 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 6562 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 6563 * children. 6564 * 6565 * @param viewId The id of the parent {@link ViewGroup} to add child into. 6566 * @param nestedView {@link RemoteViews} that describes the child. 6567 */ 6568 public void addView(@IdRes int viewId, RemoteViews nestedView) { 6569 // Clear all children when nested views omitted 6570 addAction(nestedView == null 6571 ? new ViewGroupActionRemove(viewId) 6572 : new ViewGroupActionAdd(viewId, nestedView)); 6573 } 6574 6575 /** 6576 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the given 6577 * {@link RemoteViews}. If the {@link RemoteViews} may be re-inflated or updated, 6578 * {@link #removeAllViews(int)} must be called on the same {@code viewId 6579 * } before the first call to this method for the behavior of this method to be predictable. 6580 * 6581 * The {@code stableId} will be used to identify a potential view to recycled when the remote 6582 * view is inflated. Views can be re-used if inserted in the same order, potentially with 6583 * some views appearing / disappearing. To be recycled the view must not change the layout 6584 * used to inflate it or its view id (see {@link RemoteViews#RemoteViews(String, int, int)}). 6585 * 6586 * Note: if a view is re-used, all the actions will be re-applied on it. However, its properties 6587 * are not reset, so what was applied in previous round will have an effect. As a view may be 6588 * re-created at any time by the host, the RemoteViews should not rely on keeping information 6589 * from previous applications and always re-set all the properties they need. 6590 * 6591 * @param viewId The id of the parent {@link ViewGroup} to add child into. 6592 * @param nestedView {@link RemoteViews} that describes the child. 6593 * @param stableId An id that is stable across different versions of RemoteViews. 6594 */ 6595 public void addStableView(@IdRes int viewId, @NonNull RemoteViews nestedView, int stableId) { 6596 addAction(new ViewGroupActionAdd(viewId, nestedView, -1 /* index */, stableId)); 6597 } 6598 6599 /** 6600 * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the 6601 * given {@link RemoteViews}. 6602 * 6603 * @param viewId The id of the parent {@link ViewGroup} to add the child into. 6604 * @param nestedView {@link RemoteViews} of the child to add. 6605 * @param index The position at which to add the child. 6606 * 6607 * @hide 6608 */ 6609 @UnsupportedAppUsage 6610 public void addView(@IdRes int viewId, RemoteViews nestedView, int index) { 6611 addAction(new ViewGroupActionAdd(viewId, nestedView, index)); 6612 } 6613 6614 /** 6615 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 6616 * 6617 * @param viewId The id of the parent {@link ViewGroup} to remove all 6618 * children from. 6619 */ 6620 public void removeAllViews(@IdRes int viewId) { 6621 addAction(new ViewGroupActionRemove(viewId)); 6622 } 6623 6624 /** 6625 * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any 6626 * child that has the {@code viewIdToKeep} as its id. 6627 * 6628 * @param viewId The id of the parent {@link ViewGroup} to remove children from. 6629 * @param viewIdToKeep The id of a child that should not be removed. 6630 * 6631 * @hide 6632 */ 6633 public void removeAllViewsExceptId(@IdRes int viewId, @IdRes int viewIdToKeep) { 6634 addAction(new ViewGroupActionRemove(viewId, viewIdToKeep)); 6635 } 6636 6637 /** 6638 * Removes the {@link View} specified by the {@code viewId} from its parent {@link ViewManager}. 6639 * This will do nothing if the viewId specifies the root view of this RemoteViews. 6640 * 6641 * @param viewId The id of the {@link View} to remove from its parent. 6642 * 6643 * @hide 6644 */ 6645 public void removeFromParent(@IdRes int viewId) { 6646 addAction(new RemoveFromParentAction(viewId)); 6647 } 6648 6649 /** 6650 * Equivalent to calling {@link AdapterViewAnimator#showNext()} 6651 * 6652 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} 6653 * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call 6654 * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change 6655 * unexpectedly. 6656 */ 6657 @Deprecated 6658 public void showNext(@IdRes int viewId) { 6659 addAction(new ViewContentNavigation(viewId, true /* next */)); 6660 } 6661 6662 /** 6663 * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} 6664 * 6665 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} 6666 * @deprecated As RemoteViews may be reapplied frequently, it is preferable to call 6667 * {@link #setDisplayedChild(int, int)} to ensure that the adapter index does not change 6668 * unexpectedly. 6669 */ 6670 @Deprecated 6671 public void showPrevious(@IdRes int viewId) { 6672 addAction(new ViewContentNavigation(viewId, false /* next */)); 6673 } 6674 6675 /** 6676 * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} 6677 * 6678 * @param viewId The id of the view on which to call 6679 * {@link AdapterViewAnimator#setDisplayedChild(int)} 6680 */ 6681 public void setDisplayedChild(@IdRes int viewId, int childIndex) { 6682 setInt(viewId, "setDisplayedChild", childIndex); 6683 } 6684 6685 /** 6686 * Equivalent to calling {@link View#setVisibility(int)} 6687 * 6688 * @param viewId The id of the view whose visibility should change 6689 * @param visibility The new visibility for the view 6690 */ 6691 public void setViewVisibility(@IdRes int viewId, @View.Visibility int visibility) { 6692 setInt(viewId, "setVisibility", visibility); 6693 } 6694 6695 /** 6696 * Equivalent to calling {@link TextView#setText(CharSequence)} 6697 * 6698 * @param viewId The id of the view whose text should change 6699 * @param text The new text for the view 6700 */ 6701 public void setTextViewText(@IdRes int viewId, CharSequence text) { 6702 setCharSequence(viewId, "setText", text); 6703 } 6704 6705 /** 6706 * Equivalent to calling {@link TextView#setTextSize(int, float)} 6707 * 6708 * @param viewId The id of the view whose text size should change 6709 * @param units The units of size (e.g. COMPLEX_UNIT_SP) 6710 * @param size The size of the text 6711 */ 6712 public void setTextViewTextSize(@IdRes int viewId, int units, float size) { 6713 addAction(new TextViewSizeAction(viewId, units, size)); 6714 } 6715 6716 /** 6717 * Equivalent to calling 6718 * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. 6719 * 6720 * @param viewId The id of the view whose text should change 6721 * @param left The id of a drawable to place to the left of the text, or 0 6722 * @param top The id of a drawable to place above the text, or 0 6723 * @param right The id of a drawable to place to the right of the text, or 0 6724 * @param bottom The id of a drawable to place below the text, or 0 6725 */ 6726 public void setTextViewCompoundDrawables(@IdRes int viewId, @DrawableRes int left, 6727 @DrawableRes int top, @DrawableRes int right, @DrawableRes int bottom) { 6728 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 6729 } 6730 6731 /** 6732 * Equivalent to calling {@link 6733 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. 6734 * 6735 * @param viewId The id of the view whose text should change 6736 * @param start The id of a drawable to place before the text (relative to the 6737 * layout direction), or 0 6738 * @param top The id of a drawable to place above the text, or 0 6739 * @param end The id of a drawable to place after the text, or 0 6740 * @param bottom The id of a drawable to place below the text, or 0 6741 */ 6742 public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, @DrawableRes int start, 6743 @DrawableRes int top, @DrawableRes int end, @DrawableRes int bottom) { 6744 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 6745 } 6746 6747 /** 6748 * Equivalent to calling {@link 6749 * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 6750 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 6751 * 6752 * @param viewId The id of the view whose text should change 6753 * @param left an Icon to place to the left of the text, or 0 6754 * @param top an Icon to place above the text, or 0 6755 * @param right an Icon to place to the right of the text, or 0 6756 * @param bottom an Icon to place below the text, or 0 6757 * 6758 * @hide 6759 */ 6760 public void setTextViewCompoundDrawables(@IdRes int viewId, 6761 Icon left, Icon top, Icon right, Icon bottom) { 6762 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 6763 } 6764 6765 /** 6766 * Equivalent to calling {@link 6767 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 6768 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 6769 * 6770 * @param viewId The id of the view whose text should change 6771 * @param start an Icon to place before the text (relative to the 6772 * layout direction), or 0 6773 * @param top an Icon to place above the text, or 0 6774 * @param end an Icon to place after the text, or 0 6775 * @param bottom an Icon to place below the text, or 0 6776 * 6777 * @hide 6778 */ 6779 public void setTextViewCompoundDrawablesRelative(@IdRes int viewId, 6780 Icon start, Icon top, Icon end, Icon bottom) { 6781 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 6782 } 6783 6784 /** 6785 * Equivalent to calling {@link ImageView#setImageResource(int)} 6786 * 6787 * @param viewId The id of the view whose drawable should change 6788 * @param srcId The new resource id for the drawable 6789 */ 6790 public void setImageViewResource(@IdRes int viewId, @DrawableRes int srcId) { 6791 setInt(viewId, "setImageResource", srcId); 6792 } 6793 6794 /** 6795 * Equivalent to calling {@link ImageView#setImageURI(Uri)} 6796 * 6797 * @param viewId The id of the view whose drawable should change 6798 * @param uri The Uri for the image 6799 */ 6800 public void setImageViewUri(@IdRes int viewId, Uri uri) { 6801 setUri(viewId, "setImageURI", uri); 6802 } 6803 6804 /** 6805 * Equivalent to calling {@link ImageView#setImageBitmap(Bitmap)} 6806 * 6807 * @param viewId The id of the view whose bitmap should change 6808 * @param bitmap The new Bitmap for the drawable 6809 */ 6810 public void setImageViewBitmap(@IdRes int viewId, Bitmap bitmap) { 6811 setBitmap(viewId, "setImageBitmap", bitmap); 6812 } 6813 6814 /** 6815 * Equivalent to calling {@link ImageView#setImageIcon(Icon)} 6816 * 6817 * @param viewId The id of the view whose bitmap should change 6818 * @param icon The new Icon for the ImageView 6819 */ 6820 public void setImageViewIcon(@IdRes int viewId, Icon icon) { 6821 setIcon(viewId, "setImageIcon", icon); 6822 } 6823 6824 /** 6825 * Equivalent to calling {@link AdapterView#setEmptyView(View)} 6826 * 6827 * @param viewId The id of the view on which to set the empty view 6828 * @param emptyViewId The view id of the empty view 6829 */ 6830 public void setEmptyView(@IdRes int viewId, @IdRes int emptyViewId) { 6831 addAction(new SetEmptyView(viewId, emptyViewId)); 6832 } 6833 6834 /** 6835 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 6836 * {@link Chronometer#setFormat Chronometer.setFormat}, 6837 * and {@link Chronometer#start Chronometer.start()} or 6838 * {@link Chronometer#stop Chronometer.stop()}. 6839 * 6840 * @param viewId The id of the {@link Chronometer} to change 6841 * @param base The time at which the timer would have read 0:00. This 6842 * time should be based off of 6843 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 6844 * @param format The Chronometer format string, or null to 6845 * simply display the timer value. 6846 * @param started True if you want the clock to be started, false if not. 6847 * 6848 * @see #setChronometerCountDown(int, boolean) 6849 */ 6850 public void setChronometer(@IdRes int viewId, long base, String format, boolean started) { 6851 setLong(viewId, "setBase", base); 6852 setString(viewId, "setFormat", format); 6853 setBoolean(viewId, "setStarted", started); 6854 } 6855 6856 /** 6857 * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on 6858 * the chronometer with the given viewId. 6859 * 6860 * @param viewId The id of the {@link Chronometer} to change 6861 * @param isCountDown True if you want the chronometer to count down to base instead of 6862 * counting up. 6863 */ 6864 public void setChronometerCountDown(@IdRes int viewId, boolean isCountDown) { 6865 setBoolean(viewId, "setCountDown", isCountDown); 6866 } 6867 6868 /** 6869 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 6870 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 6871 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 6872 * 6873 * If indeterminate is true, then the values for max and progress are ignored. 6874 * 6875 * @param viewId The id of the {@link ProgressBar} to change 6876 * @param max The 100% value for the progress bar 6877 * @param progress The current value of the progress bar. 6878 * @param indeterminate True if the progress bar is indeterminate, 6879 * false if not. 6880 */ 6881 public void setProgressBar(@IdRes int viewId, int max, int progress, 6882 boolean indeterminate) { 6883 setBoolean(viewId, "setIndeterminate", indeterminate); 6884 if (!indeterminate) { 6885 setInt(viewId, "setMax", max); 6886 setInt(viewId, "setProgress", progress); 6887 } 6888 } 6889 6890 /** 6891 * Equivalent to calling 6892 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 6893 * to launch the provided {@link PendingIntent}. The source bounds 6894 * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked 6895 * view in screen space. 6896 * Note that any activity options associated with the mPendingIntent may get overridden 6897 * before starting the intent. 6898 * 6899 * When setting the on-click action of items within collections (eg. {@link ListView}, 6900 * {@link StackView} etc.), this method will not work. Instead, use {@link 6901 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)} in conjunction with 6902 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 6903 * 6904 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 6905 * @param pendingIntent The {@link PendingIntent} to send when user clicks 6906 */ 6907 public void setOnClickPendingIntent(@IdRes int viewId, PendingIntent pendingIntent) { 6908 setOnClickResponse(viewId, RemoteResponse.fromPendingIntent(pendingIntent)); 6909 } 6910 6911 /** 6912 * Equivalent of calling 6913 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 6914 * to launch the provided {@link RemoteResponse}. 6915 * 6916 * @param viewId The id of the view that will trigger the {@link RemoteResponse} when clicked 6917 * @param response The {@link RemoteResponse} to send when user clicks 6918 */ 6919 public void setOnClickResponse(@IdRes int viewId, @NonNull RemoteResponse response) { 6920 addAction(new SetOnClickResponse(viewId, response)); 6921 } 6922 6923 /** 6924 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 6925 * costly to set PendingIntents on the individual items, and is hence not recommended. Instead 6926 * this method should be used to set a single PendingIntent template on the collection, and 6927 * individual items can differentiate their on-click behavior using 6928 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 6929 * 6930 * @param viewId The id of the collection who's children will use this PendingIntent template 6931 * when clicked 6932 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 6933 * by a child of viewId and executed when that child is clicked 6934 */ 6935 public void setPendingIntentTemplate(@IdRes int viewId, PendingIntent pendingIntentTemplate) { 6936 if (hasDrawInstructions()) { 6937 getPendingIntentTemplate().set(viewId, pendingIntentTemplate); 6938 tryAddRemoteResponse(viewId); 6939 } else { 6940 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 6941 } 6942 } 6943 6944 /** 6945 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 6946 * costly to set PendingIntents on the individual items, and is hence not recommended. Instead 6947 * a single PendingIntent template can be set on the collection, see {@link 6948 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 6949 * action of a given item can be distinguished by setting a fillInIntent on that item. The 6950 * fillInIntent is then combined with the PendingIntent template in order to determine the final 6951 * intent which will be executed when the item is clicked. This works as follows: any fields 6952 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 6953 * will be overwritten, and the resulting PendingIntent will be used. The rest 6954 * of the PendingIntent template will then be filled in with the associated fields that are 6955 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 6956 * 6957 * @param viewId The id of the view on which to set the fillInIntent 6958 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 6959 * in order to determine the on-click behavior of the view specified by viewId 6960 */ 6961 public void setOnClickFillInIntent(@IdRes int viewId, Intent fillInIntent) { 6962 if (hasDrawInstructions()) { 6963 getFillInIntent().set(viewId, fillInIntent); 6964 tryAddRemoteResponse(viewId); 6965 } else { 6966 setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent)); 6967 } 6968 } 6969 6970 /** 6971 * Equivalent to calling 6972 * {@link android.widget.CompoundButton#setOnCheckedChangeListener( 6973 * android.widget.CompoundButton.OnCheckedChangeListener)} 6974 * to launch the provided {@link RemoteResponse}. 6975 * 6976 * The intent will be filled with the current checked state of the view at the key 6977 * {@link #EXTRA_CHECKED}. 6978 * 6979 * The {@link RemoteResponse} will not be launched in response to check changes arising from 6980 * {@link #setCompoundButtonChecked(int, boolean)} or {@link #setRadioGroupChecked(int, int)} 6981 * usages. 6982 * 6983 * The {@link RemoteResponse} must be created using 6984 * {@link RemoteResponse#fromFillInIntent(Intent)} in conjunction with 6985 * {@link RemoteViews#setPendingIntentTemplate(int, PendingIntent)} for items inside 6986 * collections (eg. {@link ListView}, {@link StackView} etc.). 6987 * 6988 * Otherwise, create the {@link RemoteResponse} using 6989 * {@link RemoteResponse#fromPendingIntent(PendingIntent)}. 6990 * 6991 * @param viewId The id of the view that will trigger the {@link PendingIntent} when checked 6992 * state changes. 6993 * @param response The {@link RemoteResponse} to send when the checked state changes. 6994 */ 6995 public void setOnCheckedChangeResponse( 6996 @IdRes int viewId, 6997 @NonNull RemoteResponse response) { 6998 addAction( 6999 new SetOnCheckedChangeResponse( 7000 viewId, 7001 response.setInteractionType( 7002 RemoteResponse.INTERACTION_TYPE_CHECKED_CHANGE))); 7003 } 7004 7005 /** 7006 * Equivalent to calling {@link View#setHandwritingDelegatorCallback(Runnable)} to send the 7007 * provided {@link PendingIntent}. 7008 * 7009 * <p>A common use case is a remote view which looks like a text editor but does not actually 7010 * support text editing itself, and clicking on the remote view launches an activity containing 7011 * an EditText. To support handwriting initiation in this case, this method can be called on the 7012 * remote view to configure it as a handwriting delegator, meaning that stylus movement on the 7013 * remote view triggers a {@link PendingIntent} and starts handwriting mode for the delegate 7014 * EditText. The {@link PendingIntent} is typically the same as the one passed to {@link 7015 * #setOnClickPendingIntent} which launches the activity containing the EditText. The EditText 7016 * should call {@link View#setIsHandwritingDelegate} to set it as a delegate, and also use 7017 * {@link View#setAllowedHandwritingDelegatorPackage} or {@link 7018 * android.view.inputmethod.InputMethodManager#HANDWRITING_DELEGATE_FLAG_HOME_DELEGATOR_ALLOWED} 7019 * if necessary to support delegators from the package displaying the remote view. 7020 * 7021 * @param viewId identifier of the view that will trigger the {@link PendingIntent} when a 7022 * stylus {@link MotionEvent} occurs within the view's bounds 7023 * @param pendingIntent the {@link PendingIntent} to send, or {@code null} to clear the 7024 * handwriting delegation 7025 */ 7026 @FlaggedApi(FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR) 7027 public void setOnStylusHandwritingPendingIntent( 7028 @IdRes int viewId, @Nullable PendingIntent pendingIntent) { 7029 addAction(new SetOnStylusHandwritingResponse(viewId, pendingIntent)); 7030 } 7031 7032 /** 7033 * @hide 7034 * Equivalent to calling 7035 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 7036 * on the {@link Drawable} of a given view. 7037 * <p> 7038 * 7039 * @param viewId The id of the view that contains the target 7040 * {@link Drawable} 7041 * @param targetBackground If true, apply these parameters to the 7042 * {@link Drawable} returned by 7043 * {@link android.view.View#getBackground()}. Otherwise, assume 7044 * the target view is an {@link ImageView} and apply them to 7045 * {@link ImageView#getDrawable()}. 7046 * @param colorFilter Specify a color for a 7047 * {@link android.graphics.ColorFilter} for this drawable. This will be ignored if 7048 * {@code mode} is {@code null}. 7049 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 7050 * unchanged. 7051 */ 7052 public void setDrawableTint(@IdRes int viewId, boolean targetBackground, 7053 @ColorInt int colorFilter, @NonNull PorterDuff.Mode mode) { 7054 addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode)); 7055 } 7056 7057 /** 7058 * @hide 7059 * Equivalent to calling 7060 * {@link RippleDrawable#setColor(ColorStateList)} on the {@link Drawable} of a given view, 7061 * assuming it's a {@link RippleDrawable}. 7062 * <p> 7063 * 7064 * @param viewId The id of the view that contains the target 7065 * {@link RippleDrawable} 7066 * @param colorStateList Specify a color for a 7067 * {@link ColorStateList} for this drawable. 7068 */ 7069 public void setRippleDrawableColor(@IdRes int viewId, ColorStateList colorStateList) { 7070 addAction(new SetRippleDrawableColor(viewId, colorStateList)); 7071 } 7072 7073 /** 7074 * @hide 7075 * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}. 7076 * 7077 * @param viewId The id of the view whose tint should change 7078 * @param tint the tint to apply, may be {@code null} to clear tint 7079 */ 7080 public void setProgressTintList(@IdRes int viewId, ColorStateList tint) { 7081 addAction(new ReflectionAction(viewId, "setProgressTintList", 7082 BaseReflectionAction.COLOR_STATE_LIST, tint)); 7083 } 7084 7085 /** 7086 * @hide 7087 * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}. 7088 * 7089 * @param viewId The id of the view whose tint should change 7090 * @param tint the tint to apply, may be {@code null} to clear tint 7091 */ 7092 public void setProgressBackgroundTintList(@IdRes int viewId, ColorStateList tint) { 7093 addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList", 7094 BaseReflectionAction.COLOR_STATE_LIST, tint)); 7095 } 7096 7097 /** 7098 * @hide 7099 * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}. 7100 * 7101 * @param viewId The id of the view whose tint should change 7102 * @param tint the tint to apply, may be {@code null} to clear tint 7103 */ 7104 public void setProgressIndeterminateTintList(@IdRes int viewId, ColorStateList tint) { 7105 addAction(new ReflectionAction(viewId, "setIndeterminateTintList", 7106 BaseReflectionAction.COLOR_STATE_LIST, tint)); 7107 } 7108 7109 /** 7110 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 7111 * 7112 * @param viewId The id of the view whose text color should change 7113 * @param color Sets the text color for all the states (normal, selected, 7114 * focused) to be this color. 7115 */ 7116 public void setTextColor(@IdRes int viewId, @ColorInt int color) { 7117 setInt(viewId, "setTextColor", color); 7118 } 7119 7120 /** 7121 * @hide 7122 * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}. 7123 * 7124 * @param viewId The id of the view whose text color should change 7125 * @param colors the text colors to set 7126 */ 7127 public void setTextColor(@IdRes int viewId, ColorStateList colors) { 7128 addAction(new ReflectionAction(viewId, "setTextColor", 7129 BaseReflectionAction.COLOR_STATE_LIST, colors)); 7130 } 7131 7132 /** 7133 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 7134 * 7135 * @param appWidgetId The id of the app widget which contains the specified view. (This 7136 * parameter is ignored in this deprecated method) 7137 * @param viewId The id of the {@link AdapterView} 7138 * @param intent The intent of the service which will be 7139 * providing data to the RemoteViewsAdapter 7140 * @deprecated This method has been deprecated. See 7141 * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} 7142 */ 7143 @Deprecated 7144 public void setRemoteAdapter(int appWidgetId, @IdRes int viewId, Intent intent) { 7145 setRemoteAdapter(viewId, intent); 7146 } 7147 7148 /** 7149 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 7150 * Can only be used for App Widgets. 7151 * 7152 * @param viewId The id of the {@link AdapterView} 7153 * @param intent The intent of the service which will be 7154 * providing data to the RemoteViewsAdapter 7155 * @deprecated use 7156 * {@link #setRemoteAdapter(int, android.widget.RemoteViews.RemoteCollectionItems)} instead 7157 */ 7158 @Deprecated 7159 public void setRemoteAdapter(@IdRes int viewId, Intent intent) { 7160 if (remoteAdapterConversion()) { 7161 addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent)); 7162 } else { 7163 addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); 7164 } 7165 } 7166 7167 /** 7168 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 7169 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 7170 * This is a simpler but less flexible approach to populating collection widgets. Its use is 7171 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 7172 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 7173 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 7174 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 7175 * 7176 * This API is supported in the compatibility library for previous API levels, see 7177 * RemoteViewsCompat. 7178 * 7179 * @param viewId The id of the {@link AdapterView} 7180 * @param list The list of RemoteViews which will populate the view specified by viewId. 7181 * @param viewTypeCount The maximum number of unique layout id's used to construct the list of 7182 * RemoteViews. This count cannot change during the life-cycle of a given widget, so this 7183 * parameter should account for the maximum possible number of types that may appear in the 7184 * See {@link Adapter#getViewTypeCount()}. 7185 * 7186 * @hide 7187 * @deprecated this appears to have no users outside of UnsupportedAppUsage? 7188 */ 7189 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) 7190 @Deprecated 7191 public void setRemoteAdapter(@IdRes int viewId, ArrayList<RemoteViews> list, 7192 int viewTypeCount) { 7193 RemoteCollectionItems.Builder b = new RemoteCollectionItems.Builder(); 7194 for (int i = 0; i < list.size(); i++) { 7195 b.addItem(i, list.get(i)); 7196 } 7197 setRemoteAdapter(viewId, b.setViewTypeCount(viewTypeCount).build()); 7198 } 7199 7200 /** 7201 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 7202 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 7203 * This is a simpler but less flexible approach to populating collection widgets. Its use is 7204 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 7205 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 7206 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 7207 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 7208 * 7209 * This API is supported in the compatibility library for previous API levels, see 7210 * RemoteViewsCompat. 7211 * 7212 * @param viewId The id of the {@link AdapterView}. 7213 * @param items The items to display in the {@link AdapterView}. 7214 */ 7215 public void setRemoteAdapter(@IdRes int viewId, @NonNull RemoteCollectionItems items) { 7216 addAction(new SetRemoteCollectionItemListAdapterAction(viewId, items)); 7217 } 7218 7219 /** 7220 * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}. 7221 * 7222 * @param viewId The id of the view to change 7223 * @param position Scroll to this adapter position 7224 */ 7225 public void setScrollPosition(@IdRes int viewId, int position) { 7226 setInt(viewId, "smoothScrollToPosition", position); 7227 } 7228 7229 /** 7230 * Equivalent to calling {@link ListView#smoothScrollByOffset(int)}. 7231 * 7232 * @param viewId The id of the view to change 7233 * @param offset Scroll by this adapter position offset 7234 */ 7235 public void setRelativeScrollPosition(@IdRes int viewId, int offset) { 7236 setInt(viewId, "smoothScrollByOffset", offset); 7237 } 7238 7239 /** 7240 * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}. 7241 * 7242 * @param viewId The id of the view to change 7243 * @param left the left padding in pixels 7244 * @param top the top padding in pixels 7245 * @param right the right padding in pixels 7246 * @param bottom the bottom padding in pixels 7247 */ 7248 public void setViewPadding(@IdRes int viewId, 7249 @Px int left, @Px int top, @Px int right, @Px int bottom) { 7250 addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); 7251 } 7252 7253 /** 7254 * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. 7255 * Only works if the {@link View#getLayoutParams()} supports margins. 7256 * 7257 * @param viewId The id of the view to change 7258 * @param type The margin being set e.g. {@link #MARGIN_END} 7259 * @param dimen a dimension resource to apply to the margin, or 0 to clear the margin. 7260 */ 7261 public void setViewLayoutMarginDimen(@IdRes int viewId, @MarginType int type, 7262 @DimenRes int dimen) { 7263 addAction(new LayoutParamAction(viewId, type, dimen, VALUE_TYPE_RESOURCE)); 7264 } 7265 7266 /** 7267 * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. 7268 * Only works if the {@link View#getLayoutParams()} supports margins. 7269 * 7270 * @param viewId The id of the view to change 7271 * @param type The margin being set e.g. {@link #MARGIN_END} 7272 * @param attr a dimension attribute to apply to the margin, or 0 to clear the margin. 7273 */ 7274 public void setViewLayoutMarginAttr(@IdRes int viewId, @MarginType int type, 7275 @AttrRes int attr) { 7276 addAction(new LayoutParamAction(viewId, type, attr, VALUE_TYPE_ATTRIBUTE)); 7277 } 7278 7279 /** 7280 * Equivalent to calling {@link MarginLayoutParams#setMarginEnd}. 7281 * Only works if the {@link View#getLayoutParams()} supports margins. 7282 * 7283 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0. 7284 * Setting margins in pixels will behave poorly when the RemoteViews object is used on a 7285 * display with a different density. 7286 * 7287 * @param viewId The id of the view to change 7288 * @param type The margin being set e.g. {@link #MARGIN_END} 7289 * @param value a value for the margin the given units. 7290 * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} 7291 */ 7292 public void setViewLayoutMargin(@IdRes int viewId, @MarginType int type, float value, 7293 @ComplexDimensionUnit int units) { 7294 addAction(new LayoutParamAction(viewId, type, value, units)); 7295 } 7296 7297 /** 7298 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} except that you may 7299 * provide the value in any dimension units. 7300 * 7301 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, 7302 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. 7303 * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a 7304 * display with a different density. 7305 * 7306 * @param width Width of the view in the given units 7307 * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} 7308 */ 7309 public void setViewLayoutWidth(@IdRes int viewId, float width, 7310 @ComplexDimensionUnit int units) { 7311 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, width, units)); 7312 } 7313 7314 /** 7315 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with 7316 * the result of {@link Resources#getDimensionPixelSize(int)}. 7317 * 7318 * @param widthDimen the dimension resource for the view's width 7319 */ 7320 public void setViewLayoutWidthDimen(@IdRes int viewId, @DimenRes int widthDimen) { 7321 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthDimen, 7322 VALUE_TYPE_RESOURCE)); 7323 } 7324 7325 /** 7326 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width} with 7327 * the value of the given attribute in the current theme. 7328 * 7329 * @param widthAttr the dimension attribute for the view's width 7330 */ 7331 public void setViewLayoutWidthAttr(@IdRes int viewId, @AttrRes int widthAttr) { 7332 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, widthAttr, 7333 VALUE_TYPE_ATTRIBUTE)); 7334 } 7335 7336 /** 7337 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} except that you may 7338 * provide the value in any dimension units. 7339 * 7340 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0, 7341 * {@link ViewGroup.LayoutParams#WRAP_CONTENT}, or {@link ViewGroup.LayoutParams#MATCH_PARENT}. 7342 * Setting actual sizes in pixels will behave poorly when the RemoteViews object is used on a 7343 * display with a different density. 7344 * 7345 * @param height height of the view in the given units 7346 * @param units The unit type of the value e.g. {@link TypedValue#COMPLEX_UNIT_DIP} 7347 */ 7348 public void setViewLayoutHeight(@IdRes int viewId, float height, 7349 @ComplexDimensionUnit int units) { 7350 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, height, units)); 7351 } 7352 7353 /** 7354 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with 7355 * the result of {@link Resources#getDimensionPixelSize(int)}. 7356 * 7357 * @param heightDimen a dimen resource to read the height from. 7358 */ 7359 public void setViewLayoutHeightDimen(@IdRes int viewId, @DimenRes int heightDimen) { 7360 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightDimen, 7361 VALUE_TYPE_RESOURCE)); 7362 } 7363 7364 /** 7365 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#height} with 7366 * the value of the given attribute in the current theme. 7367 * 7368 * @param heightAttr a dimen attribute to read the height from. 7369 */ 7370 public void setViewLayoutHeightAttr(@IdRes int viewId, @AttrRes int heightAttr) { 7371 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_HEIGHT, heightAttr, 7372 VALUE_TYPE_ATTRIBUTE)); 7373 } 7374 7375 /** 7376 * Sets an OutlineProvider on the view whose corner radius is a dimension calculated using 7377 * {@link TypedValue#applyDimension(int, float, DisplayMetrics)}. 7378 * 7379 * <p>NOTE: It is recommended to use {@link TypedValue#COMPLEX_UNIT_PX} only for 0. 7380 * Setting margins in pixels will behave poorly when the RemoteViews object is used on a 7381 * display with a different density. 7382 */ 7383 public void setViewOutlinePreferredRadius( 7384 @IdRes int viewId, float radius, @ComplexDimensionUnit int units) { 7385 addAction(new SetViewOutlinePreferredRadiusAction(viewId, radius, units)); 7386 } 7387 7388 /** 7389 * Sets an OutlineProvider on the view whose corner radius is a dimension resource with 7390 * {@code resId}. 7391 */ 7392 public void setViewOutlinePreferredRadiusDimen(@IdRes int viewId, @DimenRes int resId) { 7393 addAction(new SetViewOutlinePreferredRadiusAction(viewId, resId, VALUE_TYPE_RESOURCE)); 7394 } 7395 7396 /** 7397 * Sets an OutlineProvider on the view whose corner radius is a dimension attribute with 7398 * {@code attrId}. 7399 */ 7400 public void setViewOutlinePreferredRadiusAttr(@IdRes int viewId, @AttrRes int attrId) { 7401 addAction(new SetViewOutlinePreferredRadiusAction(viewId, attrId, VALUE_TYPE_ATTRIBUTE)); 7402 } 7403 7404 /** 7405 * Call a method taking one boolean on a view in the layout for this RemoteViews. 7406 * 7407 * @param viewId The id of the view on which to call the method. 7408 * @param methodName The name of the method to call. 7409 * @param value The value to pass to the method. 7410 */ 7411 public void setBoolean(@IdRes int viewId, String methodName, boolean value) { 7412 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BOOLEAN, value)); 7413 } 7414 7415 /** 7416 * Call a method taking one byte on a view in the layout for this RemoteViews. 7417 * 7418 * @param viewId The id of the view on which to call the method. 7419 * @param methodName The name of the method to call. 7420 * @param value The value to pass to the method. 7421 */ 7422 public void setByte(@IdRes int viewId, String methodName, byte value) { 7423 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BYTE, value)); 7424 } 7425 7426 /** 7427 * Call a method taking one short on a view in the layout for this RemoteViews. 7428 * 7429 * @param viewId The id of the view on which to call the method. 7430 * @param methodName The name of the method to call. 7431 * @param value The value to pass to the method. 7432 */ 7433 public void setShort(@IdRes int viewId, String methodName, short value) { 7434 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.SHORT, value)); 7435 } 7436 7437 /** 7438 * Call a method taking one int on a view in the layout for this RemoteViews. 7439 * 7440 * @param viewId The id of the view on which to call the method. 7441 * @param methodName The name of the method to call. 7442 * @param value The value to pass to the method. 7443 */ 7444 public void setInt(@IdRes int viewId, String methodName, int value) { 7445 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INT, value)); 7446 } 7447 7448 /** 7449 * Call a method taking one int, a size in pixels, on a view in the layout for this 7450 * RemoteViews. 7451 * 7452 * The dimension will be resolved from the resources at the time the {@link RemoteViews} is 7453 * (re-)applied. 7454 * 7455 * Undefined resources will result in an exception, except 0 which will resolve to 0. 7456 * 7457 * @param viewId The id of the view on which to call the method. 7458 * @param methodName The name of the method to call. 7459 * @param dimenResource The resource to resolve and pass as argument to the method. 7460 */ 7461 public void setIntDimen(@IdRes int viewId, @NonNull String methodName, 7462 @DimenRes int dimenResource) { 7463 addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT, 7464 ResourceReflectionAction.DIMEN_RESOURCE, dimenResource)); 7465 } 7466 7467 /** 7468 * Call a method taking one int, a size in pixels, on a view in the layout for this 7469 * RemoteViews. 7470 * 7471 * The dimension will be resolved from the specified dimension at the time of inflation. 7472 * 7473 * @param viewId The id of the view on which to call the method. 7474 * @param methodName The name of the method to call. 7475 * @param value The value of the dimension. 7476 * @param unit The unit in which the value is specified. 7477 */ 7478 public void setIntDimen(@IdRes int viewId, @NonNull String methodName, 7479 float value, @ComplexDimensionUnit int unit) { 7480 addAction(new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.INT, 7481 value, unit)); 7482 } 7483 7484 /** 7485 * Call a method taking one int, a size in pixels, on a view in the layout for this 7486 * RemoteViews. 7487 * 7488 * The dimension will be resolved from the theme attribute at the time the 7489 * {@link RemoteViews} is (re-)applied. 7490 * 7491 * Unresolvable attributes will result in an exception, except 0 which will resolve to 0. 7492 * 7493 * @param viewId The id of the view on which to call the method. 7494 * @param methodName The name of the method to call. 7495 * @param dimenAttr The attribute to resolve and pass as argument to the method. 7496 */ 7497 public void setIntDimenAttr(@IdRes int viewId, @NonNull String methodName, 7498 @AttrRes int dimenAttr) { 7499 addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.INT, 7500 ResourceReflectionAction.DIMEN_RESOURCE, dimenAttr)); 7501 } 7502 7503 /** 7504 * Call a method taking one int, a color, on a view in the layout for this RemoteViews. 7505 * 7506 * The Color will be resolved from the resources at the time the {@link RemoteViews} is (re-) 7507 * applied. 7508 * 7509 * Undefined resources will result in an exception, except 0 which will resolve to 0. 7510 * 7511 * @param viewId The id of the view on which to call the method. 7512 * @param methodName The name of the method to call. 7513 * @param colorResource The resource to resolve and pass as argument to the method. 7514 */ 7515 public void setColor(@IdRes int viewId, @NonNull String methodName, 7516 @ColorRes int colorResource) { 7517 addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.INT, 7518 ResourceReflectionAction.COLOR_RESOURCE, colorResource)); 7519 } 7520 7521 /** 7522 * Call a method taking one int, a color, on a view in the layout for this RemoteViews. 7523 * 7524 * The Color will be resolved from the theme attribute at the time the {@link RemoteViews} is 7525 * (re-)applied. 7526 * 7527 * Unresolvable attributes will result in an exception, except 0 which will resolve to 0. 7528 * 7529 * @param viewId The id of the view on which to call the method. 7530 * @param methodName The name of the method to call. 7531 * @param colorAttribute The theme attribute to resolve and pass as argument to the method. 7532 */ 7533 public void setColorAttr(@IdRes int viewId, @NonNull String methodName, 7534 @AttrRes int colorAttribute) { 7535 addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.INT, 7536 AttributeReflectionAction.COLOR_RESOURCE, colorAttribute)); 7537 } 7538 7539 /** 7540 * Call a method taking one int, a color, on a view in the layout for this RemoteViews. 7541 * 7542 * @param viewId The id of the view on which to call the method. 7543 * @param methodName The name of the method to call. 7544 * @param notNight The value to pass to the method when the view's configuration is set to 7545 * {@link Configuration#UI_MODE_NIGHT_NO} 7546 * @param night The value to pass to the method when the view's configuration is set to 7547 * {@link Configuration#UI_MODE_NIGHT_YES} 7548 */ 7549 public void setColorInt( 7550 @IdRes int viewId, 7551 @NonNull String methodName, 7552 @ColorInt int notNight, 7553 @ColorInt int night) { 7554 addAction( 7555 new NightModeReflectionAction( 7556 viewId, 7557 methodName, 7558 BaseReflectionAction.INT, 7559 notNight, 7560 night)); 7561 } 7562 7563 7564 /** 7565 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 7566 * 7567 * @param viewId The id of the view on which to call the method. 7568 * @param methodName The name of the method to call. 7569 * @param value The value to pass to the method. 7570 */ 7571 public void setColorStateList(@IdRes int viewId, @NonNull String methodName, 7572 @Nullable ColorStateList value) { 7573 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.COLOR_STATE_LIST, 7574 value)); 7575 } 7576 7577 /** 7578 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 7579 * 7580 * @param viewId The id of the view on which to call the method. 7581 * @param methodName The name of the method to call. 7582 * @param notNight The value to pass to the method when the view's configuration is set to 7583 * {@link Configuration#UI_MODE_NIGHT_NO} 7584 * @param night The value to pass to the method when the view's configuration is set to 7585 * {@link Configuration#UI_MODE_NIGHT_YES} 7586 */ 7587 public void setColorStateList( 7588 @IdRes int viewId, 7589 @NonNull String methodName, 7590 @Nullable ColorStateList notNight, 7591 @Nullable ColorStateList night) { 7592 addAction( 7593 new NightModeReflectionAction( 7594 viewId, 7595 methodName, 7596 BaseReflectionAction.COLOR_STATE_LIST, 7597 notNight, 7598 night)); 7599 } 7600 7601 /** 7602 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 7603 * 7604 * The ColorStateList will be resolved from the resources at the time the {@link RemoteViews} is 7605 * (re-)applied. 7606 * 7607 * Undefined resources will result in an exception, except 0 which will resolve to null. 7608 * 7609 * @param viewId The id of the view on which to call the method. 7610 * @param methodName The name of the method to call. 7611 * @param colorResource The resource to resolve and pass as argument to the method. 7612 */ 7613 public void setColorStateList(@IdRes int viewId, @NonNull String methodName, 7614 @ColorRes int colorResource) { 7615 addAction(new ResourceReflectionAction(viewId, methodName, 7616 BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE, 7617 colorResource)); 7618 } 7619 7620 /** 7621 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 7622 * 7623 * The ColorStateList will be resolved from the theme attribute at the time the 7624 * {@link RemoteViews} is (re-)applied. 7625 * 7626 * Unresolvable attributes will result in an exception, except 0 which will resolve to null. 7627 * 7628 * @param viewId The id of the view on which to call the method. 7629 * @param methodName The name of the method to call. 7630 * @param colorAttr The theme attribute to resolve and pass as argument to the method. 7631 */ 7632 public void setColorStateListAttr(@IdRes int viewId, @NonNull String methodName, 7633 @AttrRes int colorAttr) { 7634 addAction(new AttributeReflectionAction(viewId, methodName, 7635 BaseReflectionAction.COLOR_STATE_LIST, ResourceReflectionAction.COLOR_RESOURCE, 7636 colorAttr)); 7637 } 7638 7639 /** 7640 * Call a method taking one long on a view in the layout for this RemoteViews. 7641 * 7642 * @param viewId The id of the view on which to call the method. 7643 * @param methodName The name of the method to call. 7644 * @param value The value to pass to the method. 7645 */ 7646 public void setLong(@IdRes int viewId, String methodName, long value) { 7647 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.LONG, value)); 7648 } 7649 7650 /** 7651 * Call a method taking one float on a view in the layout for this RemoteViews. 7652 * 7653 * @param viewId The id of the view on which to call the method. 7654 * @param methodName The name of the method to call. 7655 * @param value The value to pass to the method. 7656 */ 7657 public void setFloat(@IdRes int viewId, String methodName, float value) { 7658 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, value)); 7659 } 7660 7661 /** 7662 * Call a method taking one float, a size in pixels, on a view in the layout for this 7663 * RemoteViews. 7664 * 7665 * The dimension will be resolved from the resources at the time the {@link RemoteViews} is 7666 * (re-)applied. 7667 * 7668 * Undefined resources will result in an exception, except 0 which will resolve to 0f. 7669 * 7670 * @param viewId The id of the view on which to call the method. 7671 * @param methodName The name of the method to call. 7672 * @param dimenResource The resource to resolve and pass as argument to the method. 7673 */ 7674 public void setFloatDimen(@IdRes int viewId, @NonNull String methodName, 7675 @DimenRes int dimenResource) { 7676 addAction(new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, 7677 ResourceReflectionAction.DIMEN_RESOURCE, dimenResource)); 7678 } 7679 7680 /** 7681 * Call a method taking one float, a size in pixels, on a view in the layout for this 7682 * RemoteViews. 7683 * 7684 * The dimension will be resolved from the resources at the time the {@link RemoteViews} is 7685 * (re-)applied. 7686 * 7687 * @param viewId The id of the view on which to call the method. 7688 * @param methodName The name of the method to call. 7689 * @param value The value of the dimension. 7690 * @param unit The unit in which the value is specified. 7691 */ 7692 public void setFloatDimen(@IdRes int viewId, @NonNull String methodName, 7693 float value, @ComplexDimensionUnit int unit) { 7694 addAction( 7695 new ComplexUnitDimensionReflectionAction(viewId, methodName, ReflectionAction.FLOAT, 7696 value, unit)); 7697 } 7698 7699 /** 7700 * Call a method taking one float, a size in pixels, on a view in the layout for this 7701 * RemoteViews. 7702 * 7703 * The dimension will be resolved from the theme attribute at the time the {@link RemoteViews} 7704 * is (re-)applied. 7705 * 7706 * Unresolvable attributes will result in an exception, except 0 which will resolve to 0f. 7707 * 7708 * @param viewId The id of the view on which to call the method. 7709 * @param methodName The name of the method to call. 7710 * @param dimenAttr The attribute to resolve and pass as argument to the method. 7711 */ 7712 public void setFloatDimenAttr(@IdRes int viewId, @NonNull String methodName, 7713 @AttrRes int dimenAttr) { 7714 addAction(new AttributeReflectionAction(viewId, methodName, BaseReflectionAction.FLOAT, 7715 ResourceReflectionAction.DIMEN_RESOURCE, dimenAttr)); 7716 } 7717 7718 /** 7719 * Call a method taking one double on a view in the layout for this RemoteViews. 7720 * 7721 * @param viewId The id of the view on which to call the method. 7722 * @param methodName The name of the method to call. 7723 * @param value The value to pass to the method. 7724 */ 7725 public void setDouble(@IdRes int viewId, String methodName, double value) { 7726 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.DOUBLE, value)); 7727 } 7728 7729 /** 7730 * Call a method taking one char on a view in the layout for this RemoteViews. 7731 * 7732 * @param viewId The id of the view on which to call the method. 7733 * @param methodName The name of the method to call. 7734 * @param value The value to pass to the method. 7735 */ 7736 public void setChar(@IdRes int viewId, String methodName, char value) { 7737 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR, value)); 7738 } 7739 7740 /** 7741 * Call a method taking one String on a view in the layout for this RemoteViews. 7742 * 7743 * @param viewId The id of the view on which to call the method. 7744 * @param methodName The name of the method to call. 7745 * @param value The value to pass to the method. 7746 */ 7747 public void setString(@IdRes int viewId, String methodName, String value) { 7748 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.STRING, value)); 7749 } 7750 7751 /** 7752 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 7753 * 7754 * @param viewId The id of the view on which to call the method. 7755 * @param methodName The name of the method to call. 7756 * @param value The value to pass to the method. 7757 */ 7758 public void setCharSequence(@IdRes int viewId, String methodName, CharSequence value) { 7759 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE, 7760 value)); 7761 } 7762 7763 /** 7764 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 7765 * 7766 * The CharSequence will be resolved from the resources at the time the {@link RemoteViews} is 7767 * (re-)applied. 7768 * 7769 * Undefined resources will result in an exception, except 0 which will resolve to null. 7770 * 7771 * @param viewId The id of the view on which to call the method. 7772 * @param methodName The name of the method to call. 7773 * @param stringResource The resource to resolve and pass as argument to the method. 7774 */ 7775 public void setCharSequence(@IdRes int viewId, @NonNull String methodName, 7776 @StringRes int stringResource) { 7777 addAction( 7778 new ResourceReflectionAction(viewId, methodName, BaseReflectionAction.CHAR_SEQUENCE, 7779 ResourceReflectionAction.STRING_RESOURCE, stringResource)); 7780 } 7781 7782 /** 7783 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 7784 * 7785 * The CharSequence will be resolved from the theme attribute at the time the 7786 * {@link RemoteViews} is (re-)applied. 7787 * 7788 * Unresolvable attributes will result in an exception, except 0 which will resolve to null. 7789 * 7790 * @param viewId The id of the view on which to call the method. 7791 * @param methodName The name of the method to call. 7792 * @param stringAttribute The attribute to resolve and pass as argument to the method. 7793 */ 7794 public void setCharSequenceAttr(@IdRes int viewId, @NonNull String methodName, 7795 @AttrRes int stringAttribute) { 7796 addAction( 7797 new AttributeReflectionAction(viewId, methodName, 7798 BaseReflectionAction.CHAR_SEQUENCE, 7799 AttributeReflectionAction.STRING_RESOURCE, stringAttribute)); 7800 } 7801 7802 /** 7803 * Call a method taking one Uri on a view in the layout for this RemoteViews. 7804 * 7805 * @param viewId The id of the view on which to call the method. 7806 * @param methodName The name of the method to call. 7807 * @param value The value to pass to the method. 7808 */ 7809 public void setUri(@IdRes int viewId, String methodName, Uri value) { 7810 if (value != null) { 7811 // Resolve any filesystem path before sending remotely 7812 value = value.getCanonicalUri(); 7813 if (StrictMode.vmFileUriExposureEnabled()) { 7814 value.checkFileUriExposed("RemoteViews.setUri()"); 7815 } 7816 } 7817 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.URI, value)); 7818 } 7819 7820 /** 7821 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 7822 * @more 7823 * <p class="note">The bitmap will be flattened into the parcel if this object is 7824 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 7825 * 7826 * @param viewId The id of the view on which to call the method. 7827 * @param methodName The name of the method to call. 7828 * @param value The value to pass to the method. 7829 */ 7830 public void setBitmap(@IdRes int viewId, String methodName, Bitmap value) { 7831 addAction(new BitmapReflectionAction(viewId, methodName, value)); 7832 } 7833 7834 /** 7835 * Call a method taking one BlendMode on a view in the layout for this RemoteViews. 7836 * 7837 * @param viewId The id of the view on which to call the method. 7838 * @param methodName The name of the method to call. 7839 * @param value The value to pass to the method. 7840 */ 7841 public void setBlendMode(@IdRes int viewId, @NonNull String methodName, 7842 @Nullable BlendMode value) { 7843 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BLEND_MODE, value)); 7844 } 7845 7846 /** 7847 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 7848 * 7849 * @param viewId The id of the view on which to call the method. 7850 * @param methodName The name of the method to call. 7851 * @param value The value to pass to the method. 7852 */ 7853 public void setBundle(@IdRes int viewId, String methodName, Bundle value) { 7854 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.BUNDLE, value)); 7855 } 7856 7857 /** 7858 * Call a method taking one Intent on a view in the layout for this RemoteViews. 7859 * 7860 * @param viewId The id of the view on which to call the method. 7861 * @param methodName The name of the method to call. 7862 * @param value The {@link android.content.Intent} to pass the method. 7863 */ 7864 public void setIntent(@IdRes int viewId, String methodName, Intent value) { 7865 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.INTENT, value)); 7866 } 7867 7868 /** 7869 * Call a method taking one Icon on a view in the layout for this RemoteViews. 7870 * 7871 * @param viewId The id of the view on which to call the method. 7872 * @param methodName The name of the method to call. 7873 * @param value The {@link android.graphics.drawable.Icon} to pass the method. 7874 */ 7875 public void setIcon(@IdRes int viewId, String methodName, Icon value) { 7876 addAction(new ReflectionAction(viewId, methodName, BaseReflectionAction.ICON, value)); 7877 } 7878 7879 /** 7880 * Call a method taking one Icon on a view in the layout for this RemoteViews. 7881 * 7882 * @param viewId The id of the view on which to call the method. 7883 * @param methodName The name of the method to call. 7884 * @param notNight The value to pass to the method when the view's configuration is set to 7885 * {@link Configuration#UI_MODE_NIGHT_NO} 7886 * @param night The value to pass to the method when the view's configuration is set to 7887 * {@link Configuration#UI_MODE_NIGHT_YES} 7888 */ 7889 public void setIcon( 7890 @IdRes int viewId, 7891 @NonNull String methodName, 7892 @Nullable Icon notNight, 7893 @Nullable Icon night) { 7894 addAction( 7895 new NightModeReflectionAction( 7896 viewId, 7897 methodName, 7898 BaseReflectionAction.ICON, 7899 notNight, 7900 night)); 7901 } 7902 7903 /** 7904 * Equivalent to calling View.setContentDescription(CharSequence). 7905 * 7906 * @param viewId The id of the view whose content description should change. 7907 * @param contentDescription The new content description for the view. 7908 */ 7909 public void setContentDescription(@IdRes int viewId, CharSequence contentDescription) { 7910 setCharSequence(viewId, "setContentDescription", contentDescription); 7911 } 7912 7913 /** 7914 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. 7915 * 7916 * @param viewId The id of the view whose before view in accessibility traversal to set. 7917 * @param nextId The id of the next in the accessibility traversal. 7918 **/ 7919 public void setAccessibilityTraversalBefore(@IdRes int viewId, @IdRes int nextId) { 7920 setInt(viewId, "setAccessibilityTraversalBefore", nextId); 7921 } 7922 7923 /** 7924 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. 7925 * 7926 * @param viewId The id of the view whose after view in accessibility traversal to set. 7927 * @param nextId The id of the next in the accessibility traversal. 7928 **/ 7929 public void setAccessibilityTraversalAfter(@IdRes int viewId, @IdRes int nextId) { 7930 setInt(viewId, "setAccessibilityTraversalAfter", nextId); 7931 } 7932 7933 /** 7934 * Equivalent to calling {@link View#setLabelFor(int)}. 7935 * 7936 * @param viewId The id of the view whose property to set. 7937 * @param labeledId The id of a view for which this view serves as a label. 7938 */ 7939 public void setLabelFor(@IdRes int viewId, @IdRes int labeledId) { 7940 setInt(viewId, "setLabelFor", labeledId); 7941 } 7942 7943 /** 7944 * Equivalent to calling {@link android.widget.CompoundButton#setChecked(boolean)}. 7945 * 7946 * @param viewId The id of the view whose property to set. 7947 * @param checked true to check the button, false to uncheck it. 7948 */ 7949 public void setCompoundButtonChecked(@IdRes int viewId, boolean checked) { 7950 addAction(new SetCompoundButtonCheckedAction(viewId, checked)); 7951 } 7952 7953 /** 7954 * Equivalent to calling {@link android.widget.RadioGroup#check(int)}. 7955 * 7956 * @param viewId The id of the view whose property to set. 7957 * @param checkedId The unique id of the radio button to select in the group. 7958 */ 7959 public void setRadioGroupChecked(@IdRes int viewId, @IdRes int checkedId) { 7960 addAction(new SetRadioGroupCheckedAction(viewId, checkedId)); 7961 } 7962 7963 /** 7964 * Provides an alternate layout ID, which can be used to inflate this view. This layout will be 7965 * used by the host when the widgets displayed on a light-background where foreground elements 7966 * and text can safely draw using a dark color without any additional background protection. 7967 */ 7968 public void setLightBackgroundLayoutId(@LayoutRes int layoutId) { 7969 mLightBackgroundLayoutId = layoutId; 7970 } 7971 7972 /** 7973 * If this view supports dark text versions, creates a copy representing that version, 7974 * otherwise returns itself. 7975 * @hide 7976 */ 7977 public RemoteViews getDarkTextViews() { 7978 if (hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)) { 7979 return this; 7980 } 7981 7982 try { 7983 addFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT); 7984 return new RemoteViews(this); 7985 } finally { 7986 mApplyFlags &= ~FLAG_USE_LIGHT_BACKGROUND_LAYOUT; 7987 } 7988 } 7989 7990 private boolean hasDrawInstructions() { 7991 return mHasDrawInstructions; 7992 } 7993 7994 private RemoteViews getRemoteViewsToApply(Context context) { 7995 if (hasLandscapeAndPortraitLayouts()) { 7996 int orientation = context.getResources().getConfiguration().orientation; 7997 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 7998 return mLandscape; 7999 } 8000 return mPortrait; 8001 } 8002 if (hasSizedRemoteViews()) { 8003 return findSmallestRemoteView(); 8004 } 8005 return this; 8006 } 8007 8008 /** 8009 * Returns the square distance between two points. 8010 * 8011 * This is particularly useful when we only care about the ordering of the distances. 8012 */ 8013 private static float squareDistance(SizeF p1, SizeF p2) { 8014 float dx = p1.getWidth() - p2.getWidth(); 8015 float dy = p1.getHeight() - p2.getHeight(); 8016 return dx * dx + dy * dy; 8017 } 8018 8019 /** 8020 * Returns whether the layout fits in the space available to the widget. 8021 * 8022 * A layout fits on a widget if the widget size is known (i.e. not null) and both dimensions 8023 * are smaller than the ones of the widget, adding some padding to account for rounding errors. 8024 */ 8025 private static boolean fitsIn(SizeF sizeLayout, @Nullable SizeF sizeWidget) { 8026 return sizeWidget != null && (Math.ceil(sizeWidget.getWidth()) + 1 > sizeLayout.getWidth()) 8027 && (Math.ceil(sizeWidget.getHeight()) + 1 > sizeLayout.getHeight()); 8028 } 8029 8030 private RemoteViews findBestFitLayout(@NonNull SizeF widgetSize) { 8031 // Find the better remote view 8032 RemoteViews bestFit = null; 8033 float bestSqDist = Float.MAX_VALUE; 8034 for (RemoteViews layout : mSizedRemoteViews) { 8035 SizeF layoutSize = layout.getIdealSize(); 8036 if (layoutSize == null) { 8037 throw new IllegalStateException("Expected RemoteViews to have ideal size"); 8038 } 8039 8040 if (fitsIn(layoutSize, widgetSize)) { 8041 if (bestFit == null) { 8042 bestFit = layout; 8043 bestSqDist = squareDistance(layoutSize, widgetSize); 8044 } else { 8045 float newSqDist = squareDistance(layoutSize, widgetSize); 8046 if (newSqDist < bestSqDist) { 8047 bestFit = layout; 8048 bestSqDist = newSqDist; 8049 } 8050 } 8051 } 8052 } 8053 if (bestFit == null) { 8054 Log.w(LOG_TAG, "Could not find a RemoteViews fitting the current size: " + widgetSize); 8055 return findSmallestRemoteView(); 8056 } 8057 return bestFit; 8058 } 8059 8060 /** 8061 * Returns the most appropriate {@link RemoteViews} given the context and, if not null, the 8062 * size of the widget. 8063 * 8064 * If {@link RemoteViews#hasSizedRemoteViews()} returns true, the most appropriate view is 8065 * the one that fits in the widget (according to {@link RemoteViews#fitsIn}) and has the 8066 * diagonal the most similar to the widget. If no layout fits or the size of the widget is 8067 * not specified, the one with the smallest area will be chosen. 8068 * 8069 * @hide 8070 */ 8071 public RemoteViews getRemoteViewsToApply(@NonNull Context context, 8072 @Nullable SizeF widgetSize) { 8073 if (!hasSizedRemoteViews() || widgetSize == null) { 8074 // If there isn't multiple remote views, fall back on the previous methods. 8075 return getRemoteViewsToApply(context); 8076 } 8077 return findBestFitLayout(widgetSize); 8078 } 8079 8080 /** 8081 * Checks whether the change of size will lead to using a different {@link RemoteViews}. 8082 * 8083 * @hide 8084 */ 8085 @Nullable 8086 public RemoteViews getRemoteViewsToApplyIfDifferent(@Nullable SizeF oldSize, 8087 @NonNull SizeF newSize) { 8088 if (!hasSizedRemoteViews()) { 8089 return null; 8090 } 8091 RemoteViews oldBestFit = oldSize == null ? findSmallestRemoteView() : findBestFitLayout( 8092 oldSize); 8093 RemoteViews newBestFit = findBestFitLayout(newSize); 8094 if (oldBestFit != newBestFit) { 8095 return newBestFit; 8096 } 8097 return null; 8098 } 8099 8100 8101 /** 8102 * Inflates the view hierarchy represented by this object and applies 8103 * all of the actions. 8104 * 8105 * <p><strong>Caller beware: this may throw</strong> 8106 * 8107 * @param context Default context to use 8108 * @param parent Parent that the resulting view hierarchy will be attached to. This method 8109 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 8110 * @return The inflated view hierarchy 8111 */ 8112 public View apply(Context context, ViewGroup parent) { 8113 return apply(context, parent, null); 8114 } 8115 8116 /** @hide */ 8117 public View apply(Context context, ViewGroup parent, InteractionHandler handler) { 8118 return apply(context, parent, handler, null); 8119 } 8120 8121 /** @hide */ 8122 public View apply(@NonNull Context context, @NonNull ViewGroup parent, 8123 @Nullable InteractionHandler handler, @Nullable SizeF size) { 8124 return apply(context, parent, size, new ActionApplyParams() 8125 .withInteractionHandler(handler)); 8126 } 8127 8128 /** @hide */ 8129 public View applyWithTheme(@NonNull Context context, @NonNull ViewGroup parent, 8130 @Nullable InteractionHandler handler, @StyleRes int applyThemeResId) { 8131 return apply(context, parent, null, new ActionApplyParams() 8132 .withInteractionHandler(handler) 8133 .withThemeResId(applyThemeResId)); 8134 } 8135 8136 /** @hide */ 8137 public View apply(Context context, ViewGroup parent, InteractionHandler handler, 8138 @Nullable SizeF size, @Nullable ColorResources colorResources) { 8139 return apply(context, parent, size, new ActionApplyParams() 8140 .withInteractionHandler(handler) 8141 .withColorResources(colorResources)); 8142 } 8143 8144 /** @hide **/ 8145 public View apply(Context context, ViewGroup parent, @Nullable SizeF size, 8146 ActionApplyParams params) { 8147 return apply(context, parent, parent, size, params); 8148 } 8149 8150 private View apply(Context context, ViewGroup directParent, ViewGroup rootParent, 8151 @Nullable SizeF size, ActionApplyParams params) { 8152 RemoteViews rvToApply = getRemoteViewsToApply(context, size); 8153 View result = inflateView(context, rvToApply, directParent, 8154 params.applyThemeResId, params.colorResources); 8155 rvToApply.performApply(result, rootParent, params); 8156 return result; 8157 } 8158 8159 private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent, 8160 @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) { 8161 try { 8162 Trace.beginSection(rv.hasDrawInstructions() 8163 ? "RemoteViews#inflateViewWithDrawInstructions" 8164 : "RemoteViews#inflateView"); 8165 return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources); 8166 } finally { 8167 Trace.endSection(); 8168 } 8169 } 8170 8171 private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent, 8172 @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) { 8173 // RemoteViews may be built by an application installed in another 8174 // user. So build a context that loads resources from that user but 8175 // still returns the current users userId so settings like data / time formats 8176 // are loaded without requiring cross user persmissions. 8177 final Context contextForResources = 8178 getContextForResourcesEnsuringCorrectCachedApkPaths(context); 8179 if (colorResources != null) { 8180 colorResources.apply(contextForResources); 8181 } 8182 Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources); 8183 8184 // If mApplyThemeResId is not given, Theme.DeviceDefault will be used. 8185 if (applyThemeResId != 0) { 8186 inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId); 8187 } 8188 View v; 8189 // If the RemoteViews contains draw instructions, just use it instead. 8190 if (rv.hasDrawInstructions()) { 8191 final RemoteComposePlayer player = new RemoteComposePlayer(inflationContext); 8192 player.setDebug(Build.IS_USERDEBUG || Build.IS_ENG ? 1 : 0); 8193 v = player; 8194 } else { 8195 LayoutInflater inflater = LayoutInflater.from(context); 8196 8197 // Clone inflater so we load resources from correct context and 8198 // we don't add a filter to the static version returned by getSystemService. 8199 inflater = inflater.cloneInContext(inflationContext); 8200 inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this); 8201 if (mLayoutInflaterFactory2 != null) { 8202 inflater.setFactory2(mLayoutInflaterFactory2); 8203 } 8204 v = inflater.inflate(rv.getLayoutId(), parent, false); 8205 } 8206 if (mViewId != View.NO_ID) { 8207 v.setId(mViewId); 8208 v.setTagInternal(R.id.remote_views_override_id, mViewId); 8209 } 8210 v.setTagInternal(R.id.widget_frame, rv.getLayoutId()); 8211 return v; 8212 } 8213 8214 /** 8215 * A static filter is much lighter than RemoteViews itself. It's optimized here only for 8216 * RemoteVies class. Subclasses should always override this and return true if not overriding 8217 * {@link this#onLoadClass(Class)}. 8218 * 8219 * @hide 8220 */ 8221 protected boolean shouldUseStaticFilter() { 8222 return this.getClass().equals(RemoteViews.class); 8223 } 8224 8225 /** 8226 * Implement this interface to receive a callback when 8227 * {@link #applyAsync} or {@link #reapplyAsync} is finished. 8228 * @hide 8229 */ 8230 public interface OnViewAppliedListener { 8231 /** 8232 * Callback when the RemoteView has finished inflating, 8233 * but no actions have been applied yet. 8234 */ 8235 default void onViewInflated(View v) {} 8236 8237 void onViewApplied(View v); 8238 8239 void onError(Exception e); 8240 } 8241 8242 /** 8243 * Applies the views asynchronously, moving as much of the task on the background 8244 * thread as possible. 8245 * 8246 * @see #apply(Context, ViewGroup) 8247 * @param context Default context to use 8248 * @param parent Parent that the resulting view hierarchy will be attached to. This method 8249 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 8250 * @param listener the callback to run when all actions have been applied. May be null. 8251 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used. 8252 * @return CancellationSignal 8253 * @hide 8254 */ 8255 public CancellationSignal applyAsync( 8256 Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) { 8257 return applyAsync(context, parent, executor, listener, null /* handler */); 8258 } 8259 8260 /** @hide */ 8261 public CancellationSignal applyAsync(Context context, ViewGroup parent, 8262 Executor executor, OnViewAppliedListener listener, InteractionHandler handler) { 8263 return applyAsync(context, parent, executor, listener, handler, null /* size */); 8264 } 8265 8266 /** @hide */ 8267 public CancellationSignal applyAsync(Context context, ViewGroup parent, 8268 Executor executor, OnViewAppliedListener listener, InteractionHandler handler, 8269 SizeF size) { 8270 return applyAsync(context, parent, executor, listener, handler, size, 8271 null /* themeColors */); 8272 } 8273 8274 /** @hide */ 8275 public CancellationSignal applyAsync(Context context, ViewGroup parent, Executor executor, 8276 OnViewAppliedListener listener, InteractionHandler handler, SizeF size, 8277 ColorResources colorResources) { 8278 8279 ActionApplyParams params = new ActionApplyParams() 8280 .withInteractionHandler(handler) 8281 .withColorResources(colorResources) 8282 .withExecutor(executor); 8283 return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, 8284 params, null /* result */, true /* topLevel */).startTaskOnExecutor(executor); 8285 } 8286 8287 private AsyncApplyTask getInternalAsyncApplyTask(Context context, ViewGroup parent, 8288 OnViewAppliedListener listener, ActionApplyParams params, SizeF size, View result) { 8289 return new AsyncApplyTask(getRemoteViewsToApply(context, size), parent, context, listener, 8290 params, result, false /* topLevel */); 8291 } 8292 8293 private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> 8294 implements CancellationSignal.OnCancelListener { 8295 final CancellationSignal mCancelSignal = new CancellationSignal(); 8296 final RemoteViews mRV; 8297 final ViewGroup mParent; 8298 final Context mContext; 8299 final OnViewAppliedListener mListener; 8300 final ActionApplyParams mApplyParams; 8301 8302 /** 8303 * Whether the remote view is the top-level one (i.e. not within an action). 8304 * 8305 * This is only used if the result is specified (i.e. the view is being recycled). 8306 */ 8307 final boolean mTopLevel; 8308 8309 private View mResult; 8310 private ViewTree mTree; 8311 private Action[] mActions; 8312 private Exception mError; 8313 8314 private AsyncApplyTask( 8315 RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, 8316 ActionApplyParams applyParams, View result, boolean topLevel) { 8317 mRV = rv; 8318 mParent = parent; 8319 mContext = context; 8320 mListener = listener; 8321 mTopLevel = topLevel; 8322 mApplyParams = applyParams; 8323 mResult = result; 8324 } 8325 8326 @Nullable 8327 @Override 8328 protected ViewTree doInBackground(Void... params) { 8329 try { 8330 if (mResult == null) { 8331 mResult = inflateView(mContext, mRV, mParent, 0, mApplyParams.colorResources); 8332 } 8333 8334 mTree = new ViewTree(mResult); 8335 8336 if (mRV.mActions != null) { 8337 int count = mRV.mActions.size(); 8338 mActions = new Action[count]; 8339 try { 8340 Trace.beginSection(hasDrawInstructions() 8341 ? "RemoteViews#initActionAsyncWithDrawInstructions" 8342 : "RemoteViews#initActionAsync"); 8343 for (int i = 0; i < count && !isCancelled(); i++) { 8344 // TODO: check if isCancelled in nested views. 8345 mActions[i] = mRV.mActions.get(i) 8346 .initActionAsync(mTree, mParent, mApplyParams); 8347 } 8348 } finally { 8349 Trace.endSection(); 8350 } 8351 } else { 8352 mActions = null; 8353 } 8354 return mTree; 8355 } catch (Exception e) { 8356 mError = e; 8357 return null; 8358 } 8359 } 8360 8361 @Override 8362 protected void onPostExecute(ViewTree viewTree) { 8363 mCancelSignal.setOnCancelListener(null); 8364 if (mError == null) { 8365 if (mListener != null) { 8366 mListener.onViewInflated(viewTree.mRoot); 8367 } 8368 8369 try { 8370 if (mActions != null) { 8371 ActionApplyParams applyParams = mApplyParams.clone(); 8372 if (applyParams.handler == null) { 8373 applyParams.handler = DEFAULT_INTERACTION_HANDLER; 8374 } 8375 try { 8376 Trace.beginSection(hasDrawInstructions() 8377 ? "RemoteViews#applyActionsAsyncWithDrawInstructions" 8378 : "RemoteViews#applyActionsAsync"); 8379 for (Action a : mActions) { 8380 a.apply(viewTree.mRoot, mParent, applyParams); 8381 } 8382 } finally { 8383 Trace.endSection(); 8384 } 8385 } 8386 // If the parent of the view is has is a root, resolve the recycling. 8387 if (mTopLevel && mResult instanceof ViewGroup) { 8388 finalizeViewRecycling((ViewGroup) mResult); 8389 } 8390 } catch (Exception e) { 8391 mError = e; 8392 } 8393 } 8394 8395 if (mListener != null) { 8396 if (mError != null) { 8397 mListener.onError(mError); 8398 } else { 8399 mListener.onViewApplied(viewTree.mRoot); 8400 } 8401 } else if (mError != null) { 8402 if (mError instanceof ActionException) { 8403 throw (ActionException) mError; 8404 } else { 8405 throw new ActionException(mError); 8406 } 8407 } 8408 } 8409 8410 @Override 8411 public void onCancel() { 8412 cancel(true); 8413 } 8414 8415 private CancellationSignal startTaskOnExecutor(Executor executor) { 8416 mCancelSignal.setOnCancelListener(this); 8417 executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor); 8418 return mCancelSignal; 8419 } 8420 } 8421 8422 /** 8423 * Applies all of the actions to the provided view. 8424 * 8425 * <p><strong>Caller beware: this may throw</strong> 8426 * 8427 * @param v The view to apply the actions to. This should be the result of 8428 * the {@link #apply(Context,ViewGroup)} call. 8429 */ 8430 public void reapply(Context context, View v) { 8431 reapply(context, v, null /* size */, new ActionApplyParams()); 8432 } 8433 8434 /** @hide */ 8435 public void reapply(Context context, View v, InteractionHandler handler) { 8436 reapply(context, v, null /* size */, 8437 new ActionApplyParams().withInteractionHandler(handler)); 8438 } 8439 8440 /** @hide */ 8441 public void reapply(Context context, View v, InteractionHandler handler, SizeF size, 8442 ColorResources colorResources) { 8443 reapply(context, v, size, new ActionApplyParams() 8444 .withInteractionHandler(handler).withColorResources(colorResources)); 8445 } 8446 8447 /** @hide */ 8448 public void reapply(Context context, View v, @Nullable SizeF size, ActionApplyParams params) { 8449 reapply(context, v, (ViewGroup) v.getParent(), size, params, true); 8450 } 8451 8452 private void reapplyNestedViews(Context context, View v, ViewGroup rootParent, 8453 ActionApplyParams params) { 8454 reapply(context, v, rootParent, null, params, false); 8455 } 8456 8457 // Note: topLevel should be true only for calls on the topLevel RemoteViews, internal calls 8458 // should set it to false. 8459 private void reapply(Context context, View v, ViewGroup rootParent, 8460 @Nullable SizeF size, ActionApplyParams params, boolean topLevel) { 8461 RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); 8462 rvToApply.performApply(v, rootParent, params); 8463 8464 // If the parent of the view is has is a root, resolve the recycling. 8465 if (topLevel && v instanceof ViewGroup) { 8466 finalizeViewRecycling((ViewGroup) v); 8467 } 8468 } 8469 8470 /** @hide */ 8471 public boolean canRecycleView(@Nullable View v) { 8472 if (v == null || hasDrawInstructions()) { 8473 return false; 8474 } 8475 Integer previousLayoutId = (Integer) v.getTag(R.id.widget_frame); 8476 if (previousLayoutId == null) { 8477 return false; 8478 } 8479 Integer overrideIdTag = (Integer) v.getTag(R.id.remote_views_override_id); 8480 int overrideId = overrideIdTag == null ? View.NO_ID : overrideIdTag; 8481 // If mViewId is View.NO_ID, we only recycle if overrideId is also View.NO_ID. 8482 // Otherwise, it might be that, on a previous iteration, the view's ID was set to 8483 // something else, and it should now be reset to the ID defined in the XML layout file, 8484 // whatever it is. 8485 return previousLayoutId == getLayoutId() && mViewId == overrideId; 8486 } 8487 8488 /** 8489 * Returns the RemoteViews that should be used in the reapply operation. 8490 * 8491 * If the current RemoteViews has multiple layout, this will select the correct one. 8492 * 8493 * @throws RuntimeException If the current RemoteViews should not be reapplied onto the provided 8494 * View. 8495 */ 8496 private RemoteViews getRemoteViewsToReapply(Context context, View v, @Nullable SizeF size) { 8497 RemoteViews rvToApply = getRemoteViewsToApply(context, size); 8498 8499 // In the case that a view has this RemoteViews applied in one orientation or size, is 8500 // persisted across change, and has the RemoteViews re-applied in a different situation 8501 // (orientation or size), we throw an exception, since the layouts may be completely 8502 // unrelated. 8503 // If the ViewID has been changed on the view, or is changed by the RemoteViews, we also 8504 // may throw an exception, as the RemoteViews will probably not apply properly. 8505 // However, we need to let potentially unrelated RemoteViews apply, as this lack of testing 8506 // is already used in production code in some apps. 8507 if (hasMultipleLayouts() 8508 || rvToApply.mViewId != View.NO_ID 8509 || v.getTag(R.id.remote_views_override_id) != null) { 8510 if (!rvToApply.canRecycleView(v)) { 8511 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 8512 " that does not share the same root layout id."); 8513 } 8514 } 8515 8516 return rvToApply; 8517 } 8518 8519 /** 8520 * Applies all the actions to the provided view, moving as much of the task on the background 8521 * thread as possible. 8522 * 8523 * @see #reapply(Context, View) 8524 * @param context Default context to use 8525 * @param v The view to apply the actions to. This should be the result of 8526 * the {@link #apply(Context,ViewGroup)} call. 8527 * @param listener the callback to run when all actions have been applied. May be null. 8528 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used 8529 * @return CancellationSignal 8530 * @hide 8531 */ 8532 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 8533 OnViewAppliedListener listener) { 8534 return reapplyAsync(context, v, executor, listener, null); 8535 } 8536 8537 /** @hide */ 8538 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 8539 OnViewAppliedListener listener, InteractionHandler handler) { 8540 return reapplyAsync(context, v, executor, listener, handler, null, null); 8541 } 8542 8543 /** @hide */ 8544 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 8545 OnViewAppliedListener listener, InteractionHandler handler, SizeF size, 8546 ColorResources colorResources) { 8547 RemoteViews rvToApply = getRemoteViewsToReapply(context, v, size); 8548 8549 ActionApplyParams params = new ActionApplyParams() 8550 .withColorResources(colorResources) 8551 .withInteractionHandler(handler) 8552 .withExecutor(executor); 8553 8554 return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), 8555 context, listener, params, v, true /* topLevel */) 8556 .startTaskOnExecutor(executor); 8557 } 8558 8559 private void performApply(View v, ViewGroup parent, ActionApplyParams params) { 8560 params = params.clone(); 8561 if (params.handler == null) { 8562 params.handler = DEFAULT_INTERACTION_HANDLER; 8563 } 8564 if (v instanceof RemoteComposePlayer player) { 8565 player.setTheme(v.getResources().getConfiguration().isNightModeActive() 8566 ? Theme.DARK : Theme.LIGHT); 8567 } 8568 if (mActions != null) { 8569 final int count = mActions.size(); 8570 try { 8571 Trace.beginSection(hasDrawInstructions() 8572 ? "RemoteViews#applyActionsWithDrawInstructions" 8573 : "RemoteViews#applyActions"); 8574 for (int i = 0; i < count; i++) { 8575 mActions.get(i).apply(v, parent, params); 8576 } 8577 } finally { 8578 Trace.endSection(); 8579 } 8580 } 8581 } 8582 8583 /** 8584 * Returns true if the RemoteViews contains potentially costly operations and should be 8585 * applied asynchronously. 8586 * 8587 * @hide 8588 */ 8589 public boolean prefersAsyncApply() { 8590 if (mActions != null) { 8591 final int count = mActions.size(); 8592 for (int i = 0; i < count; i++) { 8593 if (mActions.get(i).prefersAsyncApply()) { 8594 return true; 8595 } 8596 } 8597 } 8598 return false; 8599 } 8600 8601 /** @hide */ 8602 public void updateAppInfo(@NonNull ApplicationInfo info) { 8603 ApplicationInfo existing = mApplicationInfoCache.get(info); 8604 if (existing != null && !existing.sourceDir.equals(info.sourceDir)) { 8605 // Overlay paths are generated against a particular version of an application. 8606 // The overlays paths of a newly upgraded application are incompatible with the 8607 // old version of the application. 8608 return; 8609 } 8610 8611 // If we can update to the new AppInfo, put it in the cache and propagate the change 8612 // throughout the hierarchy. 8613 mApplicationInfoCache.put(info); 8614 configureDescendantsAsChildren(); 8615 } 8616 8617 private Context getContextForResourcesEnsuringCorrectCachedApkPaths(Context context) { 8618 if (mApplication != null) { 8619 if (context.getUserId() == UserHandle.getUserId(mApplication.uid) 8620 && context.getPackageName().equals(mApplication.packageName)) { 8621 return context; 8622 } 8623 try { 8624 ApplicationInfo sanitizedApplication = mApplication; 8625 try { 8626 // Use PackageManager as the source of truth for application information, rather 8627 // than the parceled ApplicationInfo provided by the app. 8628 sanitizedApplication = context.getPackageManager().getApplicationInfoAsUser( 8629 mApplication.packageName, 0, UserHandle.getUserId(mApplication.uid)); 8630 } catch(SecurityException se) { 8631 Log.d(LOG_TAG, "Unable to fetch appInfo for " + mApplication.packageName); 8632 } 8633 8634 Context applicationContext = context.createApplicationContext( 8635 sanitizedApplication, 8636 Context.CONTEXT_RESTRICTED); 8637 // Get the correct apk paths while maintaining the current context's configuration. 8638 return applicationContext.createConfigurationContext( 8639 context.getResources().getConfiguration()); 8640 } catch (NameNotFoundException e) { 8641 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); 8642 } 8643 } 8644 8645 return context; 8646 } 8647 8648 @NonNull 8649 private SparseArray<PendingIntent> getPendingIntentTemplate() { 8650 if (mPendingIntentTemplate == null) { 8651 mPendingIntentTemplate = new SparseArray<>(); 8652 } 8653 return mPendingIntentTemplate; 8654 } 8655 8656 @NonNull 8657 private SparseArray<Intent> getFillInIntent() { 8658 if (mFillInIntent == null) { 8659 mFillInIntent = new SparseArray<>(); 8660 } 8661 return mFillInIntent; 8662 } 8663 8664 private void tryAddRemoteResponse(final int viewId) { 8665 final PendingIntent pendingIntent = getPendingIntentTemplate().get(viewId); 8666 final Intent intent = getFillInIntent().get(viewId); 8667 if (pendingIntent != null && intent != null) { 8668 addAction(new SetOnClickResponse(viewId, 8669 RemoteResponse.fromPendingIntentTemplateAndFillInIntent( 8670 pendingIntent, intent))); 8671 } 8672 } 8673 8674 /** 8675 * Utility class to hold all the options when applying the remote views 8676 * @hide 8677 */ 8678 public class ActionApplyParams { 8679 public InteractionHandler handler; 8680 public ColorResources colorResources; 8681 public Executor executor; 8682 @StyleRes public int applyThemeResId; 8683 8684 @Override 8685 public ActionApplyParams clone() { 8686 return new ActionApplyParams() 8687 .withInteractionHandler(handler) 8688 .withColorResources(colorResources) 8689 .withExecutor(executor) 8690 .withThemeResId(applyThemeResId); 8691 } 8692 8693 public ActionApplyParams withInteractionHandler(InteractionHandler handler) { 8694 this.handler = handler; 8695 return this; 8696 } 8697 8698 public ActionApplyParams withColorResources(ColorResources colorResources) { 8699 this.colorResources = colorResources; 8700 return this; 8701 } 8702 8703 public ActionApplyParams withThemeResId(@StyleRes int themeResId) { 8704 this.applyThemeResId = themeResId; 8705 return this; 8706 } 8707 8708 public ActionApplyParams withExecutor(Executor executor) { 8709 this.executor = executor; 8710 return this; 8711 } 8712 } 8713 8714 /** 8715 * Object allowing the modification of a context to overload the system's dynamic colors. 8716 * 8717 * @hide 8718 */ 8719 public static final class ColorResources { 8720 // Set of valid colors resources. 8721 private static final int FIRST_RESOURCE_COLOR_ID = android.R.color.system_neutral1_0; 8722 private static final int LAST_RESOURCE_COLOR_ID = 8723 android.R.color.system_error_1000; 8724 // Size, in bytes, of an entry in the array of colors in an ARSC file. 8725 private static final int ARSC_ENTRY_SIZE = 16; 8726 8727 private static final String OVERLAY_NAME = "remote_views_color_resources"; 8728 private static final String OVERLAY_TARGET_PACKAGE_NAME = "android"; 8729 8730 private final ResourcesLoader mLoader; 8731 private final SparseIntArray mColorMapping; 8732 8733 private ColorResources(ResourcesLoader loader, SparseIntArray colorMapping) { 8734 mLoader = loader; 8735 mColorMapping = colorMapping; 8736 } 8737 8738 /** 8739 * Apply the color resources to the given context. 8740 * 8741 * No resource resolution must have be done on the context given to that method. 8742 */ 8743 public void apply(Context context) { 8744 context.getResources().addLoaders(mLoader); 8745 } 8746 8747 public SparseIntArray getColorMapping() { 8748 return mColorMapping; 8749 } 8750 8751 private static ByteArrayOutputStream readFileContent(InputStream input) throws IOException { 8752 ByteArrayOutputStream content = new ByteArrayOutputStream(2048); 8753 byte[] buffer = new byte[4096]; 8754 while (input.available() > 0) { 8755 int read = input.read(buffer); 8756 content.write(buffer, 0, read); 8757 } 8758 return content; 8759 } 8760 8761 /** 8762 * Creates the compiled resources content from the asset stored in the APK. 8763 * 8764 * The asset is a compiled resource with the correct resources name and correct ids, only 8765 * the values are incorrect. The last value is at the very end of the file. The resources 8766 * are in an array, the array's entries are 16 bytes each. We use this to work out the 8767 * location of all the positions of the various resources. 8768 */ 8769 @Nullable 8770 private static byte[] createCompiledResourcesContent(Context context, 8771 SparseIntArray colorResources) throws IOException { 8772 byte[] content; 8773 try (InputStream input = context.getResources().openRawResource( 8774 com.android.internal.R.raw.remote_views_color_resources)) { 8775 ByteArrayOutputStream rawContent = readFileContent(input); 8776 content = rawContent.toByteArray(); 8777 } 8778 int valuesOffset = 8779 content.length - (LAST_RESOURCE_COLOR_ID & 0xffff) * ARSC_ENTRY_SIZE - 4; 8780 if (valuesOffset < 0) { 8781 Log.e(LOG_TAG, "ARSC file for theme colors is invalid."); 8782 return null; 8783 } 8784 for (int colorRes = FIRST_RESOURCE_COLOR_ID; colorRes <= LAST_RESOURCE_COLOR_ID; 8785 colorRes++) { 8786 // The last 2 bytes are the index in the color array. 8787 int index = colorRes & 0xffff; 8788 int offset = valuesOffset + index * ARSC_ENTRY_SIZE; 8789 int value = colorResources.get(colorRes, context.getColor(colorRes)); 8790 // Write the 32 bit integer in little endian 8791 for (int b = 0; b < 4; b++) { 8792 content[offset + b] = (byte) (value & 0xff); 8793 value >>= 8; 8794 } 8795 } 8796 return content; 8797 } 8798 8799 /** 8800 * Adds a resource loader for theme colors to the given context. The loader is created 8801 * based on resource files created at build time. 8802 * 8803 * <p>Only colors from {@link android.R.color#system_accent1_0} to 8804 * {@link android.R.color#system_error_1000} can be overloaded.</p> 8805 * 8806 * @param context Context of the view hosting the widget. 8807 * @param colorMapping Mapping of resources to color values. 8808 * 8809 * @hide 8810 */ 8811 @Nullable 8812 public static ColorResources create(Context context, SparseIntArray colorMapping) { 8813 try { 8814 byte[] contentBytes = createCompiledResourcesContent(context, colorMapping); 8815 if (contentBytes == null) { 8816 return null; 8817 } 8818 FileDescriptor arscFile = null; 8819 try { 8820 arscFile = Os.memfd_create("remote_views_theme_colors.arsc", 0 /* flags */); 8821 // Note: This must not be closed through the OutputStream. 8822 try (OutputStream pipeWriter = new FileOutputStream(arscFile)) { 8823 pipeWriter.write(contentBytes); 8824 8825 try (ParcelFileDescriptor pfd = ParcelFileDescriptor.dup(arscFile)) { 8826 ResourcesLoader colorsLoader = new ResourcesLoader(); 8827 colorsLoader.addProvider(ResourcesProvider 8828 .loadFromTable(pfd, null /* assetsProvider */)); 8829 return new ColorResources(colorsLoader, colorMapping.clone()); 8830 } 8831 } 8832 } finally { 8833 if (arscFile != null) { 8834 Os.close(arscFile); 8835 } 8836 } 8837 } catch (Exception ex) { 8838 Log.e(LOG_TAG, "Failed to setup the context for theme colors", ex); 8839 } 8840 return null; 8841 } 8842 8843 /** 8844 * Adds a resource loader for theme colors to the given context. The loader is created 8845 * using fabricated runtime resource overlay (FRRO). 8846 * 8847 * <p>The created class can overlay any color resources, private or public, at runtime.</p> 8848 * 8849 * @param context Context of the view hosting the widget. 8850 * @param colorMapping Mapping of resources to color values. 8851 * 8852 * @hide 8853 */ 8854 @FlaggedApi(FLAG_SELF_TARGETING_ANDROID_RESOURCE_FRRO) 8855 @Nullable 8856 public static ColorResources createWithOverlay(Context context, 8857 SparseIntArray colorMapping) { 8858 try { 8859 String owningPackage = context.getPackageName(); 8860 FabricatedOverlay overlay = new FabricatedOverlay.Builder(owningPackage, 8861 OVERLAY_NAME, OVERLAY_TARGET_PACKAGE_NAME).build(); 8862 8863 for (int i = 0; i < colorMapping.size(); i++) { 8864 overlay.setResourceValue( 8865 context.getResources().getResourceName(colorMapping.keyAt(i)), 8866 TYPE_INT_COLOR_ARGB8, colorMapping.valueAt(i), null); 8867 } 8868 OverlayManager overlayManager = context.getSystemService(OverlayManager.class); 8869 OverlayManagerTransaction.Builder transaction = 8870 new OverlayManagerTransaction.Builder() 8871 .registerFabricatedOverlay(overlay) 8872 .setSelfTargeting(true); 8873 overlayManager.commit(transaction.build()); 8874 8875 OverlayInfo overlayInfo = 8876 overlayManager.getOverlayInfosForTarget(OVERLAY_TARGET_PACKAGE_NAME) 8877 .stream() 8878 .filter(info -> TextUtils.equals(info.overlayName, OVERLAY_NAME) 8879 && TextUtils.equals(info.packageName, owningPackage)) 8880 .findFirst() 8881 .orElse(null); 8882 if (overlayInfo == null) { 8883 Log.e(LOG_TAG, "Failed to get overlay info ", new Throwable()); 8884 return null; 8885 } 8886 ResourcesLoader colorsLoader = new ResourcesLoader(); 8887 colorsLoader.addProvider(ResourcesProvider.loadOverlay(overlayInfo)); 8888 return new ColorResources(colorsLoader, colorMapping.clone()); 8889 } catch (Exception e) { 8890 Log.e(LOG_TAG, "Failed to add theme color overlay into loader", e); 8891 } 8892 return null; 8893 } 8894 } 8895 8896 /** 8897 * Returns the number of actions in this RemoteViews. Can be used as a sequence number. 8898 * 8899 * @hide 8900 */ 8901 public int getSequenceNumber() { 8902 return (mActions == null) ? 0 : mActions.size(); 8903 } 8904 8905 /** 8906 * Used to restrict the views which can be inflated 8907 * 8908 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 8909 * @deprecated Used by system to enforce safe inflation of {@link RemoteViews}. Apps should not 8910 * override this method. Changing of this method will NOT affect the process where RemoteViews 8911 * is rendered. 8912 */ 8913 @Deprecated 8914 public boolean onLoadClass(Class clazz) { 8915 return clazz.isAnnotationPresent(RemoteView.class); 8916 } 8917 8918 public int describeContents() { 8919 return 0; 8920 } 8921 8922 @Override 8923 public void writeToParcel(Parcel dest, int flags) { 8924 writeToParcel(dest, flags, /* intentsToIgnore= */ null); 8925 } 8926 8927 private void writeToParcel(Parcel dest, int flags, 8928 @Nullable SparseArray<Intent> intentsToIgnore) { 8929 boolean prevSquashingAllowed = dest.allowSquashing(); 8930 8931 if (!hasMultipleLayouts()) { 8932 dest.writeInt(MODE_NORMAL); 8933 // We only write the bitmap cache if we are the root RemoteViews, as this cache 8934 // is shared by all children. 8935 if (mIsRoot) { 8936 mBitmapCache.writeBitmapsToParcel(dest, flags); 8937 mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); 8938 } 8939 dest.writeTypedObject(mApplication, flags); 8940 if (mIsRoot || mIdealSize == null) { 8941 dest.writeInt(0); 8942 } else { 8943 dest.writeInt(1); 8944 mIdealSize.writeToParcel(dest, flags); 8945 } 8946 dest.writeInt(mLayoutId); 8947 dest.writeInt(mViewId); 8948 dest.writeInt(mLightBackgroundLayoutId); 8949 writeActionsToParcel(dest, flags); 8950 } else if (hasSizedRemoteViews()) { 8951 dest.writeInt(MODE_HAS_SIZED_REMOTEVIEWS); 8952 if (mIsRoot) { 8953 mBitmapCache.writeBitmapsToParcel(dest, flags); 8954 mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); 8955 } 8956 dest.writeInt(mSizedRemoteViews.size()); 8957 for (RemoteViews view : mSizedRemoteViews) { 8958 view.writeToParcel(dest, flags); 8959 } 8960 } else { 8961 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 8962 // We only write the bitmap cache if we are the root RemoteViews, as this cache 8963 // is shared by all children. 8964 if (mIsRoot) { 8965 mBitmapCache.writeBitmapsToParcel(dest, flags); 8966 mCollectionCache.writeToParcel(dest, flags, intentsToIgnore); 8967 } 8968 mLandscape.writeToParcel(dest, flags); 8969 // Both RemoteViews already share the same package and user 8970 mPortrait.writeToParcel(dest, flags); 8971 } 8972 dest.writeInt(mApplyFlags); 8973 dest.writeLong(mProviderInstanceId); 8974 dest.writeBoolean(mHasDrawInstructions); 8975 8976 dest.restoreAllowSquashing(prevSquashingAllowed); 8977 } 8978 8979 private void writeActionsToParcel(Parcel parcel, int flags) { 8980 int count; 8981 if (mActions != null) { 8982 count = mActions.size(); 8983 } else { 8984 count = 0; 8985 } 8986 parcel.writeInt(count); 8987 for (int i = 0; i < count; i++) { 8988 Action a = mActions.get(i); 8989 parcel.writeInt(a.getActionTag()); 8990 a.writeToParcel(parcel, flags); 8991 } 8992 } 8993 8994 @Nullable 8995 private static ApplicationInfo getApplicationInfo(@Nullable String packageName, int userId) { 8996 if (packageName == null) { 8997 return null; 8998 } 8999 9000 // Get the application for the passed in package and user. 9001 Application application = ActivityThread.currentApplication(); 9002 if (application == null) { 9003 throw new IllegalStateException("Cannot create remote views out of an aplication."); 9004 } 9005 9006 ApplicationInfo applicationInfo = application.getApplicationInfo(); 9007 if (UserHandle.getUserId(applicationInfo.uid) != userId 9008 || !applicationInfo.packageName.equals(packageName)) { 9009 try { 9010 Context context = application.getBaseContext().createPackageContextAsUser( 9011 packageName, 0, new UserHandle(userId)); 9012 applicationInfo = context.getApplicationInfo(); 9013 } catch (NameNotFoundException nnfe) { 9014 throw new IllegalArgumentException("No such package " + packageName); 9015 } 9016 } 9017 9018 return applicationInfo; 9019 } 9020 9021 /** 9022 * Returns true if the {@link #mApplication} is same as the provided info. 9023 * 9024 * @hide 9025 */ 9026 public boolean hasSameAppInfo(ApplicationInfo info) { 9027 return mApplication == null || mApplication.packageName.equals(info.packageName) 9028 && mApplication.uid == info.uid; 9029 } 9030 9031 /** 9032 * Parcelable.Creator that instantiates RemoteViews objects 9033 */ 9034 @NonNull 9035 public static final Parcelable.Creator<RemoteViews> CREATOR = 9036 new Parcelable.Creator<RemoteViews>() { 9037 public RemoteViews createFromParcel(Parcel parcel) { 9038 return new RemoteViews(parcel); 9039 } 9040 9041 public RemoteViews[] newArray(int size) { 9042 return new RemoteViews[size]; 9043 } 9044 }; 9045 9046 /** 9047 * A representation of the view hierarchy. Only views which have a valid ID are added 9048 * and can be searched. 9049 */ 9050 private static class ViewTree { 9051 private static final int INSERT_AT_END_INDEX = -1; 9052 private View mRoot; 9053 private ArrayList<ViewTree> mChildren; 9054 9055 private ViewTree(View root) { 9056 mRoot = root; 9057 } 9058 9059 public void createTree() { 9060 if (mChildren != null) { 9061 return; 9062 } 9063 9064 mChildren = new ArrayList<>(); 9065 if (mRoot instanceof ViewGroup) { 9066 ViewGroup vg = (ViewGroup) mRoot; 9067 int count = vg.getChildCount(); 9068 for (int i = 0; i < count; i++) { 9069 addViewChild(vg.getChildAt(i)); 9070 } 9071 } 9072 } 9073 9074 @Nullable 9075 public ViewTree findViewTreeById(@IdRes int id) { 9076 if (mRoot.getId() == id) { 9077 return this; 9078 } 9079 if (mChildren == null) { 9080 return null; 9081 } 9082 for (ViewTree tree : mChildren) { 9083 ViewTree result = tree.findViewTreeById(id); 9084 if (result != null) { 9085 return result; 9086 } 9087 } 9088 return null; 9089 } 9090 9091 @Nullable 9092 public ViewTree findViewTreeParentOf(ViewTree child) { 9093 if (mChildren == null) { 9094 return null; 9095 } 9096 for (ViewTree tree : mChildren) { 9097 if (tree == child) { 9098 return this; 9099 } 9100 ViewTree result = tree.findViewTreeParentOf(child); 9101 if (result != null) { 9102 return result; 9103 } 9104 } 9105 return null; 9106 } 9107 9108 public void replaceView(View v) { 9109 mRoot = v; 9110 mChildren = null; 9111 createTree(); 9112 } 9113 9114 @Nullable 9115 public <T extends View> T findViewById(@IdRes int id) { 9116 if (mChildren == null) { 9117 return mRoot.findViewById(id); 9118 } 9119 ViewTree tree = findViewTreeById(id); 9120 return tree == null ? null : (T) tree.mRoot; 9121 } 9122 9123 public void addChild(ViewTree child) { 9124 addChild(child, INSERT_AT_END_INDEX); 9125 } 9126 9127 /** 9128 * Adds the given {@link ViewTree} as a child at the given index. 9129 * 9130 * @param index The position at which to add the child or -1 to add last. 9131 */ 9132 public void addChild(ViewTree child, int index) { 9133 if (mChildren == null) { 9134 mChildren = new ArrayList<>(); 9135 } 9136 child.createTree(); 9137 9138 if (index == INSERT_AT_END_INDEX) { 9139 mChildren.add(child); 9140 return; 9141 } 9142 9143 mChildren.add(index, child); 9144 } 9145 9146 public void removeChildren(int start, int count) { 9147 if (mChildren != null) { 9148 for (int i = 0; i < count; i++) { 9149 mChildren.remove(start); 9150 } 9151 } 9152 } 9153 9154 private void addViewChild(View v) { 9155 // ViewTree only contains Views which can be found using findViewById. 9156 // If isRootNamespace is true, this view is skipped. 9157 // @see ViewGroup#findViewTraversal(int) 9158 if (v.isRootNamespace()) { 9159 return; 9160 } 9161 final ViewTree target; 9162 9163 // If the view has a valid id, i.e., if can be found using findViewById, add it to the 9164 // tree, otherwise skip this view and add its children instead. 9165 if (v.getId() != 0) { 9166 ViewTree tree = new ViewTree(v); 9167 mChildren.add(tree); 9168 target = tree; 9169 } else { 9170 target = this; 9171 } 9172 9173 if (v instanceof ViewGroup) { 9174 if (target.mChildren == null) { 9175 target.mChildren = new ArrayList<>(); 9176 ViewGroup vg = (ViewGroup) v; 9177 int count = vg.getChildCount(); 9178 for (int i = 0; i < count; i++) { 9179 target.addViewChild(vg.getChildAt(i)); 9180 } 9181 } 9182 } 9183 } 9184 9185 /** Find the first child for which the condition is true and return its index. */ 9186 public int findChildIndex(Predicate<View> condition) { 9187 return findChildIndex(0, condition); 9188 } 9189 9190 /** 9191 * Find the first child, starting at {@code startIndex}, for which the condition is true and 9192 * return its index. 9193 */ 9194 public int findChildIndex(int startIndex, Predicate<View> condition) { 9195 if (mChildren == null) { 9196 return -1; 9197 } 9198 9199 for (int i = startIndex; i < mChildren.size(); i++) { 9200 if (condition.test(mChildren.get(i).mRoot)) { 9201 return i; 9202 } 9203 } 9204 return -1; 9205 } 9206 } 9207 9208 /** 9209 * Class representing a response to an action performed on any element of a RemoteViews. 9210 */ 9211 public static class RemoteResponse { 9212 9213 /** @hide **/ 9214 @IntDef(prefix = "INTERACTION_TYPE_", value = { 9215 INTERACTION_TYPE_CLICK, 9216 INTERACTION_TYPE_CHECKED_CHANGE, 9217 }) 9218 @Retention(RetentionPolicy.SOURCE) 9219 @interface InteractionType {} 9220 /** @hide */ 9221 public static final int INTERACTION_TYPE_CLICK = 0; 9222 /** @hide */ 9223 public static final int INTERACTION_TYPE_CHECKED_CHANGE = 1; 9224 9225 private PendingIntent mPendingIntent; 9226 private Intent mFillIntent; 9227 9228 private int mInteractionType = INTERACTION_TYPE_CLICK; 9229 private IntArray mViewIds; 9230 private ArrayList<String> mElementNames; 9231 9232 /** 9233 * Creates a response which sends a pending intent as part of the response. The source 9234 * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the 9235 * target view in screen space. 9236 * Note that any activity options associated with the mPendingIntent may get overridden 9237 * before starting the intent. 9238 * 9239 * @param pendingIntent The {@link PendingIntent} to send as part of the response 9240 */ 9241 @NonNull 9242 public static RemoteResponse fromPendingIntent(@NonNull PendingIntent pendingIntent) { 9243 RemoteResponse response = new RemoteResponse(); 9244 response.mPendingIntent = pendingIntent; 9245 return response; 9246 } 9247 9248 /** 9249 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is 9250 * very costly to set PendingIntents on the individual items, and is hence not recommended. 9251 * Instead a single PendingIntent template can be set on the collection, see {@link 9252 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 9253 * action of a given item can be distinguished by setting a fillInIntent on that item. The 9254 * fillInIntent is then combined with the PendingIntent template in order to determine the 9255 * final intent which will be executed when the item is clicked. This works as follows: any 9256 * fields which are left blank in the PendingIntent template, but are provided by the 9257 * fillInIntent will be overwritten, and the resulting PendingIntent will be used. The rest 9258 * of the PendingIntent template will then be filled in with the associated fields that are 9259 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 9260 * Creates a response which sends a pending intent as part of the response. The source 9261 * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the 9262 * target view in screen space. 9263 * Note that any activity options associated with the mPendingIntent may get overridden 9264 * before starting the intent. 9265 * 9266 * @param fillIntent The intent which will be combined with the parent's PendingIntent in 9267 * order to determine the behavior of the response 9268 * @see RemoteViews#setPendingIntentTemplate(int, PendingIntent) 9269 * @see RemoteViews#setOnClickFillInIntent(int, Intent) 9270 */ 9271 @NonNull 9272 public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) { 9273 RemoteResponse response = new RemoteResponse(); 9274 response.mFillIntent = fillIntent; 9275 if (fillIntent != null) { 9276 // Although the parameter is marked as @NonNull, it is nullable. The method that 9277 // calls it (RemoteReviews.setOnClickFillInIntent()) passes its fillInIntent 9278 // parameter to this method and it does not guarantee that the fillInIntent is 9279 // non-null. 9280 fillIntent.collectExtraIntentKeys(); 9281 } 9282 return response; 9283 } 9284 9285 private static RemoteResponse fromPendingIntentTemplateAndFillInIntent( 9286 @NonNull final PendingIntent pendingIntent, @NonNull final Intent intent) { 9287 RemoteResponse response = new RemoteResponse(); 9288 response.mPendingIntent = pendingIntent; 9289 response.mFillIntent = intent; 9290 intent.collectExtraIntentKeys(); 9291 return response; 9292 } 9293 9294 /** 9295 * Adds a shared element to be transferred as part of the transition between Activities 9296 * using cross-Activity scene animations. The position of the first element will be used as 9297 * the epicenter for the exit Transition. The position of the associated shared element in 9298 * the launched Activity will be the epicenter of its entering Transition. 9299 * 9300 * @param viewId The id of the view to be shared as part of the transition 9301 * @param sharedElementName The shared element name for this view 9302 * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[]) 9303 */ 9304 @NonNull 9305 public RemoteResponse addSharedElement(@IdRes int viewId, 9306 @NonNull String sharedElementName) { 9307 if (mViewIds == null) { 9308 mViewIds = new IntArray(); 9309 mElementNames = new ArrayList<>(); 9310 } 9311 mViewIds.add(viewId); 9312 mElementNames.add(sharedElementName); 9313 return this; 9314 } 9315 9316 /** 9317 * Sets the interaction type for which this RemoteResponse responds. 9318 * 9319 * @param type the type of interaction for which this is a response, such as clicking or 9320 * checked state changing 9321 * 9322 * @hide 9323 */ 9324 @NonNull 9325 public RemoteResponse setInteractionType(@InteractionType int type) { 9326 mInteractionType = type; 9327 return this; 9328 } 9329 9330 private void writeToParcel(Parcel dest, int flags) { 9331 PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest); 9332 dest.writeBoolean((mFillIntent != null)); 9333 if (mFillIntent != null) { 9334 dest.writeTypedObject(mFillIntent, flags); 9335 } 9336 dest.writeInt(mInteractionType); 9337 dest.writeIntArray(mViewIds == null ? null : mViewIds.toArray()); 9338 dest.writeStringList(mElementNames); 9339 } 9340 9341 private void readFromParcel(Parcel parcel) { 9342 mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 9343 mFillIntent = parcel.readBoolean() ? parcel.readTypedObject(Intent.CREATOR) : null; 9344 mInteractionType = parcel.readInt(); 9345 int[] viewIds = parcel.createIntArray(); 9346 mViewIds = viewIds == null ? null : IntArray.wrap(viewIds); 9347 mElementNames = parcel.createStringArrayList(); 9348 } 9349 9350 private void handleViewInteraction( 9351 View v, 9352 InteractionHandler handler) { 9353 final PendingIntent pi; 9354 if (mPendingIntent != null) { 9355 pi = mPendingIntent; 9356 } else if (mFillIntent != null) { 9357 AdapterView<?> ancestor = getAdapterViewAncestor(v); 9358 if (ancestor == null) { 9359 Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent"); 9360 return; 9361 } 9362 9363 // Ensure that a template pending intent has been set on the ancestor 9364 if (!(ancestor.getTag() instanceof PendingIntent)) { 9365 Log.e(LOG_TAG, "Attempting setOnClickFillInIntent or " 9366 + "setOnCheckedChangeFillInIntent without calling " 9367 + "setPendingIntentTemplate on parent."); 9368 return; 9369 } 9370 9371 pi = (PendingIntent) ancestor.getTag(); 9372 } else { 9373 Log.e(LOG_TAG, "Response has neither pendingIntent nor fillInIntent"); 9374 return; 9375 } 9376 9377 handler.onInteraction(v, pi, this); 9378 } 9379 9380 /** 9381 * Returns the closest ancestor of the view that is an AdapterView or null if none could be 9382 * found. 9383 */ 9384 @Nullable 9385 private static AdapterView<?> getAdapterViewAncestor(@Nullable View view) { 9386 if (view == null) return null; 9387 9388 View parent = (View) view.getParent(); 9389 // Break the for loop on the first encounter of: 9390 // 1) an AdapterView, 9391 // 2) an AppWidgetHostView that is not a child of an adapter view, or 9392 // 3) a null parent. 9393 // 2) and 3) are unexpected and catch the case where a child is not 9394 // correctly parented in an AdapterView. 9395 while (parent != null && !(parent instanceof AdapterView<?>) 9396 && !((parent instanceof AppWidgetHostView) 9397 && !(parent instanceof AppWidgetHostView.AdapterChildHostView))) { 9398 parent = (View) parent.getParent(); 9399 } 9400 9401 return parent instanceof AdapterView<?> ? (AdapterView<?>) parent : null; 9402 } 9403 9404 /** @hide */ 9405 public Pair<Intent, ActivityOptions> getLaunchOptions(View view) { 9406 Intent intent = mFillIntent == null ? new Intent() : new Intent(mFillIntent); 9407 intent.setSourceBounds(getSourceBounds(view)); 9408 9409 if (view instanceof CompoundButton 9410 && mInteractionType == INTERACTION_TYPE_CHECKED_CHANGE) { 9411 intent.putExtra(EXTRA_CHECKED, ((CompoundButton) view).isChecked()); 9412 } 9413 9414 ActivityOptions opts = null; 9415 9416 Context context = view.getContext(); 9417 if (context.getResources().getBoolean( 9418 com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { 9419 TypedArray windowStyle = context.getTheme().obtainStyledAttributes( 9420 com.android.internal.R.styleable.Window); 9421 int windowAnimations = windowStyle.getResourceId( 9422 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 9423 TypedArray windowAnimationStyle = context.obtainStyledAttributes( 9424 windowAnimations, com.android.internal.R.styleable.WindowAnimation); 9425 int enterAnimationId = windowAnimationStyle.getResourceId(com.android.internal.R 9426 .styleable.WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0); 9427 windowStyle.recycle(); 9428 windowAnimationStyle.recycle(); 9429 9430 if (enterAnimationId != 0) { 9431 opts = ActivityOptions.makeCustomAnimation(context, 9432 enterAnimationId, 0); 9433 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 9434 } 9435 } 9436 9437 if (opts == null && mViewIds != null && mElementNames != null) { 9438 View parent = (View) view.getParent(); 9439 while (parent != null && !(parent instanceof AppWidgetHostView)) { 9440 parent = (View) parent.getParent(); 9441 } 9442 if (parent instanceof AppWidgetHostView) { 9443 opts = ((AppWidgetHostView) parent).createSharedElementActivityOptions( 9444 mViewIds.toArray(), 9445 mElementNames.toArray(new String[mElementNames.size()]), intent); 9446 } 9447 } 9448 9449 if (opts == null) { 9450 opts = ActivityOptions.makeBasic(); 9451 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 9452 } 9453 if (view.getDisplay() != null) { 9454 opts.setLaunchDisplayId(view.getDisplay().getDisplayId()); 9455 } else { 9456 // TODO(b/218409359): Remove once bug is fixed. 9457 Log.w(LOG_TAG, "getLaunchOptions: view.getDisplay() is null!", 9458 new Exception()); 9459 } 9460 // If the user interacts with a visible element it is safe to assume they consent that 9461 // something is going to start. 9462 opts.setPendingIntentBackgroundActivityStartMode( 9463 ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS); 9464 return Pair.create(intent, opts); 9465 } 9466 } 9467 9468 /** @hide */ 9469 public static boolean startPendingIntent(View view, PendingIntent pendingIntent, 9470 Pair<Intent, ActivityOptions> options) { 9471 try { 9472 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 9473 Context context = view.getContext(); 9474 // The NEW_TASK flags are applied through the activity options and not as a part of 9475 // the call to startIntentSender() to ensure that they are consistently applied to 9476 // both mutable and immutable PendingIntents. 9477 context.startIntentSender( 9478 pendingIntent.getIntentSender(), options.first, 9479 0, 0, 0, options.second.toBundle()); 9480 } catch (IntentSender.SendIntentException e) { 9481 Log.e(LOG_TAG, "Cannot send pending intent: ", e); 9482 return false; 9483 } catch (Exception e) { 9484 Log.e(LOG_TAG, "Cannot send pending intent due to unknown exception: ", e); 9485 return false; 9486 } 9487 return true; 9488 } 9489 9490 private int getBitmapMemoryUsedByActions() { 9491 Set<Integer> bitmapIdSet = getBitmapIdsUsedByActions(new HashSet<>()); 9492 int result = 0; 9493 for (int bitmapId: bitmapIdSet) { 9494 Bitmap currentBitmap = mBitmapCache.getBitmapForId(bitmapId); 9495 if (currentBitmap == null) { 9496 continue; 9497 } 9498 result += currentBitmap.getAllocationByteCount(); 9499 } 9500 9501 return result; 9502 } 9503 9504 private Set<Integer> getBitmapIdsUsedByActions(@NonNull Set<Integer> intSet) { 9505 if (hasSizedRemoteViews()) { 9506 for (RemoteViews views: mSizedRemoteViews) { 9507 views.getBitmapIdsUsedByActions(intSet); 9508 } 9509 } else if (hasLandscapeAndPortraitLayouts()) { 9510 mLandscape.getBitmapIdsUsedByActions(intSet); 9511 mPortrait.getBitmapIdsUsedByActions(intSet); 9512 } else if (mActions != null) { 9513 for (Action action: mActions) { 9514 if (action instanceof ViewGroupActionAdd vgaa 9515 && vgaa.mNestedViews != null) { 9516 vgaa.mNestedViews.getBitmapIdsUsedByActions(intSet); 9517 } else if (action instanceof BitmapReflectionAction bitmapAction) { 9518 intSet.add(bitmapAction.mBitmapId); 9519 } 9520 } 9521 } 9522 9523 return intSet; 9524 } 9525 9526 /** Representation of a fixed list of items to be displayed in a RemoteViews collection. */ 9527 public static final class RemoteCollectionItems implements Parcelable { 9528 private final long[] mIds; 9529 private final RemoteViews[] mViews; 9530 private final boolean mHasStableIds; 9531 private final int mViewTypeCount; 9532 9533 private HierarchyRootData mHierarchyRootData; 9534 9535 RemoteCollectionItems( 9536 long[] ids, RemoteViews[] views, boolean hasStableIds, int viewTypeCount) { 9537 mIds = ids; 9538 mViews = views; 9539 mHasStableIds = hasStableIds; 9540 mViewTypeCount = viewTypeCount; 9541 if (ids.length != views.length) { 9542 throw new IllegalArgumentException( 9543 "RemoteCollectionItems has different number of ids and views"); 9544 } 9545 if (viewTypeCount < 1) { 9546 throw new IllegalArgumentException("View type count must be >= 1"); 9547 } 9548 int layoutIdCount = (int) Arrays.stream(views) 9549 .mapToInt(RemoteViews::getLayoutId) 9550 .distinct() 9551 .count(); 9552 if (layoutIdCount > viewTypeCount) { 9553 throw new IllegalArgumentException( 9554 "View type count is set to " + viewTypeCount + ", but the collection " 9555 + "contains " + layoutIdCount + " different layout ids"); 9556 } 9557 9558 // Until the collection items are attached to a parent, we configure the first item 9559 // to be the root of the others to share caches and save space during serialization. 9560 if (views.length > 0) { 9561 setHierarchyRootData(views[0].getHierarchyRootData()); 9562 views[0].mIsRoot = true; 9563 } 9564 } 9565 9566 RemoteCollectionItems(@NonNull Parcel in, @Nullable HierarchyRootData hierarchyRootData) { 9567 mHasStableIds = in.readBoolean(); 9568 mViewTypeCount = in.readInt(); 9569 int length = in.readInt(); 9570 mIds = new long[length]; 9571 in.readLongArray(mIds); 9572 9573 boolean attached = in.readBoolean(); 9574 mViews = new RemoteViews[length]; 9575 int firstChildIndex; 9576 if (attached) { 9577 if (hierarchyRootData == null) { 9578 throw new IllegalStateException("Cannot unparcel a RemoteCollectionItems that " 9579 + "was parceled as attached without providing data for a root " 9580 + "RemoteViews"); 9581 } 9582 mHierarchyRootData = hierarchyRootData; 9583 firstChildIndex = 0; 9584 } else { 9585 mViews[0] = new RemoteViews(in); 9586 mHierarchyRootData = mViews[0].getHierarchyRootData(); 9587 firstChildIndex = 1; 9588 } 9589 9590 for (int i = firstChildIndex; i < length; i++) { 9591 mViews[i] = new RemoteViews( 9592 in, 9593 mHierarchyRootData, 9594 /* info= */ null, 9595 /* depth= */ 0); 9596 } 9597 } 9598 9599 void setHierarchyRootData(@NonNull HierarchyRootData rootData) { 9600 mHierarchyRootData = rootData; 9601 for (RemoteViews view : mViews) { 9602 view.configureAsChild(rootData); 9603 } 9604 } 9605 9606 @Override 9607 public int describeContents() { 9608 return 0; 9609 } 9610 9611 @Override 9612 public void writeToParcel(@NonNull Parcel dest, int flags) { 9613 writeToParcel(dest, flags, /* attached= */ false); 9614 } 9615 9616 private void writeToParcel(@NonNull Parcel dest, int flags, boolean attached) { 9617 boolean prevAllowSquashing = dest.allowSquashing(); 9618 9619 dest.writeBoolean(mHasStableIds); 9620 dest.writeInt(mViewTypeCount); 9621 dest.writeInt(mIds.length); 9622 dest.writeLongArray(mIds); 9623 9624 if (attached && mHierarchyRootData == null) { 9625 throw new IllegalStateException("Cannot call writeToParcelAttached for a " 9626 + "RemoteCollectionItems without first calling setHierarchyRootData()"); 9627 } 9628 9629 // Write whether we parceled as attached or not. This allows cleaner validation and 9630 // proper error messaging when unparceling later. 9631 dest.writeBoolean(attached); 9632 boolean restoreRoot = false; 9633 if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) { 9634 // If we're writing unattached, temporarily set the first item as the root so that 9635 // the bitmap cache is written to the parcel. 9636 restoreRoot = true; 9637 mViews[0].mIsRoot = true; 9638 } 9639 9640 for (RemoteViews view : mViews) { 9641 view.writeToParcel(dest, flags); 9642 } 9643 9644 if (restoreRoot) mViews[0].mIsRoot = false; 9645 dest.restoreAllowSquashing(prevAllowSquashing); 9646 } 9647 9648 /** @hide */ 9649 public void writeToProto(Context context, ProtoOutputStream out) { 9650 writeToProto(context, out, /* attached= */ false); 9651 } 9652 9653 private void writeToProto(Context context, ProtoOutputStream out, boolean attached) { 9654 for (long id : mIds) { 9655 out.write(RemoteViewsProto.RemoteCollectionItems.IDS, id); 9656 } 9657 9658 boolean restoreRoot = false; 9659 out.write(RemoteViewsProto.RemoteCollectionItems.ATTACHED, attached); 9660 if (!attached && mViews.length > 0 && !mViews[0].mIsRoot) { 9661 restoreRoot = true; 9662 mViews[0].mIsRoot = true; 9663 } 9664 for (RemoteViews view : mViews) { 9665 final long viewsToken = out.start(RemoteViewsProto.RemoteCollectionItems.VIEWS); 9666 view.writePreviewToProto(context, out); 9667 out.end(viewsToken); 9668 } 9669 if (restoreRoot) mViews[0].mIsRoot = false; 9670 out.write(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, mHasStableIds); 9671 out.write(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, mViewTypeCount); 9672 } 9673 9674 /** 9675 * Overload used for testing unattached RemoteCollectionItems serialization. 9676 * 9677 * @hide 9678 */ 9679 public static RemoteCollectionItems createFromProto(Context context, ProtoInputStream in) 9680 throws Exception { 9681 return createFromProto(in).create(context, context.getResources(), /* rootData= */ 9682 null, 0); 9683 } 9684 9685 /** @hide */ 9686 public static PendingResources<RemoteCollectionItems> createFromProto(ProtoInputStream in) 9687 throws Exception { 9688 final LongSparseArray<Object> values = new LongSparseArray<>(); 9689 9690 values.put(RemoteViewsProto.RemoteCollectionItems.IDS, new ArrayList<Long>()); 9691 values.put(RemoteViewsProto.RemoteCollectionItems.VIEWS, 9692 new ArrayList<PendingResources<RemoteViews>>()); 9693 9694 while (in.nextField() != NO_MORE_FIELDS) { 9695 switch (in.getFieldNumber()) { 9696 case (int) RemoteViewsProto.RemoteCollectionItems.IDS: 9697 ((ArrayList<Long>) values.get( 9698 RemoteViewsProto.RemoteCollectionItems.IDS)).add( 9699 in.readLong(RemoteViewsProto.RemoteCollectionItems.IDS)); 9700 break; 9701 case (int) RemoteViewsProto.RemoteCollectionItems.VIEWS: 9702 final long viewsToken = in.start( 9703 RemoteViewsProto.RemoteCollectionItems.VIEWS); 9704 ((ArrayList<PendingResources<RemoteViews>>) values.get( 9705 RemoteViewsProto.RemoteCollectionItems.VIEWS)).add( 9706 RemoteViews.createFromProto(in)); 9707 in.end(viewsToken); 9708 break; 9709 case (int) RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS: 9710 values.put(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, 9711 in.readBoolean( 9712 RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS)); 9713 break; 9714 case (int) RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT: 9715 values.put(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, 9716 in.readInt(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT)); 9717 break; 9718 case (int) RemoteViewsProto.RemoteCollectionItems.ATTACHED: 9719 values.put(RemoteViewsProto.RemoteCollectionItems.ATTACHED, 9720 in.readBoolean(RemoteViewsProto.RemoteCollectionItems.ATTACHED)); 9721 break; 9722 default: 9723 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 9724 + ProtoUtils.currentFieldToString(in)); 9725 } 9726 } 9727 9728 checkContainsKeys(values, 9729 new long[]{RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT}); 9730 9731 return (context, resources, rootData, depth) -> { 9732 List<Long> idList = (List<Long>) values.get( 9733 RemoteViewsProto.RemoteCollectionItems.IDS); 9734 long[] ids = new long[idList.size()]; 9735 for (int i = 0; i < idList.size(); i++) { 9736 ids[i] = idList.get(i); 9737 } 9738 boolean attached = (boolean) values.get( 9739 RemoteViewsProto.RemoteCollectionItems.ATTACHED, false); 9740 List<PendingResources<RemoteViews>> pendingViews = 9741 (List<PendingResources<RemoteViews>>) values.get( 9742 RemoteViewsProto.RemoteCollectionItems.VIEWS); 9743 RemoteViews[] views = new RemoteViews[pendingViews.size()]; 9744 9745 if (attached && rootData == null) { 9746 throw new IllegalStateException("Cannot create a RemoteCollectionItems from " 9747 + "proto that was attached without providing HierarchyRootData"); 9748 } 9749 9750 int firstChildIndex = 0; 9751 if (!attached && pendingViews.size() > 0) { 9752 // If written as unattached, get HierarchyRootData from first view 9753 views[0] = pendingViews.get(0).create(context, resources, /* rootData= */ null, 9754 /* depth= */ 0); 9755 rootData = views[0].getHierarchyRootData(); 9756 firstChildIndex = 1; 9757 } 9758 for (int i = firstChildIndex; i < views.length; i++) { 9759 // Depth is reset to 0 for RemoteCollectionItems item views, see Parcel 9760 // constructor. 9761 views[i] = pendingViews.get(i).create(context, resources, rootData, 9762 /* depth= */ 0); 9763 } 9764 return new RemoteCollectionItems(ids, views, 9765 (boolean) values.get(RemoteViewsProto.RemoteCollectionItems.HAS_STABLE_IDS, 9766 false), 9767 (int) values.get(RemoteViewsProto.RemoteCollectionItems.VIEW_TYPE_COUNT, 9768 0)); 9769 }; 9770 } 9771 9772 /** 9773 * Returns the id for {@code position}. See {@link #hasStableIds()} for whether this id 9774 * should be considered meaningful across collection updates. 9775 * 9776 * @return Id for the position. 9777 */ 9778 public long getItemId(int position) { 9779 return mIds[position]; 9780 } 9781 9782 /** 9783 * Returns the {@link RemoteViews} to display at {@code position}. 9784 * 9785 * @return RemoteViews for the position. 9786 */ 9787 @NonNull 9788 public RemoteViews getItemView(int position) { 9789 return mViews[position]; 9790 } 9791 9792 /** 9793 * Returns the number of elements in the collection. 9794 * 9795 * @return Count of items. 9796 */ 9797 public int getItemCount() { 9798 return mIds.length; 9799 } 9800 9801 /** 9802 * Returns the view type count for the collection when used in an adapter 9803 * 9804 * @return Count of view types for the collection when used in an adapter. 9805 * @see android.widget.Adapter#getViewTypeCount() 9806 */ 9807 public int getViewTypeCount() { 9808 return mViewTypeCount; 9809 } 9810 9811 /** 9812 * Indicates whether the item ids are stable across changes to the underlying data. 9813 * 9814 * @return True if the same id always refers to the same object. 9815 * @see android.widget.Adapter#hasStableIds() 9816 */ 9817 public boolean hasStableIds() { 9818 return mHasStableIds; 9819 } 9820 9821 @NonNull 9822 public static final Creator<RemoteCollectionItems> CREATOR = 9823 new Creator<RemoteCollectionItems>() { 9824 @NonNull 9825 @Override 9826 public RemoteCollectionItems createFromParcel(@NonNull Parcel source) { 9827 return new RemoteCollectionItems(source, /* hierarchyRoot= */ null); 9828 } 9829 9830 @NonNull 9831 @Override 9832 public RemoteCollectionItems[] newArray(int size) { 9833 return new RemoteCollectionItems[size]; 9834 } 9835 }; 9836 9837 /** Builder class for {@link RemoteCollectionItems} objects.*/ 9838 public static final class Builder { 9839 private final LongArray mIds = new LongArray(); 9840 private final List<RemoteViews> mViews = new ArrayList<>(); 9841 private boolean mHasStableIds; 9842 private int mViewTypeCount; 9843 9844 /** 9845 * Adds a {@link RemoteViews} to the collection. 9846 * 9847 * @param id Id to associate with the row. Use {@link #setHasStableIds(boolean)} to 9848 * indicate that ids are stable across changes to the collection. 9849 * @param view RemoteViews to display for the row. 9850 */ 9851 @NonNull 9852 // Covered by getItemId, getItemView, getItemCount. 9853 @SuppressLint("MissingGetterMatchingBuilder") 9854 public Builder addItem(long id, @NonNull RemoteViews view) { 9855 if (view == null) throw new NullPointerException(); 9856 if (view.hasMultipleLayouts()) { 9857 throw new IllegalArgumentException( 9858 "RemoteViews used in a RemoteCollectionItems cannot specify separate " 9859 + "layouts for orientations or sizes."); 9860 } 9861 mIds.add(id); 9862 mViews.add(view); 9863 return this; 9864 } 9865 9866 /** 9867 * Sets whether the item ids are stable across changes to the underlying data. 9868 * 9869 * @see android.widget.Adapter#hasStableIds() 9870 */ 9871 @NonNull 9872 public Builder setHasStableIds(boolean hasStableIds) { 9873 mHasStableIds = hasStableIds; 9874 return this; 9875 } 9876 9877 /** 9878 * Sets the view type count for the collection when used in an adapter. This can be set 9879 * to the maximum number of different layout ids that will be used by RemoteViews in 9880 * this collection. 9881 * 9882 * If this value is not set, then a value will be inferred from the provided items. As 9883 * a result, the adapter may need to be recreated when the list is updated with 9884 * previously unseen RemoteViews layouts for new items. 9885 * 9886 * @see android.widget.Adapter#getViewTypeCount() 9887 */ 9888 @NonNull 9889 public Builder setViewTypeCount(int viewTypeCount) { 9890 mViewTypeCount = viewTypeCount; 9891 return this; 9892 } 9893 9894 /** Creates the {@link RemoteCollectionItems} defined by this builder. */ 9895 @NonNull 9896 public RemoteCollectionItems build() { 9897 if (mViewTypeCount < 1) { 9898 // If a view type count wasn't specified, set it to be the number of distinct 9899 // layout ids used in the items. 9900 mViewTypeCount = (int) mViews.stream() 9901 .mapToInt(RemoteViews::getLayoutId) 9902 .distinct() 9903 .count(); 9904 } 9905 return new RemoteCollectionItems( 9906 mIds.toArray(), 9907 mViews.toArray(new RemoteViews[0]), 9908 mHasStableIds, 9909 Math.max(mViewTypeCount, 1)); 9910 } 9911 } 9912 9913 /** 9914 * See {@link RemoteViews#visitUris(Consumer)}. 9915 */ 9916 private void visitUris(@NonNull Consumer<Uri> visitor) { 9917 for (RemoteViews view : mViews) { 9918 view.visitUris(visitor); 9919 } 9920 } 9921 9922 /** 9923 * See {@link RemoteViews#visitIcons(Consumer)}. 9924 */ 9925 private void visitIcons(@NonNull Consumer<Icon> visitor) { 9926 for (RemoteViews view : mViews) { 9927 view.visitIcons(visitor); 9928 } 9929 } 9930 } 9931 9932 /** 9933 * A data parcel that carries the instructions to draw the RemoteViews, as an alternative to 9934 * XML layout. 9935 */ 9936 @FlaggedApi(FLAG_DRAW_DATA_PARCEL) 9937 public static final class DrawInstructions { 9938 9939 private static final long VERSION = 1L; 9940 9941 @NonNull 9942 final List<byte[]> mInstructions; 9943 9944 private DrawInstructions() { 9945 throw new UnsupportedOperationException( 9946 "DrawInstructions cannot be instantiate without instructions"); 9947 } 9948 9949 private DrawInstructions(@NonNull List<byte[]> instructions) { 9950 // Create and retain an immutable copy of given instructions. 9951 mInstructions = new ArrayList<>(instructions.size()); 9952 for (byte[] instruction : instructions) { 9953 final int len = instruction.length; 9954 final byte[] target = new byte[len]; 9955 System.arraycopy(instruction, 0, target, 0, len); 9956 mInstructions.add(target); 9957 } 9958 } 9959 9960 @Nullable 9961 private static DrawInstructions readFromParcel(@NonNull final Parcel in) { 9962 int size = in.readInt(); 9963 if (size == -1) { 9964 return null; 9965 } 9966 byte[] instruction; 9967 final List<byte[]> instructions = new ArrayList<>(size); 9968 for (int i = 0; i < size; i++) { 9969 instruction = in.readBlob(); 9970 instructions.add(instruction); 9971 } 9972 return new DrawInstructions(instructions); 9973 } 9974 9975 private static void writeToParcel(@Nullable final DrawInstructions drawInstructions, 9976 @NonNull final Parcel dest, final int flags) { 9977 if (drawInstructions == null) { 9978 dest.writeInt(-1); 9979 return; 9980 } 9981 final List<byte[]> instructions = drawInstructions.mInstructions; 9982 dest.writeInt(instructions.size()); 9983 for (byte[] instruction : instructions) { 9984 dest.writeBlob(instruction); 9985 } 9986 } 9987 9988 /** 9989 * Version number of {@link DrawInstructions} currently supported. 9990 */ 9991 @FlaggedApi(FLAG_DRAW_DATA_PARCEL) 9992 public static long getSupportedVersion() { 9993 return (long) CoreDocument.getDocumentApiLevel(); 9994 } 9995 9996 /** 9997 * Builder class for {@link DrawInstructions} objects. 9998 */ 9999 @FlaggedApi(FLAG_DRAW_DATA_PARCEL) 10000 public static final class Builder { 10001 10002 private final List<byte[]> mInstructions; 10003 10004 /** 10005 * Constructor. 10006 * 10007 * @param instructions Information to draw the RemoteViews. 10008 */ 10009 @FlaggedApi(FLAG_DRAW_DATA_PARCEL) 10010 public Builder(@NonNull final List<byte[]> instructions) { 10011 mInstructions = new ArrayList<>(instructions); 10012 } 10013 10014 /** 10015 * Creates a {@link DrawInstructions} instance. 10016 */ 10017 @NonNull 10018 @FlaggedApi(FLAG_DRAW_DATA_PARCEL) 10019 public DrawInstructions build() { 10020 return new DrawInstructions(mInstructions); 10021 } 10022 } 10023 } 10024 10025 /** 10026 * Get the ID of the top-level view of the XML layout, if set using 10027 * {@link RemoteViews#RemoteViews(String, int, int)}. 10028 */ 10029 @IdRes 10030 public int getViewId() { 10031 return mViewId; 10032 } 10033 10034 /** 10035 * Set the provider instance ID. 10036 * 10037 * This should only be used by {@link com.android.server.appwidget.AppWidgetService}. 10038 * @hide 10039 */ 10040 public void setProviderInstanceId(long id) { 10041 mProviderInstanceId = id; 10042 } 10043 10044 /** 10045 * Get the provider instance id. 10046 * 10047 * This should uniquely identifies {@link RemoteViews} coming from a given App Widget 10048 * Provider. This changes each time the App Widget provider update the {@link RemoteViews} of 10049 * its widget. Returns -1 if the {@link RemoteViews} doesn't come from an App Widget provider. 10050 * @hide 10051 */ 10052 public long getProviderInstanceId() { 10053 return mProviderInstanceId; 10054 } 10055 10056 /** 10057 * Identify the child of this {@link RemoteViews}, or 0 if this is not a child. 10058 * 10059 * The returned value is always a small integer, currently between 0 and 17. 10060 */ 10061 private int getChildId(@NonNull RemoteViews child) { 10062 if (child == this) { 10063 return 0; 10064 } 10065 if (hasSizedRemoteViews()) { 10066 for (int i = 0; i < mSizedRemoteViews.size(); i++) { 10067 if (mSizedRemoteViews.get(i) == child) { 10068 return i + 1; 10069 } 10070 } 10071 } 10072 if (hasLandscapeAndPortraitLayouts()) { 10073 if (mLandscape == child) { 10074 return 1; 10075 } else if (mPortrait == child) { 10076 return 2; 10077 } 10078 } 10079 // This is not a child of this RemoteViews. 10080 return 0; 10081 } 10082 10083 /** 10084 * Identify uniquely this RemoteViews, or returns -1 if not possible. 10085 * 10086 * @param parent If the {@link RemoteViews} is not a root {@link RemoteViews}, this should be 10087 * the parent that contains it. 10088 * 10089 * @hide 10090 */ 10091 public long computeUniqueId(@Nullable RemoteViews parent) { 10092 if (mIsRoot) { 10093 long viewId = getProviderInstanceId(); 10094 if (viewId != -1) { 10095 viewId <<= 8; 10096 } 10097 return viewId; 10098 } 10099 if (parent == null) { 10100 return -1; 10101 } 10102 long viewId = parent.getProviderInstanceId(); 10103 if (viewId == -1) { 10104 return -1; 10105 } 10106 int childId = parent.getChildId(this); 10107 if (childId == -1) { 10108 return -1; 10109 } 10110 viewId <<= 8; 10111 viewId |= childId; 10112 return viewId; 10113 } 10114 10115 @Nullable 10116 private static Pair<String, Integer> getPackageUserKey(@Nullable ApplicationInfo info) { 10117 if (info == null || info.packageName == null) return null; 10118 return Pair.create(info.packageName, info.uid); 10119 } 10120 10121 private HierarchyRootData getHierarchyRootData() { 10122 return new HierarchyRootData(mBitmapCache, mCollectionCache, 10123 mApplicationInfoCache, mClassCookies); 10124 } 10125 10126 private static final class HierarchyRootData { 10127 final BitmapCache mBitmapCache; 10128 final RemoteCollectionCache mRemoteCollectionCache; 10129 final ApplicationInfoCache mApplicationInfoCache; 10130 final Map<Class, Object> mClassCookies; 10131 10132 HierarchyRootData( 10133 BitmapCache bitmapCache, 10134 RemoteCollectionCache remoteCollectionCache, 10135 ApplicationInfoCache applicationInfoCache, 10136 Map<Class, Object> classCookies) { 10137 mBitmapCache = bitmapCache; 10138 mRemoteCollectionCache = remoteCollectionCache; 10139 mApplicationInfoCache = applicationInfoCache; 10140 mClassCookies = classCookies; 10141 } 10142 } 10143 10144 /** 10145 * Write this RemoteViews to proto. 10146 * @hide 10147 */ 10148 @FlaggedApi(FLAG_REMOTE_VIEWS_PROTO) 10149 public void writePreviewToProto(@NonNull Context context, @NonNull ProtoOutputStream out) { 10150 if (mApplication != null) { 10151 // mApplication may be null if this was created with DrawInstructions constructor. 10152 out.write(RemoteViewsProto.PACKAGE_NAME, mApplication.packageName); 10153 out.write(RemoteViewsProto.UID, mApplication.uid); 10154 } 10155 Resources appResources = getContextForResourcesEnsuringCorrectCachedApkPaths( 10156 context).getResources(); 10157 if (mLayoutId != 0) { 10158 out.write(RemoteViewsProto.LAYOUT_ID, appResources.getResourceName(mLayoutId)); 10159 } 10160 if (mLightBackgroundLayoutId != 0) { 10161 out.write(RemoteViewsProto.LIGHT_BACKGROUND_LAYOUT_ID, 10162 appResources.getResourceName(mLightBackgroundLayoutId)); 10163 } 10164 if (mViewId != 0 && mViewId != -1) { 10165 out.write(RemoteViewsProto.VIEW_ID, appResources.getResourceName(mViewId)); 10166 } 10167 if (mIsRoot) { 10168 mBitmapCache.writeBitmapsToProto(out); 10169 mCollectionCache.writeToProto(context, out); 10170 } 10171 out.write(RemoteViewsProto.IS_ROOT, mIsRoot); 10172 out.write(RemoteViewsProto.APPLY_FLAGS, mApplyFlags); 10173 out.write(RemoteViewsProto.HAS_DRAW_INSTRUCTIONS, mHasDrawInstructions); 10174 if (mProviderInstanceId != -1) { 10175 out.write(RemoteViewsProto.PROVIDER_INSTANCE_ID, mProviderInstanceId); 10176 } 10177 10178 if (!hasMultipleLayouts()) { 10179 out.write(RemoteViewsProto.MODE, MODE_NORMAL); 10180 if (mIdealSize != null) { 10181 final long token = out.start(RemoteViewsProto.IDEAL_SIZE); 10182 out.write(SizeFProto.WIDTH, mIdealSize.getWidth()); 10183 out.write(SizeFProto.HEIGHT, mIdealSize.getHeight()); 10184 out.end(token); 10185 } 10186 10187 if (mActions != null) { 10188 for (Action action : mActions) { 10189 if (action.canWriteToProto()) { 10190 final long token = out.start(RemoteViewsProto.ACTIONS); 10191 action.writeToProto(out, context, appResources); 10192 out.end(token); 10193 } 10194 } 10195 } 10196 } else if (hasSizedRemoteViews()) { 10197 out.write(RemoteViewsProto.MODE, MODE_HAS_SIZED_REMOTEVIEWS); 10198 for (RemoteViews view : mSizedRemoteViews) { 10199 final long sizedViewToken = out.start(RemoteViewsProto.SIZED_REMOTEVIEWS); 10200 view.writePreviewToProto(context, out); 10201 out.end(sizedViewToken); 10202 } 10203 } else { 10204 out.write(RemoteViewsProto.MODE, MODE_HAS_LANDSCAPE_AND_PORTRAIT); 10205 final long landscapeViewToken = out.start(RemoteViewsProto.LANDSCAPE_REMOTEVIEWS); 10206 mLandscape.writePreviewToProto(context, out); 10207 out.end(landscapeViewToken); 10208 final long portraitViewToken = out.start(RemoteViewsProto.PORTRAIT_REMOTEVIEWS); 10209 mPortrait.writePreviewToProto(context, out); 10210 out.end(portraitViewToken); 10211 } 10212 } 10213 10214 /** 10215 * Create a RemoteViews from proto input. 10216 * @hide 10217 */ 10218 @FlaggedApi(FLAG_REMOTE_VIEWS_PROTO) 10219 public static RemoteViews createPreviewFromProto(Context context, ProtoInputStream in) 10220 throws Exception { 10221 return createFromProto(in).create(context, context.getResources(), /* rootData= */ null, 10222 /* depth= */ 0); 10223 } 10224 10225 private static PendingResources<RemoteViews> createFromProto(ProtoInputStream in) 10226 throws Exception { 10227 // Grouping these variables into an anonymous object allows us to access them through `ref` 10228 // (which is final) later in the lambda. 10229 final var ref = new Object() { 10230 final RemoteViews mRv = new RemoteViews(); 10231 int mMode = 0; 10232 int mApplyFlags = 0; 10233 long mProviderInstanceId = -1; 10234 String mPackageName = null; 10235 Integer mUid = null; 10236 SizeF mIdealSize = null; 10237 String mLayoutResName = null; 10238 String mLightBackgroundResName = null; 10239 String mViewResName = null; 10240 final List<PendingResources<Action>> mActions = new ArrayList<>(); 10241 final List<PendingResources<RemoteViews>> mSizedRemoteViews = new ArrayList<>(); 10242 PendingResources<RemoteViews> mLandscapeViews = null; 10243 PendingResources<RemoteViews> mPortraitViews = null; 10244 PendingResources<RemoteCollectionCache> mPopulateRemoteCollectionCache = null; 10245 boolean mIsRoot = false; 10246 boolean mHasDrawInstructions = false; 10247 }; 10248 10249 try { 10250 while (in.nextField() != NO_MORE_FIELDS) { 10251 switch (in.getFieldNumber()) { 10252 case (int) RemoteViewsProto.MODE: 10253 ref.mMode = in.readInt(RemoteViewsProto.MODE); 10254 break; 10255 case (int) RemoteViewsProto.PACKAGE_NAME: 10256 ref.mPackageName = in.readString(RemoteViewsProto.PACKAGE_NAME); 10257 break; 10258 case (int) RemoteViewsProto.UID: 10259 ref.mUid = in.readInt(RemoteViewsProto.UID); 10260 break; 10261 case (int) RemoteViewsProto.IDEAL_SIZE: 10262 final long idealSizeToken = in.start(RemoteViewsProto.IDEAL_SIZE); 10263 ref.mIdealSize = createSizeFFromProto(in); 10264 in.end(idealSizeToken); 10265 break; 10266 case (int) RemoteViewsProto.LAYOUT_ID: 10267 ref.mLayoutResName = in.readString(RemoteViewsProto.LAYOUT_ID); 10268 break; 10269 case (int) RemoteViewsProto.LIGHT_BACKGROUND_LAYOUT_ID: 10270 ref.mLightBackgroundResName = in.readString( 10271 RemoteViewsProto.LIGHT_BACKGROUND_LAYOUT_ID); 10272 break; 10273 case (int) RemoteViewsProto.VIEW_ID: 10274 ref.mViewResName = in.readString(RemoteViewsProto.VIEW_ID); 10275 break; 10276 case (int) RemoteViewsProto.APPLY_FLAGS: 10277 ref.mApplyFlags = in.readInt(RemoteViewsProto.APPLY_FLAGS); 10278 break; 10279 case (int) RemoteViewsProto.PROVIDER_INSTANCE_ID: 10280 ref.mProviderInstanceId = in.readInt(RemoteViewsProto.PROVIDER_INSTANCE_ID); 10281 break; 10282 case (int) RemoteViewsProto.ACTIONS: 10283 final long actionsToken = in.start(RemoteViewsProto.ACTIONS); 10284 final PendingResources<Action> action = createActionFromProto(ref.mRv, in); 10285 if (action != null) { 10286 ref.mActions.add(action); 10287 } 10288 in.end(actionsToken); 10289 break; 10290 case (int) RemoteViewsProto.SIZED_REMOTEVIEWS: 10291 final long sizedToken = in.start(RemoteViewsProto.SIZED_REMOTEVIEWS); 10292 ref.mSizedRemoteViews.add(createFromProto(in)); 10293 in.end(sizedToken); 10294 break; 10295 case (int) RemoteViewsProto.LANDSCAPE_REMOTEVIEWS: 10296 final long landscapeToken = in.start( 10297 RemoteViewsProto.LANDSCAPE_REMOTEVIEWS); 10298 ref.mLandscapeViews = createFromProto(in); 10299 in.end(landscapeToken); 10300 break; 10301 case (int) RemoteViewsProto.PORTRAIT_REMOTEVIEWS: 10302 final long portraitToken = in.start(RemoteViewsProto.PORTRAIT_REMOTEVIEWS); 10303 ref.mPortraitViews = createFromProto(in); 10304 in.end(portraitToken); 10305 break; 10306 case (int) RemoteViewsProto.BITMAP_CACHE: 10307 byte[] src = in.readBytes(RemoteViewsProto.BITMAP_CACHE); 10308 Bitmap bitmap = BitmapFactory.decodeByteArray(src, 0, src.length); 10309 ref.mRv.mBitmapCache.getBitmapId(bitmap); 10310 break; 10311 case (int) RemoteViewsProto.REMOTE_COLLECTION_CACHE: 10312 final long collectionToken = in.start( 10313 RemoteViewsProto.REMOTE_COLLECTION_CACHE); 10314 ref.mPopulateRemoteCollectionCache = 10315 ref.mRv.populateRemoteCollectionCacheFromProto(in); 10316 in.end(collectionToken); 10317 break; 10318 case (int) RemoteViewsProto.IS_ROOT: 10319 ref.mIsRoot = in.readBoolean(RemoteViewsProto.IS_ROOT); 10320 break; 10321 case (int) RemoteViewsProto.HAS_DRAW_INSTRUCTIONS: 10322 ref.mHasDrawInstructions = in.readBoolean( 10323 RemoteViewsProto.HAS_DRAW_INSTRUCTIONS); 10324 break; 10325 default: 10326 Log.w(LOG_TAG, "Unhandled field while reading RemoteViews proto!\n" 10327 + ProtoUtils.currentFieldToString(in)); 10328 } 10329 } 10330 } catch (IOException e) { 10331 throw new RuntimeException(e); 10332 } 10333 10334 return (context, resources, rootData, depth) -> { 10335 if (depth > MAX_NESTED_VIEWS && (UserHandle.getAppId(Binder.getCallingUid()) 10336 != Process.SYSTEM_UID)) { 10337 throw new IllegalArgumentException("Too many nested views."); 10338 } 10339 depth++; 10340 10341 RemoteViews rv = ref.mRv; 10342 rv.mApplyFlags = ref.mApplyFlags; 10343 rv.mIsRoot = ref.mIsRoot; 10344 rv.mHasDrawInstructions = ref.mHasDrawInstructions; 10345 10346 // The root view will read its HierarchyRootData (bitmap cache, collection cache) from 10347 // proto; all nested views will instead get it through the rootData parameter. 10348 if (rootData == null) { 10349 if (!rv.mIsRoot || depth != 1) { 10350 throw new IllegalStateException( 10351 "A nested view did not receive HierarchyRootData"); 10352 } 10353 rootData = rv.getHierarchyRootData(); 10354 } else { 10355 rv.configureAsChild(rootData); 10356 } 10357 10358 Context appContext = null; 10359 Resources appResources = null; 10360 if (!ref.mHasDrawInstructions) { 10361 checkProtoResultNotNull(ref.mPackageName, "No application info"); 10362 checkProtoResultNotNull(ref.mUid, "No uid"); 10363 rv.mApplication = context.getPackageManager().getApplicationInfoAsUser( 10364 ref.mPackageName, /* flags= */ 0, UserHandle.getUserId(ref.mUid)); 10365 appContext = rv.getContextForResourcesEnsuringCorrectCachedApkPaths(context); 10366 appResources = appContext.getResources(); 10367 10368 checkProtoResultNotNull(ref.mLayoutResName, "No layout id"); 10369 rv.mLayoutId = appResources.getIdentifier(ref.mLayoutResName, /* defType= */ null, 10370 /* defPackage= */ null); 10371 checkValidResource(rv.mLayoutId, "Invalid layout id", ref.mLayoutResName); 10372 10373 if (ref.mViewResName != null) { 10374 rv.mViewId = appResources.getIdentifier(ref.mViewResName, /* defType= */ null, 10375 /* defPackage= */ null); 10376 checkValidResource(rv.mViewId, "Invalid view id", ref.mViewResName); 10377 } 10378 10379 if (ref.mLightBackgroundResName != null) { 10380 int lightBackgroundLayoutId = appResources.getIdentifier( 10381 ref.mLightBackgroundResName, 10382 /* defType= */ null, /* defPackage= */ null); 10383 checkValidResource(lightBackgroundLayoutId, 10384 "Invalid light background layout id", ref.mLightBackgroundResName); 10385 rv.setLightBackgroundLayoutId(lightBackgroundLayoutId); 10386 } 10387 } 10388 if (ref.mPopulateRemoteCollectionCache != null) { 10389 ref.mPopulateRemoteCollectionCache.create(appContext, appResources, rootData, 10390 depth); 10391 } 10392 if (ref.mProviderInstanceId != -1) { 10393 rv.mProviderInstanceId = ref.mProviderInstanceId; 10394 } 10395 if (ref.mMode == MODE_NORMAL) { 10396 rv.setIdealSize(ref.mIdealSize); 10397 boolean hasDrawInstructionAction = false; 10398 for (PendingResources<Action> pendingAction : ref.mActions) { 10399 Action action = pendingAction.create(appContext, appResources, rootData, depth); 10400 if (action != null) { 10401 if (action instanceof SetDrawInstructionAction) { 10402 hasDrawInstructionAction = true; 10403 } 10404 rv.addAction(action); 10405 } 10406 } 10407 if (rv.mHasDrawInstructions && !hasDrawInstructionAction) { 10408 throw new InvalidProtoException( 10409 "RemoteViews proto is missing DrawInstructions"); 10410 } 10411 return rv; 10412 } else if (ref.mMode == MODE_HAS_SIZED_REMOTEVIEWS) { 10413 List<RemoteViews> sizedViews = new ArrayList<>(); 10414 for (RemoteViews.PendingResources<RemoteViews> pendingViews : 10415 ref.mSizedRemoteViews) { 10416 RemoteViews views = pendingViews.create(appContext, appResources, rootData, 10417 depth); 10418 sizedViews.add(views); 10419 } 10420 rv.initializeSizedRemoteViews(sizedViews.iterator()); 10421 return rv; 10422 } else if (ref.mMode == MODE_HAS_LANDSCAPE_AND_PORTRAIT) { 10423 checkProtoResultNotNull(ref.mLandscapeViews, "Missing landscape views"); 10424 checkProtoResultNotNull(ref.mPortraitViews, "Missing portrait views"); 10425 RemoteViews parentRv = new RemoteViews( 10426 ref.mLandscapeViews.create(appContext, appResources, rootData, depth), 10427 ref.mPortraitViews.create(appContext, appResources, rootData, depth)); 10428 parentRv.initializeFrom(/* src= */ rv, /* hierarchyRoot= */ rv); 10429 return parentRv; 10430 } else { 10431 throw new InvalidProtoException(ref.mMode + " is not a valid mode."); 10432 } 10433 }; 10434 } 10435 10436 private static class InvalidProtoException extends Exception { 10437 InvalidProtoException(String message) { 10438 super(message); 10439 } 10440 } 10441 10442 interface PendingResources<T> { 10443 T create(Context context, Resources appResources, HierarchyRootData rootData, int depth) 10444 throws Exception; 10445 } 10446 10447 @Nullable 10448 private static PendingResources<Action> createActionFromProto(RemoteViews rv, 10449 ProtoInputStream in) throws Exception { 10450 int actionFieldId = in.nextField(); 10451 if (actionFieldId == NO_MORE_FIELDS) { 10452 // action was omitted 10453 return null; 10454 } 10455 switch (actionFieldId) { 10456 case (int) RemoteViewsProto.Action.ATTRIBUTE_REFLECTION_ACTION: 10457 return AttributeReflectionAction.createFromProto(in); 10458 case (int) RemoteViewsProto.Action.BITMAP_REFLECTION_ACTION: 10459 return rv.createFromBitmapReflectionActionFromProto(in); 10460 case (int) RemoteViewsProto.Action.COMPLEX_UNIT_DIMENSION_REFLECTION_ACTION: 10461 return ComplexUnitDimensionReflectionAction.createFromProto(in); 10462 case (int) RemoteViewsProto.Action.LAYOUT_PARAM_ACTION: 10463 return LayoutParamAction.createFromProto(in); 10464 case (int) RemoteViewsProto.Action.NIGHT_MODE_REFLECTION_ACTION: 10465 return NightModeReflectionAction.createFromProto(in); 10466 case (int) RemoteViewsProto.Action.REFLECTION_ACTION: 10467 return ReflectionAction.createFromProto(in); 10468 case (int) RemoteViewsProto.Action.REMOVE_FROM_PARENT_ACTION: 10469 return RemoveFromParentAction.createFromProto(in); 10470 case (int) RemoteViewsProto.Action.RESOURCE_REFLECTION_ACTION: 10471 return ResourceReflectionAction.createFromProto(in); 10472 case (int) RemoteViewsProto.Action.SET_COMPOUND_BUTTON_CHECKED_ACTION: 10473 return SetCompoundButtonCheckedAction.createFromProto(in); 10474 case (int) RemoteViewsProto.Action.SET_DRAWABLE_TINT_ACTION: 10475 return SetDrawableTint.createFromProto(in); 10476 case (int) RemoteViewsProto.Action.SET_EMPTY_VIEW_ACTION: 10477 return SetEmptyView.createFromProto(in); 10478 case (int) RemoteViewsProto.Action.SET_INT_TAG_ACTION: 10479 return SetIntTagAction.createFromProto(in); 10480 case (int) RemoteViewsProto.Action.SET_RADIO_GROUP_CHECKED_ACTION: 10481 return SetRadioGroupCheckedAction.createFromProto(in); 10482 case (int) RemoteViewsProto.Action.SET_REMOTE_COLLECTION_ITEM_LIST_ADAPTER_ACTION: 10483 return rv.createSetRemoteCollectionItemListAdapterActionFromProto(in); 10484 case (int) RemoteViewsProto.Action.SET_RIPPLE_DRAWABLE_COLOR_ACTION: 10485 return SetRippleDrawableColor.createFromProto(in); 10486 case (int) RemoteViewsProto.Action.SET_VIEW_OUTLINE_PREFERRED_RADIUS_ACTION: 10487 return SetViewOutlinePreferredRadiusAction.createFromProto(in); 10488 case (int) RemoteViewsProto.Action.TEXT_VIEW_DRAWABLE_ACTION: 10489 return TextViewDrawableAction.createFromProto(in); 10490 case (int) RemoteViewsProto.Action.TEXT_VIEW_SIZE_ACTION: 10491 return TextViewSizeAction.createFromProto(in); 10492 case (int) RemoteViewsProto.Action.VIEW_GROUP_ADD_ACTION: 10493 return rv.createViewGroupActionAddFromProto(in); 10494 case (int) RemoteViewsProto.Action.VIEW_GROUP_REMOVE_ACTION: 10495 return ViewGroupActionRemove.createFromProto(in); 10496 case (int) RemoteViewsProto.Action.VIEW_PADDING_ACTION: 10497 return ViewPaddingAction.createFromProto(in); 10498 case (int) RemoteViewsProto.Action.SET_DRAW_INSTRUCTION_ACTION: 10499 if (!drawDataParcel()) { 10500 return null; 10501 } 10502 return rv.createSetDrawInstructionActionFromProto(in); 10503 default: 10504 throw new RuntimeException("Unhandled field while reading Action proto!\n" 10505 + ProtoUtils.currentFieldToString(in)); 10506 } 10507 } 10508 10509 private static void checkValidResource(int id, String message, String resName) 10510 throws Exception { 10511 if (id == 0) throw new Exception(message + ": " + resName); 10512 } 10513 10514 private static void checkProtoResultNotNull(Object o, String message) 10515 throws InvalidProtoException { 10516 if (o == null) { 10517 throw new InvalidProtoException(message); 10518 } 10519 } 10520 10521 private static void checkContainsKeys(LongSparseArray<?> array, long[] requiredFields) { 10522 for (long requiredField : requiredFields) { 10523 if (array.indexOfKey(requiredField) < 0) { 10524 throw new IllegalArgumentException( 10525 "RemoteViews proto missing field: " + ProtoStream.getFieldIdString( 10526 requiredField)); 10527 } 10528 } 10529 } 10530 10531 private static int getAsIdentifier(Resources resources, LongSparseArray<?> array, long fieldId) 10532 throws Exception { 10533 String resName = (String) array.get(fieldId); 10534 int id = resources.getIdentifier(resName, /* defType= */ null, /* defPackage= */ null); 10535 checkValidResource(id, "Invalid id", resName); 10536 return id; 10537 } 10538 10539 private static int getAsIdentifier(Resources resources, SparseArray<?> array, int key) 10540 throws Exception { 10541 String resName = (String) array.get(key); 10542 int id = resources.getIdentifier(resName, /* defType= */ null, /* defPackage= */ null); 10543 checkValidResource(id, "Invalid id", resName); 10544 return id; 10545 } 10546 10547 private static SizeF createSizeFFromProto(ProtoInputStream in) throws Exception { 10548 float width = 0; 10549 float height = 0; 10550 while (in.nextField() != NO_MORE_FIELDS) { 10551 switch (in.getFieldNumber()) { 10552 case (int) SizeFProto.WIDTH: 10553 width = in.readFloat(SizeFProto.WIDTH); 10554 break; 10555 case (int) SizeFProto.HEIGHT: 10556 height = in.readFloat(SizeFProto.HEIGHT); 10557 break; 10558 default: 10559 Log.w(LOG_TAG, "Unhandled field while reading SizeF proto!\n" 10560 + ProtoUtils.currentFieldToString(in)); 10561 } 10562 } 10563 10564 return new SizeF(width, height); 10565 } 10566 10567 private static void writeIconToProto(ProtoOutputStream out, Resources appResources, Icon icon, 10568 long fieldId) { 10569 long token = out.start(fieldId); 10570 RemoteViewsSerializers.writeIconToProto(out, appResources, icon); 10571 out.end(token); 10572 } 10573 10574 private static PendingResources<Icon> createIconFromProto(ProtoInputStream in, long fieldId) 10575 throws Exception { 10576 long token = in.start(fieldId); 10577 Function<Resources, Icon> icon = RemoteViewsSerializers.createIconFromProto(in); 10578 in.end(token); 10579 return (context, resources, rootData, depth) -> icon.apply(resources); 10580 } 10581 10582 private static void writeColorStateListToProto(ProtoOutputStream out, 10583 ColorStateList colorStateList, long fieldId) { 10584 long token = out.start(fieldId); 10585 colorStateList.writeToProto(out); 10586 out.end(token); 10587 } 10588 10589 private static ColorStateList createColorStateListFromProto(ProtoInputStream in, long fieldId) 10590 throws Exception { 10591 long token = in.start(fieldId); 10592 ColorStateList colorStateList = ColorStateList.createFromProto(in); 10593 in.end(token); 10594 return colorStateList; 10595 } 10596 10597 private static CharSequence createCharSequenceFromProto(ProtoInputStream in, long fieldId) 10598 throws Exception { 10599 long token = in.start(fieldId); 10600 CharSequence cs = RemoteViewsSerializers.createCharSequenceFromProto(in); 10601 in.end(token); 10602 return cs; 10603 } 10604 10605 } 10606