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