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