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