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