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