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