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