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