1 /* 2 * Copyright (C) 2007 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package android.widget; 18 19 import android.annotation.ColorInt; 20 import android.annotation.DimenRes; 21 import android.annotation.IntDef; 22 import android.annotation.LayoutRes; 23 import android.annotation.NonNull; 24 import android.annotation.StyleRes; 25 import android.app.Activity; 26 import android.app.ActivityOptions; 27 import android.app.ActivityThread; 28 import android.app.Application; 29 import android.app.PendingIntent; 30 import android.app.RemoteInput; 31 import android.appwidget.AppWidgetHostView; 32 import android.compat.annotation.UnsupportedAppUsage; 33 import android.content.Context; 34 import android.content.ContextWrapper; 35 import android.content.Intent; 36 import android.content.IntentSender; 37 import android.content.pm.ApplicationInfo; 38 import android.content.pm.PackageManager.NameNotFoundException; 39 import android.content.res.ColorStateList; 40 import android.content.res.Configuration; 41 import android.content.res.Resources; 42 import android.content.res.TypedArray; 43 import android.graphics.Bitmap; 44 import android.graphics.PorterDuff; 45 import android.graphics.Rect; 46 import android.graphics.drawable.Drawable; 47 import android.graphics.drawable.Icon; 48 import android.graphics.drawable.RippleDrawable; 49 import android.net.Uri; 50 import android.os.AsyncTask; 51 import android.os.Binder; 52 import android.os.Build; 53 import android.os.Bundle; 54 import android.os.CancellationSignal; 55 import android.os.Parcel; 56 import android.os.Parcelable; 57 import android.os.Process; 58 import android.os.StrictMode; 59 import android.os.UserHandle; 60 import android.text.TextUtils; 61 import android.util.ArrayMap; 62 import android.util.IntArray; 63 import android.util.Log; 64 import android.util.Pair; 65 import android.view.ContextThemeWrapper; 66 import android.view.LayoutInflater; 67 import android.view.LayoutInflater.Filter; 68 import android.view.RemotableViewMethod; 69 import android.view.View; 70 import android.view.ViewGroup; 71 import android.view.ViewStub; 72 import android.widget.AdapterView.OnItemClickListener; 73 74 import com.android.internal.R; 75 import com.android.internal.util.ContrastColorUtil; 76 import com.android.internal.util.Preconditions; 77 78 import java.lang.annotation.ElementType; 79 import java.lang.annotation.Retention; 80 import java.lang.annotation.RetentionPolicy; 81 import java.lang.annotation.Target; 82 import java.lang.invoke.MethodHandle; 83 import java.lang.invoke.MethodHandles; 84 import java.lang.invoke.MethodType; 85 import java.lang.reflect.Method; 86 import java.util.ArrayList; 87 import java.util.HashMap; 88 import java.util.Map; 89 import java.util.Objects; 90 import java.util.Stack; 91 import java.util.concurrent.Executor; 92 import java.util.function.Consumer; 93 94 /** 95 * A class that describes a view hierarchy that can be displayed in 96 * another process. The hierarchy is inflated from a layout resource 97 * file, and this class provides some basic operations for modifying 98 * the content of the inflated hierarchy. 99 * 100 * <p>{@code RemoteViews} is limited to support for the following layouts:</p> 101 * <ul> 102 * <li>{@link android.widget.AdapterViewFlipper}</li> 103 * <li>{@link android.widget.FrameLayout}</li> 104 * <li>{@link android.widget.GridLayout}</li> 105 * <li>{@link android.widget.GridView}</li> 106 * <li>{@link android.widget.LinearLayout}</li> 107 * <li>{@link android.widget.ListView}</li> 108 * <li>{@link android.widget.RelativeLayout}</li> 109 * <li>{@link android.widget.StackView}</li> 110 * <li>{@link android.widget.ViewFlipper}</li> 111 * </ul> 112 * <p>And the following widgets:</p> 113 * <ul> 114 * <li>{@link android.widget.AnalogClock}</li> 115 * <li>{@link android.widget.Button}</li> 116 * <li>{@link android.widget.Chronometer}</li> 117 * <li>{@link android.widget.ImageButton}</li> 118 * <li>{@link android.widget.ImageView}</li> 119 * <li>{@link android.widget.ProgressBar}</li> 120 * <li>{@link android.widget.TextClock}</li> 121 * <li>{@link android.widget.TextView}</li> 122 * </ul> 123 * <p>Descendants of these classes are not supported.</p> 124 */ 125 public class RemoteViews implements Parcelable, Filter { 126 127 private static final String LOG_TAG = "RemoteViews"; 128 129 /** 130 * The intent extra that contains the appWidgetId. 131 * @hide 132 */ 133 static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; 134 135 /** 136 * The intent extra that contains {@code true} if inflating as dak text theme. 137 * @hide 138 */ 139 static final String EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND = "remoteAdapterOnLightBackground"; 140 141 /** 142 * The intent extra that contains the bounds for all shared elements. 143 */ 144 public static final String EXTRA_SHARED_ELEMENT_BOUNDS = 145 "android.widget.extra.SHARED_ELEMENT_BOUNDS"; 146 147 /** 148 * Maximum depth of nested views calls from {@link #addView(int, RemoteViews)} and 149 * {@link #RemoteViews(RemoteViews, RemoteViews)}. 150 */ 151 private static final int MAX_NESTED_VIEWS = 10; 152 153 // The unique identifiers for each custom {@link Action}. 154 private static final int SET_ON_CLICK_RESPONSE_TAG = 1; 155 private static final int REFLECTION_ACTION_TAG = 2; 156 private static final int SET_DRAWABLE_TINT_TAG = 3; 157 private static final int VIEW_GROUP_ACTION_ADD_TAG = 4; 158 private static final int VIEW_CONTENT_NAVIGATION_TAG = 5; 159 private static final int SET_EMPTY_VIEW_ACTION_TAG = 6; 160 private static final int VIEW_GROUP_ACTION_REMOVE_TAG = 7; 161 private static final int SET_PENDING_INTENT_TEMPLATE_TAG = 8; 162 private static final int SET_REMOTE_VIEW_ADAPTER_INTENT_TAG = 10; 163 private static final int TEXT_VIEW_DRAWABLE_ACTION_TAG = 11; 164 private static final int BITMAP_REFLECTION_ACTION_TAG = 12; 165 private static final int TEXT_VIEW_SIZE_ACTION_TAG = 13; 166 private static final int VIEW_PADDING_ACTION_TAG = 14; 167 private static final int SET_REMOTE_VIEW_ADAPTER_LIST_TAG = 15; 168 private static final int SET_REMOTE_INPUTS_ACTION_TAG = 18; 169 private static final int LAYOUT_PARAM_ACTION_TAG = 19; 170 private static final int OVERRIDE_TEXT_COLORS_TAG = 20; 171 private static final int SET_RIPPLE_DRAWABLE_COLOR_TAG = 21; 172 private static final int SET_INT_TAG_TAG = 22; 173 174 /** @hide **/ 175 @IntDef(flag = true, value = { 176 FLAG_REAPPLY_DISALLOWED, 177 FLAG_WIDGET_IS_COLLECTION_CHILD, 178 FLAG_USE_LIGHT_BACKGROUND_LAYOUT 179 }) 180 @Retention(RetentionPolicy.SOURCE) 181 public @interface ApplyFlags {} 182 /** 183 * Whether reapply is disallowed on this remoteview. This maybe be true if some actions modify 184 * the layout in a way that isn't recoverable, since views are being removed. 185 * @hide 186 */ 187 public static final int FLAG_REAPPLY_DISALLOWED = 1; 188 /** 189 * This flag indicates whether this RemoteViews object is being created from a 190 * RemoteViewsService for use as a child of a widget collection. This flag is used 191 * to determine whether or not certain features are available, in particular, 192 * setting on click extras and setting on click pending intents. The former is enabled, 193 * and the latter disabled when this flag is true. 194 * @hide 195 */ 196 public static final int FLAG_WIDGET_IS_COLLECTION_CHILD = 2; 197 /** 198 * When this flag is set, the views is inflated with {@link #mLightBackgroundLayoutId} instead 199 * of {link #mLayoutId} 200 * @hide 201 */ 202 public static final int FLAG_USE_LIGHT_BACKGROUND_LAYOUT = 4; 203 204 /** 205 * Used to restrict the views which can be inflated 206 * 207 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 208 */ 209 private static final LayoutInflater.Filter INFLATER_FILTER = 210 (clazz) -> clazz.isAnnotationPresent(RemoteViews.RemoteView.class); 211 212 /** 213 * Application that hosts the remote views. 214 * 215 * @hide 216 */ 217 @UnsupportedAppUsage 218 public ApplicationInfo mApplication; 219 220 /** 221 * The resource ID of the layout file. (Added to the parcel) 222 */ 223 @UnsupportedAppUsage 224 private final int mLayoutId; 225 226 /** 227 * The resource ID of the layout file in dark text mode. (Added to the parcel) 228 */ 229 private int mLightBackgroundLayoutId = 0; 230 231 /** 232 * An array of actions to perform on the view tree once it has been 233 * inflated 234 */ 235 @UnsupportedAppUsage 236 private ArrayList<Action> mActions; 237 238 /** 239 * Maps bitmaps to unique indicies to avoid Bitmap duplication. 240 */ 241 @UnsupportedAppUsage 242 private BitmapCache mBitmapCache; 243 244 /** 245 * Indicates whether or not this RemoteViews object is contained as a child of any other 246 * RemoteViews. 247 */ 248 private boolean mIsRoot = true; 249 250 /** 251 * Constants to whether or not this RemoteViews is composed of a landscape and portrait 252 * RemoteViews. 253 */ 254 private static final int MODE_NORMAL = 0; 255 private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; 256 257 /** 258 * Used in conjunction with the special constructor 259 * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait 260 * RemoteViews. 261 */ 262 private RemoteViews mLandscape = null; 263 @UnsupportedAppUsage 264 private RemoteViews mPortrait = null; 265 266 @ApplyFlags 267 private int mApplyFlags = 0; 268 269 /** Class cookies of the Parcel this instance was read from. */ 270 private final Map<Class, Object> mClassCookies; 271 272 private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = (view, pendingIntent, response) 273 -> startPendingIntent(view, pendingIntent, response.getLaunchOptions(view)); 274 275 private static final ArrayMap<MethodKey, MethodArgs> sMethods = new ArrayMap<>(); 276 277 /** 278 * This key is used to perform lookups in sMethods without causing allocations. 279 */ 280 private static final MethodKey sLookupKey = new MethodKey(); 281 282 /** 283 * @hide 284 */ setRemoteInputs(int viewId, RemoteInput[] remoteInputs)285 public void setRemoteInputs(int viewId, RemoteInput[] remoteInputs) { 286 mActions.add(new SetRemoteInputsAction(viewId, remoteInputs)); 287 } 288 289 /** 290 * Reduces all images and ensures that they are all below the given sizes. 291 * 292 * @param maxWidth the maximum width allowed 293 * @param maxHeight the maximum height allowed 294 * 295 * @hide 296 */ reduceImageSizes(int maxWidth, int maxHeight)297 public void reduceImageSizes(int maxWidth, int maxHeight) { 298 ArrayList<Bitmap> cache = mBitmapCache.mBitmaps; 299 for (int i = 0; i < cache.size(); i++) { 300 Bitmap bitmap = cache.get(i); 301 cache.set(i, Icon.scaleDownIfNecessary(bitmap, maxWidth, maxHeight)); 302 } 303 } 304 305 /** 306 * Override all text colors in this layout and replace them by the given text color. 307 * 308 * @param textColor The color to use. 309 * 310 * @hide 311 */ overrideTextColors(int textColor)312 public void overrideTextColors(int textColor) { 313 addAction(new OverrideTextColorsAction(textColor)); 314 } 315 316 /** 317 * Sets an integer tag to the view. 318 * 319 * @hide 320 */ setIntTag(int viewId, int key, int tag)321 public void setIntTag(int viewId, int key, int tag) { 322 addAction(new SetIntTagAction(viewId, key, tag)); 323 } 324 325 /** 326 * Set that it is disallowed to reapply another remoteview with the same layout as this view. 327 * This should be done if an action is destroying the view tree of the base layout. 328 * 329 * @hide 330 */ addFlags(@pplyFlags int flags)331 public void addFlags(@ApplyFlags int flags) { 332 mApplyFlags = mApplyFlags | flags; 333 } 334 335 /** 336 * @hide 337 */ hasFlags(@pplyFlags int flag)338 public boolean hasFlags(@ApplyFlags int flag) { 339 return (mApplyFlags & flag) == flag; 340 } 341 342 /** 343 * Stores information related to reflection method lookup. 344 */ 345 static class MethodKey { 346 public Class targetClass; 347 public Class paramClass; 348 public String methodName; 349 350 @Override equals(Object o)351 public boolean equals(Object o) { 352 if (!(o instanceof MethodKey)) { 353 return false; 354 } 355 MethodKey p = (MethodKey) o; 356 return Objects.equals(p.targetClass, targetClass) 357 && Objects.equals(p.paramClass, paramClass) 358 && Objects.equals(p.methodName, methodName); 359 } 360 361 @Override hashCode()362 public int hashCode() { 363 return Objects.hashCode(targetClass) ^ Objects.hashCode(paramClass) 364 ^ Objects.hashCode(methodName); 365 } 366 set(Class targetClass, Class paramClass, String methodName)367 public void set(Class targetClass, Class paramClass, String methodName) { 368 this.targetClass = targetClass; 369 this.paramClass = paramClass; 370 this.methodName = methodName; 371 } 372 } 373 374 375 /** 376 * Stores information related to reflection method lookup result. 377 */ 378 static class MethodArgs { 379 public MethodHandle syncMethod; 380 public MethodHandle asyncMethod; 381 public String asyncMethodName; 382 } 383 384 /** 385 * This annotation indicates that a subclass of View is allowed to be used 386 * with the {@link RemoteViews} mechanism. 387 */ 388 @Target({ ElementType.TYPE }) 389 @Retention(RetentionPolicy.RUNTIME) 390 public @interface RemoteView { 391 } 392 393 /** 394 * Exception to send when something goes wrong executing an action 395 * 396 */ 397 public static class ActionException extends RuntimeException { ActionException(Exception ex)398 public ActionException(Exception ex) { 399 super(ex); 400 } ActionException(String message)401 public ActionException(String message) { 402 super(message); 403 } 404 /** 405 * @hide 406 */ ActionException(Throwable t)407 public ActionException(Throwable t) { 408 super(t); 409 } 410 } 411 412 /** @hide */ 413 public interface OnClickHandler { 414 415 /** @hide */ onClickHandler(View view, PendingIntent pendingIntent, RemoteResponse response)416 boolean onClickHandler(View view, PendingIntent pendingIntent, RemoteResponse response); 417 } 418 419 /** 420 * Base class for all actions that can be performed on an 421 * inflated view. 422 * 423 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 424 */ 425 private abstract static class Action implements Parcelable { apply(View root, ViewGroup rootParent, OnClickHandler handler)426 public abstract void apply(View root, ViewGroup rootParent, 427 OnClickHandler handler) throws ActionException; 428 429 public static final int MERGE_REPLACE = 0; 430 public static final int MERGE_APPEND = 1; 431 public static final int MERGE_IGNORE = 2; 432 describeContents()433 public int describeContents() { 434 return 0; 435 } 436 setBitmapCache(BitmapCache bitmapCache)437 public void setBitmapCache(BitmapCache bitmapCache) { 438 // Do nothing 439 } 440 441 @UnsupportedAppUsage mergeBehavior()442 public int mergeBehavior() { 443 return MERGE_REPLACE; 444 } 445 getActionTag()446 public abstract int getActionTag(); 447 getUniqueKey()448 public String getUniqueKey() { 449 return (getActionTag() + "_" + viewId); 450 } 451 452 /** 453 * This is called on the background thread. It should perform any non-ui computations 454 * and return the final action which will run on the UI thread. 455 * Override this if some of the tasks can be performed async. 456 */ initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)457 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 458 return this; 459 } 460 prefersAsyncApply()461 public boolean prefersAsyncApply() { 462 return false; 463 } 464 465 /** 466 * Overridden by subclasses which have (or inherit) an ApplicationInfo instance 467 * as member variable 468 */ hasSameAppInfo(ApplicationInfo parentInfo)469 public boolean hasSameAppInfo(ApplicationInfo parentInfo) { 470 return true; 471 } 472 visitUris(@onNull Consumer<Uri> visitor)473 public void visitUris(@NonNull Consumer<Uri> visitor) { 474 // Nothing to visit by default 475 } 476 477 @UnsupportedAppUsage 478 int viewId; 479 } 480 481 /** 482 * Action class used during async inflation of RemoteViews. Subclasses are not parcelable. 483 */ 484 private static abstract class RuntimeAction extends Action { 485 @Override getActionTag()486 public final int getActionTag() { 487 return 0; 488 } 489 490 @Override writeToParcel(Parcel dest, int flags)491 public final void writeToParcel(Parcel dest, int flags) { 492 throw new UnsupportedOperationException(); 493 } 494 } 495 496 // Constant used during async execution. It is not parcelable. 497 private static final Action ACTION_NOOP = new RuntimeAction() { 498 @Override 499 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { } 500 }; 501 502 /** 503 * Merges the passed RemoteViews actions with this RemoteViews actions according to 504 * action-specific merge rules. 505 * 506 * @param newRv 507 * 508 * @hide 509 */ 510 @UnsupportedAppUsage mergeRemoteViews(RemoteViews newRv)511 public void mergeRemoteViews(RemoteViews newRv) { 512 if (newRv == null) return; 513 // We first copy the new RemoteViews, as the process of merging modifies the way the actions 514 // reference the bitmap cache. We don't want to modify the object as it may need to 515 // be merged and applied multiple times. 516 RemoteViews copy = new RemoteViews(newRv); 517 518 HashMap<String, Action> map = new HashMap<String, Action>(); 519 if (mActions == null) { 520 mActions = new ArrayList<Action>(); 521 } 522 523 int count = mActions.size(); 524 for (int i = 0; i < count; i++) { 525 Action a = mActions.get(i); 526 map.put(a.getUniqueKey(), a); 527 } 528 529 ArrayList<Action> newActions = copy.mActions; 530 if (newActions == null) return; 531 count = newActions.size(); 532 for (int i = 0; i < count; i++) { 533 Action a = newActions.get(i); 534 String key = newActions.get(i).getUniqueKey(); 535 int mergeBehavior = newActions.get(i).mergeBehavior(); 536 if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) { 537 mActions.remove(map.get(key)); 538 map.remove(key); 539 } 540 541 // If the merge behavior is ignore, we don't bother keeping the extra action 542 if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) { 543 mActions.add(a); 544 } 545 } 546 547 // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache 548 mBitmapCache = new BitmapCache(); 549 setBitmapCache(mBitmapCache); 550 } 551 552 /** 553 * Note all {@link Uri} that are referenced internally, with the expectation 554 * that Uri permission grants will need to be issued to ensure the recipient 555 * of this object is able to render its contents. 556 * 557 * @hide 558 */ visitUris(@onNull Consumer<Uri> visitor)559 public void visitUris(@NonNull Consumer<Uri> visitor) { 560 if (mActions != null) { 561 for (int i = 0; i < mActions.size(); i++) { 562 mActions.get(i).visitUris(visitor); 563 } 564 } 565 } 566 visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor)567 private static void visitIconUri(Icon icon, @NonNull Consumer<Uri> visitor) { 568 if (icon != null && (icon.getType() == Icon.TYPE_URI 569 || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP)) { 570 visitor.accept(icon.getUri()); 571 } 572 } 573 574 private static class RemoteViewsContextWrapper extends ContextWrapper { 575 private final Context mContextForResources; 576 RemoteViewsContextWrapper(Context context, Context contextForResources)577 RemoteViewsContextWrapper(Context context, Context contextForResources) { 578 super(context); 579 mContextForResources = contextForResources; 580 } 581 582 @Override getResources()583 public Resources getResources() { 584 return mContextForResources.getResources(); 585 } 586 587 @Override getTheme()588 public Resources.Theme getTheme() { 589 return mContextForResources.getTheme(); 590 } 591 592 @Override getPackageName()593 public String getPackageName() { 594 return mContextForResources.getPackageName(); 595 } 596 } 597 598 private class SetEmptyView extends Action { 599 int emptyViewId; 600 SetEmptyView(int viewId, int emptyViewId)601 SetEmptyView(int viewId, int emptyViewId) { 602 this.viewId = viewId; 603 this.emptyViewId = emptyViewId; 604 } 605 SetEmptyView(Parcel in)606 SetEmptyView(Parcel in) { 607 this.viewId = in.readInt(); 608 this.emptyViewId = in.readInt(); 609 } 610 writeToParcel(Parcel out, int flags)611 public void writeToParcel(Parcel out, int flags) { 612 out.writeInt(this.viewId); 613 out.writeInt(this.emptyViewId); 614 } 615 616 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)617 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 618 final View view = root.findViewById(viewId); 619 if (!(view instanceof AdapterView<?>)) return; 620 621 AdapterView<?> adapterView = (AdapterView<?>) view; 622 623 final View emptyView = root.findViewById(emptyViewId); 624 if (emptyView == null) return; 625 626 adapterView.setEmptyView(emptyView); 627 } 628 629 @Override getActionTag()630 public int getActionTag() { 631 return SET_EMPTY_VIEW_ACTION_TAG; 632 } 633 } 634 635 private class SetPendingIntentTemplate extends Action { SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate)636 public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { 637 this.viewId = id; 638 this.pendingIntentTemplate = pendingIntentTemplate; 639 } 640 SetPendingIntentTemplate(Parcel parcel)641 public SetPendingIntentTemplate(Parcel parcel) { 642 viewId = parcel.readInt(); 643 pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 644 } 645 writeToParcel(Parcel dest, int flags)646 public void writeToParcel(Parcel dest, int flags) { 647 dest.writeInt(viewId); 648 PendingIntent.writePendingIntentOrNullToParcel(pendingIntentTemplate, dest); 649 } 650 651 @Override apply(View root, ViewGroup rootParent, final OnClickHandler handler)652 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 653 final View target = root.findViewById(viewId); 654 if (target == null) return; 655 656 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 657 if (target instanceof AdapterView<?>) { 658 AdapterView<?> av = (AdapterView<?>) target; 659 // The PendingIntent template is stored in the view's tag. 660 OnItemClickListener listener = new OnItemClickListener() { 661 public void onItemClick(AdapterView<?> parent, View view, 662 int position, long id) { 663 // The view should be a frame layout 664 if (view instanceof ViewGroup) { 665 ViewGroup vg = (ViewGroup) view; 666 667 // AdapterViews contain their children in a frame 668 // so we need to go one layer deeper here. 669 if (parent instanceof AdapterViewAnimator) { 670 vg = (ViewGroup) vg.getChildAt(0); 671 } 672 if (vg == null) return; 673 674 RemoteResponse response = null; 675 int childCount = vg.getChildCount(); 676 for (int i = 0; i < childCount; i++) { 677 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent); 678 if (tag instanceof RemoteResponse) { 679 response = (RemoteResponse) tag; 680 break; 681 } 682 } 683 if (response == null) return; 684 response.handleViewClick(view, handler); 685 } 686 } 687 }; 688 av.setOnItemClickListener(listener); 689 av.setTag(pendingIntentTemplate); 690 } else { 691 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" + 692 "an AdapterView (id: " + viewId + ")"); 693 return; 694 } 695 } 696 697 @Override getActionTag()698 public int getActionTag() { 699 return SET_PENDING_INTENT_TEMPLATE_TAG; 700 } 701 702 @UnsupportedAppUsage 703 PendingIntent pendingIntentTemplate; 704 } 705 706 private class SetRemoteViewsAdapterList extends Action { SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount)707 public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) { 708 this.viewId = id; 709 this.list = list; 710 this.viewTypeCount = viewTypeCount; 711 } 712 SetRemoteViewsAdapterList(Parcel parcel)713 public SetRemoteViewsAdapterList(Parcel parcel) { 714 viewId = parcel.readInt(); 715 viewTypeCount = parcel.readInt(); 716 list = parcel.createTypedArrayList(RemoteViews.CREATOR); 717 } 718 writeToParcel(Parcel dest, int flags)719 public void writeToParcel(Parcel dest, int flags) { 720 dest.writeInt(viewId); 721 dest.writeInt(viewTypeCount); 722 dest.writeTypedList(list, flags); 723 } 724 725 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)726 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 727 final View target = root.findViewById(viewId); 728 if (target == null) return; 729 730 // Ensure that we are applying to an AppWidget root 731 if (!(rootParent instanceof AppWidgetHostView)) { 732 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + 733 "AppWidgets (root id: " + viewId + ")"); 734 return; 735 } 736 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 737 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 738 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + 739 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 740 return; 741 } 742 743 if (target instanceof AbsListView) { 744 AbsListView v = (AbsListView) target; 745 Adapter a = v.getAdapter(); 746 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 747 ((RemoteViewsListAdapter) a).setViewsList(list); 748 } else { 749 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); 750 } 751 } else if (target instanceof AdapterViewAnimator) { 752 AdapterViewAnimator v = (AdapterViewAnimator) target; 753 Adapter a = v.getAdapter(); 754 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 755 ((RemoteViewsListAdapter) a).setViewsList(list); 756 } else { 757 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); 758 } 759 } 760 } 761 762 @Override getActionTag()763 public int getActionTag() { 764 return SET_REMOTE_VIEW_ADAPTER_LIST_TAG; 765 } 766 767 int viewTypeCount; 768 ArrayList<RemoteViews> list; 769 } 770 771 private class SetRemoteViewsAdapterIntent extends Action { SetRemoteViewsAdapterIntent(int id, Intent intent)772 public SetRemoteViewsAdapterIntent(int id, Intent intent) { 773 this.viewId = id; 774 this.intent = intent; 775 } 776 SetRemoteViewsAdapterIntent(Parcel parcel)777 public SetRemoteViewsAdapterIntent(Parcel parcel) { 778 viewId = parcel.readInt(); 779 intent = parcel.readTypedObject(Intent.CREATOR); 780 } 781 writeToParcel(Parcel dest, int flags)782 public void writeToParcel(Parcel dest, int flags) { 783 dest.writeInt(viewId); 784 dest.writeTypedObject(intent, flags); 785 } 786 787 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)788 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 789 final View target = root.findViewById(viewId); 790 if (target == null) return; 791 792 // Ensure that we are applying to an AppWidget root 793 if (!(rootParent instanceof AppWidgetHostView)) { 794 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + 795 "AppWidgets (root id: " + viewId + ")"); 796 return; 797 } 798 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 799 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 800 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + 801 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 802 return; 803 } 804 805 // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent 806 // RemoteViewsService 807 AppWidgetHostView host = (AppWidgetHostView) rootParent; 808 intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()) 809 .putExtra(EXTRA_REMOTEADAPTER_ON_LIGHT_BACKGROUND, 810 hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)); 811 812 if (target instanceof AbsListView) { 813 AbsListView v = (AbsListView) target; 814 v.setRemoteViewsAdapter(intent, isAsync); 815 v.setRemoteViewsOnClickHandler(handler); 816 } else if (target instanceof AdapterViewAnimator) { 817 AdapterViewAnimator v = (AdapterViewAnimator) target; 818 v.setRemoteViewsAdapter(intent, isAsync); 819 v.setRemoteViewsOnClickHandler(handler); 820 } 821 } 822 823 @Override initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)824 public Action initActionAsync(ViewTree root, ViewGroup rootParent, 825 OnClickHandler handler) { 826 SetRemoteViewsAdapterIntent copy = new SetRemoteViewsAdapterIntent(viewId, intent); 827 copy.isAsync = true; 828 return copy; 829 } 830 831 @Override getActionTag()832 public int getActionTag() { 833 return SET_REMOTE_VIEW_ADAPTER_INTENT_TAG; 834 } 835 836 Intent intent; 837 boolean isAsync = false; 838 } 839 840 /** 841 * Equivalent to calling 842 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 843 * to launch the provided {@link PendingIntent}. 844 */ 845 private class SetOnClickResponse extends Action { 846 SetOnClickResponse(int id, RemoteResponse response)847 SetOnClickResponse(int id, RemoteResponse response) { 848 this.viewId = id; 849 this.mResponse = response; 850 } 851 SetOnClickResponse(Parcel parcel)852 SetOnClickResponse(Parcel parcel) { 853 viewId = parcel.readInt(); 854 mResponse = new RemoteResponse(); 855 mResponse.readFromParcel(parcel); 856 } 857 writeToParcel(Parcel dest, int flags)858 public void writeToParcel(Parcel dest, int flags) { 859 dest.writeInt(viewId); 860 mResponse.writeToParcel(dest, flags); 861 } 862 863 @Override apply(View root, ViewGroup rootParent, final OnClickHandler handler)864 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 865 final View target = root.findViewById(viewId); 866 if (target == null) return; 867 868 if (mResponse.mPendingIntent != null) { 869 // If the view is an AdapterView, setting a PendingIntent on click doesn't make 870 // much sense, do they mean to set a PendingIntent template for the 871 // AdapterView's children? 872 if (hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 873 Log.w(LOG_TAG, "Cannot SetOnClickResponse for collection item " 874 + "(id: " + viewId + ")"); 875 ApplicationInfo appInfo = root.getContext().getApplicationInfo(); 876 877 // We let this slide for HC and ICS so as to not break compatibility. It should 878 // have been disabled from the outset, but was left open by accident. 879 if (appInfo != null 880 && appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { 881 return; 882 } 883 } 884 target.setTagInternal(R.id.pending_intent_tag, mResponse.mPendingIntent); 885 } else if (mResponse.mFillIntent != null) { 886 if (!hasFlags(FLAG_WIDGET_IS_COLLECTION_CHILD)) { 887 Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " 888 + "only from RemoteViewsFactory (ie. on collection items)."); 889 return; 890 } 891 if (target == root) { 892 // Target is a root node of an AdapterView child. Set the response in the tag. 893 // Actual click handling is done by OnItemClickListener in 894 // SetPendingIntentTemplate, which uses this tag information. 895 target.setTagInternal(com.android.internal.R.id.fillInIntent, mResponse); 896 return; 897 } 898 } else { 899 // No intent to apply 900 target.setOnClickListener(null); 901 return; 902 } 903 target.setOnClickListener(v -> mResponse.handleViewClick(v, handler)); 904 } 905 906 @Override getActionTag()907 public int getActionTag() { 908 return SET_ON_CLICK_RESPONSE_TAG; 909 } 910 911 final RemoteResponse mResponse; 912 } 913 914 /** @hide **/ getSourceBounds(View v)915 public static Rect getSourceBounds(View v) { 916 final float appScale = v.getContext().getResources() 917 .getCompatibilityInfo().applicationScale; 918 final int[] pos = new int[2]; 919 v.getLocationOnScreen(pos); 920 921 final Rect rect = new Rect(); 922 rect.left = (int) (pos[0] * appScale + 0.5f); 923 rect.top = (int) (pos[1] * appScale + 0.5f); 924 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 925 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 926 return rect; 927 } 928 getMethod(View view, String methodName, Class<?> paramType, boolean async)929 private MethodHandle getMethod(View view, String methodName, Class<?> paramType, 930 boolean async) { 931 MethodArgs result; 932 Class<? extends View> klass = view.getClass(); 933 934 synchronized (sMethods) { 935 // The key is defined by the view class, param class and method name. 936 sLookupKey.set(klass, paramType, methodName); 937 result = sMethods.get(sLookupKey); 938 939 if (result == null) { 940 Method method; 941 try { 942 if (paramType == null) { 943 method = klass.getMethod(methodName); 944 } else { 945 method = klass.getMethod(methodName, paramType); 946 } 947 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 948 throw new ActionException("view: " + klass.getName() 949 + " can't use method with RemoteViews: " 950 + methodName + getParameters(paramType)); 951 } 952 953 result = new MethodArgs(); 954 result.syncMethod = MethodHandles.publicLookup().unreflect(method); 955 result.asyncMethodName = 956 method.getAnnotation(RemotableViewMethod.class).asyncImpl(); 957 } catch (NoSuchMethodException | IllegalAccessException ex) { 958 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 959 + methodName + getParameters(paramType)); 960 } 961 962 MethodKey key = new MethodKey(); 963 key.set(klass, paramType, methodName); 964 sMethods.put(key, result); 965 } 966 967 if (!async) { 968 return result.syncMethod; 969 } 970 // Check this so see if async method is implemented or not. 971 if (result.asyncMethodName.isEmpty()) { 972 return null; 973 } 974 // Async method is lazily loaded. If it is not yet loaded, load now. 975 if (result.asyncMethod == null) { 976 MethodType asyncType = result.syncMethod.type() 977 .dropParameterTypes(0, 1).changeReturnType(Runnable.class); 978 try { 979 result.asyncMethod = MethodHandles.publicLookup().findVirtual( 980 klass, result.asyncMethodName, asyncType); 981 } catch (NoSuchMethodException | IllegalAccessException ex) { 982 throw new ActionException("Async implementation declared as " 983 + result.asyncMethodName + " but not defined for " + methodName 984 + ": public Runnable " + result.asyncMethodName + " (" 985 + TextUtils.join(",", asyncType.parameterArray()) + ")"); 986 } 987 } 988 return result.asyncMethod; 989 } 990 } 991 getParameters(Class<?> paramType)992 private static String getParameters(Class<?> paramType) { 993 if (paramType == null) return "()"; 994 return "(" + paramType + ")"; 995 } 996 997 /** 998 * Equivalent to calling 999 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 1000 * on the {@link Drawable} of a given view. 1001 * <p> 1002 * The operation will be performed on the {@link Drawable} returned by the 1003 * target {@link View#getBackground()} by default. If targetBackground is false, 1004 * we assume the target is an {@link ImageView} and try applying the operations 1005 * to {@link ImageView#getDrawable()}. 1006 * <p> 1007 */ 1008 private class SetDrawableTint extends Action { SetDrawableTint(int id, boolean targetBackground, int colorFilter, @NonNull PorterDuff.Mode mode)1009 SetDrawableTint(int id, boolean targetBackground, 1010 int colorFilter, @NonNull PorterDuff.Mode mode) { 1011 this.viewId = id; 1012 this.targetBackground = targetBackground; 1013 this.colorFilter = colorFilter; 1014 this.filterMode = mode; 1015 } 1016 SetDrawableTint(Parcel parcel)1017 SetDrawableTint(Parcel parcel) { 1018 viewId = parcel.readInt(); 1019 targetBackground = parcel.readInt() != 0; 1020 colorFilter = parcel.readInt(); 1021 filterMode = PorterDuff.intToMode(parcel.readInt()); 1022 } 1023 writeToParcel(Parcel dest, int flags)1024 public void writeToParcel(Parcel dest, int flags) { 1025 dest.writeInt(viewId); 1026 dest.writeInt(targetBackground ? 1 : 0); 1027 dest.writeInt(colorFilter); 1028 dest.writeInt(PorterDuff.modeToInt(filterMode)); 1029 } 1030 1031 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1032 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1033 final View target = root.findViewById(viewId); 1034 if (target == null) return; 1035 1036 // Pick the correct drawable to modify for this view 1037 Drawable targetDrawable = null; 1038 if (targetBackground) { 1039 targetDrawable = target.getBackground(); 1040 } else if (target instanceof ImageView) { 1041 ImageView imageView = (ImageView) target; 1042 targetDrawable = imageView.getDrawable(); 1043 } 1044 1045 if (targetDrawable != null) { 1046 targetDrawable.mutate().setColorFilter(colorFilter, filterMode); 1047 } 1048 } 1049 1050 @Override getActionTag()1051 public int getActionTag() { 1052 return SET_DRAWABLE_TINT_TAG; 1053 } 1054 1055 boolean targetBackground; 1056 int colorFilter; 1057 PorterDuff.Mode filterMode; 1058 } 1059 1060 /** 1061 * Equivalent to calling 1062 * {@link RippleDrawable#setColor(ColorStateList)}, 1063 * on the {@link Drawable} of a given view. 1064 * <p> 1065 * The operation will be performed on the {@link Drawable} returned by the 1066 * target {@link View#getBackground()}. 1067 * <p> 1068 */ 1069 private class SetRippleDrawableColor extends Action { 1070 1071 ColorStateList mColorStateList; 1072 SetRippleDrawableColor(int id, ColorStateList colorStateList)1073 SetRippleDrawableColor(int id, ColorStateList colorStateList) { 1074 this.viewId = id; 1075 this.mColorStateList = colorStateList; 1076 } 1077 SetRippleDrawableColor(Parcel parcel)1078 SetRippleDrawableColor(Parcel parcel) { 1079 viewId = parcel.readInt(); 1080 mColorStateList = parcel.readParcelable(null); 1081 } 1082 writeToParcel(Parcel dest, int flags)1083 public void writeToParcel(Parcel dest, int flags) { 1084 dest.writeInt(viewId); 1085 dest.writeParcelable(mColorStateList, 0); 1086 } 1087 1088 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1089 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1090 final View target = root.findViewById(viewId); 1091 if (target == null) return; 1092 1093 // Pick the correct drawable to modify for this view 1094 Drawable targetDrawable = target.getBackground(); 1095 1096 if (targetDrawable instanceof RippleDrawable) { 1097 ((RippleDrawable) targetDrawable.mutate()).setColor(mColorStateList); 1098 } 1099 } 1100 1101 @Override getActionTag()1102 public int getActionTag() { 1103 return SET_RIPPLE_DRAWABLE_COLOR_TAG; 1104 } 1105 } 1106 1107 private final class ViewContentNavigation extends Action { 1108 final boolean mNext; 1109 ViewContentNavigation(int viewId, boolean next)1110 ViewContentNavigation(int viewId, boolean next) { 1111 this.viewId = viewId; 1112 this.mNext = next; 1113 } 1114 ViewContentNavigation(Parcel in)1115 ViewContentNavigation(Parcel in) { 1116 this.viewId = in.readInt(); 1117 this.mNext = in.readBoolean(); 1118 } 1119 writeToParcel(Parcel out, int flags)1120 public void writeToParcel(Parcel out, int flags) { 1121 out.writeInt(this.viewId); 1122 out.writeBoolean(this.mNext); 1123 } 1124 1125 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1126 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1127 final View view = root.findViewById(viewId); 1128 if (view == null) return; 1129 1130 try { 1131 getMethod(view, 1132 mNext ? "showNext" : "showPrevious", null, false /* async */).invoke(view); 1133 } catch (Throwable ex) { 1134 throw new ActionException(ex); 1135 } 1136 } 1137 mergeBehavior()1138 public int mergeBehavior() { 1139 return MERGE_IGNORE; 1140 } 1141 1142 @Override getActionTag()1143 public int getActionTag() { 1144 return VIEW_CONTENT_NAVIGATION_TAG; 1145 } 1146 } 1147 1148 private static class BitmapCache { 1149 1150 @UnsupportedAppUsage 1151 ArrayList<Bitmap> mBitmaps; 1152 int mBitmapMemory = -1; 1153 BitmapCache()1154 public BitmapCache() { 1155 mBitmaps = new ArrayList<>(); 1156 } 1157 BitmapCache(Parcel source)1158 public BitmapCache(Parcel source) { 1159 mBitmaps = source.createTypedArrayList(Bitmap.CREATOR); 1160 } 1161 getBitmapId(Bitmap b)1162 public int getBitmapId(Bitmap b) { 1163 if (b == null) { 1164 return -1; 1165 } else { 1166 if (mBitmaps.contains(b)) { 1167 return mBitmaps.indexOf(b); 1168 } else { 1169 mBitmaps.add(b); 1170 mBitmapMemory = -1; 1171 return (mBitmaps.size() - 1); 1172 } 1173 } 1174 } 1175 getBitmapForId(int id)1176 public Bitmap getBitmapForId(int id) { 1177 if (id == -1 || id >= mBitmaps.size()) { 1178 return null; 1179 } else { 1180 return mBitmaps.get(id); 1181 } 1182 } 1183 writeBitmapsToParcel(Parcel dest, int flags)1184 public void writeBitmapsToParcel(Parcel dest, int flags) { 1185 dest.writeTypedList(mBitmaps, flags); 1186 } 1187 getBitmapMemory()1188 public int getBitmapMemory() { 1189 if (mBitmapMemory < 0) { 1190 mBitmapMemory = 0; 1191 int count = mBitmaps.size(); 1192 for (int i = 0; i < count; i++) { 1193 mBitmapMemory += mBitmaps.get(i).getAllocationByteCount(); 1194 } 1195 } 1196 return mBitmapMemory; 1197 } 1198 } 1199 1200 private class BitmapReflectionAction extends Action { 1201 int bitmapId; 1202 @UnsupportedAppUsage 1203 Bitmap bitmap; 1204 @UnsupportedAppUsage 1205 String methodName; 1206 BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap)1207 BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { 1208 this.bitmap = bitmap; 1209 this.viewId = viewId; 1210 this.methodName = methodName; 1211 bitmapId = mBitmapCache.getBitmapId(bitmap); 1212 } 1213 BitmapReflectionAction(Parcel in)1214 BitmapReflectionAction(Parcel in) { 1215 viewId = in.readInt(); 1216 methodName = in.readString8(); 1217 bitmapId = in.readInt(); 1218 bitmap = mBitmapCache.getBitmapForId(bitmapId); 1219 } 1220 1221 @Override writeToParcel(Parcel dest, int flags)1222 public void writeToParcel(Parcel dest, int flags) { 1223 dest.writeInt(viewId); 1224 dest.writeString8(methodName); 1225 dest.writeInt(bitmapId); 1226 } 1227 1228 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1229 public void apply(View root, ViewGroup rootParent, 1230 OnClickHandler handler) throws ActionException { 1231 ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, 1232 bitmap); 1233 ra.apply(root, rootParent, handler); 1234 } 1235 1236 @Override setBitmapCache(BitmapCache bitmapCache)1237 public void setBitmapCache(BitmapCache bitmapCache) { 1238 bitmapId = bitmapCache.getBitmapId(bitmap); 1239 } 1240 1241 @Override getActionTag()1242 public int getActionTag() { 1243 return BITMAP_REFLECTION_ACTION_TAG; 1244 } 1245 } 1246 1247 /** 1248 * Base class for the reflection actions. 1249 */ 1250 private final class ReflectionAction extends Action { 1251 static final int BOOLEAN = 1; 1252 static final int BYTE = 2; 1253 static final int SHORT = 3; 1254 static final int INT = 4; 1255 static final int LONG = 5; 1256 static final int FLOAT = 6; 1257 static final int DOUBLE = 7; 1258 static final int CHAR = 8; 1259 static final int STRING = 9; 1260 static final int CHAR_SEQUENCE = 10; 1261 static final int URI = 11; 1262 // BITMAP actions are never stored in the list of actions. They are only used locally 1263 // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache. 1264 static final int BITMAP = 12; 1265 static final int BUNDLE = 13; 1266 static final int INTENT = 14; 1267 static final int COLOR_STATE_LIST = 15; 1268 static final int ICON = 16; 1269 1270 @UnsupportedAppUsage 1271 String methodName; 1272 int type; 1273 @UnsupportedAppUsage 1274 Object value; 1275 ReflectionAction(int viewId, String methodName, int type, Object value)1276 ReflectionAction(int viewId, String methodName, int type, Object value) { 1277 this.viewId = viewId; 1278 this.methodName = methodName; 1279 this.type = type; 1280 this.value = value; 1281 } 1282 ReflectionAction(Parcel in)1283 ReflectionAction(Parcel in) { 1284 this.viewId = in.readInt(); 1285 this.methodName = in.readString8(); 1286 this.type = in.readInt(); 1287 //noinspection ConstantIfStatement 1288 if (false) { 1289 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId) 1290 + " methodName=" + this.methodName + " type=" + this.type); 1291 } 1292 1293 // For some values that may have been null, we first check a flag to see if they were 1294 // written to the parcel. 1295 switch (this.type) { 1296 case BOOLEAN: 1297 this.value = in.readBoolean(); 1298 break; 1299 case BYTE: 1300 this.value = in.readByte(); 1301 break; 1302 case SHORT: 1303 this.value = (short)in.readInt(); 1304 break; 1305 case INT: 1306 this.value = in.readInt(); 1307 break; 1308 case LONG: 1309 this.value = in.readLong(); 1310 break; 1311 case FLOAT: 1312 this.value = in.readFloat(); 1313 break; 1314 case DOUBLE: 1315 this.value = in.readDouble(); 1316 break; 1317 case CHAR: 1318 this.value = (char)in.readInt(); 1319 break; 1320 case STRING: 1321 this.value = in.readString8(); 1322 break; 1323 case CHAR_SEQUENCE: 1324 this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1325 break; 1326 case URI: 1327 this.value = in.readTypedObject(Uri.CREATOR); 1328 break; 1329 case BITMAP: 1330 this.value = in.readTypedObject(Bitmap.CREATOR); 1331 break; 1332 case BUNDLE: 1333 this.value = in.readBundle(); 1334 break; 1335 case INTENT: 1336 this.value = in.readTypedObject(Intent.CREATOR); 1337 break; 1338 case COLOR_STATE_LIST: 1339 this.value = in.readTypedObject(ColorStateList.CREATOR); 1340 break; 1341 case ICON: 1342 this.value = in.readTypedObject(Icon.CREATOR); 1343 default: 1344 break; 1345 } 1346 } 1347 writeToParcel(Parcel out, int flags)1348 public void writeToParcel(Parcel out, int flags) { 1349 out.writeInt(this.viewId); 1350 out.writeString8(this.methodName); 1351 out.writeInt(this.type); 1352 //noinspection ConstantIfStatement 1353 if (false) { 1354 Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId) 1355 + " methodName=" + this.methodName + " type=" + this.type); 1356 } 1357 1358 // For some values which are null, we record an integer flag to indicate whether 1359 // we have written a valid value to the parcel. 1360 switch (this.type) { 1361 case BOOLEAN: 1362 out.writeBoolean((Boolean) this.value); 1363 break; 1364 case BYTE: 1365 out.writeByte((Byte) this.value); 1366 break; 1367 case SHORT: 1368 out.writeInt((Short) this.value); 1369 break; 1370 case INT: 1371 out.writeInt((Integer) this.value); 1372 break; 1373 case LONG: 1374 out.writeLong((Long) this.value); 1375 break; 1376 case FLOAT: 1377 out.writeFloat((Float) this.value); 1378 break; 1379 case DOUBLE: 1380 out.writeDouble((Double) this.value); 1381 break; 1382 case CHAR: 1383 out.writeInt((int)((Character)this.value).charValue()); 1384 break; 1385 case STRING: 1386 out.writeString8((String)this.value); 1387 break; 1388 case CHAR_SEQUENCE: 1389 TextUtils.writeToParcel((CharSequence)this.value, out, flags); 1390 break; 1391 case BUNDLE: 1392 out.writeBundle((Bundle) this.value); 1393 break; 1394 case URI: 1395 case BITMAP: 1396 case INTENT: 1397 case COLOR_STATE_LIST: 1398 case ICON: 1399 out.writeTypedObject((Parcelable) this.value, flags); 1400 break; 1401 default: 1402 break; 1403 } 1404 } 1405 getParameterType()1406 private Class<?> getParameterType() { 1407 switch (this.type) { 1408 case BOOLEAN: 1409 return boolean.class; 1410 case BYTE: 1411 return byte.class; 1412 case SHORT: 1413 return short.class; 1414 case INT: 1415 return int.class; 1416 case LONG: 1417 return long.class; 1418 case FLOAT: 1419 return float.class; 1420 case DOUBLE: 1421 return double.class; 1422 case CHAR: 1423 return char.class; 1424 case STRING: 1425 return String.class; 1426 case CHAR_SEQUENCE: 1427 return CharSequence.class; 1428 case URI: 1429 return Uri.class; 1430 case BITMAP: 1431 return Bitmap.class; 1432 case BUNDLE: 1433 return Bundle.class; 1434 case INTENT: 1435 return Intent.class; 1436 case COLOR_STATE_LIST: 1437 return ColorStateList.class; 1438 case ICON: 1439 return Icon.class; 1440 default: 1441 return null; 1442 } 1443 } 1444 1445 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1446 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1447 final View view = root.findViewById(viewId); 1448 if (view == null) return; 1449 1450 Class<?> param = getParameterType(); 1451 if (param == null) { 1452 throw new ActionException("bad type: " + this.type); 1453 } 1454 try { 1455 getMethod(view, this.methodName, param, false /* async */).invoke(view, this.value); 1456 } catch (Throwable ex) { 1457 throw new ActionException(ex); 1458 } 1459 } 1460 1461 @Override initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)1462 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1463 final View view = root.findViewById(viewId); 1464 if (view == null) return ACTION_NOOP; 1465 1466 Class<?> param = getParameterType(); 1467 if (param == null) { 1468 throw new ActionException("bad type: " + this.type); 1469 } 1470 1471 try { 1472 MethodHandle method = getMethod(view, this.methodName, param, true /* async */); 1473 1474 if (method != null) { 1475 Runnable endAction = (Runnable) method.invoke(view, this.value); 1476 if (endAction == null) { 1477 return ACTION_NOOP; 1478 } else { 1479 // Special case view stub 1480 if (endAction instanceof ViewStub.ViewReplaceRunnable) { 1481 root.createTree(); 1482 // Replace child tree 1483 root.findViewTreeById(viewId).replaceView( 1484 ((ViewStub.ViewReplaceRunnable) endAction).view); 1485 } 1486 return new RunnableAction(endAction); 1487 } 1488 } 1489 } catch (Throwable ex) { 1490 throw new ActionException(ex); 1491 } 1492 1493 return this; 1494 } 1495 mergeBehavior()1496 public int mergeBehavior() { 1497 // smoothScrollBy is cumulative, everything else overwites. 1498 if (methodName.equals("smoothScrollBy")) { 1499 return MERGE_APPEND; 1500 } else { 1501 return MERGE_REPLACE; 1502 } 1503 } 1504 1505 @Override getActionTag()1506 public int getActionTag() { 1507 return REFLECTION_ACTION_TAG; 1508 } 1509 1510 @Override getUniqueKey()1511 public String getUniqueKey() { 1512 // Each type of reflection action corresponds to a setter, so each should be seen as 1513 // unique from the standpoint of merging. 1514 return super.getUniqueKey() + this.methodName + this.type; 1515 } 1516 1517 @Override prefersAsyncApply()1518 public boolean prefersAsyncApply() { 1519 return this.type == URI || this.type == ICON; 1520 } 1521 1522 @Override visitUris(@onNull Consumer<Uri> visitor)1523 public void visitUris(@NonNull Consumer<Uri> visitor) { 1524 switch (this.type) { 1525 case URI: 1526 final Uri uri = (Uri) this.value; 1527 visitor.accept(uri); 1528 break; 1529 case ICON: 1530 final Icon icon = (Icon) this.value; 1531 visitIconUri(icon, visitor); 1532 break; 1533 } 1534 } 1535 } 1536 1537 /** 1538 * This is only used for async execution of actions and it not parcelable. 1539 */ 1540 private static final class RunnableAction extends RuntimeAction { 1541 private final Runnable mRunnable; 1542 RunnableAction(Runnable r)1543 RunnableAction(Runnable r) { 1544 mRunnable = r; 1545 } 1546 1547 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1548 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1549 mRunnable.run(); 1550 } 1551 } 1552 configureRemoteViewsAsChild(RemoteViews rv)1553 private void configureRemoteViewsAsChild(RemoteViews rv) { 1554 rv.setBitmapCache(mBitmapCache); 1555 rv.setNotRoot(); 1556 } 1557 setNotRoot()1558 void setNotRoot() { 1559 mIsRoot = false; 1560 } 1561 1562 /** 1563 * ViewGroup methods that are related to adding Views. 1564 */ 1565 private class ViewGroupActionAdd extends Action { 1566 @UnsupportedAppUsage 1567 private RemoteViews mNestedViews; 1568 private int mIndex; 1569 ViewGroupActionAdd(int viewId, RemoteViews nestedViews)1570 ViewGroupActionAdd(int viewId, RemoteViews nestedViews) { 1571 this(viewId, nestedViews, -1 /* index */); 1572 } 1573 ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index)1574 ViewGroupActionAdd(int viewId, RemoteViews nestedViews, int index) { 1575 this.viewId = viewId; 1576 mNestedViews = nestedViews; 1577 mIndex = index; 1578 if (nestedViews != null) { 1579 configureRemoteViewsAsChild(nestedViews); 1580 } 1581 } 1582 ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, Map<Class, Object> classCookies)1583 ViewGroupActionAdd(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, 1584 int depth, Map<Class, Object> classCookies) { 1585 viewId = parcel.readInt(); 1586 mIndex = parcel.readInt(); 1587 mNestedViews = new RemoteViews(parcel, bitmapCache, info, depth, classCookies); 1588 mNestedViews.addFlags(mApplyFlags); 1589 } 1590 writeToParcel(Parcel dest, int flags)1591 public void writeToParcel(Parcel dest, int flags) { 1592 dest.writeInt(viewId); 1593 dest.writeInt(mIndex); 1594 mNestedViews.writeToParcel(dest, flags); 1595 } 1596 1597 @Override hasSameAppInfo(ApplicationInfo parentInfo)1598 public boolean hasSameAppInfo(ApplicationInfo parentInfo) { 1599 return mNestedViews.hasSameAppInfo(parentInfo); 1600 } 1601 1602 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1603 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1604 final Context context = root.getContext(); 1605 final ViewGroup target = root.findViewById(viewId); 1606 1607 if (target == null) { 1608 return; 1609 } 1610 1611 // Inflate nested views and add as children 1612 target.addView(mNestedViews.apply(context, target, handler), mIndex); 1613 } 1614 1615 @Override initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler)1616 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1617 // In the async implementation, update the view tree so that subsequent calls to 1618 // findViewById return the current view. 1619 root.createTree(); 1620 ViewTree target = root.findViewTreeById(viewId); 1621 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 1622 return ACTION_NOOP; 1623 } 1624 final ViewGroup targetVg = (ViewGroup) target.mRoot; 1625 1626 // Inflate nested views and perform all the async tasks for the child remoteView. 1627 final Context context = root.mRoot.getContext(); 1628 final AsyncApplyTask task = mNestedViews.getAsyncApplyTask( 1629 context, targetVg, null, handler); 1630 final ViewTree tree = task.doInBackground(); 1631 1632 if (tree == null) { 1633 throw new ActionException(task.mError); 1634 } 1635 1636 // Update the global view tree, so that next call to findViewTreeById 1637 // goes through the subtree as well. 1638 target.addChild(tree, mIndex); 1639 1640 return new RuntimeAction() { 1641 @Override 1642 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) 1643 throws ActionException { 1644 task.onPostExecute(tree); 1645 targetVg.addView(task.mResult, mIndex); 1646 } 1647 }; 1648 } 1649 1650 @Override setBitmapCache(BitmapCache bitmapCache)1651 public void setBitmapCache(BitmapCache bitmapCache) { 1652 mNestedViews.setBitmapCache(bitmapCache); 1653 } 1654 1655 @Override mergeBehavior()1656 public int mergeBehavior() { 1657 return MERGE_APPEND; 1658 } 1659 1660 @Override prefersAsyncApply()1661 public boolean prefersAsyncApply() { 1662 return mNestedViews.prefersAsyncApply(); 1663 } 1664 1665 @Override getActionTag()1666 public int getActionTag() { 1667 return VIEW_GROUP_ACTION_ADD_TAG; 1668 } 1669 } 1670 1671 /** 1672 * ViewGroup methods related to removing child views. 1673 */ 1674 private class ViewGroupActionRemove extends Action { 1675 /** 1676 * Id that indicates that all child views of the affected ViewGroup should be removed. 1677 * 1678 * <p>Using -2 because the default id is -1. This avoids accidentally matching that. 1679 */ 1680 private static final int REMOVE_ALL_VIEWS_ID = -2; 1681 1682 private int mViewIdToKeep; 1683 1684 ViewGroupActionRemove(int viewId) { 1685 this(viewId, REMOVE_ALL_VIEWS_ID); 1686 } 1687 1688 ViewGroupActionRemove(int viewId, int viewIdToKeep) { 1689 this.viewId = viewId; 1690 mViewIdToKeep = viewIdToKeep; 1691 } 1692 1693 ViewGroupActionRemove(Parcel parcel) { 1694 viewId = parcel.readInt(); 1695 mViewIdToKeep = parcel.readInt(); 1696 } 1697 1698 public void writeToParcel(Parcel dest, int flags) { 1699 dest.writeInt(viewId); 1700 dest.writeInt(mViewIdToKeep); 1701 } 1702 1703 @Override 1704 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1705 final ViewGroup target = root.findViewById(viewId); 1706 1707 if (target == null) { 1708 return; 1709 } 1710 1711 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 1712 target.removeAllViews(); 1713 return; 1714 } 1715 1716 removeAllViewsExceptIdToKeep(target); 1717 } 1718 1719 @Override 1720 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1721 // In the async implementation, update the view tree so that subsequent calls to 1722 // findViewById return the current view. 1723 root.createTree(); 1724 ViewTree target = root.findViewTreeById(viewId); 1725 1726 if ((target == null) || !(target.mRoot instanceof ViewGroup)) { 1727 return ACTION_NOOP; 1728 } 1729 1730 final ViewGroup targetVg = (ViewGroup) target.mRoot; 1731 1732 // Clear all children when nested views omitted 1733 target.mChildren = null; 1734 return new RuntimeAction() { 1735 @Override 1736 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) 1737 throws ActionException { 1738 if (mViewIdToKeep == REMOVE_ALL_VIEWS_ID) { 1739 targetVg.removeAllViews(); 1740 return; 1741 } 1742 1743 removeAllViewsExceptIdToKeep(targetVg); 1744 } 1745 }; 1746 } 1747 1748 /** 1749 * Iterates through the children in the given ViewGroup and removes all the views that 1750 * do not have an id of {@link #mViewIdToKeep}. 1751 */ 1752 private void removeAllViewsExceptIdToKeep(ViewGroup viewGroup) { 1753 // Otherwise, remove all the views that do not match the id to keep. 1754 int index = viewGroup.getChildCount() - 1; 1755 while (index >= 0) { 1756 if (viewGroup.getChildAt(index).getId() != mViewIdToKeep) { 1757 viewGroup.removeViewAt(index); 1758 } 1759 index--; 1760 } 1761 } 1762 1763 @Override 1764 public int getActionTag() { 1765 return VIEW_GROUP_ACTION_REMOVE_TAG; 1766 } 1767 1768 @Override 1769 public int mergeBehavior() { 1770 return MERGE_APPEND; 1771 } 1772 } 1773 1774 /** 1775 * Helper action to set compound drawables on a TextView. Supports relative 1776 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 1777 */ 1778 private class TextViewDrawableAction extends Action { 1779 public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) { 1780 this.viewId = viewId; 1781 this.isRelative = isRelative; 1782 this.useIcons = false; 1783 this.d1 = d1; 1784 this.d2 = d2; 1785 this.d3 = d3; 1786 this.d4 = d4; 1787 } 1788 1789 public TextViewDrawableAction(int viewId, boolean isRelative, 1790 Icon i1, Icon i2, Icon i3, Icon i4) { 1791 this.viewId = viewId; 1792 this.isRelative = isRelative; 1793 this.useIcons = true; 1794 this.i1 = i1; 1795 this.i2 = i2; 1796 this.i3 = i3; 1797 this.i4 = i4; 1798 } 1799 1800 public TextViewDrawableAction(Parcel parcel) { 1801 viewId = parcel.readInt(); 1802 isRelative = (parcel.readInt() != 0); 1803 useIcons = (parcel.readInt() != 0); 1804 if (useIcons) { 1805 i1 = parcel.readTypedObject(Icon.CREATOR); 1806 i2 = parcel.readTypedObject(Icon.CREATOR); 1807 i3 = parcel.readTypedObject(Icon.CREATOR); 1808 i4 = parcel.readTypedObject(Icon.CREATOR); 1809 } else { 1810 d1 = parcel.readInt(); 1811 d2 = parcel.readInt(); 1812 d3 = parcel.readInt(); 1813 d4 = parcel.readInt(); 1814 } 1815 } 1816 1817 public void writeToParcel(Parcel dest, int flags) { 1818 dest.writeInt(viewId); 1819 dest.writeInt(isRelative ? 1 : 0); 1820 dest.writeInt(useIcons ? 1 : 0); 1821 if (useIcons) { 1822 dest.writeTypedObject(i1, 0); 1823 dest.writeTypedObject(i2, 0); 1824 dest.writeTypedObject(i3, 0); 1825 dest.writeTypedObject(i4, 0); 1826 } else { 1827 dest.writeInt(d1); 1828 dest.writeInt(d2); 1829 dest.writeInt(d3); 1830 dest.writeInt(d4); 1831 } 1832 } 1833 1834 @Override 1835 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1836 final TextView target = root.findViewById(viewId); 1837 if (target == null) return; 1838 if (drawablesLoaded) { 1839 if (isRelative) { 1840 target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4); 1841 } else { 1842 target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4); 1843 } 1844 } else if (useIcons) { 1845 final Context ctx = target.getContext(); 1846 final Drawable id1 = i1 == null ? null : i1.loadDrawable(ctx); 1847 final Drawable id2 = i2 == null ? null : i2.loadDrawable(ctx); 1848 final Drawable id3 = i3 == null ? null : i3.loadDrawable(ctx); 1849 final Drawable id4 = i4 == null ? null : i4.loadDrawable(ctx); 1850 if (isRelative) { 1851 target.setCompoundDrawablesRelativeWithIntrinsicBounds(id1, id2, id3, id4); 1852 } else { 1853 target.setCompoundDrawablesWithIntrinsicBounds(id1, id2, id3, id4); 1854 } 1855 } else { 1856 if (isRelative) { 1857 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4); 1858 } else { 1859 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4); 1860 } 1861 } 1862 } 1863 1864 @Override 1865 public Action initActionAsync(ViewTree root, ViewGroup rootParent, OnClickHandler handler) { 1866 final TextView target = root.findViewById(viewId); 1867 if (target == null) return ACTION_NOOP; 1868 1869 TextViewDrawableAction copy = useIcons ? 1870 new TextViewDrawableAction(viewId, isRelative, i1, i2, i3, i4) : 1871 new TextViewDrawableAction(viewId, isRelative, d1, d2, d3, d4); 1872 1873 // Load the drawables on the background thread. 1874 copy.drawablesLoaded = true; 1875 final Context ctx = target.getContext(); 1876 1877 if (useIcons) { 1878 copy.id1 = i1 == null ? null : i1.loadDrawable(ctx); 1879 copy.id2 = i2 == null ? null : i2.loadDrawable(ctx); 1880 copy.id3 = i3 == null ? null : i3.loadDrawable(ctx); 1881 copy.id4 = i4 == null ? null : i4.loadDrawable(ctx); 1882 } else { 1883 copy.id1 = d1 == 0 ? null : ctx.getDrawable(d1); 1884 copy.id2 = d2 == 0 ? null : ctx.getDrawable(d2); 1885 copy.id3 = d3 == 0 ? null : ctx.getDrawable(d3); 1886 copy.id4 = d4 == 0 ? null : ctx.getDrawable(d4); 1887 } 1888 return copy; 1889 } 1890 1891 @Override 1892 public boolean prefersAsyncApply() { 1893 return useIcons; 1894 } 1895 1896 @Override 1897 public int getActionTag() { 1898 return TEXT_VIEW_DRAWABLE_ACTION_TAG; 1899 } 1900 1901 @Override 1902 public void visitUris(@NonNull Consumer<Uri> visitor) { 1903 if (useIcons) { 1904 visitIconUri(i1, visitor); 1905 visitIconUri(i2, visitor); 1906 visitIconUri(i3, visitor); 1907 visitIconUri(i4, visitor); 1908 } 1909 } 1910 1911 boolean isRelative = false; 1912 boolean useIcons = false; 1913 int d1, d2, d3, d4; 1914 Icon i1, i2, i3, i4; 1915 1916 boolean drawablesLoaded = false; 1917 Drawable id1, id2, id3, id4; 1918 } 1919 1920 /** 1921 * Helper action to set text size on a TextView in any supported units. 1922 */ 1923 private class TextViewSizeAction extends Action { 1924 public TextViewSizeAction(int viewId, int units, float size) { 1925 this.viewId = viewId; 1926 this.units = units; 1927 this.size = size; 1928 } 1929 1930 public TextViewSizeAction(Parcel parcel) { 1931 viewId = parcel.readInt(); 1932 units = parcel.readInt(); 1933 size = parcel.readFloat(); 1934 } 1935 1936 public void writeToParcel(Parcel dest, int flags) { 1937 dest.writeInt(viewId); 1938 dest.writeInt(units); 1939 dest.writeFloat(size); 1940 } 1941 1942 @Override 1943 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1944 final TextView target = root.findViewById(viewId); 1945 if (target == null) return; 1946 target.setTextSize(units, size); 1947 } 1948 1949 @Override 1950 public int getActionTag() { 1951 return TEXT_VIEW_SIZE_ACTION_TAG; 1952 } 1953 1954 int units; 1955 float size; 1956 } 1957 1958 /** 1959 * Helper action to set padding on a View. 1960 */ 1961 private class ViewPaddingAction extends Action { 1962 public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) { 1963 this.viewId = viewId; 1964 this.left = left; 1965 this.top = top; 1966 this.right = right; 1967 this.bottom = bottom; 1968 } 1969 1970 public ViewPaddingAction(Parcel parcel) { 1971 viewId = parcel.readInt(); 1972 left = parcel.readInt(); 1973 top = parcel.readInt(); 1974 right = parcel.readInt(); 1975 bottom = parcel.readInt(); 1976 } 1977 1978 public void writeToParcel(Parcel dest, int flags) { 1979 dest.writeInt(viewId); 1980 dest.writeInt(left); 1981 dest.writeInt(top); 1982 dest.writeInt(right); 1983 dest.writeInt(bottom); 1984 } 1985 1986 @Override 1987 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1988 final View target = root.findViewById(viewId); 1989 if (target == null) return; 1990 target.setPadding(left, top, right, bottom); 1991 } 1992 1993 @Override 1994 public int getActionTag() { 1995 return VIEW_PADDING_ACTION_TAG; 1996 } 1997 1998 int left, top, right, bottom; 1999 } 2000 2001 /** 2002 * Helper action to set layout params on a View. 2003 */ 2004 private static class LayoutParamAction extends Action { 2005 2006 /** Set marginEnd */ 2007 public static final int LAYOUT_MARGIN_END_DIMEN = 1; 2008 /** Set width */ 2009 public static final int LAYOUT_WIDTH = 2; 2010 public static final int LAYOUT_MARGIN_BOTTOM_DIMEN = 3; 2011 public static final int LAYOUT_MARGIN_END = 4; 2012 2013 final int mProperty; 2014 final int mValue; 2015 2016 /** 2017 * @param viewId ID of the view alter 2018 * @param property which layout parameter to alter 2019 * @param value new value of the layout parameter 2020 */ 2021 public LayoutParamAction(int viewId, int property, int value) { 2022 this.viewId = viewId; 2023 this.mProperty = property; 2024 this.mValue = value; 2025 } 2026 2027 public LayoutParamAction(Parcel parcel) { 2028 viewId = parcel.readInt(); 2029 mProperty = parcel.readInt(); 2030 mValue = parcel.readInt(); 2031 } 2032 2033 public void writeToParcel(Parcel dest, int flags) { 2034 dest.writeInt(viewId); 2035 dest.writeInt(mProperty); 2036 dest.writeInt(mValue); 2037 } 2038 2039 @Override 2040 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 2041 final View target = root.findViewById(viewId); 2042 if (target == null) { 2043 return; 2044 } 2045 ViewGroup.LayoutParams layoutParams = target.getLayoutParams(); 2046 if (layoutParams == null) { 2047 return; 2048 } 2049 int value = mValue; 2050 switch (mProperty) { 2051 case LAYOUT_MARGIN_END_DIMEN: 2052 value = resolveDimenPixelOffset(target, mValue); 2053 // fall-through 2054 case LAYOUT_MARGIN_END: 2055 if (layoutParams instanceof ViewGroup.MarginLayoutParams) { 2056 ((ViewGroup.MarginLayoutParams) layoutParams).setMarginEnd(value); 2057 target.setLayoutParams(layoutParams); 2058 } 2059 break; 2060 case LAYOUT_MARGIN_BOTTOM_DIMEN: 2061 if (layoutParams instanceof ViewGroup.MarginLayoutParams) { 2062 int resolved = resolveDimenPixelOffset(target, mValue); 2063 ((ViewGroup.MarginLayoutParams) layoutParams).bottomMargin = resolved; 2064 target.setLayoutParams(layoutParams); 2065 } 2066 break; 2067 case LAYOUT_WIDTH: 2068 layoutParams.width = mValue; 2069 target.setLayoutParams(layoutParams); 2070 break; 2071 default: 2072 throw new IllegalArgumentException("Unknown property " + mProperty); 2073 } 2074 } 2075 2076 private static int resolveDimenPixelOffset(View target, int value) { 2077 if (value == 0) { 2078 return 0; 2079 } 2080 return target.getContext().getResources().getDimensionPixelOffset(value); 2081 } 2082 2083 @Override 2084 public int getActionTag() { 2085 return LAYOUT_PARAM_ACTION_TAG; 2086 } 2087 2088 @Override 2089 public String getUniqueKey() { 2090 return super.getUniqueKey() + mProperty; 2091 } 2092 } 2093 2094 /** 2095 * Helper action to add a view tag with RemoteInputs. 2096 */ 2097 private class SetRemoteInputsAction extends Action { 2098 2099 public SetRemoteInputsAction(int viewId, RemoteInput[] remoteInputs) { 2100 this.viewId = viewId; 2101 this.remoteInputs = remoteInputs; 2102 } 2103 2104 public SetRemoteInputsAction(Parcel parcel) { 2105 viewId = parcel.readInt(); 2106 remoteInputs = parcel.createTypedArray(RemoteInput.CREATOR); 2107 } 2108 2109 public void writeToParcel(Parcel dest, int flags) { 2110 dest.writeInt(viewId); 2111 dest.writeTypedArray(remoteInputs, flags); 2112 } 2113 2114 @Override 2115 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 2116 final View target = root.findViewById(viewId); 2117 if (target == null) return; 2118 2119 target.setTagInternal(R.id.remote_input_tag, remoteInputs); 2120 } 2121 2122 @Override 2123 public int getActionTag() { 2124 return SET_REMOTE_INPUTS_ACTION_TAG; 2125 } 2126 2127 final Parcelable[] remoteInputs; 2128 } 2129 2130 /** 2131 * Helper action to override all textViewColors 2132 */ 2133 private class OverrideTextColorsAction extends Action { 2134 2135 private final int textColor; 2136 2137 public OverrideTextColorsAction(int textColor) { 2138 this.textColor = textColor; 2139 } 2140 2141 public OverrideTextColorsAction(Parcel parcel) { 2142 textColor = parcel.readInt(); 2143 } 2144 2145 public void writeToParcel(Parcel dest, int flags) { 2146 dest.writeInt(textColor); 2147 } 2148 2149 @Override 2150 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 2151 // Let's traverse the viewtree and override all textColors! 2152 Stack<View> viewsToProcess = new Stack<>(); 2153 viewsToProcess.add(root); 2154 while (!viewsToProcess.isEmpty()) { 2155 View v = viewsToProcess.pop(); 2156 if (v instanceof TextView) { 2157 TextView textView = (TextView) v; 2158 textView.setText(ContrastColorUtil.clearColorSpans(textView.getText())); 2159 textView.setTextColor(textColor); 2160 } 2161 if (v instanceof ViewGroup) { 2162 ViewGroup viewGroup = (ViewGroup) v; 2163 for (int i = 0; i < viewGroup.getChildCount(); i++) { 2164 viewsToProcess.push(viewGroup.getChildAt(i)); 2165 } 2166 } 2167 } 2168 } 2169 2170 @Override 2171 public int getActionTag() { 2172 return OVERRIDE_TEXT_COLORS_TAG; 2173 } 2174 } 2175 2176 private class SetIntTagAction extends Action { 2177 private final int mViewId; 2178 private final int mKey; 2179 private final int mTag; 2180 2181 SetIntTagAction(int viewId, int key, int tag) { 2182 mViewId = viewId; 2183 mKey = key; 2184 mTag = tag; 2185 } 2186 2187 SetIntTagAction(Parcel parcel) { 2188 mViewId = parcel.readInt(); 2189 mKey = parcel.readInt(); 2190 mTag = parcel.readInt(); 2191 } 2192 2193 public void writeToParcel(Parcel dest, int flags) { 2194 dest.writeInt(mViewId); 2195 dest.writeInt(mKey); 2196 dest.writeInt(mTag); 2197 } 2198 2199 @Override 2200 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 2201 final View target = root.findViewById(mViewId); 2202 if (target == null) return; 2203 2204 target.setTagInternal(mKey, mTag); 2205 } 2206 2207 @Override 2208 public int getActionTag() { 2209 return SET_INT_TAG_TAG; 2210 } 2211 } 2212 2213 /** 2214 * Create a new RemoteViews object that will display the views contained 2215 * in the specified layout file. 2216 * 2217 * @param packageName Name of the package that contains the layout resource 2218 * @param layoutId The id of the layout resource 2219 */ 2220 public RemoteViews(String packageName, int layoutId) { 2221 this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId); 2222 } 2223 2224 /** 2225 * Create a new RemoteViews object that will display the views contained 2226 * in the specified layout file. 2227 * 2228 * @param packageName Name of the package that contains the layout resource. 2229 * @param userId The user under which the package is running. 2230 * @param layoutId The id of the layout resource. 2231 * 2232 * @hide 2233 */ 2234 public RemoteViews(String packageName, int userId, @LayoutRes int layoutId) { 2235 this(getApplicationInfo(packageName, userId), layoutId); 2236 } 2237 2238 /** 2239 * Create a new RemoteViews object that will display the views contained 2240 * in the specified layout file. 2241 * 2242 * @param application The application whose content is shown by the views. 2243 * @param layoutId The id of the layout resource. 2244 * 2245 * @hide 2246 */ 2247 protected RemoteViews(ApplicationInfo application, @LayoutRes int layoutId) { 2248 mApplication = application; 2249 mLayoutId = layoutId; 2250 mBitmapCache = new BitmapCache(); 2251 mClassCookies = null; 2252 } 2253 2254 private boolean hasLandscapeAndPortraitLayouts() { 2255 return (mLandscape != null) && (mPortrait != null); 2256 } 2257 2258 /** 2259 * Create a new RemoteViews object that will inflate as the specified 2260 * landspace or portrait RemoteViews, depending on the current configuration. 2261 * 2262 * @param landscape The RemoteViews to inflate in landscape configuration 2263 * @param portrait The RemoteViews to inflate in portrait configuration 2264 */ 2265 public RemoteViews(RemoteViews landscape, RemoteViews portrait) { 2266 if (landscape == null || portrait == null) { 2267 throw new RuntimeException("Both RemoteViews must be non-null"); 2268 } 2269 if (!landscape.hasSameAppInfo(portrait.mApplication)) { 2270 throw new RuntimeException("Both RemoteViews must share the same package and user"); 2271 } 2272 mApplication = portrait.mApplication; 2273 mLayoutId = portrait.mLayoutId; 2274 mLightBackgroundLayoutId = portrait.mLightBackgroundLayoutId; 2275 2276 mLandscape = landscape; 2277 mPortrait = portrait; 2278 2279 mBitmapCache = new BitmapCache(); 2280 configureRemoteViewsAsChild(landscape); 2281 configureRemoteViewsAsChild(portrait); 2282 2283 mClassCookies = (portrait.mClassCookies != null) 2284 ? portrait.mClassCookies : landscape.mClassCookies; 2285 } 2286 2287 /** 2288 * Creates a copy of another RemoteViews. 2289 */ 2290 public RemoteViews(RemoteViews src) { 2291 mBitmapCache = src.mBitmapCache; 2292 mApplication = src.mApplication; 2293 mIsRoot = src.mIsRoot; 2294 mLayoutId = src.mLayoutId; 2295 mLightBackgroundLayoutId = src.mLightBackgroundLayoutId; 2296 mApplyFlags = src.mApplyFlags; 2297 mClassCookies = src.mClassCookies; 2298 2299 if (src.hasLandscapeAndPortraitLayouts()) { 2300 mLandscape = new RemoteViews(src.mLandscape); 2301 mPortrait = new RemoteViews(src.mPortrait); 2302 } 2303 2304 if (src.mActions != null) { 2305 Parcel p = Parcel.obtain(); 2306 p.putClassCookies(mClassCookies); 2307 src.writeActionsToParcel(p); 2308 p.setDataPosition(0); 2309 // Since src is already in memory, we do not care about stack overflow as it has 2310 // already been read once. 2311 readActionsFromParcel(p, 0); 2312 p.recycle(); 2313 } 2314 2315 // Now that everything is initialized and duplicated, setting a new BitmapCache will 2316 // re-initialize the cache. 2317 setBitmapCache(new BitmapCache()); 2318 } 2319 2320 /** 2321 * Reads a RemoteViews object from a parcel. 2322 * 2323 * @param parcel 2324 */ 2325 public RemoteViews(Parcel parcel) { 2326 this(parcel, null, null, 0, null); 2327 } 2328 2329 private RemoteViews(Parcel parcel, BitmapCache bitmapCache, ApplicationInfo info, int depth, 2330 Map<Class, Object> classCookies) { 2331 if (depth > MAX_NESTED_VIEWS 2332 && (UserHandle.getAppId(Binder.getCallingUid()) != Process.SYSTEM_UID)) { 2333 throw new IllegalArgumentException("Too many nested views."); 2334 } 2335 depth++; 2336 2337 int mode = parcel.readInt(); 2338 2339 // We only store a bitmap cache in the root of the RemoteViews. 2340 if (bitmapCache == null) { 2341 mBitmapCache = new BitmapCache(parcel); 2342 // Store the class cookies such that they are available when we clone this RemoteView. 2343 mClassCookies = parcel.copyClassCookies(); 2344 } else { 2345 setBitmapCache(bitmapCache); 2346 mClassCookies = classCookies; 2347 setNotRoot(); 2348 } 2349 2350 if (mode == MODE_NORMAL) { 2351 mApplication = parcel.readInt() == 0 ? info : 2352 ApplicationInfo.CREATOR.createFromParcel(parcel); 2353 mLayoutId = parcel.readInt(); 2354 mLightBackgroundLayoutId = parcel.readInt(); 2355 2356 readActionsFromParcel(parcel, depth); 2357 } else { 2358 // MODE_HAS_LANDSCAPE_AND_PORTRAIT 2359 mLandscape = new RemoteViews(parcel, mBitmapCache, info, depth, mClassCookies); 2360 mPortrait = new RemoteViews(parcel, mBitmapCache, mLandscape.mApplication, depth, 2361 mClassCookies); 2362 mApplication = mPortrait.mApplication; 2363 mLayoutId = mPortrait.mLayoutId; 2364 mLightBackgroundLayoutId = mPortrait.mLightBackgroundLayoutId; 2365 } 2366 mApplyFlags = parcel.readInt(); 2367 } 2368 2369 private void readActionsFromParcel(Parcel parcel, int depth) { 2370 int count = parcel.readInt(); 2371 if (count > 0) { 2372 mActions = new ArrayList<>(count); 2373 for (int i = 0; i < count; i++) { 2374 mActions.add(getActionFromParcel(parcel, depth)); 2375 } 2376 } 2377 } 2378 2379 private Action getActionFromParcel(Parcel parcel, int depth) { 2380 int tag = parcel.readInt(); 2381 switch (tag) { 2382 case SET_ON_CLICK_RESPONSE_TAG: 2383 return new SetOnClickResponse(parcel); 2384 case SET_DRAWABLE_TINT_TAG: 2385 return new SetDrawableTint(parcel); 2386 case REFLECTION_ACTION_TAG: 2387 return new ReflectionAction(parcel); 2388 case VIEW_GROUP_ACTION_ADD_TAG: 2389 return new ViewGroupActionAdd(parcel, mBitmapCache, mApplication, depth, 2390 mClassCookies); 2391 case VIEW_GROUP_ACTION_REMOVE_TAG: 2392 return new ViewGroupActionRemove(parcel); 2393 case VIEW_CONTENT_NAVIGATION_TAG: 2394 return new ViewContentNavigation(parcel); 2395 case SET_EMPTY_VIEW_ACTION_TAG: 2396 return new SetEmptyView(parcel); 2397 case SET_PENDING_INTENT_TEMPLATE_TAG: 2398 return new SetPendingIntentTemplate(parcel); 2399 case SET_REMOTE_VIEW_ADAPTER_INTENT_TAG: 2400 return new SetRemoteViewsAdapterIntent(parcel); 2401 case TEXT_VIEW_DRAWABLE_ACTION_TAG: 2402 return new TextViewDrawableAction(parcel); 2403 case TEXT_VIEW_SIZE_ACTION_TAG: 2404 return new TextViewSizeAction(parcel); 2405 case VIEW_PADDING_ACTION_TAG: 2406 return new ViewPaddingAction(parcel); 2407 case BITMAP_REFLECTION_ACTION_TAG: 2408 return new BitmapReflectionAction(parcel); 2409 case SET_REMOTE_VIEW_ADAPTER_LIST_TAG: 2410 return new SetRemoteViewsAdapterList(parcel); 2411 case SET_REMOTE_INPUTS_ACTION_TAG: 2412 return new SetRemoteInputsAction(parcel); 2413 case LAYOUT_PARAM_ACTION_TAG: 2414 return new LayoutParamAction(parcel); 2415 case OVERRIDE_TEXT_COLORS_TAG: 2416 return new OverrideTextColorsAction(parcel); 2417 case SET_RIPPLE_DRAWABLE_COLOR_TAG: 2418 return new SetRippleDrawableColor(parcel); 2419 case SET_INT_TAG_TAG: 2420 return new SetIntTagAction(parcel); 2421 default: 2422 throw new ActionException("Tag " + tag + " not found"); 2423 } 2424 }; 2425 2426 /** 2427 * Returns a deep copy of the RemoteViews object. The RemoteView may not be 2428 * attached to another RemoteView -- it must be the root of a hierarchy. 2429 * 2430 * @deprecated use {@link #RemoteViews(RemoteViews)} instead. 2431 * @throws IllegalStateException if this is not the root of a RemoteView 2432 * hierarchy 2433 */ 2434 @Override 2435 @Deprecated 2436 public RemoteViews clone() { 2437 Preconditions.checkState(mIsRoot, "RemoteView has been attached to another RemoteView. " 2438 + "May only clone the root of a RemoteView hierarchy."); 2439 2440 return new RemoteViews(this); 2441 } 2442 2443 public String getPackage() { 2444 return (mApplication != null) ? mApplication.packageName : null; 2445 } 2446 2447 /** 2448 * Returns the layout id of the root layout associated with this RemoteViews. In the case 2449 * that the RemoteViews has both a landscape and portrait root, this will return the layout 2450 * id associated with the portrait layout. 2451 * 2452 * @return the layout id. 2453 */ 2454 public int getLayoutId() { 2455 return hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT) && (mLightBackgroundLayoutId != 0) 2456 ? mLightBackgroundLayoutId : mLayoutId; 2457 } 2458 2459 /** 2460 * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. 2461 */ 2462 private void setBitmapCache(BitmapCache bitmapCache) { 2463 mBitmapCache = bitmapCache; 2464 if (!hasLandscapeAndPortraitLayouts()) { 2465 if (mActions != null) { 2466 final int count = mActions.size(); 2467 for (int i= 0; i < count; ++i) { 2468 mActions.get(i).setBitmapCache(bitmapCache); 2469 } 2470 } 2471 } else { 2472 mLandscape.setBitmapCache(bitmapCache); 2473 mPortrait.setBitmapCache(bitmapCache); 2474 } 2475 } 2476 2477 /** 2478 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 2479 */ 2480 /** @hide */ 2481 @UnsupportedAppUsage 2482 public int estimateMemoryUsage() { 2483 return mBitmapCache.getBitmapMemory(); 2484 } 2485 2486 /** 2487 * Add an action to be executed on the remote side when apply is called. 2488 * 2489 * @param a The action to add 2490 */ 2491 private void addAction(Action a) { 2492 if (hasLandscapeAndPortraitLayouts()) { 2493 throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + 2494 " layouts cannot be modified. Instead, fully configure the landscape and" + 2495 " portrait layouts individually before constructing the combined layout."); 2496 } 2497 if (mActions == null) { 2498 mActions = new ArrayList<>(); 2499 } 2500 mActions.add(a); 2501 } 2502 2503 /** 2504 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 2505 * given {@link RemoteViews}. This allows users to build "nested" 2506 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 2507 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 2508 * children. 2509 * 2510 * @param viewId The id of the parent {@link ViewGroup} to add child into. 2511 * @param nestedView {@link RemoteViews} that describes the child. 2512 */ 2513 public void addView(int viewId, RemoteViews nestedView) { 2514 addAction(nestedView == null 2515 ? new ViewGroupActionRemove(viewId) 2516 : new ViewGroupActionAdd(viewId, nestedView)); 2517 } 2518 2519 /** 2520 * Equivalent to calling {@link ViewGroup#addView(View, int)} after inflating the 2521 * given {@link RemoteViews}. 2522 * 2523 * @param viewId The id of the parent {@link ViewGroup} to add the child into. 2524 * @param nestedView {@link RemoteViews} of the child to add. 2525 * @param index The position at which to add the child. 2526 * 2527 * @hide 2528 */ 2529 @UnsupportedAppUsage 2530 public void addView(int viewId, RemoteViews nestedView, int index) { 2531 addAction(new ViewGroupActionAdd(viewId, nestedView, index)); 2532 } 2533 2534 /** 2535 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 2536 * 2537 * @param viewId The id of the parent {@link ViewGroup} to remove all 2538 * children from. 2539 */ 2540 public void removeAllViews(int viewId) { 2541 addAction(new ViewGroupActionRemove(viewId)); 2542 } 2543 2544 /** 2545 * Removes all views in the {@link ViewGroup} specified by the {@code viewId} except for any 2546 * child that has the {@code viewIdToKeep} as its id. 2547 * 2548 * @param viewId The id of the parent {@link ViewGroup} to remove children from. 2549 * @param viewIdToKeep The id of a child that should not be removed. 2550 * 2551 * @hide 2552 */ 2553 public void removeAllViewsExceptId(int viewId, int viewIdToKeep) { 2554 addAction(new ViewGroupActionRemove(viewId, viewIdToKeep)); 2555 } 2556 2557 /** 2558 * Equivalent to calling {@link AdapterViewAnimator#showNext()} 2559 * 2560 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} 2561 */ 2562 public void showNext(int viewId) { 2563 addAction(new ViewContentNavigation(viewId, true /* next */)); 2564 } 2565 2566 /** 2567 * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} 2568 * 2569 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} 2570 */ 2571 public void showPrevious(int viewId) { 2572 addAction(new ViewContentNavigation(viewId, false /* next */)); 2573 } 2574 2575 /** 2576 * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} 2577 * 2578 * @param viewId The id of the view on which to call 2579 * {@link AdapterViewAnimator#setDisplayedChild(int)} 2580 */ 2581 public void setDisplayedChild(int viewId, int childIndex) { 2582 setInt(viewId, "setDisplayedChild", childIndex); 2583 } 2584 2585 /** 2586 * Equivalent to calling {@link View#setVisibility(int)} 2587 * 2588 * @param viewId The id of the view whose visibility should change 2589 * @param visibility The new visibility for the view 2590 */ 2591 public void setViewVisibility(int viewId, int visibility) { 2592 setInt(viewId, "setVisibility", visibility); 2593 } 2594 2595 /** 2596 * Equivalent to calling {@link TextView#setText(CharSequence)} 2597 * 2598 * @param viewId The id of the view whose text should change 2599 * @param text The new text for the view 2600 */ 2601 public void setTextViewText(int viewId, CharSequence text) { 2602 setCharSequence(viewId, "setText", text); 2603 } 2604 2605 /** 2606 * Equivalent to calling {@link TextView#setTextSize(int, float)} 2607 * 2608 * @param viewId The id of the view whose text size should change 2609 * @param units The units of size (e.g. COMPLEX_UNIT_SP) 2610 * @param size The size of the text 2611 */ 2612 public void setTextViewTextSize(int viewId, int units, float size) { 2613 addAction(new TextViewSizeAction(viewId, units, size)); 2614 } 2615 2616 /** 2617 * Equivalent to calling 2618 * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. 2619 * 2620 * @param viewId The id of the view whose text should change 2621 * @param left The id of a drawable to place to the left of the text, or 0 2622 * @param top The id of a drawable to place above the text, or 0 2623 * @param right The id of a drawable to place to the right of the text, or 0 2624 * @param bottom The id of a drawable to place below the text, or 0 2625 */ 2626 public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { 2627 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 2628 } 2629 2630 /** 2631 * Equivalent to calling {@link 2632 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. 2633 * 2634 * @param viewId The id of the view whose text should change 2635 * @param start The id of a drawable to place before the text (relative to the 2636 * layout direction), or 0 2637 * @param top The id of a drawable to place above the text, or 0 2638 * @param end The id of a drawable to place after the text, or 0 2639 * @param bottom The id of a drawable to place below the text, or 0 2640 */ 2641 public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { 2642 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 2643 } 2644 2645 /** 2646 * Equivalent to calling {@link 2647 * TextView#setCompoundDrawablesWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 2648 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 2649 * 2650 * @param viewId The id of the view whose text should change 2651 * @param left an Icon to place to the left of the text, or 0 2652 * @param top an Icon to place above the text, or 0 2653 * @param right an Icon to place to the right of the text, or 0 2654 * @param bottom an Icon to place below the text, or 0 2655 * 2656 * @hide 2657 */ 2658 public void setTextViewCompoundDrawables(int viewId, Icon left, Icon top, Icon right, Icon bottom) { 2659 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 2660 } 2661 2662 /** 2663 * Equivalent to calling {@link 2664 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(Drawable, Drawable, Drawable, Drawable)} 2665 * using the drawables yielded by {@link Icon#loadDrawable(Context)}. 2666 * 2667 * @param viewId The id of the view whose text should change 2668 * @param start an Icon to place before the text (relative to the 2669 * layout direction), or 0 2670 * @param top an Icon to place above the text, or 0 2671 * @param end an Icon to place after the text, or 0 2672 * @param bottom an Icon to place below the text, or 0 2673 * 2674 * @hide 2675 */ 2676 public void setTextViewCompoundDrawablesRelative(int viewId, Icon start, Icon top, Icon end, Icon bottom) { 2677 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 2678 } 2679 2680 /** 2681 * Equivalent to calling {@link ImageView#setImageResource(int)} 2682 * 2683 * @param viewId The id of the view whose drawable should change 2684 * @param srcId The new resource id for the drawable 2685 */ 2686 public void setImageViewResource(int viewId, int srcId) { 2687 setInt(viewId, "setImageResource", srcId); 2688 } 2689 2690 /** 2691 * Equivalent to calling {@link ImageView#setImageURI(Uri)} 2692 * 2693 * @param viewId The id of the view whose drawable should change 2694 * @param uri The Uri for the image 2695 */ 2696 public void setImageViewUri(int viewId, Uri uri) { 2697 setUri(viewId, "setImageURI", uri); 2698 } 2699 2700 /** 2701 * Equivalent to calling {@link ImageView#setImageBitmap(Bitmap)} 2702 * 2703 * @param viewId The id of the view whose bitmap should change 2704 * @param bitmap The new Bitmap for the drawable 2705 */ 2706 public void setImageViewBitmap(int viewId, Bitmap bitmap) { 2707 setBitmap(viewId, "setImageBitmap", bitmap); 2708 } 2709 2710 /** 2711 * Equivalent to calling {@link ImageView#setImageIcon(Icon)} 2712 * 2713 * @param viewId The id of the view whose bitmap should change 2714 * @param icon The new Icon for the ImageView 2715 */ 2716 public void setImageViewIcon(int viewId, Icon icon) { 2717 setIcon(viewId, "setImageIcon", icon); 2718 } 2719 2720 /** 2721 * Equivalent to calling {@link AdapterView#setEmptyView(View)} 2722 * 2723 * @param viewId The id of the view on which to set the empty view 2724 * @param emptyViewId The view id of the empty view 2725 */ 2726 public void setEmptyView(int viewId, int emptyViewId) { 2727 addAction(new SetEmptyView(viewId, emptyViewId)); 2728 } 2729 2730 /** 2731 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 2732 * {@link Chronometer#setFormat Chronometer.setFormat}, 2733 * and {@link Chronometer#start Chronometer.start()} or 2734 * {@link Chronometer#stop Chronometer.stop()}. 2735 * 2736 * @param viewId The id of the {@link Chronometer} to change 2737 * @param base The time at which the timer would have read 0:00. This 2738 * time should be based off of 2739 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 2740 * @param format The Chronometer format string, or null to 2741 * simply display the timer value. 2742 * @param started True if you want the clock to be started, false if not. 2743 * 2744 * @see #setChronometerCountDown(int, boolean) 2745 */ 2746 public void setChronometer(int viewId, long base, String format, boolean started) { 2747 setLong(viewId, "setBase", base); 2748 setString(viewId, "setFormat", format); 2749 setBoolean(viewId, "setStarted", started); 2750 } 2751 2752 /** 2753 * Equivalent to calling {@link Chronometer#setCountDown(boolean) Chronometer.setCountDown} on 2754 * the chronometer with the given viewId. 2755 * 2756 * @param viewId The id of the {@link Chronometer} to change 2757 * @param isCountDown True if you want the chronometer to count down to base instead of 2758 * counting up. 2759 */ 2760 public void setChronometerCountDown(int viewId, boolean isCountDown) { 2761 setBoolean(viewId, "setCountDown", isCountDown); 2762 } 2763 2764 /** 2765 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 2766 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 2767 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 2768 * 2769 * If indeterminate is true, then the values for max and progress are ignored. 2770 * 2771 * @param viewId The id of the {@link ProgressBar} to change 2772 * @param max The 100% value for the progress bar 2773 * @param progress The current value of the progress bar. 2774 * @param indeterminate True if the progress bar is indeterminate, 2775 * false if not. 2776 */ 2777 public void setProgressBar(int viewId, int max, int progress, 2778 boolean indeterminate) { 2779 setBoolean(viewId, "setIndeterminate", indeterminate); 2780 if (!indeterminate) { 2781 setInt(viewId, "setMax", max); 2782 setInt(viewId, "setProgress", progress); 2783 } 2784 } 2785 2786 /** 2787 * Equivalent to calling 2788 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 2789 * to launch the provided {@link PendingIntent}. The source bounds 2790 * ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the clicked 2791 * view in screen space. 2792 * Note that any activity options associated with the mPendingIntent may get overridden 2793 * before starting the intent. 2794 * 2795 * When setting the on-click action of items within collections (eg. {@link ListView}, 2796 * {@link StackView} etc.), this method will not work. Instead, use {@link 2797 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)} in conjunction with 2798 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 2799 * 2800 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 2801 * @param pendingIntent The {@link PendingIntent} to send when user clicks 2802 */ 2803 public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { 2804 setOnClickResponse(viewId, RemoteResponse.fromPendingIntent(pendingIntent)); 2805 } 2806 2807 /** 2808 * Equivalent of calling 2809 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 2810 * to launch the provided {@link RemoteResponse}. 2811 * 2812 * @param viewId The id of the view that will trigger the {@link RemoteResponse} when clicked 2813 * @param response The {@link RemoteResponse} to send when user clicks 2814 */ 2815 public void setOnClickResponse(int viewId, @NonNull RemoteResponse response) { 2816 addAction(new SetOnClickResponse(viewId, response)); 2817 } 2818 2819 /** 2820 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 2821 * costly to set PendingIntents on the individual items, and is hence not recommended. Instead 2822 * this method should be used to set a single PendingIntent template on the collection, and 2823 * individual items can differentiate their on-click behavior using 2824 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 2825 * 2826 * @param viewId The id of the collection who's children will use this PendingIntent template 2827 * when clicked 2828 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 2829 * by a child of viewId and executed when that child is clicked 2830 */ 2831 public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { 2832 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 2833 } 2834 2835 /** 2836 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 2837 * costly to set PendingIntents on the individual items, and is hence not recommended. Instead 2838 * a single PendingIntent template can be set on the collection, see {@link 2839 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 2840 * action of a given item can be distinguished by setting a fillInIntent on that item. The 2841 * fillInIntent is then combined with the PendingIntent template in order to determine the final 2842 * intent which will be executed when the item is clicked. This works as follows: any fields 2843 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 2844 * will be overwritten, and the resulting PendingIntent will be used. The rest 2845 * of the PendingIntent template will then be filled in with the associated fields that are 2846 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 2847 * 2848 * @param viewId The id of the view on which to set the fillInIntent 2849 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 2850 * in order to determine the on-click behavior of the view specified by viewId 2851 */ 2852 public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { 2853 setOnClickResponse(viewId, RemoteResponse.fromFillInIntent(fillInIntent)); 2854 } 2855 2856 /** 2857 * @hide 2858 * Equivalent to calling 2859 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 2860 * on the {@link Drawable} of a given view. 2861 * <p> 2862 * 2863 * @param viewId The id of the view that contains the target 2864 * {@link Drawable} 2865 * @param targetBackground If true, apply these parameters to the 2866 * {@link Drawable} returned by 2867 * {@link android.view.View#getBackground()}. Otherwise, assume 2868 * the target view is an {@link ImageView} and apply them to 2869 * {@link ImageView#getDrawable()}. 2870 * @param colorFilter Specify a color for a 2871 * {@link android.graphics.ColorFilter} for this drawable. This will be ignored if 2872 * {@code mode} is {@code null}. 2873 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 2874 * unchanged. 2875 */ 2876 public void setDrawableTint(int viewId, boolean targetBackground, 2877 int colorFilter, @NonNull PorterDuff.Mode mode) { 2878 addAction(new SetDrawableTint(viewId, targetBackground, colorFilter, mode)); 2879 } 2880 2881 /** 2882 * @hide 2883 * Equivalent to calling 2884 * {@link RippleDrawable#setColor(ColorStateList)} on the {@link Drawable} of a given view, 2885 * assuming it's a {@link RippleDrawable}. 2886 * <p> 2887 * 2888 * @param viewId The id of the view that contains the target 2889 * {@link RippleDrawable} 2890 * @param colorStateList Specify a color for a 2891 * {@link ColorStateList} for this drawable. 2892 */ 2893 public void setRippleDrawableColor(int viewId, ColorStateList colorStateList) { 2894 addAction(new SetRippleDrawableColor(viewId, colorStateList)); 2895 } 2896 2897 /** 2898 * @hide 2899 * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}. 2900 * 2901 * @param viewId The id of the view whose tint should change 2902 * @param tint the tint to apply, may be {@code null} to clear tint 2903 */ 2904 public void setProgressTintList(int viewId, ColorStateList tint) { 2905 addAction(new ReflectionAction(viewId, "setProgressTintList", 2906 ReflectionAction.COLOR_STATE_LIST, tint)); 2907 } 2908 2909 /** 2910 * @hide 2911 * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}. 2912 * 2913 * @param viewId The id of the view whose tint should change 2914 * @param tint the tint to apply, may be {@code null} to clear tint 2915 */ 2916 public void setProgressBackgroundTintList(int viewId, ColorStateList tint) { 2917 addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList", 2918 ReflectionAction.COLOR_STATE_LIST, tint)); 2919 } 2920 2921 /** 2922 * @hide 2923 * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}. 2924 * 2925 * @param viewId The id of the view whose tint should change 2926 * @param tint the tint to apply, may be {@code null} to clear tint 2927 */ 2928 public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) { 2929 addAction(new ReflectionAction(viewId, "setIndeterminateTintList", 2930 ReflectionAction.COLOR_STATE_LIST, tint)); 2931 } 2932 2933 /** 2934 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 2935 * 2936 * @param viewId The id of the view whose text color should change 2937 * @param color Sets the text color for all the states (normal, selected, 2938 * focused) to be this color. 2939 */ 2940 public void setTextColor(int viewId, @ColorInt int color) { 2941 setInt(viewId, "setTextColor", color); 2942 } 2943 2944 /** 2945 * @hide 2946 * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}. 2947 * 2948 * @param viewId The id of the view whose text color should change 2949 * @param colors the text colors to set 2950 */ 2951 public void setTextColor(int viewId, @ColorInt ColorStateList colors) { 2952 addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST, 2953 colors)); 2954 } 2955 2956 /** 2957 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2958 * 2959 * @param appWidgetId The id of the app widget which contains the specified view. (This 2960 * parameter is ignored in this deprecated method) 2961 * @param viewId The id of the {@link AdapterView} 2962 * @param intent The intent of the service which will be 2963 * providing data to the RemoteViewsAdapter 2964 * @deprecated This method has been deprecated. See 2965 * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} 2966 */ 2967 @Deprecated 2968 public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) { 2969 setRemoteAdapter(viewId, intent); 2970 } 2971 2972 /** 2973 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2974 * Can only be used for App Widgets. 2975 * 2976 * @param viewId The id of the {@link AdapterView} 2977 * @param intent The intent of the service which will be 2978 * providing data to the RemoteViewsAdapter 2979 */ 2980 public void setRemoteAdapter(int viewId, Intent intent) { 2981 addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); 2982 } 2983 2984 /** 2985 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 2986 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 2987 * This is a simpler but less flexible approach to populating collection widgets. Its use is 2988 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 2989 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 2990 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 2991 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 2992 * 2993 * This API is supported in the compatibility library for previous API levels, see 2994 * RemoteViewsCompat. 2995 * 2996 * @param viewId The id of the {@link AdapterView} 2997 * @param list The list of RemoteViews which will populate the view specified by viewId. 2998 * @param viewTypeCount The maximum number of unique layout id's used to construct the list of 2999 * RemoteViews. This count cannot change during the life-cycle of a given widget, so this 3000 * parameter should account for the maximum possible number of types that may appear in the 3001 * See {@link Adapter#getViewTypeCount()}. 3002 * 3003 * @hide 3004 * @deprecated this appears to have no users outside of UnsupportedAppUsage? 3005 */ 3006 @UnsupportedAppUsage 3007 @Deprecated 3008 public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) { 3009 addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); 3010 } 3011 3012 /** 3013 * Equivalent to calling {@link ListView#smoothScrollToPosition(int)}. 3014 * 3015 * @param viewId The id of the view to change 3016 * @param position Scroll to this adapter position 3017 */ 3018 public void setScrollPosition(int viewId, int position) { 3019 setInt(viewId, "smoothScrollToPosition", position); 3020 } 3021 3022 /** 3023 * Equivalent to calling {@link ListView#smoothScrollByOffset(int)}. 3024 * 3025 * @param viewId The id of the view to change 3026 * @param offset Scroll by this adapter position offset 3027 */ 3028 public void setRelativeScrollPosition(int viewId, int offset) { 3029 setInt(viewId, "smoothScrollByOffset", offset); 3030 } 3031 3032 /** 3033 * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}. 3034 * 3035 * @param viewId The id of the view to change 3036 * @param left the left padding in pixels 3037 * @param top the top padding in pixels 3038 * @param right the right padding in pixels 3039 * @param bottom the bottom padding in pixels 3040 */ 3041 public void setViewPadding(int viewId, int left, int top, int right, int bottom) { 3042 addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); 3043 } 3044 3045 /** 3046 * @hide 3047 * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. 3048 * Only works if the {@link View#getLayoutParams()} supports margins. 3049 * Hidden for now since we don't want to support this for all different layout margins yet. 3050 * 3051 * @param viewId The id of the view to change 3052 * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin. 3053 */ 3054 public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) { 3055 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN, 3056 endMarginDimen)); 3057 } 3058 3059 /** 3060 * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. 3061 * Only works if the {@link View#getLayoutParams()} supports margins. 3062 * Hidden for now since we don't want to support this for all different layout margins yet. 3063 * 3064 * @param viewId The id of the view to change 3065 * @param endMargin a value in pixels for the end margin. 3066 * @hide 3067 */ 3068 public void setViewLayoutMarginEnd(int viewId, @DimenRes int endMargin) { 3069 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END, 3070 endMargin)); 3071 } 3072 3073 /** 3074 * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. 3075 * 3076 * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin. 3077 * @hide 3078 */ 3079 public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) { 3080 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN, 3081 bottomMarginDimen)); 3082 } 3083 3084 /** 3085 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. 3086 * 3087 * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed 3088 * because they behave poorly when the density changes. 3089 * @hide 3090 */ 3091 public void setViewLayoutWidth(int viewId, int layoutWidth) { 3092 if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT 3093 && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) { 3094 throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT"); 3095 } 3096 mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth)); 3097 } 3098 3099 /** 3100 * Call a method taking one boolean on a view in the layout for this RemoteViews. 3101 * 3102 * @param viewId The id of the view on which to call the method. 3103 * @param methodName The name of the method to call. 3104 * @param value The value to pass to the method. 3105 */ 3106 public void setBoolean(int viewId, String methodName, boolean value) { 3107 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); 3108 } 3109 3110 /** 3111 * Call a method taking one byte on a view in the layout for this RemoteViews. 3112 * 3113 * @param viewId The id of the view on which to call the method. 3114 * @param methodName The name of the method to call. 3115 * @param value The value to pass to the method. 3116 */ 3117 public void setByte(int viewId, String methodName, byte value) { 3118 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); 3119 } 3120 3121 /** 3122 * Call a method taking one short on a view in the layout for this RemoteViews. 3123 * 3124 * @param viewId The id of the view on which to call the method. 3125 * @param methodName The name of the method to call. 3126 * @param value The value to pass to the method. 3127 */ 3128 public void setShort(int viewId, String methodName, short value) { 3129 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); 3130 } 3131 3132 /** 3133 * Call a method taking one int on a view in the layout for this RemoteViews. 3134 * 3135 * @param viewId The id of the view on which to call the method. 3136 * @param methodName The name of the method to call. 3137 * @param value The value to pass to the method. 3138 */ 3139 public void setInt(int viewId, String methodName, int value) { 3140 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); 3141 } 3142 3143 /** 3144 * Call a method taking one ColorStateList on a view in the layout for this RemoteViews. 3145 * 3146 * @param viewId The id of the view on which to call the method. 3147 * @param methodName The name of the method to call. 3148 * @param value The value to pass to the method. 3149 * 3150 * @hide 3151 */ 3152 public void setColorStateList(int viewId, String methodName, ColorStateList value) { 3153 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.COLOR_STATE_LIST, 3154 value)); 3155 } 3156 3157 3158 /** 3159 * Call a method taking one long on a view in the layout for this RemoteViews. 3160 * 3161 * @param viewId The id of the view on which to call the method. 3162 * @param methodName The name of the method to call. 3163 * @param value The value to pass to the method. 3164 */ 3165 public void setLong(int viewId, String methodName, long value) { 3166 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); 3167 } 3168 3169 /** 3170 * Call a method taking one float on a view in the layout for this RemoteViews. 3171 * 3172 * @param viewId The id of the view on which to call the method. 3173 * @param methodName The name of the method to call. 3174 * @param value The value to pass to the method. 3175 */ 3176 public void setFloat(int viewId, String methodName, float value) { 3177 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); 3178 } 3179 3180 /** 3181 * Call a method taking one double on a view in the layout for this RemoteViews. 3182 * 3183 * @param viewId The id of the view on which to call the method. 3184 * @param methodName The name of the method to call. 3185 * @param value The value to pass to the method. 3186 */ 3187 public void setDouble(int viewId, String methodName, double value) { 3188 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); 3189 } 3190 3191 /** 3192 * Call a method taking one char on a view in the layout for this RemoteViews. 3193 * 3194 * @param viewId The id of the view on which to call the method. 3195 * @param methodName The name of the method to call. 3196 * @param value The value to pass to the method. 3197 */ 3198 public void setChar(int viewId, String methodName, char value) { 3199 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); 3200 } 3201 3202 /** 3203 * Call a method taking one String on a view in the layout for this RemoteViews. 3204 * 3205 * @param viewId The id of the view on which to call the method. 3206 * @param methodName The name of the method to call. 3207 * @param value The value to pass to the method. 3208 */ 3209 public void setString(int viewId, String methodName, String value) { 3210 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); 3211 } 3212 3213 /** 3214 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 3215 * 3216 * @param viewId The id of the view on which to call the method. 3217 * @param methodName The name of the method to call. 3218 * @param value The value to pass to the method. 3219 */ 3220 public void setCharSequence(int viewId, String methodName, CharSequence value) { 3221 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); 3222 } 3223 3224 /** 3225 * Call a method taking one Uri on a view in the layout for this RemoteViews. 3226 * 3227 * @param viewId The id of the view on which to call the method. 3228 * @param methodName The name of the method to call. 3229 * @param value The value to pass to the method. 3230 */ 3231 public void setUri(int viewId, String methodName, Uri value) { 3232 if (value != null) { 3233 // Resolve any filesystem path before sending remotely 3234 value = value.getCanonicalUri(); 3235 if (StrictMode.vmFileUriExposureEnabled()) { 3236 value.checkFileUriExposed("RemoteViews.setUri()"); 3237 } 3238 } 3239 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); 3240 } 3241 3242 /** 3243 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 3244 * @more 3245 * <p class="note">The bitmap will be flattened into the parcel if this object is 3246 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 3247 * 3248 * @param viewId The id of the view on which to call the method. 3249 * @param methodName The name of the method to call. 3250 * @param value The value to pass to the method. 3251 */ 3252 public void setBitmap(int viewId, String methodName, Bitmap value) { 3253 addAction(new BitmapReflectionAction(viewId, methodName, value)); 3254 } 3255 3256 /** 3257 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 3258 * 3259 * @param viewId The id of the view on which to call the method. 3260 * @param methodName The name of the method to call. 3261 * @param value The value to pass to the method. 3262 */ 3263 public void setBundle(int viewId, String methodName, Bundle value) { 3264 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); 3265 } 3266 3267 /** 3268 * Call a method taking one Intent on a view in the layout for this RemoteViews. 3269 * 3270 * @param viewId The id of the view on which to call the method. 3271 * @param methodName The name of the method to call. 3272 * @param value The {@link android.content.Intent} to pass the method. 3273 */ 3274 public void setIntent(int viewId, String methodName, Intent value) { 3275 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); 3276 } 3277 3278 /** 3279 * Call a method taking one Icon on a view in the layout for this RemoteViews. 3280 * 3281 * @param viewId The id of the view on which to call the method. 3282 * @param methodName The name of the method to call. 3283 * @param value The {@link android.graphics.drawable.Icon} to pass the method. 3284 */ 3285 public void setIcon(int viewId, String methodName, Icon value) { 3286 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value)); 3287 } 3288 3289 /** 3290 * Equivalent to calling View.setContentDescription(CharSequence). 3291 * 3292 * @param viewId The id of the view whose content description should change. 3293 * @param contentDescription The new content description for the view. 3294 */ 3295 public void setContentDescription(int viewId, CharSequence contentDescription) { 3296 setCharSequence(viewId, "setContentDescription", contentDescription); 3297 } 3298 3299 /** 3300 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. 3301 * 3302 * @param viewId The id of the view whose before view in accessibility traversal to set. 3303 * @param nextId The id of the next in the accessibility traversal. 3304 **/ 3305 public void setAccessibilityTraversalBefore(int viewId, int nextId) { 3306 setInt(viewId, "setAccessibilityTraversalBefore", nextId); 3307 } 3308 3309 /** 3310 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. 3311 * 3312 * @param viewId The id of the view whose after view in accessibility traversal to set. 3313 * @param nextId The id of the next in the accessibility traversal. 3314 **/ 3315 public void setAccessibilityTraversalAfter(int viewId, int nextId) { 3316 setInt(viewId, "setAccessibilityTraversalAfter", nextId); 3317 } 3318 3319 /** 3320 * Equivalent to calling {@link View#setLabelFor(int)}. 3321 * 3322 * @param viewId The id of the view whose property to set. 3323 * @param labeledId The id of a view for which this view serves as a label. 3324 */ 3325 public void setLabelFor(int viewId, int labeledId) { 3326 setInt(viewId, "setLabelFor", labeledId); 3327 } 3328 3329 /** 3330 * Provides an alternate layout ID, which can be used to inflate this view. This layout will be 3331 * used by the host when the widgets displayed on a light-background where foreground elements 3332 * and text can safely draw using a dark color without any additional background protection. 3333 */ 3334 public void setLightBackgroundLayoutId(@LayoutRes int layoutId) { 3335 mLightBackgroundLayoutId = layoutId; 3336 } 3337 3338 /** 3339 * If this view supports dark text versions, creates a copy representing that version, 3340 * otherwise returns itself. 3341 * @hide 3342 */ 3343 public RemoteViews getDarkTextViews() { 3344 if (hasFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT)) { 3345 return this; 3346 } 3347 3348 try { 3349 addFlags(FLAG_USE_LIGHT_BACKGROUND_LAYOUT); 3350 return new RemoteViews(this); 3351 } finally { 3352 mApplyFlags &= ~FLAG_USE_LIGHT_BACKGROUND_LAYOUT; 3353 } 3354 } 3355 3356 private RemoteViews getRemoteViewsToApply(Context context) { 3357 if (hasLandscapeAndPortraitLayouts()) { 3358 int orientation = context.getResources().getConfiguration().orientation; 3359 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 3360 return mLandscape; 3361 } else { 3362 return mPortrait; 3363 } 3364 } 3365 return this; 3366 } 3367 3368 /** 3369 * Inflates the view hierarchy represented by this object and applies 3370 * all of the actions. 3371 * 3372 * <p><strong>Caller beware: this may throw</strong> 3373 * 3374 * @param context Default context to use 3375 * @param parent Parent that the resulting view hierarchy will be attached to. This method 3376 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 3377 * @return The inflated view hierarchy 3378 */ 3379 public View apply(Context context, ViewGroup parent) { 3380 return apply(context, parent, null); 3381 } 3382 3383 /** @hide */ 3384 public View apply(Context context, ViewGroup parent, OnClickHandler handler) { 3385 RemoteViews rvToApply = getRemoteViewsToApply(context); 3386 3387 View result = inflateView(context, rvToApply, parent); 3388 rvToApply.performApply(result, parent, handler); 3389 return result; 3390 } 3391 3392 /** @hide */ 3393 public View applyWithTheme(Context context, ViewGroup parent, OnClickHandler handler, 3394 @StyleRes int applyThemeResId) { 3395 RemoteViews rvToApply = getRemoteViewsToApply(context); 3396 3397 View result = inflateView(context, rvToApply, parent, applyThemeResId); 3398 rvToApply.performApply(result, parent, handler); 3399 return result; 3400 } 3401 3402 private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { 3403 return inflateView(context, rv, parent, 0); 3404 } 3405 3406 private View inflateView(Context context, RemoteViews rv, ViewGroup parent, 3407 @StyleRes int applyThemeResId) { 3408 // RemoteViews may be built by an application installed in another 3409 // user. So build a context that loads resources from that user but 3410 // still returns the current users userId so settings like data / time formats 3411 // are loaded without requiring cross user persmissions. 3412 final Context contextForResources = getContextForResources(context); 3413 Context inflationContext = new RemoteViewsContextWrapper(context, contextForResources); 3414 3415 // If mApplyThemeResId is not given, Theme.DeviceDefault will be used. 3416 if (applyThemeResId != 0) { 3417 inflationContext = new ContextThemeWrapper(inflationContext, applyThemeResId); 3418 } 3419 LayoutInflater inflater = (LayoutInflater) 3420 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 3421 3422 // Clone inflater so we load resources from correct context and 3423 // we don't add a filter to the static version returned by getSystemService. 3424 inflater = inflater.cloneInContext(inflationContext); 3425 inflater.setFilter(shouldUseStaticFilter() ? INFLATER_FILTER : this); 3426 View v = inflater.inflate(rv.getLayoutId(), parent, false); 3427 v.setTagInternal(R.id.widget_frame, rv.getLayoutId()); 3428 return v; 3429 } 3430 3431 /** 3432 * A static filter is much lighter than RemoteViews itself. It's optimized here only for 3433 * RemoteVies class. Subclasses should always override this and return true if not overriding 3434 * {@link this#onLoadClass(Class)}. 3435 * 3436 * @hide 3437 */ 3438 protected boolean shouldUseStaticFilter() { 3439 return this.getClass().equals(RemoteViews.class); 3440 } 3441 3442 /** 3443 * Implement this interface to receive a callback when 3444 * {@link #applyAsync} or {@link #reapplyAsync} is finished. 3445 * @hide 3446 */ 3447 public interface OnViewAppliedListener { 3448 /** 3449 * Callback when the RemoteView has finished inflating, 3450 * but no actions have been applied yet. 3451 */ 3452 default void onViewInflated(View v) {}; 3453 3454 void onViewApplied(View v); 3455 3456 void onError(Exception e); 3457 } 3458 3459 /** 3460 * Applies the views asynchronously, moving as much of the task on the background 3461 * thread as possible. 3462 * 3463 * @see #apply(Context, ViewGroup) 3464 * @param context Default context to use 3465 * @param parent Parent that the resulting view hierarchy will be attached to. This method 3466 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 3467 * @param listener the callback to run when all actions have been applied. May be null. 3468 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used. 3469 * @return CancellationSignal 3470 * @hide 3471 */ 3472 public CancellationSignal applyAsync( 3473 Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) { 3474 return applyAsync(context, parent, executor, listener, null); 3475 } 3476 3477 /** @hide */ 3478 public CancellationSignal applyAsync(Context context, ViewGroup parent, 3479 Executor executor, OnViewAppliedListener listener, OnClickHandler handler) { 3480 return getAsyncApplyTask(context, parent, listener, handler).startTaskOnExecutor(executor); 3481 } 3482 3483 private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, 3484 OnViewAppliedListener listener, OnClickHandler handler) { 3485 return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener, 3486 handler, null); 3487 } 3488 3489 private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> 3490 implements CancellationSignal.OnCancelListener { 3491 final CancellationSignal mCancelSignal = new CancellationSignal(); 3492 final RemoteViews mRV; 3493 final ViewGroup mParent; 3494 final Context mContext; 3495 final OnViewAppliedListener mListener; 3496 final OnClickHandler mHandler; 3497 3498 private View mResult; 3499 private ViewTree mTree; 3500 private Action[] mActions; 3501 private Exception mError; 3502 3503 private AsyncApplyTask( 3504 RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, 3505 OnClickHandler handler, View result) { 3506 mRV = rv; 3507 mParent = parent; 3508 mContext = context; 3509 mListener = listener; 3510 mHandler = handler; 3511 3512 mResult = result; 3513 } 3514 3515 @Override 3516 protected ViewTree doInBackground(Void... params) { 3517 try { 3518 if (mResult == null) { 3519 mResult = inflateView(mContext, mRV, mParent); 3520 } 3521 3522 mTree = new ViewTree(mResult); 3523 if (mRV.mActions != null) { 3524 int count = mRV.mActions.size(); 3525 mActions = new Action[count]; 3526 for (int i = 0; i < count && !isCancelled(); i++) { 3527 // TODO: check if isCancelled in nested views. 3528 mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler); 3529 } 3530 } else { 3531 mActions = null; 3532 } 3533 return mTree; 3534 } catch (Exception e) { 3535 mError = e; 3536 return null; 3537 } 3538 } 3539 3540 @Override 3541 protected void onPostExecute(ViewTree viewTree) { 3542 mCancelSignal.setOnCancelListener(null); 3543 if (mError == null) { 3544 if (mListener != null) { 3545 mListener.onViewInflated(viewTree.mRoot); 3546 } 3547 3548 try { 3549 if (mActions != null) { 3550 OnClickHandler handler = mHandler == null 3551 ? DEFAULT_ON_CLICK_HANDLER : mHandler; 3552 for (Action a : mActions) { 3553 a.apply(viewTree.mRoot, mParent, handler); 3554 } 3555 } 3556 } catch (Exception e) { 3557 mError = e; 3558 } 3559 } 3560 3561 if (mListener != null) { 3562 if (mError != null) { 3563 mListener.onError(mError); 3564 } else { 3565 mListener.onViewApplied(viewTree.mRoot); 3566 } 3567 } else if (mError != null) { 3568 if (mError instanceof ActionException) { 3569 throw (ActionException) mError; 3570 } else { 3571 throw new ActionException(mError); 3572 } 3573 } 3574 } 3575 3576 @Override 3577 public void onCancel() { 3578 cancel(true); 3579 } 3580 3581 private CancellationSignal startTaskOnExecutor(Executor executor) { 3582 mCancelSignal.setOnCancelListener(this); 3583 executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor); 3584 return mCancelSignal; 3585 } 3586 } 3587 3588 /** 3589 * Applies all of the actions to the provided view. 3590 * 3591 * <p><strong>Caller beware: this may throw</strong> 3592 * 3593 * @param v The view to apply the actions to. This should be the result of 3594 * the {@link #apply(Context,ViewGroup)} call. 3595 */ 3596 public void reapply(Context context, View v) { 3597 reapply(context, v, null); 3598 } 3599 3600 /** @hide */ 3601 public void reapply(Context context, View v, OnClickHandler handler) { 3602 RemoteViews rvToApply = getRemoteViewsToApply(context); 3603 3604 // In the case that a view has this RemoteViews applied in one orientation, is persisted 3605 // across orientation change, and has the RemoteViews re-applied in the new orientation, 3606 // we throw an exception, since the layouts may be completely unrelated. 3607 if (hasLandscapeAndPortraitLayouts()) { 3608 if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { 3609 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 3610 " that does not share the same root layout id."); 3611 } 3612 } 3613 3614 rvToApply.performApply(v, (ViewGroup) v.getParent(), handler); 3615 } 3616 3617 /** 3618 * Applies all the actions to the provided view, moving as much of the task on the background 3619 * thread as possible. 3620 * 3621 * @see #reapply(Context, View) 3622 * @param context Default context to use 3623 * @param v The view to apply the actions to. This should be the result of 3624 * the {@link #apply(Context,ViewGroup)} call. 3625 * @param listener the callback to run when all actions have been applied. May be null. 3626 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used 3627 * @return CancellationSignal 3628 * @hide 3629 */ 3630 public CancellationSignal reapplyAsync( 3631 Context context, View v, Executor executor, OnViewAppliedListener listener) { 3632 return reapplyAsync(context, v, executor, listener, null); 3633 } 3634 3635 /** @hide */ 3636 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 3637 OnViewAppliedListener listener, OnClickHandler handler) { 3638 RemoteViews rvToApply = getRemoteViewsToApply(context); 3639 3640 // In the case that a view has this RemoteViews applied in one orientation, is persisted 3641 // across orientation change, and has the RemoteViews re-applied in the new orientation, 3642 // we throw an exception, since the layouts may be completely unrelated. 3643 if (hasLandscapeAndPortraitLayouts()) { 3644 if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { 3645 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 3646 " that does not share the same root layout id."); 3647 } 3648 } 3649 3650 return new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), 3651 context, listener, handler, v).startTaskOnExecutor(executor); 3652 } 3653 3654 private void performApply(View v, ViewGroup parent, OnClickHandler handler) { 3655 if (mActions != null) { 3656 handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler; 3657 final int count = mActions.size(); 3658 for (int i = 0; i < count; i++) { 3659 Action a = mActions.get(i); 3660 a.apply(v, parent, handler); 3661 } 3662 } 3663 } 3664 3665 /** 3666 * Returns true if the RemoteViews contains potentially costly operations and should be 3667 * applied asynchronously. 3668 * 3669 * @hide 3670 */ 3671 public boolean prefersAsyncApply() { 3672 if (mActions != null) { 3673 final int count = mActions.size(); 3674 for (int i = 0; i < count; i++) { 3675 if (mActions.get(i).prefersAsyncApply()) { 3676 return true; 3677 } 3678 } 3679 } 3680 return false; 3681 } 3682 3683 private Context getContextForResources(Context context) { 3684 if (mApplication != null) { 3685 if (context.getUserId() == UserHandle.getUserId(mApplication.uid) 3686 && context.getPackageName().equals(mApplication.packageName)) { 3687 return context; 3688 } 3689 try { 3690 return context.createApplicationContext(mApplication, 3691 Context.CONTEXT_RESTRICTED); 3692 } catch (NameNotFoundException e) { 3693 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); 3694 } 3695 } 3696 3697 return context; 3698 } 3699 3700 /** 3701 * Returns the number of actions in this RemoteViews. Can be used as a sequence number. 3702 * 3703 * @hide 3704 */ 3705 public int getSequenceNumber() { 3706 return (mActions == null) ? 0 : mActions.size(); 3707 } 3708 3709 /** 3710 * Used to restrict the views which can be inflated 3711 * 3712 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 3713 * @deprecated Used by system to enforce safe inflation of {@link RemoteViews}. Apps should not 3714 * override this method. Changing of this method will NOT affect the process where RemoteViews 3715 * is rendered. 3716 */ 3717 @Deprecated 3718 public boolean onLoadClass(Class clazz) { 3719 return clazz.isAnnotationPresent(RemoteView.class); 3720 } 3721 3722 public int describeContents() { 3723 return 0; 3724 } 3725 3726 public void writeToParcel(Parcel dest, int flags) { 3727 if (!hasLandscapeAndPortraitLayouts()) { 3728 dest.writeInt(MODE_NORMAL); 3729 // We only write the bitmap cache if we are the root RemoteViews, as this cache 3730 // is shared by all children. 3731 if (mIsRoot) { 3732 mBitmapCache.writeBitmapsToParcel(dest, flags); 3733 } 3734 if (!mIsRoot && (flags & PARCELABLE_ELIDE_DUPLICATES) != 0) { 3735 dest.writeInt(0); 3736 } else { 3737 dest.writeInt(1); 3738 mApplication.writeToParcel(dest, flags); 3739 } 3740 dest.writeInt(mLayoutId); 3741 dest.writeInt(mLightBackgroundLayoutId); 3742 writeActionsToParcel(dest); 3743 } else { 3744 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 3745 // We only write the bitmap cache if we are the root RemoteViews, as this cache 3746 // is shared by all children. 3747 if (mIsRoot) { 3748 mBitmapCache.writeBitmapsToParcel(dest, flags); 3749 } 3750 mLandscape.writeToParcel(dest, flags); 3751 // Both RemoteViews already share the same package and user 3752 mPortrait.writeToParcel(dest, flags | PARCELABLE_ELIDE_DUPLICATES); 3753 } 3754 dest.writeInt(mApplyFlags); 3755 } 3756 3757 private void writeActionsToParcel(Parcel parcel) { 3758 int count; 3759 if (mActions != null) { 3760 count = mActions.size(); 3761 } else { 3762 count = 0; 3763 } 3764 parcel.writeInt(count); 3765 for (int i = 0; i < count; i++) { 3766 Action a = mActions.get(i); 3767 parcel.writeInt(a.getActionTag()); 3768 a.writeToParcel(parcel, a.hasSameAppInfo(mApplication) 3769 ? PARCELABLE_ELIDE_DUPLICATES : 0); 3770 } 3771 } 3772 3773 private static ApplicationInfo getApplicationInfo(String packageName, int userId) { 3774 if (packageName == null) { 3775 return null; 3776 } 3777 3778 // Get the application for the passed in package and user. 3779 Application application = ActivityThread.currentApplication(); 3780 if (application == null) { 3781 throw new IllegalStateException("Cannot create remote views out of an aplication."); 3782 } 3783 3784 ApplicationInfo applicationInfo = application.getApplicationInfo(); 3785 if (UserHandle.getUserId(applicationInfo.uid) != userId 3786 || !applicationInfo.packageName.equals(packageName)) { 3787 try { 3788 Context context = application.getBaseContext().createPackageContextAsUser( 3789 packageName, 0, new UserHandle(userId)); 3790 applicationInfo = context.getApplicationInfo(); 3791 } catch (NameNotFoundException nnfe) { 3792 throw new IllegalArgumentException("No such package " + packageName); 3793 } 3794 } 3795 3796 return applicationInfo; 3797 } 3798 3799 /** 3800 * Returns true if the {@link #mApplication} is same as the provided info. 3801 * 3802 * @hide 3803 */ 3804 public boolean hasSameAppInfo(ApplicationInfo info) { 3805 return mApplication.packageName.equals(info.packageName) && mApplication.uid == info.uid; 3806 } 3807 3808 /** 3809 * Parcelable.Creator that instantiates RemoteViews objects 3810 */ 3811 public static final @android.annotation.NonNull Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 3812 public RemoteViews createFromParcel(Parcel parcel) { 3813 return new RemoteViews(parcel); 3814 } 3815 3816 public RemoteViews[] newArray(int size) { 3817 return new RemoteViews[size]; 3818 } 3819 }; 3820 3821 /** 3822 * A representation of the view hierarchy. Only views which have a valid ID are added 3823 * and can be searched. 3824 */ 3825 private static class ViewTree { 3826 private static final int INSERT_AT_END_INDEX = -1; 3827 private View mRoot; 3828 private ArrayList<ViewTree> mChildren; 3829 3830 private ViewTree(View root) { 3831 mRoot = root; 3832 } 3833 3834 public void createTree() { 3835 if (mChildren != null) { 3836 return; 3837 } 3838 3839 mChildren = new ArrayList<>(); 3840 if (mRoot instanceof ViewGroup) { 3841 ViewGroup vg = (ViewGroup) mRoot; 3842 int count = vg.getChildCount(); 3843 for (int i = 0; i < count; i++) { 3844 addViewChild(vg.getChildAt(i)); 3845 } 3846 } 3847 } 3848 3849 public ViewTree findViewTreeById(int id) { 3850 if (mRoot.getId() == id) { 3851 return this; 3852 } 3853 if (mChildren == null) { 3854 return null; 3855 } 3856 for (ViewTree tree : mChildren) { 3857 ViewTree result = tree.findViewTreeById(id); 3858 if (result != null) { 3859 return result; 3860 } 3861 } 3862 return null; 3863 } 3864 3865 public void replaceView(View v) { 3866 mRoot = v; 3867 mChildren = null; 3868 createTree(); 3869 } 3870 3871 public <T extends View> T findViewById(int id) { 3872 if (mChildren == null) { 3873 return mRoot.findViewById(id); 3874 } 3875 ViewTree tree = findViewTreeById(id); 3876 return tree == null ? null : (T) tree.mRoot; 3877 } 3878 3879 public void addChild(ViewTree child) { 3880 addChild(child, INSERT_AT_END_INDEX); 3881 } 3882 3883 /** 3884 * Adds the given {@link ViewTree} as a child at the given index. 3885 * 3886 * @param index The position at which to add the child or -1 to add last. 3887 */ 3888 public void addChild(ViewTree child, int index) { 3889 if (mChildren == null) { 3890 mChildren = new ArrayList<>(); 3891 } 3892 child.createTree(); 3893 3894 if (index == INSERT_AT_END_INDEX) { 3895 mChildren.add(child); 3896 return; 3897 } 3898 3899 mChildren.add(index, child); 3900 } 3901 3902 private void addViewChild(View v) { 3903 // ViewTree only contains Views which can be found using findViewById. 3904 // If isRootNamespace is true, this view is skipped. 3905 // @see ViewGroup#findViewTraversal(int) 3906 if (v.isRootNamespace()) { 3907 return; 3908 } 3909 final ViewTree target; 3910 3911 // If the view has a valid id, i.e., if can be found using findViewById, add it to the 3912 // tree, otherwise skip this view and add its children instead. 3913 if (v.getId() != 0) { 3914 ViewTree tree = new ViewTree(v); 3915 mChildren.add(tree); 3916 target = tree; 3917 } else { 3918 target = this; 3919 } 3920 3921 if (v instanceof ViewGroup) { 3922 if (target.mChildren == null) { 3923 target.mChildren = new ArrayList<>(); 3924 ViewGroup vg = (ViewGroup) v; 3925 int count = vg.getChildCount(); 3926 for (int i = 0; i < count; i++) { 3927 target.addViewChild(vg.getChildAt(i)); 3928 } 3929 } 3930 } 3931 } 3932 } 3933 3934 /** 3935 * Class representing a response to an action performed on any element of a RemoteViews. 3936 */ 3937 public static class RemoteResponse { 3938 3939 private PendingIntent mPendingIntent; 3940 private Intent mFillIntent; 3941 3942 private IntArray mViewIds; 3943 private ArrayList<String> mElementNames; 3944 3945 /** 3946 * Creates a response which sends a pending intent as part of the response. The source 3947 * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the 3948 * target view in screen space. 3949 * Note that any activity options associated with the mPendingIntent may get overridden 3950 * before starting the intent. 3951 * 3952 * @param pendingIntent The {@link PendingIntent} to send as part of the response 3953 */ 3954 @NonNull 3955 public static RemoteResponse fromPendingIntent(@NonNull PendingIntent pendingIntent) { 3956 RemoteResponse response = new RemoteResponse(); 3957 response.mPendingIntent = pendingIntent; 3958 return response; 3959 } 3960 3961 /** 3962 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is 3963 * very costly to set PendingIntents on the individual items, and is hence not recommended. 3964 * Instead a single PendingIntent template can be set on the collection, see {@link 3965 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 3966 * action of a given item can be distinguished by setting a fillInIntent on that item. The 3967 * fillInIntent is then combined with the PendingIntent template in order to determine the 3968 * final intent which will be executed when the item is clicked. This works as follows: any 3969 * fields which are left blank in the PendingIntent template, but are provided by the 3970 * fillInIntent will be overwritten, and the resulting PendingIntent will be used. The rest 3971 * of the PendingIntent template will then be filled in with the associated fields that are 3972 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 3973 * Creates a response which sends a pending intent as part of the response. The source 3974 * bounds ({@link Intent#getSourceBounds()}) of the intent will be set to the bounds of the 3975 * target view in screen space. 3976 * Note that any activity options associated with the mPendingIntent may get overridden 3977 * before starting the intent. 3978 * 3979 * @param fillIntent The intent which will be combined with the parent's PendingIntent in 3980 * order to determine the behavior of the response 3981 * 3982 * @see RemoteViews#setPendingIntentTemplate(int, PendingIntent) 3983 * @see RemoteViews#setOnClickFillInIntent(int, Intent) 3984 * @return 3985 */ 3986 @NonNull 3987 public static RemoteResponse fromFillInIntent(@NonNull Intent fillIntent) { 3988 RemoteResponse response = new RemoteResponse(); 3989 response.mFillIntent = fillIntent; 3990 return response; 3991 } 3992 3993 /** 3994 * Adds a shared element to be transferred as part of the transition between Activities 3995 * using cross-Activity scene animations. The position of the first element will be used as 3996 * the epicenter for the exit Transition. The position of the associated shared element in 3997 * the launched Activity will be the epicenter of its entering Transition. 3998 * 3999 * @param viewId The id of the view to be shared as part of the transition 4000 * @param sharedElementName The shared element name for this view 4001 * 4002 * @see ActivityOptions#makeSceneTransitionAnimation(Activity, Pair[]) 4003 */ 4004 @NonNull 4005 public RemoteResponse addSharedElement(int viewId, @NonNull String sharedElementName) { 4006 if (mViewIds == null) { 4007 mViewIds = new IntArray(); 4008 mElementNames = new ArrayList<>(); 4009 } 4010 mViewIds.add(viewId); 4011 mElementNames.add(sharedElementName); 4012 return this; 4013 } 4014 4015 private void writeToParcel(Parcel dest, int flags) { 4016 PendingIntent.writePendingIntentOrNullToParcel(mPendingIntent, dest); 4017 if (mPendingIntent == null) { 4018 // Only write the intent if pending intent is null 4019 dest.writeTypedObject(mFillIntent, flags); 4020 } 4021 dest.writeIntArray(mViewIds == null ? null : mViewIds.toArray()); 4022 dest.writeStringList(mElementNames); 4023 } 4024 4025 private void readFromParcel(Parcel parcel) { 4026 mPendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 4027 if (mPendingIntent == null) { 4028 mFillIntent = parcel.readTypedObject(Intent.CREATOR); 4029 } 4030 int[] viewIds = parcel.createIntArray(); 4031 mViewIds = viewIds == null ? null : IntArray.wrap(viewIds); 4032 mElementNames = parcel.createStringArrayList(); 4033 } 4034 4035 private void handleViewClick(View v, OnClickHandler handler) { 4036 final PendingIntent pi; 4037 if (mPendingIntent != null) { 4038 pi = mPendingIntent; 4039 } else if (mFillIntent != null) { 4040 // Insure that this view is a child of an AdapterView 4041 View parent = (View) v.getParent(); 4042 // Break the for loop on the first encounter of: 4043 // 1) an AdapterView, 4044 // 2) an AppWidgetHostView that is not a RemoteViewsFrameLayout, or 4045 // 3) a null parent. 4046 // 2) and 3) are unexpected and catch the case where a child is not 4047 // correctly parented in an AdapterView. 4048 while (parent != null && !(parent instanceof AdapterView<?>) 4049 && !((parent instanceof AppWidgetHostView) 4050 && !(parent instanceof RemoteViewsAdapter.RemoteViewsFrameLayout))) { 4051 parent = (View) parent.getParent(); 4052 } 4053 4054 if (!(parent instanceof AdapterView<?>)) { 4055 // Somehow they've managed to get this far without having 4056 // and AdapterView as a parent. 4057 Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent"); 4058 return; 4059 } 4060 // Insure that a template pending intent has been set on an ancestor 4061 if (!(parent.getTag() instanceof PendingIntent)) { 4062 Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" 4063 + " calling setPendingIntentTemplate on parent."); 4064 return; 4065 } 4066 4067 pi = (PendingIntent) parent.getTag(); 4068 } else { 4069 Log.e(LOG_TAG, "Response has neither pendingIntent nor fillInIntent"); 4070 return; 4071 } 4072 4073 handler.onClickHandler(v, pi, this); 4074 } 4075 4076 /** @hide */ 4077 public Pair<Intent, ActivityOptions> getLaunchOptions(View view) { 4078 Intent intent = mPendingIntent != null ? new Intent() : new Intent(mFillIntent); 4079 intent.setSourceBounds(getSourceBounds(view)); 4080 4081 ActivityOptions opts = null; 4082 4083 Context context = view.getContext(); 4084 if (context.getResources().getBoolean( 4085 com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { 4086 TypedArray windowStyle = context.getTheme().obtainStyledAttributes( 4087 com.android.internal.R.styleable.Window); 4088 int windowAnimations = windowStyle.getResourceId( 4089 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 4090 TypedArray windowAnimationStyle = context.obtainStyledAttributes( 4091 windowAnimations, com.android.internal.R.styleable.WindowAnimation); 4092 int enterAnimationId = windowAnimationStyle.getResourceId(com.android.internal.R 4093 .styleable.WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0); 4094 windowStyle.recycle(); 4095 windowAnimationStyle.recycle(); 4096 4097 if (enterAnimationId != 0) { 4098 opts = ActivityOptions.makeCustomAnimation(context, 4099 enterAnimationId, 0); 4100 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 4101 } 4102 } 4103 4104 if (opts == null && mViewIds != null && mElementNames != null) { 4105 View parent = (View) view.getParent(); 4106 while (parent != null && !(parent instanceof AppWidgetHostView)) { 4107 parent = (View) parent.getParent(); 4108 } 4109 if (parent instanceof AppWidgetHostView) { 4110 opts = ((AppWidgetHostView) parent).createSharedElementActivityOptions( 4111 mViewIds.toArray(), 4112 mElementNames.toArray(new String[mElementNames.size()]), intent); 4113 } 4114 } 4115 4116 if (opts == null) { 4117 opts = ActivityOptions.makeBasic(); 4118 opts.setPendingIntentLaunchFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 4119 } 4120 return Pair.create(intent, opts); 4121 } 4122 } 4123 4124 /** @hide */ 4125 public static boolean startPendingIntent(View view, PendingIntent pendingIntent, 4126 Pair<Intent, ActivityOptions> options) { 4127 try { 4128 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 4129 Context context = view.getContext(); 4130 // The NEW_TASK flags are applied through the activity options and not as a part of 4131 // the call to startIntentSender() to ensure that they are consistently applied to 4132 // both mutable and immutable PendingIntents. 4133 context.startIntentSender( 4134 pendingIntent.getIntentSender(), options.first, 4135 0, 0, 0, options.second.toBundle()); 4136 } catch (IntentSender.SendIntentException e) { 4137 Log.e(LOG_TAG, "Cannot send pending intent: ", e); 4138 return false; 4139 } catch (Exception e) { 4140 Log.e(LOG_TAG, "Cannot send pending intent due to unknown exception: ", e); 4141 return false; 4142 } 4143 return true; 4144 } 4145 } 4146