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 * @hide 2792 * Equivalent to calling {@link android.widget.TextView#setTextColor(ColorStateList)}. 2793 * 2794 * @param viewId The id of the view whose text color should change 2795 * @param colors the text colors to set 2796 */ 2797 public void setTextColor(int viewId, @ColorInt ColorStateList colors) { 2798 addAction(new ReflectionAction(viewId, "setTextColor", ReflectionAction.COLOR_STATE_LIST, 2799 colors)); 2800 } 2801 2802 /** 2803 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2804 * 2805 * @param appWidgetId The id of the app widget which contains the specified view. (This 2806 * parameter is ignored in this deprecated method) 2807 * @param viewId The id of the {@link AdapterView} 2808 * @param intent The intent of the service which will be 2809 * providing data to the RemoteViewsAdapter 2810 * @deprecated This method has been deprecated. See 2811 * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} 2812 */ 2813 @Deprecated 2814 public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) { 2815 setRemoteAdapter(viewId, intent); 2816 } 2817 2818 /** 2819 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2820 * Can only be used for App Widgets. 2821 * 2822 * @param viewId The id of the {@link AdapterView} 2823 * @param intent The intent of the service which will be 2824 * providing data to the RemoteViewsAdapter 2825 */ 2826 public void setRemoteAdapter(int viewId, Intent intent) { 2827 addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); 2828 } 2829 2830 /** 2831 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 2832 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 2833 * This is a simpler but less flexible approach to populating collection widgets. Its use is 2834 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 2835 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 2836 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 2837 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 2838 * 2839 * This API is supported in the compatibility library for previous API levels, see 2840 * RemoteViewsCompat. 2841 * 2842 * @param viewId The id of the {@link AdapterView} 2843 * @param list The list of RemoteViews which will populate the view specified by viewId. 2844 * @param viewTypeCount The maximum number of unique layout id's used to construct the list of 2845 * RemoteViews. This count cannot change during the life-cycle of a given widget, so this 2846 * parameter should account for the maximum possible number of types that may appear in the 2847 * See {@link Adapter#getViewTypeCount()}. 2848 * 2849 * @hide 2850 */ 2851 public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) { 2852 addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); 2853 } 2854 2855 /** 2856 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 2857 * 2858 * @param viewId The id of the view to change 2859 * @param position Scroll to this adapter position 2860 */ 2861 public void setScrollPosition(int viewId, int position) { 2862 setInt(viewId, "smoothScrollToPosition", position); 2863 } 2864 2865 /** 2866 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 2867 * 2868 * @param viewId The id of the view to change 2869 * @param offset Scroll by this adapter position offset 2870 */ 2871 public void setRelativeScrollPosition(int viewId, int offset) { 2872 setInt(viewId, "smoothScrollByOffset", offset); 2873 } 2874 2875 /** 2876 * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}. 2877 * 2878 * @param viewId The id of the view to change 2879 * @param left the left padding in pixels 2880 * @param top the top padding in pixels 2881 * @param right the right padding in pixels 2882 * @param bottom the bottom padding in pixels 2883 */ 2884 public void setViewPadding(int viewId, int left, int top, int right, int bottom) { 2885 addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); 2886 } 2887 2888 /** 2889 * @hide 2890 * Equivalent to calling {@link android.view.ViewGroup.MarginLayoutParams#setMarginEnd(int)}. 2891 * Only works if the {@link View#getLayoutParams()} supports margins. 2892 * Hidden for now since we don't want to support this for all different layout margins yet. 2893 * 2894 * @param viewId The id of the view to change 2895 * @param endMarginDimen a dimen resource to read the margin from or 0 to clear the margin. 2896 */ 2897 public void setViewLayoutMarginEndDimen(int viewId, @DimenRes int endMarginDimen) { 2898 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_END_DIMEN, 2899 endMarginDimen)); 2900 } 2901 2902 /** 2903 * Equivalent to setting {@link android.view.ViewGroup.MarginLayoutParams#bottomMargin}. 2904 * 2905 * @param bottomMarginDimen a dimen resource to read the margin from or 0 to clear the margin. 2906 * @hide 2907 */ 2908 public void setViewLayoutMarginBottomDimen(int viewId, @DimenRes int bottomMarginDimen) { 2909 addAction(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_MARGIN_BOTTOM_DIMEN, 2910 bottomMarginDimen)); 2911 } 2912 2913 /** 2914 * Equivalent to setting {@link android.view.ViewGroup.LayoutParams#width}. 2915 * 2916 * @param layoutWidth one of 0, MATCH_PARENT or WRAP_CONTENT. Other sizes are not allowed 2917 * because they behave poorly when the density changes. 2918 * @hide 2919 */ 2920 public void setViewLayoutWidth(int viewId, int layoutWidth) { 2921 if (layoutWidth != 0 && layoutWidth != ViewGroup.LayoutParams.MATCH_PARENT 2922 && layoutWidth != ViewGroup.LayoutParams.WRAP_CONTENT) { 2923 throw new IllegalArgumentException("Only supports 0, WRAP_CONTENT and MATCH_PARENT"); 2924 } 2925 mActions.add(new LayoutParamAction(viewId, LayoutParamAction.LAYOUT_WIDTH, layoutWidth)); 2926 } 2927 2928 /** 2929 * Call a method taking one boolean on a view in the layout for this RemoteViews. 2930 * 2931 * @param viewId The id of the view on which to call the method. 2932 * @param methodName The name of the method to call. 2933 * @param value The value to pass to the method. 2934 */ 2935 public void setBoolean(int viewId, String methodName, boolean value) { 2936 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); 2937 } 2938 2939 /** 2940 * Call a method taking one byte on a view in the layout for this RemoteViews. 2941 * 2942 * @param viewId The id of the view on which to call the method. 2943 * @param methodName The name of the method to call. 2944 * @param value The value to pass to the method. 2945 */ 2946 public void setByte(int viewId, String methodName, byte value) { 2947 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); 2948 } 2949 2950 /** 2951 * Call a method taking one short on a view in the layout for this RemoteViews. 2952 * 2953 * @param viewId The id of the view on which to call the method. 2954 * @param methodName The name of the method to call. 2955 * @param value The value to pass to the method. 2956 */ 2957 public void setShort(int viewId, String methodName, short value) { 2958 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); 2959 } 2960 2961 /** 2962 * Call a method taking one int on a view in the layout for this RemoteViews. 2963 * 2964 * @param viewId The id of the view on which to call the method. 2965 * @param methodName The name of the method to call. 2966 * @param value The value to pass to the method. 2967 */ 2968 public void setInt(int viewId, String methodName, int value) { 2969 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); 2970 } 2971 2972 /** 2973 * Call a method taking one long on a view in the layout for this RemoteViews. 2974 * 2975 * @param viewId The id of the view on which to call the method. 2976 * @param methodName The name of the method to call. 2977 * @param value The value to pass to the method. 2978 */ 2979 public void setLong(int viewId, String methodName, long value) { 2980 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); 2981 } 2982 2983 /** 2984 * Call a method taking one float on a view in the layout for this RemoteViews. 2985 * 2986 * @param viewId The id of the view on which to call the method. 2987 * @param methodName The name of the method to call. 2988 * @param value The value to pass to the method. 2989 */ 2990 public void setFloat(int viewId, String methodName, float value) { 2991 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); 2992 } 2993 2994 /** 2995 * Call a method taking one double on a view in the layout for this RemoteViews. 2996 * 2997 * @param viewId The id of the view on which to call the method. 2998 * @param methodName The name of the method to call. 2999 * @param value The value to pass to the method. 3000 */ 3001 public void setDouble(int viewId, String methodName, double value) { 3002 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); 3003 } 3004 3005 /** 3006 * Call a method taking one char on a view in the layout for this RemoteViews. 3007 * 3008 * @param viewId The id of the view on which to call the method. 3009 * @param methodName The name of the method to call. 3010 * @param value The value to pass to the method. 3011 */ 3012 public void setChar(int viewId, String methodName, char value) { 3013 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); 3014 } 3015 3016 /** 3017 * Call a method taking one String on a view in the layout for this RemoteViews. 3018 * 3019 * @param viewId The id of the view on which to call the method. 3020 * @param methodName The name of the method to call. 3021 * @param value The value to pass to the method. 3022 */ 3023 public void setString(int viewId, String methodName, String value) { 3024 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); 3025 } 3026 3027 /** 3028 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 3029 * 3030 * @param viewId The id of the view on which to call the method. 3031 * @param methodName The name of the method to call. 3032 * @param value The value to pass to the method. 3033 */ 3034 public void setCharSequence(int viewId, String methodName, CharSequence value) { 3035 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); 3036 } 3037 3038 /** 3039 * Call a method taking one Uri on a view in the layout for this RemoteViews. 3040 * 3041 * @param viewId The id of the view on which to call the method. 3042 * @param methodName The name of the method to call. 3043 * @param value The value to pass to the method. 3044 */ 3045 public void setUri(int viewId, String methodName, Uri value) { 3046 if (value != null) { 3047 // Resolve any filesystem path before sending remotely 3048 value = value.getCanonicalUri(); 3049 if (StrictMode.vmFileUriExposureEnabled()) { 3050 value.checkFileUriExposed("RemoteViews.setUri()"); 3051 } 3052 } 3053 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); 3054 } 3055 3056 /** 3057 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 3058 * @more 3059 * <p class="note">The bitmap will be flattened into the parcel if this object is 3060 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 3061 * 3062 * @param viewId The id of the view on which to call the method. 3063 * @param methodName The name of the method to call. 3064 * @param value The value to pass to the method. 3065 */ 3066 public void setBitmap(int viewId, String methodName, Bitmap value) { 3067 addAction(new BitmapReflectionAction(viewId, methodName, value)); 3068 } 3069 3070 /** 3071 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 3072 * 3073 * @param viewId The id of the view on which to call the method. 3074 * @param methodName The name of the method to call. 3075 * @param value The value to pass to the method. 3076 */ 3077 public void setBundle(int viewId, String methodName, Bundle value) { 3078 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); 3079 } 3080 3081 /** 3082 * Call a method taking one Intent on a view in the layout for this RemoteViews. 3083 * 3084 * @param viewId The id of the view on which to call the method. 3085 * @param methodName The name of the method to call. 3086 * @param value The {@link android.content.Intent} to pass the method. 3087 */ 3088 public void setIntent(int viewId, String methodName, Intent value) { 3089 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); 3090 } 3091 3092 /** 3093 * Call a method taking one Icon on a view in the layout for this RemoteViews. 3094 * 3095 * @param viewId The id of the view on which to call the method. 3096 * @param methodName The name of the method to call. 3097 * @param value The {@link android.graphics.drawable.Icon} to pass the method. 3098 */ 3099 public void setIcon(int viewId, String methodName, Icon value) { 3100 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.ICON, value)); 3101 } 3102 3103 /** 3104 * Equivalent to calling View.setContentDescription(CharSequence). 3105 * 3106 * @param viewId The id of the view whose content description should change. 3107 * @param contentDescription The new content description for the view. 3108 */ 3109 public void setContentDescription(int viewId, CharSequence contentDescription) { 3110 setCharSequence(viewId, "setContentDescription", contentDescription); 3111 } 3112 3113 /** 3114 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. 3115 * 3116 * @param viewId The id of the view whose before view in accessibility traversal to set. 3117 * @param nextId The id of the next in the accessibility traversal. 3118 **/ 3119 public void setAccessibilityTraversalBefore(int viewId, int nextId) { 3120 setInt(viewId, "setAccessibilityTraversalBefore", nextId); 3121 } 3122 3123 /** 3124 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. 3125 * 3126 * @param viewId The id of the view whose after view in accessibility traversal to set. 3127 * @param nextId The id of the next in the accessibility traversal. 3128 **/ 3129 public void setAccessibilityTraversalAfter(int viewId, int nextId) { 3130 setInt(viewId, "setAccessibilityTraversalAfter", nextId); 3131 } 3132 3133 /** 3134 * Equivalent to calling View.setLabelFor(int). 3135 * 3136 * @param viewId The id of the view whose property to set. 3137 * @param labeledId The id of a view for which this view serves as a label. 3138 */ 3139 public void setLabelFor(int viewId, int labeledId) { 3140 setInt(viewId, "setLabelFor", labeledId); 3141 } 3142 3143 private RemoteViews getRemoteViewsToApply(Context context) { 3144 if (hasLandscapeAndPortraitLayouts()) { 3145 int orientation = context.getResources().getConfiguration().orientation; 3146 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 3147 return mLandscape; 3148 } else { 3149 return mPortrait; 3150 } 3151 } 3152 return this; 3153 } 3154 3155 /** 3156 * Inflates the view hierarchy represented by this object and applies 3157 * all of the actions. 3158 * 3159 * <p><strong>Caller beware: this may throw</strong> 3160 * 3161 * @param context Default context to use 3162 * @param parent Parent that the resulting view hierarchy will be attached to. This method 3163 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 3164 * @return The inflated view hierarchy 3165 */ 3166 public View apply(Context context, ViewGroup parent) { 3167 return apply(context, parent, null); 3168 } 3169 3170 /** @hide */ 3171 public View apply(Context context, ViewGroup parent, OnClickHandler handler) { 3172 RemoteViews rvToApply = getRemoteViewsToApply(context); 3173 3174 View result = inflateView(context, rvToApply, parent); 3175 loadTransitionOverride(context, handler); 3176 3177 rvToApply.performApply(result, parent, handler); 3178 3179 return result; 3180 } 3181 3182 private View inflateView(Context context, RemoteViews rv, ViewGroup parent) { 3183 // RemoteViews may be built by an application installed in another 3184 // user. So build a context that loads resources from that user but 3185 // still returns the current users userId so settings like data / time formats 3186 // are loaded without requiring cross user persmissions. 3187 final Context contextForResources = getContextForResources(context); 3188 Context inflationContext = new ContextWrapper(context) { 3189 @Override 3190 public Resources getResources() { 3191 return contextForResources.getResources(); 3192 } 3193 @Override 3194 public Resources.Theme getTheme() { 3195 return contextForResources.getTheme(); 3196 } 3197 @Override 3198 public String getPackageName() { 3199 return contextForResources.getPackageName(); 3200 } 3201 }; 3202 3203 LayoutInflater inflater = (LayoutInflater) 3204 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 3205 3206 // Clone inflater so we load resources from correct context and 3207 // we don't add a filter to the static version returned by getSystemService. 3208 inflater = inflater.cloneInContext(inflationContext); 3209 inflater.setFilter(this); 3210 View v = inflater.inflate(rv.getLayoutId(), parent, false); 3211 v.setTagInternal(R.id.widget_frame, rv.getLayoutId()); 3212 return v; 3213 } 3214 3215 private static void loadTransitionOverride(Context context, 3216 RemoteViews.OnClickHandler handler) { 3217 if (handler != null && context.getResources().getBoolean( 3218 com.android.internal.R.bool.config_overrideRemoteViewsActivityTransition)) { 3219 TypedArray windowStyle = context.getTheme().obtainStyledAttributes( 3220 com.android.internal.R.styleable.Window); 3221 int windowAnimations = windowStyle.getResourceId( 3222 com.android.internal.R.styleable.Window_windowAnimationStyle, 0); 3223 TypedArray windowAnimationStyle = context.obtainStyledAttributes( 3224 windowAnimations, com.android.internal.R.styleable.WindowAnimation); 3225 handler.setEnterAnimationId(windowAnimationStyle.getResourceId( 3226 com.android.internal.R.styleable. 3227 WindowAnimation_activityOpenRemoteViewsEnterAnimation, 0)); 3228 windowStyle.recycle(); 3229 windowAnimationStyle.recycle(); 3230 } 3231 } 3232 3233 /** 3234 * Implement this interface to receive a callback when 3235 * {@link #applyAsync} or {@link #reapplyAsync} is finished. 3236 * @hide 3237 */ 3238 public interface OnViewAppliedListener { 3239 void onViewApplied(View v); 3240 3241 void onError(Exception e); 3242 } 3243 3244 /** 3245 * Applies the views asynchronously, moving as much of the task on the background 3246 * thread as possible. 3247 * 3248 * @see {@link #apply(Context, ViewGroup)} 3249 * @param context Default context to use 3250 * @param parent Parent that the resulting view hierarchy will be attached to. This method 3251 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 3252 * @param listener the callback to run when all actions have been applied. May be null. 3253 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used. 3254 * @return CancellationSignal 3255 * @hide 3256 */ 3257 public CancellationSignal applyAsync( 3258 Context context, ViewGroup parent, Executor executor, OnViewAppliedListener listener) { 3259 return applyAsync(context, parent, executor, listener, null); 3260 } 3261 3262 private CancellationSignal startTaskOnExecutor(AsyncApplyTask task, Executor executor) { 3263 CancellationSignal cancelSignal = new CancellationSignal(); 3264 cancelSignal.setOnCancelListener(task); 3265 3266 task.executeOnExecutor(executor == null ? AsyncTask.THREAD_POOL_EXECUTOR : executor); 3267 return cancelSignal; 3268 } 3269 3270 /** @hide */ 3271 public CancellationSignal applyAsync(Context context, ViewGroup parent, 3272 Executor executor, OnViewAppliedListener listener, OnClickHandler handler) { 3273 return startTaskOnExecutor(getAsyncApplyTask(context, parent, listener, handler), executor); 3274 } 3275 3276 private AsyncApplyTask getAsyncApplyTask(Context context, ViewGroup parent, 3277 OnViewAppliedListener listener, OnClickHandler handler) { 3278 return new AsyncApplyTask(getRemoteViewsToApply(context), parent, context, listener, 3279 handler, null); 3280 } 3281 3282 private class AsyncApplyTask extends AsyncTask<Void, Void, ViewTree> 3283 implements CancellationSignal.OnCancelListener { 3284 final RemoteViews mRV; 3285 final ViewGroup mParent; 3286 final Context mContext; 3287 final OnViewAppliedListener mListener; 3288 final OnClickHandler mHandler; 3289 3290 private View mResult; 3291 private ViewTree mTree; 3292 private Action[] mActions; 3293 private Exception mError; 3294 3295 private AsyncApplyTask( 3296 RemoteViews rv, ViewGroup parent, Context context, OnViewAppliedListener listener, 3297 OnClickHandler handler, View result) { 3298 mRV = rv; 3299 mParent = parent; 3300 mContext = context; 3301 mListener = listener; 3302 mHandler = handler; 3303 3304 mResult = result; 3305 loadTransitionOverride(context, handler); 3306 } 3307 3308 @Override 3309 protected ViewTree doInBackground(Void... params) { 3310 try { 3311 if (mResult == null) { 3312 mResult = inflateView(mContext, mRV, mParent); 3313 } 3314 3315 mTree = new ViewTree(mResult); 3316 if (mRV.mActions != null) { 3317 int count = mRV.mActions.size(); 3318 mActions = new Action[count]; 3319 for (int i = 0; i < count && !isCancelled(); i++) { 3320 // TODO: check if isCanclled in nested views. 3321 mActions[i] = mRV.mActions.get(i).initActionAsync(mTree, mParent, mHandler); 3322 } 3323 } else { 3324 mActions = null; 3325 } 3326 return mTree; 3327 } catch (Exception e) { 3328 mError = e; 3329 return null; 3330 } 3331 } 3332 3333 @Override 3334 protected void onPostExecute(ViewTree viewTree) { 3335 if (mError == null) { 3336 try { 3337 if (mActions != null) { 3338 OnClickHandler handler = mHandler == null 3339 ? DEFAULT_ON_CLICK_HANDLER : mHandler; 3340 for (Action a : mActions) { 3341 a.apply(viewTree.mRoot, mParent, handler); 3342 } 3343 } 3344 } catch (Exception e) { 3345 mError = e; 3346 } 3347 } 3348 3349 if (mListener != null) { 3350 if (mError != null) { 3351 mListener.onError(mError); 3352 } else { 3353 mListener.onViewApplied(viewTree.mRoot); 3354 } 3355 } else if (mError != null) { 3356 if (mError instanceof ActionException) { 3357 throw (ActionException) mError; 3358 } else { 3359 throw new ActionException(mError); 3360 } 3361 } 3362 } 3363 3364 @Override 3365 public void onCancel() { 3366 cancel(true); 3367 } 3368 } 3369 3370 /** 3371 * Applies all of the actions to the provided view. 3372 * 3373 * <p><strong>Caller beware: this may throw</strong> 3374 * 3375 * @param v The view to apply the actions to. This should be the result of 3376 * the {@link #apply(Context,ViewGroup)} call. 3377 */ 3378 public void reapply(Context context, View v) { 3379 reapply(context, v, null); 3380 } 3381 3382 /** @hide */ 3383 public void reapply(Context context, View v, OnClickHandler handler) { 3384 RemoteViews rvToApply = getRemoteViewsToApply(context); 3385 3386 // In the case that a view has this RemoteViews applied in one orientation, is persisted 3387 // across orientation change, and has the RemoteViews re-applied in the new orientation, 3388 // we throw an exception, since the layouts may be completely unrelated. 3389 if (hasLandscapeAndPortraitLayouts()) { 3390 if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { 3391 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 3392 " that does not share the same root layout id."); 3393 } 3394 } 3395 3396 rvToApply.performApply(v, (ViewGroup) v.getParent(), handler); 3397 } 3398 3399 /** 3400 * Applies all the actions to the provided view, moving as much of the task on the background 3401 * thread as possible. 3402 * 3403 * @see {@link #reapply(Context, View)} 3404 * @param context Default context to use 3405 * @param v The view to apply the actions to. This should be the result of 3406 * the {@link #apply(Context,ViewGroup)} call. 3407 * @param listener the callback to run when all actions have been applied. May be null. 3408 * @param executor The executor to use. If null {@link AsyncTask#THREAD_POOL_EXECUTOR} is used 3409 * @return CancellationSignal 3410 * @hide 3411 */ 3412 public CancellationSignal reapplyAsync( 3413 Context context, View v, Executor executor, OnViewAppliedListener listener) { 3414 return reapplyAsync(context, v, executor, listener, null); 3415 } 3416 3417 /** @hide */ 3418 public CancellationSignal reapplyAsync(Context context, View v, Executor executor, 3419 OnViewAppliedListener listener, OnClickHandler handler) { 3420 RemoteViews rvToApply = getRemoteViewsToApply(context); 3421 3422 // In the case that a view has this RemoteViews applied in one orientation, is persisted 3423 // across orientation change, and has the RemoteViews re-applied in the new orientation, 3424 // we throw an exception, since the layouts may be completely unrelated. 3425 if (hasLandscapeAndPortraitLayouts()) { 3426 if ((Integer) v.getTag(R.id.widget_frame) != rvToApply.getLayoutId()) { 3427 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 3428 " that does not share the same root layout id."); 3429 } 3430 } 3431 3432 return startTaskOnExecutor(new AsyncApplyTask(rvToApply, (ViewGroup) v.getParent(), 3433 context, listener, handler, v), executor); 3434 } 3435 3436 private void performApply(View v, ViewGroup parent, OnClickHandler handler) { 3437 if (mActions != null) { 3438 handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler; 3439 final int count = mActions.size(); 3440 for (int i = 0; i < count; i++) { 3441 Action a = mActions.get(i); 3442 a.apply(v, parent, handler); 3443 } 3444 } 3445 } 3446 3447 private Context getContextForResources(Context context) { 3448 if (mApplication != null) { 3449 if (context.getUserId() == UserHandle.getUserId(mApplication.uid) 3450 && context.getPackageName().equals(mApplication.packageName)) { 3451 return context; 3452 } 3453 try { 3454 return context.createApplicationContext(mApplication, 3455 Context.CONTEXT_RESTRICTED); 3456 } catch (NameNotFoundException e) { 3457 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); 3458 } 3459 } 3460 3461 return context; 3462 } 3463 3464 /** 3465 * Returns the number of actions in this RemoteViews. Can be used as a sequence number. 3466 * 3467 * @hide 3468 */ 3469 public int getSequenceNumber() { 3470 return (mActions == null) ? 0 : mActions.size(); 3471 } 3472 3473 /* (non-Javadoc) 3474 * Used to restrict the views which can be inflated 3475 * 3476 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 3477 */ 3478 public boolean onLoadClass(Class clazz) { 3479 return clazz.isAnnotationPresent(RemoteView.class); 3480 } 3481 3482 public int describeContents() { 3483 return 0; 3484 } 3485 3486 public void writeToParcel(Parcel dest, int flags) { 3487 if (!hasLandscapeAndPortraitLayouts()) { 3488 dest.writeInt(MODE_NORMAL); 3489 // We only write the bitmap cache if we are the root RemoteViews, as this cache 3490 // is shared by all children. 3491 if (mIsRoot) { 3492 mBitmapCache.writeBitmapsToParcel(dest, flags); 3493 } 3494 dest.writeParcelable(mApplication, flags); 3495 dest.writeInt(mLayoutId); 3496 dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); 3497 int count; 3498 if (mActions != null) { 3499 count = mActions.size(); 3500 } else { 3501 count = 0; 3502 } 3503 dest.writeInt(count); 3504 for (int i=0; i<count; i++) { 3505 Action a = mActions.get(i); 3506 a.writeToParcel(dest, 0); 3507 } 3508 } else { 3509 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 3510 // We only write the bitmap cache if we are the root RemoteViews, as this cache 3511 // is shared by all children. 3512 if (mIsRoot) { 3513 mBitmapCache.writeBitmapsToParcel(dest, flags); 3514 } 3515 mLandscape.writeToParcel(dest, flags); 3516 mPortrait.writeToParcel(dest, flags); 3517 } 3518 } 3519 3520 private static ApplicationInfo getApplicationInfo(String packageName, int userId) { 3521 if (packageName == null) { 3522 return null; 3523 } 3524 3525 // Get the application for the passed in package and user. 3526 Application application = ActivityThread.currentApplication(); 3527 if (application == null) { 3528 throw new IllegalStateException("Cannot create remote views out of an aplication."); 3529 } 3530 3531 ApplicationInfo applicationInfo = application.getApplicationInfo(); 3532 if (UserHandle.getUserId(applicationInfo.uid) != userId 3533 || !applicationInfo.packageName.equals(packageName)) { 3534 try { 3535 Context context = application.getBaseContext().createPackageContextAsUser( 3536 packageName, 0, new UserHandle(userId)); 3537 applicationInfo = context.getApplicationInfo(); 3538 } catch (NameNotFoundException nnfe) { 3539 throw new IllegalArgumentException("No such package " + packageName); 3540 } 3541 } 3542 3543 return applicationInfo; 3544 } 3545 3546 /** 3547 * Parcelable.Creator that instantiates RemoteViews objects 3548 */ 3549 public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 3550 public RemoteViews createFromParcel(Parcel parcel) { 3551 return new RemoteViews(parcel); 3552 } 3553 3554 public RemoteViews[] newArray(int size) { 3555 return new RemoteViews[size]; 3556 } 3557 }; 3558 3559 /** 3560 * A representation of the view hierarchy. Only views which have a valid ID are added 3561 * and can be searched. 3562 */ 3563 private static class ViewTree { 3564 private final View mRoot; 3565 3566 private ArrayList<ViewTree> mChildren; 3567 3568 private ViewTree(View root) { 3569 mRoot = root; 3570 } 3571 3572 public void createTree() { 3573 if (mChildren != null) { 3574 return; 3575 } 3576 3577 mChildren = new ArrayList<>(); 3578 if (mRoot instanceof ViewGroup && mRoot.isRootNamespace()) { 3579 ViewGroup vg = (ViewGroup) mRoot; 3580 int count = vg.getChildCount(); 3581 for (int i = 0; i < count; i++) { 3582 addViewChild(vg.getChildAt(i)); 3583 } 3584 } 3585 } 3586 3587 public ViewTree findViewTreeById(int id) { 3588 if (mRoot.getId() == id) { 3589 return this; 3590 } 3591 if (mChildren == null) { 3592 return null; 3593 } 3594 for (ViewTree tree : mChildren) { 3595 ViewTree result = tree.findViewTreeById(id); 3596 if (result != null) { 3597 return result; 3598 } 3599 } 3600 return null; 3601 } 3602 3603 public View findViewById(int id) { 3604 if (mChildren == null) { 3605 return mRoot.findViewById(id); 3606 } 3607 ViewTree tree = findViewTreeById(id); 3608 return tree == null ? null : tree.mRoot; 3609 } 3610 3611 public void addChild(ViewTree child) { 3612 if (mChildren == null) { 3613 mChildren = new ArrayList<>(); 3614 } 3615 child.createTree(); 3616 mChildren.add(child); 3617 } 3618 3619 private void addViewChild(View v) { 3620 final ViewTree target; 3621 3622 // If the view has a valid id, i.e., if can be found using findViewById, add it to the 3623 // tree, otherwise skip this view and add its children instead. 3624 if (v.getId() != 0) { 3625 ViewTree tree = new ViewTree(v); 3626 mChildren.add(tree); 3627 target = tree; 3628 } else { 3629 target = this; 3630 } 3631 3632 if (v instanceof ViewGroup && v.isRootNamespace()) { 3633 if (target.mChildren == null) { 3634 target.mChildren = new ArrayList<>(); 3635 ViewGroup vg = (ViewGroup) v; 3636 int count = vg.getChildCount(); 3637 for (int i = 0; i < count; i++) { 3638 target.addViewChild(vg.getChildAt(i)); 3639 } 3640 } 3641 } 3642 } 3643 } 3644 } 3645