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