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