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.app.ActivityOptions; 20 import android.app.ActivityThread; 21 import android.app.Application; 22 import android.app.PendingIntent; 23 import android.appwidget.AppWidgetHostView; 24 import android.content.Context; 25 import android.content.ContextWrapper; 26 import android.content.Intent; 27 import android.content.IntentSender; 28 import android.content.pm.ApplicationInfo; 29 import android.content.pm.PackageManager.NameNotFoundException; 30 import android.content.res.ColorStateList; 31 import android.content.res.Configuration; 32 import android.content.res.Resources; 33 import android.graphics.Bitmap; 34 import android.graphics.PorterDuff; 35 import android.graphics.Rect; 36 import android.graphics.drawable.Drawable; 37 import android.net.Uri; 38 import android.os.Build; 39 import android.os.Bundle; 40 import android.os.Parcel; 41 import android.os.Parcelable; 42 import android.os.StrictMode; 43 import android.os.UserHandle; 44 import android.text.TextUtils; 45 import android.util.ArrayMap; 46 import android.util.Log; 47 import android.view.LayoutInflater; 48 import android.view.LayoutInflater.Filter; 49 import android.view.RemotableViewMethod; 50 import android.view.View; 51 import android.view.View.OnClickListener; 52 import android.view.ViewGroup; 53 import android.widget.AdapterView.OnItemClickListener; 54 import libcore.util.Objects; 55 56 import java.lang.annotation.ElementType; 57 import java.lang.annotation.Retention; 58 import java.lang.annotation.RetentionPolicy; 59 import java.lang.annotation.Target; 60 import java.lang.reflect.Method; 61 import java.util.ArrayList; 62 import java.util.HashMap; 63 64 /** 65 * A class that describes a view hierarchy that can be displayed in 66 * another process. The hierarchy is inflated from a layout resource 67 * file, and this class provides some basic operations for modifying 68 * the content of the inflated hierarchy. 69 */ 70 public class RemoteViews implements Parcelable, Filter { 71 72 private static final String LOG_TAG = "RemoteViews"; 73 74 /** 75 * The intent extra that contains the appWidgetId. 76 * @hide 77 */ 78 static final String EXTRA_REMOTEADAPTER_APPWIDGET_ID = "remoteAdapterAppWidgetId"; 79 80 /** 81 * Application that hosts the remote views. 82 * 83 * @hide 84 */ 85 private ApplicationInfo mApplication; 86 87 /** 88 * The resource ID of the layout file. (Added to the parcel) 89 */ 90 private final int mLayoutId; 91 92 /** 93 * An array of actions to perform on the view tree once it has been 94 * inflated 95 */ 96 private ArrayList<Action> mActions; 97 98 /** 99 * A class to keep track of memory usage by this RemoteViews 100 */ 101 private MemoryUsageCounter mMemoryUsageCounter; 102 103 /** 104 * Maps bitmaps to unique indicies to avoid Bitmap duplication. 105 */ 106 private BitmapCache mBitmapCache; 107 108 /** 109 * Indicates whether or not this RemoteViews object is contained as a child of any other 110 * RemoteViews. 111 */ 112 private boolean mIsRoot = true; 113 114 /** 115 * Constants to whether or not this RemoteViews is composed of a landscape and portrait 116 * RemoteViews. 117 */ 118 private static final int MODE_NORMAL = 0; 119 private static final int MODE_HAS_LANDSCAPE_AND_PORTRAIT = 1; 120 121 /** 122 * Used in conjunction with the special constructor 123 * {@link #RemoteViews(RemoteViews, RemoteViews)} to keep track of the landscape and portrait 124 * RemoteViews. 125 */ 126 private RemoteViews mLandscape = null; 127 private RemoteViews mPortrait = null; 128 129 /** 130 * This flag indicates whether this RemoteViews object is being created from a 131 * RemoteViewsService for use as a child of a widget collection. This flag is used 132 * to determine whether or not certain features are available, in particular, 133 * setting on click extras and setting on click pending intents. The former is enabled, 134 * and the latter disabled when this flag is true. 135 */ 136 private boolean mIsWidgetCollectionChild = false; 137 138 private static final OnClickHandler DEFAULT_ON_CLICK_HANDLER = new OnClickHandler(); 139 140 private static final Object[] sMethodsLock = new Object[0]; 141 private static final ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>> sMethods = 142 new ArrayMap<Class<? extends View>, ArrayMap<MutablePair<String, Class<?>>, Method>>(); 143 private static final ThreadLocal<Object[]> sInvokeArgsTls = new ThreadLocal<Object[]>() { 144 @Override 145 protected Object[] initialValue() { 146 return new Object[1]; 147 } 148 }; 149 150 /** 151 * Handle with care! 152 */ 153 static class MutablePair<F, S> { 154 F first; 155 S second; 156 MutablePair(F first, S second)157 MutablePair(F first, S second) { 158 this.first = first; 159 this.second = second; 160 } 161 162 @Override equals(Object o)163 public boolean equals(Object o) { 164 if (!(o instanceof MutablePair)) { 165 return false; 166 } 167 MutablePair<?, ?> p = (MutablePair<?, ?>) o; 168 return Objects.equal(p.first, first) && Objects.equal(p.second, second); 169 } 170 171 @Override hashCode()172 public int hashCode() { 173 return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode()); 174 } 175 } 176 177 /** 178 * This pair is used to perform lookups in sMethods without causing allocations. 179 */ 180 private final MutablePair<String, Class<?>> mPair = 181 new MutablePair<String, Class<?>>(null, null); 182 183 /** 184 * This annotation indicates that a subclass of View is alllowed to be used 185 * with the {@link RemoteViews} mechanism. 186 */ 187 @Target({ ElementType.TYPE }) 188 @Retention(RetentionPolicy.RUNTIME) 189 public @interface RemoteView { 190 } 191 192 /** 193 * Exception to send when something goes wrong executing an action 194 * 195 */ 196 public static class ActionException extends RuntimeException { ActionException(Exception ex)197 public ActionException(Exception ex) { 198 super(ex); 199 } ActionException(String message)200 public ActionException(String message) { 201 super(message); 202 } 203 } 204 205 /** @hide */ 206 public static class OnClickHandler { onClickHandler(View view, PendingIntent pendingIntent, Intent fillInIntent)207 public boolean onClickHandler(View view, PendingIntent pendingIntent, 208 Intent fillInIntent) { 209 try { 210 // TODO: Unregister this handler if PendingIntent.FLAG_ONE_SHOT? 211 Context context = view.getContext(); 212 ActivityOptions opts = ActivityOptions.makeScaleUpAnimation(view, 213 0, 0, 214 view.getMeasuredWidth(), view.getMeasuredHeight()); 215 context.startIntentSender( 216 pendingIntent.getIntentSender(), fillInIntent, 217 Intent.FLAG_ACTIVITY_NEW_TASK, 218 Intent.FLAG_ACTIVITY_NEW_TASK, 0, opts.toBundle()); 219 } catch (IntentSender.SendIntentException e) { 220 android.util.Log.e(LOG_TAG, "Cannot send pending intent: ", e); 221 return false; 222 } catch (Exception e) { 223 android.util.Log.e(LOG_TAG, "Cannot send pending intent due to " + 224 "unknown exception: ", e); 225 return false; 226 } 227 return true; 228 } 229 } 230 231 /** 232 * Base class for all actions that can be performed on an 233 * inflated view. 234 * 235 * SUBCLASSES MUST BE IMMUTABLE SO CLONE WORKS!!!!! 236 */ 237 private abstract static class Action implements Parcelable { apply(View root, ViewGroup rootParent, OnClickHandler handler)238 public abstract void apply(View root, ViewGroup rootParent, 239 OnClickHandler handler) throws ActionException; 240 241 public static final int MERGE_REPLACE = 0; 242 public static final int MERGE_APPEND = 1; 243 public static final int MERGE_IGNORE = 2; 244 describeContents()245 public int describeContents() { 246 return 0; 247 } 248 249 /** 250 * Overridden by each class to report on it's own memory usage 251 */ updateMemoryUsageEstimate(MemoryUsageCounter counter)252 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 253 // We currently only calculate Bitmap memory usage, so by default, 254 // don't do anything here 255 } 256 setBitmapCache(BitmapCache bitmapCache)257 public void setBitmapCache(BitmapCache bitmapCache) { 258 // Do nothing 259 } 260 mergeBehavior()261 public int mergeBehavior() { 262 return MERGE_REPLACE; 263 } 264 getActionName()265 public abstract String getActionName(); 266 getUniqueKey()267 public String getUniqueKey() { 268 return (getActionName() + viewId); 269 } 270 271 int viewId; 272 } 273 274 /** 275 * Merges the passed RemoteViews actions with this RemoteViews actions according to 276 * action-specific merge rules. 277 * 278 * @param newRv 279 * 280 * @hide 281 */ mergeRemoteViews(RemoteViews newRv)282 public void mergeRemoteViews(RemoteViews newRv) { 283 if (newRv == null) return; 284 // We first copy the new RemoteViews, as the process of merging modifies the way the actions 285 // reference the bitmap cache. We don't want to modify the object as it may need to 286 // be merged and applied multiple times. 287 RemoteViews copy = newRv.clone(); 288 289 HashMap<String, Action> map = new HashMap<String, Action>(); 290 if (mActions == null) { 291 mActions = new ArrayList<Action>(); 292 } 293 294 int count = mActions.size(); 295 for (int i = 0; i < count; i++) { 296 Action a = mActions.get(i); 297 map.put(a.getUniqueKey(), a); 298 } 299 300 ArrayList<Action> newActions = copy.mActions; 301 if (newActions == null) return; 302 count = newActions.size(); 303 for (int i = 0; i < count; i++) { 304 Action a = newActions.get(i); 305 String key = newActions.get(i).getUniqueKey(); 306 int mergeBehavior = newActions.get(i).mergeBehavior(); 307 if (map.containsKey(key) && mergeBehavior == Action.MERGE_REPLACE) { 308 mActions.remove(map.get(key)); 309 map.remove(key); 310 } 311 312 // If the merge behavior is ignore, we don't bother keeping the extra action 313 if (mergeBehavior == Action.MERGE_REPLACE || mergeBehavior == Action.MERGE_APPEND) { 314 mActions.add(a); 315 } 316 } 317 318 // Because pruning can remove the need for bitmaps, we reconstruct the bitmap cache 319 mBitmapCache = new BitmapCache(); 320 setBitmapCache(mBitmapCache); 321 } 322 323 private class SetEmptyView extends Action { 324 int viewId; 325 int emptyViewId; 326 327 public final static int TAG = 6; 328 SetEmptyView(int viewId, int emptyViewId)329 SetEmptyView(int viewId, int emptyViewId) { 330 this.viewId = viewId; 331 this.emptyViewId = emptyViewId; 332 } 333 SetEmptyView(Parcel in)334 SetEmptyView(Parcel in) { 335 this.viewId = in.readInt(); 336 this.emptyViewId = in.readInt(); 337 } 338 writeToParcel(Parcel out, int flags)339 public void writeToParcel(Parcel out, int flags) { 340 out.writeInt(TAG); 341 out.writeInt(this.viewId); 342 out.writeInt(this.emptyViewId); 343 } 344 345 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)346 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 347 final View view = root.findViewById(viewId); 348 if (!(view instanceof AdapterView<?>)) return; 349 350 AdapterView<?> adapterView = (AdapterView<?>) view; 351 352 final View emptyView = root.findViewById(emptyViewId); 353 if (emptyView == null) return; 354 355 adapterView.setEmptyView(emptyView); 356 } 357 getActionName()358 public String getActionName() { 359 return "SetEmptyView"; 360 } 361 } 362 363 private class SetOnClickFillInIntent extends Action { SetOnClickFillInIntent(int id, Intent fillInIntent)364 public SetOnClickFillInIntent(int id, Intent fillInIntent) { 365 this.viewId = id; 366 this.fillInIntent = fillInIntent; 367 } 368 SetOnClickFillInIntent(Parcel parcel)369 public SetOnClickFillInIntent(Parcel parcel) { 370 viewId = parcel.readInt(); 371 fillInIntent = Intent.CREATOR.createFromParcel(parcel); 372 } 373 writeToParcel(Parcel dest, int flags)374 public void writeToParcel(Parcel dest, int flags) { 375 dest.writeInt(TAG); 376 dest.writeInt(viewId); 377 fillInIntent.writeToParcel(dest, 0 /* no flags */); 378 } 379 380 @Override apply(View root, ViewGroup rootParent, final OnClickHandler handler)381 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 382 final View target = root.findViewById(viewId); 383 if (target == null) return; 384 385 if (!mIsWidgetCollectionChild) { 386 Log.e(LOG_TAG, "The method setOnClickFillInIntent is available " + 387 "only from RemoteViewsFactory (ie. on collection items)."); 388 return; 389 } 390 if (target == root) { 391 target.setTagInternal(com.android.internal.R.id.fillInIntent, fillInIntent); 392 } else if (fillInIntent != null) { 393 OnClickListener listener = new OnClickListener() { 394 public void onClick(View v) { 395 // Insure that this view is a child of an AdapterView 396 View parent = (View) v.getParent(); 397 while (parent != null && !(parent instanceof AdapterView<?>) 398 && !(parent instanceof AppWidgetHostView)) { 399 parent = (View) parent.getParent(); 400 } 401 402 if (parent instanceof AppWidgetHostView || parent == null) { 403 // Somehow they've managed to get this far without having 404 // and AdapterView as a parent. 405 Log.e(LOG_TAG, "Collection item doesn't have AdapterView parent"); 406 return; 407 } 408 409 // Insure that a template pending intent has been set on an ancestor 410 if (!(parent.getTag() instanceof PendingIntent)) { 411 Log.e(LOG_TAG, "Attempting setOnClickFillInIntent without" + 412 " calling setPendingIntentTemplate on parent."); 413 return; 414 } 415 416 PendingIntent pendingIntent = (PendingIntent) parent.getTag(); 417 418 final Rect rect = getSourceBounds(v); 419 420 fillInIntent.setSourceBounds(rect); 421 handler.onClickHandler(v, pendingIntent, fillInIntent); 422 } 423 424 }; 425 target.setOnClickListener(listener); 426 } 427 } 428 getActionName()429 public String getActionName() { 430 return "SetOnClickFillInIntent"; 431 } 432 433 Intent fillInIntent; 434 435 public final static int TAG = 9; 436 } 437 438 private class SetPendingIntentTemplate extends Action { SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate)439 public SetPendingIntentTemplate(int id, PendingIntent pendingIntentTemplate) { 440 this.viewId = id; 441 this.pendingIntentTemplate = pendingIntentTemplate; 442 } 443 SetPendingIntentTemplate(Parcel parcel)444 public SetPendingIntentTemplate(Parcel parcel) { 445 viewId = parcel.readInt(); 446 pendingIntentTemplate = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 447 } 448 writeToParcel(Parcel dest, int flags)449 public void writeToParcel(Parcel dest, int flags) { 450 dest.writeInt(TAG); 451 dest.writeInt(viewId); 452 pendingIntentTemplate.writeToParcel(dest, 0 /* no flags */); 453 } 454 455 @Override apply(View root, ViewGroup rootParent, final OnClickHandler handler)456 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 457 final View target = root.findViewById(viewId); 458 if (target == null) return; 459 460 // If the view isn't an AdapterView, setting a PendingIntent template doesn't make sense 461 if (target instanceof AdapterView<?>) { 462 AdapterView<?> av = (AdapterView<?>) target; 463 // The PendingIntent template is stored in the view's tag. 464 OnItemClickListener listener = new OnItemClickListener() { 465 public void onItemClick(AdapterView<?> parent, View view, 466 int position, long id) { 467 // The view should be a frame layout 468 if (view instanceof ViewGroup) { 469 ViewGroup vg = (ViewGroup) view; 470 471 // AdapterViews contain their children in a frame 472 // so we need to go one layer deeper here. 473 if (parent instanceof AdapterViewAnimator) { 474 vg = (ViewGroup) vg.getChildAt(0); 475 } 476 if (vg == null) return; 477 478 Intent fillInIntent = null; 479 int childCount = vg.getChildCount(); 480 for (int i = 0; i < childCount; i++) { 481 Object tag = vg.getChildAt(i).getTag(com.android.internal.R.id.fillInIntent); 482 if (tag instanceof Intent) { 483 fillInIntent = (Intent) tag; 484 break; 485 } 486 } 487 if (fillInIntent == null) return; 488 489 final Rect rect = getSourceBounds(view); 490 491 final Intent intent = new Intent(); 492 intent.setSourceBounds(rect); 493 handler.onClickHandler(view, pendingIntentTemplate, fillInIntent); 494 } 495 } 496 }; 497 av.setOnItemClickListener(listener); 498 av.setTag(pendingIntentTemplate); 499 } else { 500 Log.e(LOG_TAG, "Cannot setPendingIntentTemplate on a view which is not" + 501 "an AdapterView (id: " + viewId + ")"); 502 return; 503 } 504 } 505 getActionName()506 public String getActionName() { 507 return "SetPendingIntentTemplate"; 508 } 509 510 PendingIntent pendingIntentTemplate; 511 512 public final static int TAG = 8; 513 } 514 515 private class SetRemoteViewsAdapterList extends Action { SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount)516 public SetRemoteViewsAdapterList(int id, ArrayList<RemoteViews> list, int viewTypeCount) { 517 this.viewId = id; 518 this.list = list; 519 this.viewTypeCount = viewTypeCount; 520 } 521 SetRemoteViewsAdapterList(Parcel parcel)522 public SetRemoteViewsAdapterList(Parcel parcel) { 523 viewId = parcel.readInt(); 524 viewTypeCount = parcel.readInt(); 525 int count = parcel.readInt(); 526 list = new ArrayList<RemoteViews>(); 527 528 for (int i = 0; i < count; i++) { 529 RemoteViews rv = RemoteViews.CREATOR.createFromParcel(parcel); 530 list.add(rv); 531 } 532 } 533 writeToParcel(Parcel dest, int flags)534 public void writeToParcel(Parcel dest, int flags) { 535 dest.writeInt(TAG); 536 dest.writeInt(viewId); 537 dest.writeInt(viewTypeCount); 538 539 if (list == null || list.size() == 0) { 540 dest.writeInt(0); 541 } else { 542 int count = list.size(); 543 dest.writeInt(count); 544 for (int i = 0; i < count; i++) { 545 RemoteViews rv = list.get(i); 546 rv.writeToParcel(dest, flags); 547 } 548 } 549 } 550 551 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)552 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 553 final View target = root.findViewById(viewId); 554 if (target == null) return; 555 556 // Ensure that we are applying to an AppWidget root 557 if (!(rootParent instanceof AppWidgetHostView)) { 558 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + 559 "AppWidgets (root id: " + viewId + ")"); 560 return; 561 } 562 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 563 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 564 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + 565 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 566 return; 567 } 568 569 if (target instanceof AbsListView) { 570 AbsListView v = (AbsListView) target; 571 Adapter a = v.getAdapter(); 572 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 573 ((RemoteViewsListAdapter) a).setViewsList(list); 574 } else { 575 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); 576 } 577 } else if (target instanceof AdapterViewAnimator) { 578 AdapterViewAnimator v = (AdapterViewAnimator) target; 579 Adapter a = v.getAdapter(); 580 if (a instanceof RemoteViewsListAdapter && viewTypeCount <= a.getViewTypeCount()) { 581 ((RemoteViewsListAdapter) a).setViewsList(list); 582 } else { 583 v.setAdapter(new RemoteViewsListAdapter(v.getContext(), list, viewTypeCount)); 584 } 585 } 586 } 587 getActionName()588 public String getActionName() { 589 return "SetRemoteViewsAdapterList"; 590 } 591 592 int viewTypeCount; 593 ArrayList<RemoteViews> list; 594 public final static int TAG = 15; 595 } 596 597 private class SetRemoteViewsAdapterIntent extends Action { SetRemoteViewsAdapterIntent(int id, Intent intent)598 public SetRemoteViewsAdapterIntent(int id, Intent intent) { 599 this.viewId = id; 600 this.intent = intent; 601 } 602 SetRemoteViewsAdapterIntent(Parcel parcel)603 public SetRemoteViewsAdapterIntent(Parcel parcel) { 604 viewId = parcel.readInt(); 605 intent = Intent.CREATOR.createFromParcel(parcel); 606 } 607 writeToParcel(Parcel dest, int flags)608 public void writeToParcel(Parcel dest, int flags) { 609 dest.writeInt(TAG); 610 dest.writeInt(viewId); 611 intent.writeToParcel(dest, flags); 612 } 613 614 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)615 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 616 final View target = root.findViewById(viewId); 617 if (target == null) return; 618 619 // Ensure that we are applying to an AppWidget root 620 if (!(rootParent instanceof AppWidgetHostView)) { 621 Log.e(LOG_TAG, "SetRemoteViewsAdapterIntent action can only be used for " + 622 "AppWidgets (root id: " + viewId + ")"); 623 return; 624 } 625 // Ensure that we are calling setRemoteAdapter on an AdapterView that supports it 626 if (!(target instanceof AbsListView) && !(target instanceof AdapterViewAnimator)) { 627 Log.e(LOG_TAG, "Cannot setRemoteViewsAdapter on a view which is not " + 628 "an AbsListView or AdapterViewAnimator (id: " + viewId + ")"); 629 return; 630 } 631 632 // Embed the AppWidget Id for use in RemoteViewsAdapter when connecting to the intent 633 // RemoteViewsService 634 AppWidgetHostView host = (AppWidgetHostView) rootParent; 635 intent.putExtra(EXTRA_REMOTEADAPTER_APPWIDGET_ID, host.getAppWidgetId()); 636 if (target instanceof AbsListView) { 637 AbsListView v = (AbsListView) target; 638 v.setRemoteViewsAdapter(intent); 639 v.setRemoteViewsOnClickHandler(handler); 640 } else if (target instanceof AdapterViewAnimator) { 641 AdapterViewAnimator v = (AdapterViewAnimator) target; 642 v.setRemoteViewsAdapter(intent); 643 v.setRemoteViewsOnClickHandler(handler); 644 } 645 } 646 getActionName()647 public String getActionName() { 648 return "SetRemoteViewsAdapterIntent"; 649 } 650 651 Intent intent; 652 653 public final static int TAG = 10; 654 } 655 656 /** 657 * Equivalent to calling 658 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 659 * to launch the provided {@link PendingIntent}. 660 */ 661 private class SetOnClickPendingIntent extends Action { SetOnClickPendingIntent(int id, PendingIntent pendingIntent)662 public SetOnClickPendingIntent(int id, PendingIntent pendingIntent) { 663 this.viewId = id; 664 this.pendingIntent = pendingIntent; 665 } 666 SetOnClickPendingIntent(Parcel parcel)667 public SetOnClickPendingIntent(Parcel parcel) { 668 viewId = parcel.readInt(); 669 670 // We check a flag to determine if the parcel contains a PendingIntent. 671 if (parcel.readInt() != 0) { 672 pendingIntent = PendingIntent.readPendingIntentOrNullFromParcel(parcel); 673 } 674 } 675 writeToParcel(Parcel dest, int flags)676 public void writeToParcel(Parcel dest, int flags) { 677 dest.writeInt(TAG); 678 dest.writeInt(viewId); 679 680 // We use a flag to indicate whether the parcel contains a valid object. 681 dest.writeInt(pendingIntent != null ? 1 : 0); 682 if (pendingIntent != null) { 683 pendingIntent.writeToParcel(dest, 0 /* no flags */); 684 } 685 } 686 687 @Override apply(View root, ViewGroup rootParent, final OnClickHandler handler)688 public void apply(View root, ViewGroup rootParent, final OnClickHandler handler) { 689 final View target = root.findViewById(viewId); 690 if (target == null) return; 691 692 // If the view is an AdapterView, setting a PendingIntent on click doesn't make much 693 // sense, do they mean to set a PendingIntent template for the AdapterView's children? 694 if (mIsWidgetCollectionChild) { 695 Log.w(LOG_TAG, "Cannot setOnClickPendingIntent for collection item " + 696 "(id: " + viewId + ")"); 697 ApplicationInfo appInfo = root.getContext().getApplicationInfo(); 698 699 // We let this slide for HC and ICS so as to not break compatibility. It should have 700 // been disabled from the outset, but was left open by accident. 701 if (appInfo != null && 702 appInfo.targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN) { 703 return; 704 } 705 } 706 707 // If the pendingIntent is null, we clear the onClickListener 708 OnClickListener listener = null; 709 if (pendingIntent != null) { 710 listener = new OnClickListener() { 711 public void onClick(View v) { 712 // Find target view location in screen coordinates and 713 // fill into PendingIntent before sending. 714 final Rect rect = getSourceBounds(v); 715 716 final Intent intent = new Intent(); 717 intent.setSourceBounds(rect); 718 handler.onClickHandler(v, pendingIntent, intent); 719 } 720 }; 721 } 722 target.setOnClickListener(listener); 723 } 724 getActionName()725 public String getActionName() { 726 return "SetOnClickPendingIntent"; 727 } 728 729 PendingIntent pendingIntent; 730 731 public final static int TAG = 1; 732 } 733 getSourceBounds(View v)734 private static Rect getSourceBounds(View v) { 735 final float appScale = v.getContext().getResources() 736 .getCompatibilityInfo().applicationScale; 737 final int[] pos = new int[2]; 738 v.getLocationOnScreen(pos); 739 740 final Rect rect = new Rect(); 741 rect.left = (int) (pos[0] * appScale + 0.5f); 742 rect.top = (int) (pos[1] * appScale + 0.5f); 743 rect.right = (int) ((pos[0] + v.getWidth()) * appScale + 0.5f); 744 rect.bottom = (int) ((pos[1] + v.getHeight()) * appScale + 0.5f); 745 return rect; 746 } 747 getMethod(View view, String methodName, Class<?> paramType)748 private Method getMethod(View view, String methodName, Class<?> paramType) { 749 Method method; 750 Class<? extends View> klass = view.getClass(); 751 752 synchronized (sMethodsLock) { 753 ArrayMap<MutablePair<String, Class<?>>, Method> methods = sMethods.get(klass); 754 if (methods == null) { 755 methods = new ArrayMap<MutablePair<String, Class<?>>, Method>(); 756 sMethods.put(klass, methods); 757 } 758 759 mPair.first = methodName; 760 mPair.second = paramType; 761 762 method = methods.get(mPair); 763 if (method == null) { 764 try { 765 if (paramType == null) { 766 method = klass.getMethod(methodName); 767 } else { 768 method = klass.getMethod(methodName, paramType); 769 } 770 } catch (NoSuchMethodException ex) { 771 throw new ActionException("view: " + klass.getName() + " doesn't have method: " 772 + methodName + getParameters(paramType)); 773 } 774 775 if (!method.isAnnotationPresent(RemotableViewMethod.class)) { 776 throw new ActionException("view: " + klass.getName() 777 + " can't use method with RemoteViews: " 778 + methodName + getParameters(paramType)); 779 } 780 781 methods.put(new MutablePair<String, Class<?>>(methodName, paramType), method); 782 } 783 } 784 785 return method; 786 } 787 getParameters(Class<?> paramType)788 private static String getParameters(Class<?> paramType) { 789 if (paramType == null) return "()"; 790 return "(" + paramType + ")"; 791 } 792 wrapArg(Object value)793 private static Object[] wrapArg(Object value) { 794 Object[] args = sInvokeArgsTls.get(); 795 args[0] = value; 796 return args; 797 } 798 799 /** 800 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 801 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 802 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given view. 803 * <p> 804 * These operations will be performed on the {@link Drawable} returned by the 805 * target {@link View#getBackground()} by default. If targetBackground is false, 806 * we assume the target is an {@link ImageView} and try applying the operations 807 * to {@link ImageView#getDrawable()}. 808 * <p> 809 * You can omit specific calls by marking their values with null or -1. 810 */ 811 private class SetDrawableParameters extends Action { SetDrawableParameters(int id, boolean targetBackground, int alpha, int colorFilter, PorterDuff.Mode mode, int level)812 public SetDrawableParameters(int id, boolean targetBackground, int alpha, 813 int colorFilter, PorterDuff.Mode mode, int level) { 814 this.viewId = id; 815 this.targetBackground = targetBackground; 816 this.alpha = alpha; 817 this.colorFilter = colorFilter; 818 this.filterMode = mode; 819 this.level = level; 820 } 821 SetDrawableParameters(Parcel parcel)822 public SetDrawableParameters(Parcel parcel) { 823 viewId = parcel.readInt(); 824 targetBackground = parcel.readInt() != 0; 825 alpha = parcel.readInt(); 826 colorFilter = parcel.readInt(); 827 boolean hasMode = parcel.readInt() != 0; 828 if (hasMode) { 829 filterMode = PorterDuff.Mode.valueOf(parcel.readString()); 830 } else { 831 filterMode = null; 832 } 833 level = parcel.readInt(); 834 } 835 writeToParcel(Parcel dest, int flags)836 public void writeToParcel(Parcel dest, int flags) { 837 dest.writeInt(TAG); 838 dest.writeInt(viewId); 839 dest.writeInt(targetBackground ? 1 : 0); 840 dest.writeInt(alpha); 841 dest.writeInt(colorFilter); 842 if (filterMode != null) { 843 dest.writeInt(1); 844 dest.writeString(filterMode.toString()); 845 } else { 846 dest.writeInt(0); 847 } 848 dest.writeInt(level); 849 } 850 851 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)852 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 853 final View target = root.findViewById(viewId); 854 if (target == null) return; 855 856 // Pick the correct drawable to modify for this view 857 Drawable targetDrawable = null; 858 if (targetBackground) { 859 targetDrawable = target.getBackground(); 860 } else if (target instanceof ImageView) { 861 ImageView imageView = (ImageView) target; 862 targetDrawable = imageView.getDrawable(); 863 } 864 865 if (targetDrawable != null) { 866 // Perform modifications only if values are set correctly 867 if (alpha != -1) { 868 targetDrawable.setAlpha(alpha); 869 } 870 if (filterMode != null) { 871 targetDrawable.setColorFilter(colorFilter, filterMode); 872 } 873 if (level != -1) { 874 targetDrawable.setLevel(level); 875 } 876 } 877 } 878 getActionName()879 public String getActionName() { 880 return "SetDrawableParameters"; 881 } 882 883 boolean targetBackground; 884 int alpha; 885 int colorFilter; 886 PorterDuff.Mode filterMode; 887 int level; 888 889 public final static int TAG = 3; 890 } 891 892 private final class ReflectionActionWithoutParams extends Action { 893 final String methodName; 894 895 public final static int TAG = 5; 896 ReflectionActionWithoutParams(int viewId, String methodName)897 ReflectionActionWithoutParams(int viewId, String methodName) { 898 this.viewId = viewId; 899 this.methodName = methodName; 900 } 901 ReflectionActionWithoutParams(Parcel in)902 ReflectionActionWithoutParams(Parcel in) { 903 this.viewId = in.readInt(); 904 this.methodName = in.readString(); 905 } 906 writeToParcel(Parcel out, int flags)907 public void writeToParcel(Parcel out, int flags) { 908 out.writeInt(TAG); 909 out.writeInt(this.viewId); 910 out.writeString(this.methodName); 911 } 912 913 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)914 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 915 final View view = root.findViewById(viewId); 916 if (view == null) return; 917 918 try { 919 getMethod(view, this.methodName, null).invoke(view); 920 } catch (ActionException e) { 921 throw e; 922 } catch (Exception ex) { 923 throw new ActionException(ex); 924 } 925 } 926 mergeBehavior()927 public int mergeBehavior() { 928 // we don't need to build up showNext or showPrevious calls 929 if (methodName.equals("showNext") || methodName.equals("showPrevious")) { 930 return MERGE_IGNORE; 931 } else { 932 return MERGE_REPLACE; 933 } 934 } 935 getActionName()936 public String getActionName() { 937 return "ReflectionActionWithoutParams"; 938 } 939 } 940 941 private static class BitmapCache { 942 ArrayList<Bitmap> mBitmaps; 943 BitmapCache()944 public BitmapCache() { 945 mBitmaps = new ArrayList<Bitmap>(); 946 } 947 BitmapCache(Parcel source)948 public BitmapCache(Parcel source) { 949 int count = source.readInt(); 950 mBitmaps = new ArrayList<Bitmap>(); 951 for (int i = 0; i < count; i++) { 952 Bitmap b = Bitmap.CREATOR.createFromParcel(source); 953 mBitmaps.add(b); 954 } 955 } 956 getBitmapId(Bitmap b)957 public int getBitmapId(Bitmap b) { 958 if (b == null) { 959 return -1; 960 } else { 961 if (mBitmaps.contains(b)) { 962 return mBitmaps.indexOf(b); 963 } else { 964 mBitmaps.add(b); 965 return (mBitmaps.size() - 1); 966 } 967 } 968 } 969 getBitmapForId(int id)970 public Bitmap getBitmapForId(int id) { 971 if (id == -1 || id >= mBitmaps.size()) { 972 return null; 973 } else { 974 return mBitmaps.get(id); 975 } 976 } 977 writeBitmapsToParcel(Parcel dest, int flags)978 public void writeBitmapsToParcel(Parcel dest, int flags) { 979 int count = mBitmaps.size(); 980 dest.writeInt(count); 981 for (int i = 0; i < count; i++) { 982 mBitmaps.get(i).writeToParcel(dest, flags); 983 } 984 } 985 assimilate(BitmapCache bitmapCache)986 public void assimilate(BitmapCache bitmapCache) { 987 ArrayList<Bitmap> bitmapsToBeAdded = bitmapCache.mBitmaps; 988 int count = bitmapsToBeAdded.size(); 989 for (int i = 0; i < count; i++) { 990 Bitmap b = bitmapsToBeAdded.get(i); 991 if (!mBitmaps.contains(b)) { 992 mBitmaps.add(b); 993 } 994 } 995 } 996 addBitmapMemory(MemoryUsageCounter memoryCounter)997 public void addBitmapMemory(MemoryUsageCounter memoryCounter) { 998 for (int i = 0; i < mBitmaps.size(); i++) { 999 memoryCounter.addBitmapMemory(mBitmaps.get(i)); 1000 } 1001 } 1002 } 1003 1004 private class BitmapReflectionAction extends Action { 1005 int bitmapId; 1006 Bitmap bitmap; 1007 String methodName; 1008 BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap)1009 BitmapReflectionAction(int viewId, String methodName, Bitmap bitmap) { 1010 this.bitmap = bitmap; 1011 this.viewId = viewId; 1012 this.methodName = methodName; 1013 bitmapId = mBitmapCache.getBitmapId(bitmap); 1014 } 1015 BitmapReflectionAction(Parcel in)1016 BitmapReflectionAction(Parcel in) { 1017 viewId = in.readInt(); 1018 methodName = in.readString(); 1019 bitmapId = in.readInt(); 1020 bitmap = mBitmapCache.getBitmapForId(bitmapId); 1021 } 1022 1023 @Override writeToParcel(Parcel dest, int flags)1024 public void writeToParcel(Parcel dest, int flags) { 1025 dest.writeInt(TAG); 1026 dest.writeInt(viewId); 1027 dest.writeString(methodName); 1028 dest.writeInt(bitmapId); 1029 } 1030 1031 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1032 public void apply(View root, ViewGroup rootParent, 1033 OnClickHandler handler) throws ActionException { 1034 ReflectionAction ra = new ReflectionAction(viewId, methodName, ReflectionAction.BITMAP, 1035 bitmap); 1036 ra.apply(root, rootParent, handler); 1037 } 1038 1039 @Override setBitmapCache(BitmapCache bitmapCache)1040 public void setBitmapCache(BitmapCache bitmapCache) { 1041 bitmapId = bitmapCache.getBitmapId(bitmap); 1042 } 1043 getActionName()1044 public String getActionName() { 1045 return "BitmapReflectionAction"; 1046 } 1047 1048 public final static int TAG = 12; 1049 } 1050 1051 /** 1052 * Base class for the reflection actions. 1053 */ 1054 private final class ReflectionAction extends Action { 1055 static final int TAG = 2; 1056 1057 static final int BOOLEAN = 1; 1058 static final int BYTE = 2; 1059 static final int SHORT = 3; 1060 static final int INT = 4; 1061 static final int LONG = 5; 1062 static final int FLOAT = 6; 1063 static final int DOUBLE = 7; 1064 static final int CHAR = 8; 1065 static final int STRING = 9; 1066 static final int CHAR_SEQUENCE = 10; 1067 static final int URI = 11; 1068 // BITMAP actions are never stored in the list of actions. They are only used locally 1069 // to implement BitmapReflectionAction, which eliminates duplicates using BitmapCache. 1070 static final int BITMAP = 12; 1071 static final int BUNDLE = 13; 1072 static final int INTENT = 14; 1073 static final int COLOR_STATE_LIST = 15; 1074 1075 String methodName; 1076 int type; 1077 Object value; 1078 ReflectionAction(int viewId, String methodName, int type, Object value)1079 ReflectionAction(int viewId, String methodName, int type, Object value) { 1080 this.viewId = viewId; 1081 this.methodName = methodName; 1082 this.type = type; 1083 this.value = value; 1084 } 1085 ReflectionAction(Parcel in)1086 ReflectionAction(Parcel in) { 1087 this.viewId = in.readInt(); 1088 this.methodName = in.readString(); 1089 this.type = in.readInt(); 1090 //noinspection ConstantIfStatement 1091 if (false) { 1092 Log.d(LOG_TAG, "read viewId=0x" + Integer.toHexString(this.viewId) 1093 + " methodName=" + this.methodName + " type=" + this.type); 1094 } 1095 1096 // For some values that may have been null, we first check a flag to see if they were 1097 // written to the parcel. 1098 switch (this.type) { 1099 case BOOLEAN: 1100 this.value = in.readInt() != 0; 1101 break; 1102 case BYTE: 1103 this.value = in.readByte(); 1104 break; 1105 case SHORT: 1106 this.value = (short)in.readInt(); 1107 break; 1108 case INT: 1109 this.value = in.readInt(); 1110 break; 1111 case LONG: 1112 this.value = in.readLong(); 1113 break; 1114 case FLOAT: 1115 this.value = in.readFloat(); 1116 break; 1117 case DOUBLE: 1118 this.value = in.readDouble(); 1119 break; 1120 case CHAR: 1121 this.value = (char)in.readInt(); 1122 break; 1123 case STRING: 1124 this.value = in.readString(); 1125 break; 1126 case CHAR_SEQUENCE: 1127 this.value = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 1128 break; 1129 case URI: 1130 if (in.readInt() != 0) { 1131 this.value = Uri.CREATOR.createFromParcel(in); 1132 } 1133 break; 1134 case BITMAP: 1135 if (in.readInt() != 0) { 1136 this.value = Bitmap.CREATOR.createFromParcel(in); 1137 } 1138 break; 1139 case BUNDLE: 1140 this.value = in.readBundle(); 1141 break; 1142 case INTENT: 1143 if (in.readInt() != 0) { 1144 this.value = Intent.CREATOR.createFromParcel(in); 1145 } 1146 break; 1147 case COLOR_STATE_LIST: 1148 if (in.readInt() != 0) { 1149 this.value = ColorStateList.CREATOR.createFromParcel(in); 1150 } 1151 break; 1152 default: 1153 break; 1154 } 1155 } 1156 writeToParcel(Parcel out, int flags)1157 public void writeToParcel(Parcel out, int flags) { 1158 out.writeInt(TAG); 1159 out.writeInt(this.viewId); 1160 out.writeString(this.methodName); 1161 out.writeInt(this.type); 1162 //noinspection ConstantIfStatement 1163 if (false) { 1164 Log.d(LOG_TAG, "write viewId=0x" + Integer.toHexString(this.viewId) 1165 + " methodName=" + this.methodName + " type=" + this.type); 1166 } 1167 1168 // For some values which are null, we record an integer flag to indicate whether 1169 // we have written a valid value to the parcel. 1170 switch (this.type) { 1171 case BOOLEAN: 1172 out.writeInt((Boolean) this.value ? 1 : 0); 1173 break; 1174 case BYTE: 1175 out.writeByte((Byte) this.value); 1176 break; 1177 case SHORT: 1178 out.writeInt((Short) this.value); 1179 break; 1180 case INT: 1181 out.writeInt((Integer) this.value); 1182 break; 1183 case LONG: 1184 out.writeLong((Long) this.value); 1185 break; 1186 case FLOAT: 1187 out.writeFloat((Float) this.value); 1188 break; 1189 case DOUBLE: 1190 out.writeDouble((Double) this.value); 1191 break; 1192 case CHAR: 1193 out.writeInt((int)((Character)this.value).charValue()); 1194 break; 1195 case STRING: 1196 out.writeString((String)this.value); 1197 break; 1198 case CHAR_SEQUENCE: 1199 TextUtils.writeToParcel((CharSequence)this.value, out, flags); 1200 break; 1201 case URI: 1202 out.writeInt(this.value != null ? 1 : 0); 1203 if (this.value != null) { 1204 ((Uri)this.value).writeToParcel(out, flags); 1205 } 1206 break; 1207 case BITMAP: 1208 out.writeInt(this.value != null ? 1 : 0); 1209 if (this.value != null) { 1210 ((Bitmap)this.value).writeToParcel(out, flags); 1211 } 1212 break; 1213 case BUNDLE: 1214 out.writeBundle((Bundle) this.value); 1215 break; 1216 case INTENT: 1217 out.writeInt(this.value != null ? 1 : 0); 1218 if (this.value != null) { 1219 ((Intent)this.value).writeToParcel(out, flags); 1220 } 1221 break; 1222 case COLOR_STATE_LIST: 1223 out.writeInt(this.value != null ? 1 : 0); 1224 if (this.value != null) { 1225 ((ColorStateList)this.value).writeToParcel(out, flags); 1226 } 1227 default: 1228 break; 1229 } 1230 } 1231 getParameterType()1232 private Class<?> getParameterType() { 1233 switch (this.type) { 1234 case BOOLEAN: 1235 return boolean.class; 1236 case BYTE: 1237 return byte.class; 1238 case SHORT: 1239 return short.class; 1240 case INT: 1241 return int.class; 1242 case LONG: 1243 return long.class; 1244 case FLOAT: 1245 return float.class; 1246 case DOUBLE: 1247 return double.class; 1248 case CHAR: 1249 return char.class; 1250 case STRING: 1251 return String.class; 1252 case CHAR_SEQUENCE: 1253 return CharSequence.class; 1254 case URI: 1255 return Uri.class; 1256 case BITMAP: 1257 return Bitmap.class; 1258 case BUNDLE: 1259 return Bundle.class; 1260 case INTENT: 1261 return Intent.class; 1262 case COLOR_STATE_LIST: 1263 return ColorStateList.class; 1264 default: 1265 return null; 1266 } 1267 } 1268 1269 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1270 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1271 final View view = root.findViewById(viewId); 1272 if (view == null) return; 1273 1274 Class<?> param = getParameterType(); 1275 if (param == null) { 1276 throw new ActionException("bad type: " + this.type); 1277 } 1278 1279 try { 1280 getMethod(view, this.methodName, param).invoke(view, wrapArg(this.value)); 1281 } catch (ActionException e) { 1282 throw e; 1283 } catch (Exception ex) { 1284 throw new ActionException(ex); 1285 } 1286 } 1287 mergeBehavior()1288 public int mergeBehavior() { 1289 // smoothScrollBy is cumulative, everything else overwites. 1290 if (methodName.equals("smoothScrollBy")) { 1291 return MERGE_APPEND; 1292 } else { 1293 return MERGE_REPLACE; 1294 } 1295 } 1296 getActionName()1297 public String getActionName() { 1298 // Each type of reflection action corresponds to a setter, so each should be seen as 1299 // unique from the standpoint of merging. 1300 return "ReflectionAction" + this.methodName + this.type; 1301 } 1302 } 1303 configureRemoteViewsAsChild(RemoteViews rv)1304 private void configureRemoteViewsAsChild(RemoteViews rv) { 1305 mBitmapCache.assimilate(rv.mBitmapCache); 1306 rv.setBitmapCache(mBitmapCache); 1307 rv.setNotRoot(); 1308 } 1309 setNotRoot()1310 void setNotRoot() { 1311 mIsRoot = false; 1312 } 1313 1314 /** 1315 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1316 * given {@link RemoteViews}, or calling {@link ViewGroup#removeAllViews()} 1317 * when null. This allows users to build "nested" {@link RemoteViews}. 1318 */ 1319 private class ViewGroupAction extends Action { ViewGroupAction(int viewId, RemoteViews nestedViews)1320 public ViewGroupAction(int viewId, RemoteViews nestedViews) { 1321 this.viewId = viewId; 1322 this.nestedViews = nestedViews; 1323 if (nestedViews != null) { 1324 configureRemoteViewsAsChild(nestedViews); 1325 } 1326 } 1327 ViewGroupAction(Parcel parcel, BitmapCache bitmapCache)1328 public ViewGroupAction(Parcel parcel, BitmapCache bitmapCache) { 1329 viewId = parcel.readInt(); 1330 boolean nestedViewsNull = parcel.readInt() == 0; 1331 if (!nestedViewsNull) { 1332 nestedViews = new RemoteViews(parcel, bitmapCache); 1333 } else { 1334 nestedViews = null; 1335 } 1336 } 1337 writeToParcel(Parcel dest, int flags)1338 public void writeToParcel(Parcel dest, int flags) { 1339 dest.writeInt(TAG); 1340 dest.writeInt(viewId); 1341 if (nestedViews != null) { 1342 dest.writeInt(1); 1343 nestedViews.writeToParcel(dest, flags); 1344 } else { 1345 // signifies null 1346 dest.writeInt(0); 1347 } 1348 } 1349 1350 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1351 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1352 final Context context = root.getContext(); 1353 final ViewGroup target = (ViewGroup) root.findViewById(viewId); 1354 if (target == null) return; 1355 if (nestedViews != null) { 1356 // Inflate nested views and add as children 1357 target.addView(nestedViews.apply(context, target, handler)); 1358 } else { 1359 // Clear all children when nested views omitted 1360 target.removeAllViews(); 1361 } 1362 } 1363 1364 @Override updateMemoryUsageEstimate(MemoryUsageCounter counter)1365 public void updateMemoryUsageEstimate(MemoryUsageCounter counter) { 1366 if (nestedViews != null) { 1367 counter.increment(nestedViews.estimateMemoryUsage()); 1368 } 1369 } 1370 1371 @Override setBitmapCache(BitmapCache bitmapCache)1372 public void setBitmapCache(BitmapCache bitmapCache) { 1373 if (nestedViews != null) { 1374 nestedViews.setBitmapCache(bitmapCache); 1375 } 1376 } 1377 getActionName()1378 public String getActionName() { 1379 return "ViewGroupAction" + (nestedViews == null ? "Remove" : "Add"); 1380 } 1381 mergeBehavior()1382 public int mergeBehavior() { 1383 return MERGE_APPEND; 1384 } 1385 1386 RemoteViews nestedViews; 1387 1388 public final static int TAG = 4; 1389 } 1390 1391 /** 1392 * Helper action to set compound drawables on a TextView. Supports relative 1393 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 1394 */ 1395 private class TextViewDrawableAction extends Action { TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4)1396 public TextViewDrawableAction(int viewId, boolean isRelative, int d1, int d2, int d3, int d4) { 1397 this.viewId = viewId; 1398 this.isRelative = isRelative; 1399 this.d1 = d1; 1400 this.d2 = d2; 1401 this.d3 = d3; 1402 this.d4 = d4; 1403 } 1404 TextViewDrawableAction(Parcel parcel)1405 public TextViewDrawableAction(Parcel parcel) { 1406 viewId = parcel.readInt(); 1407 isRelative = (parcel.readInt() != 0); 1408 d1 = parcel.readInt(); 1409 d2 = parcel.readInt(); 1410 d3 = parcel.readInt(); 1411 d4 = parcel.readInt(); 1412 } 1413 writeToParcel(Parcel dest, int flags)1414 public void writeToParcel(Parcel dest, int flags) { 1415 dest.writeInt(TAG); 1416 dest.writeInt(viewId); 1417 dest.writeInt(isRelative ? 1 : 0); 1418 dest.writeInt(d1); 1419 dest.writeInt(d2); 1420 dest.writeInt(d3); 1421 dest.writeInt(d4); 1422 } 1423 1424 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1425 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1426 final TextView target = (TextView) root.findViewById(viewId); 1427 if (target == null) return; 1428 if (isRelative) { 1429 target.setCompoundDrawablesRelativeWithIntrinsicBounds(d1, d2, d3, d4); 1430 } else { 1431 target.setCompoundDrawablesWithIntrinsicBounds(d1, d2, d3, d4); 1432 } 1433 } 1434 getActionName()1435 public String getActionName() { 1436 return "TextViewDrawableAction"; 1437 } 1438 1439 boolean isRelative = false; 1440 int d1, d2, d3, d4; 1441 1442 public final static int TAG = 11; 1443 } 1444 1445 /** 1446 * Helper action to set text size on a TextView in any supported units. 1447 */ 1448 private class TextViewSizeAction extends Action { TextViewSizeAction(int viewId, int units, float size)1449 public TextViewSizeAction(int viewId, int units, float size) { 1450 this.viewId = viewId; 1451 this.units = units; 1452 this.size = size; 1453 } 1454 TextViewSizeAction(Parcel parcel)1455 public TextViewSizeAction(Parcel parcel) { 1456 viewId = parcel.readInt(); 1457 units = parcel.readInt(); 1458 size = parcel.readFloat(); 1459 } 1460 writeToParcel(Parcel dest, int flags)1461 public void writeToParcel(Parcel dest, int flags) { 1462 dest.writeInt(TAG); 1463 dest.writeInt(viewId); 1464 dest.writeInt(units); 1465 dest.writeFloat(size); 1466 } 1467 1468 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1469 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1470 final TextView target = (TextView) root.findViewById(viewId); 1471 if (target == null) return; 1472 target.setTextSize(units, size); 1473 } 1474 getActionName()1475 public String getActionName() { 1476 return "TextViewSizeAction"; 1477 } 1478 1479 int units; 1480 float size; 1481 1482 public final static int TAG = 13; 1483 } 1484 1485 /** 1486 * Helper action to set padding on a View. 1487 */ 1488 private class ViewPaddingAction extends Action { ViewPaddingAction(int viewId, int left, int top, int right, int bottom)1489 public ViewPaddingAction(int viewId, int left, int top, int right, int bottom) { 1490 this.viewId = viewId; 1491 this.left = left; 1492 this.top = top; 1493 this.right = right; 1494 this.bottom = bottom; 1495 } 1496 ViewPaddingAction(Parcel parcel)1497 public ViewPaddingAction(Parcel parcel) { 1498 viewId = parcel.readInt(); 1499 left = parcel.readInt(); 1500 top = parcel.readInt(); 1501 right = parcel.readInt(); 1502 bottom = parcel.readInt(); 1503 } 1504 writeToParcel(Parcel dest, int flags)1505 public void writeToParcel(Parcel dest, int flags) { 1506 dest.writeInt(TAG); 1507 dest.writeInt(viewId); 1508 dest.writeInt(left); 1509 dest.writeInt(top); 1510 dest.writeInt(right); 1511 dest.writeInt(bottom); 1512 } 1513 1514 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1515 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1516 final View target = root.findViewById(viewId); 1517 if (target == null) return; 1518 target.setPadding(left, top, right, bottom); 1519 } 1520 getActionName()1521 public String getActionName() { 1522 return "ViewPaddingAction"; 1523 } 1524 1525 int left, top, right, bottom; 1526 1527 public final static int TAG = 14; 1528 } 1529 1530 /** 1531 * Helper action to set a color filter on a compound drawable on a TextView. Supports relative 1532 * (s/t/e/b) or cardinal (l/t/r/b) arrangement. 1533 */ 1534 private class TextViewDrawableColorFilterAction extends Action { TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index, int color, PorterDuff.Mode mode)1535 public TextViewDrawableColorFilterAction(int viewId, boolean isRelative, int index, 1536 int color, PorterDuff.Mode mode) { 1537 this.viewId = viewId; 1538 this.isRelative = isRelative; 1539 this.index = index; 1540 this.color = color; 1541 this.mode = mode; 1542 } 1543 TextViewDrawableColorFilterAction(Parcel parcel)1544 public TextViewDrawableColorFilterAction(Parcel parcel) { 1545 viewId = parcel.readInt(); 1546 isRelative = (parcel.readInt() != 0); 1547 index = parcel.readInt(); 1548 color = parcel.readInt(); 1549 mode = readPorterDuffMode(parcel); 1550 } 1551 readPorterDuffMode(Parcel parcel)1552 private PorterDuff.Mode readPorterDuffMode(Parcel parcel) { 1553 int mode = parcel.readInt(); 1554 if (mode >= 0 && mode < PorterDuff.Mode.values().length) { 1555 return PorterDuff.Mode.values()[mode]; 1556 } else { 1557 return PorterDuff.Mode.CLEAR; 1558 } 1559 } 1560 writeToParcel(Parcel dest, int flags)1561 public void writeToParcel(Parcel dest, int flags) { 1562 dest.writeInt(TAG); 1563 dest.writeInt(viewId); 1564 dest.writeInt(isRelative ? 1 : 0); 1565 dest.writeInt(index); 1566 dest.writeInt(color); 1567 dest.writeInt(mode.ordinal()); 1568 } 1569 1570 @Override apply(View root, ViewGroup rootParent, OnClickHandler handler)1571 public void apply(View root, ViewGroup rootParent, OnClickHandler handler) { 1572 final TextView target = (TextView) root.findViewById(viewId); 1573 if (target == null) return; 1574 Drawable[] drawables = isRelative 1575 ? target.getCompoundDrawablesRelative() 1576 : target.getCompoundDrawables(); 1577 if (index < 0 || index >= 4) { 1578 throw new IllegalStateException("index must be in range [0, 3]."); 1579 } 1580 Drawable d = drawables[index]; 1581 if (d != null) { 1582 d.mutate(); 1583 d.setColorFilter(color, mode); 1584 } 1585 } 1586 getActionName()1587 public String getActionName() { 1588 return "TextViewDrawableColorFilterAction"; 1589 } 1590 1591 final boolean isRelative; 1592 final int index; 1593 final int color; 1594 final PorterDuff.Mode mode; 1595 1596 public final static int TAG = 17; 1597 } 1598 1599 /** 1600 * Simple class used to keep track of memory usage in a RemoteViews. 1601 * 1602 */ 1603 private class MemoryUsageCounter { clear()1604 public void clear() { 1605 mMemoryUsage = 0; 1606 } 1607 increment(int numBytes)1608 public void increment(int numBytes) { 1609 mMemoryUsage += numBytes; 1610 } 1611 getMemoryUsage()1612 public int getMemoryUsage() { 1613 return mMemoryUsage; 1614 } 1615 1616 @SuppressWarnings("deprecation") addBitmapMemory(Bitmap b)1617 public void addBitmapMemory(Bitmap b) { 1618 final Bitmap.Config c = b.getConfig(); 1619 // If we don't know, be pessimistic and assume 4 1620 int bpp = 4; 1621 if (c != null) { 1622 switch (c) { 1623 case ALPHA_8: 1624 bpp = 1; 1625 break; 1626 case RGB_565: 1627 case ARGB_4444: 1628 bpp = 2; 1629 break; 1630 case ARGB_8888: 1631 bpp = 4; 1632 break; 1633 } 1634 } 1635 increment(b.getWidth() * b.getHeight() * bpp); 1636 } 1637 1638 int mMemoryUsage; 1639 } 1640 1641 /** 1642 * Create a new RemoteViews object that will display the views contained 1643 * in the specified layout file. 1644 * 1645 * @param packageName Name of the package that contains the layout resource 1646 * @param layoutId The id of the layout resource 1647 */ RemoteViews(String packageName, int layoutId)1648 public RemoteViews(String packageName, int layoutId) { 1649 this(getApplicationInfo(packageName, UserHandle.myUserId()), layoutId); 1650 } 1651 1652 /** 1653 * Create a new RemoteViews object that will display the views contained 1654 * in the specified layout file. 1655 * 1656 * @param packageName Name of the package that contains the layout resource. 1657 * @param userId The user under which the package is running. 1658 * @param layoutId The id of the layout resource. 1659 * 1660 * @hide 1661 */ RemoteViews(String packageName, int userId, int layoutId)1662 public RemoteViews(String packageName, int userId, int layoutId) { 1663 this(getApplicationInfo(packageName, userId), layoutId); 1664 } 1665 1666 /** 1667 * Create a new RemoteViews object that will display the views contained 1668 * in the specified layout file. 1669 * 1670 * @param application The application whose content is shown by the views. 1671 * @param layoutId The id of the layout resource. 1672 * 1673 * @hide 1674 */ RemoteViews(ApplicationInfo application, int layoutId)1675 protected RemoteViews(ApplicationInfo application, int layoutId) { 1676 mApplication = application; 1677 mLayoutId = layoutId; 1678 mBitmapCache = new BitmapCache(); 1679 // setup the memory usage statistics 1680 mMemoryUsageCounter = new MemoryUsageCounter(); 1681 recalculateMemoryUsage(); 1682 } 1683 hasLandscapeAndPortraitLayouts()1684 private boolean hasLandscapeAndPortraitLayouts() { 1685 return (mLandscape != null) && (mPortrait != null); 1686 } 1687 1688 /** 1689 * Create a new RemoteViews object that will inflate as the specified 1690 * landspace or portrait RemoteViews, depending on the current configuration. 1691 * 1692 * @param landscape The RemoteViews to inflate in landscape configuration 1693 * @param portrait The RemoteViews to inflate in portrait configuration 1694 */ RemoteViews(RemoteViews landscape, RemoteViews portrait)1695 public RemoteViews(RemoteViews landscape, RemoteViews portrait) { 1696 if (landscape == null || portrait == null) { 1697 throw new RuntimeException("Both RemoteViews must be non-null"); 1698 } 1699 if (landscape.mApplication.uid != portrait.mApplication.uid 1700 || !landscape.mApplication.packageName.equals(portrait.mApplication.packageName)) { 1701 throw new RuntimeException("Both RemoteViews must share the same package and user"); 1702 } 1703 mApplication = portrait.mApplication; 1704 mLayoutId = portrait.getLayoutId(); 1705 1706 mLandscape = landscape; 1707 mPortrait = portrait; 1708 1709 // setup the memory usage statistics 1710 mMemoryUsageCounter = new MemoryUsageCounter(); 1711 1712 mBitmapCache = new BitmapCache(); 1713 configureRemoteViewsAsChild(landscape); 1714 configureRemoteViewsAsChild(portrait); 1715 1716 recalculateMemoryUsage(); 1717 } 1718 1719 /** 1720 * Reads a RemoteViews object from a parcel. 1721 * 1722 * @param parcel 1723 */ RemoteViews(Parcel parcel)1724 public RemoteViews(Parcel parcel) { 1725 this(parcel, null); 1726 } 1727 RemoteViews(Parcel parcel, BitmapCache bitmapCache)1728 private RemoteViews(Parcel parcel, BitmapCache bitmapCache) { 1729 int mode = parcel.readInt(); 1730 1731 // We only store a bitmap cache in the root of the RemoteViews. 1732 if (bitmapCache == null) { 1733 mBitmapCache = new BitmapCache(parcel); 1734 } else { 1735 setBitmapCache(bitmapCache); 1736 setNotRoot(); 1737 } 1738 1739 if (mode == MODE_NORMAL) { 1740 mApplication = parcel.readParcelable(null); 1741 mLayoutId = parcel.readInt(); 1742 mIsWidgetCollectionChild = parcel.readInt() == 1; 1743 1744 int count = parcel.readInt(); 1745 if (count > 0) { 1746 mActions = new ArrayList<Action>(count); 1747 for (int i=0; i<count; i++) { 1748 int tag = parcel.readInt(); 1749 switch (tag) { 1750 case SetOnClickPendingIntent.TAG: 1751 mActions.add(new SetOnClickPendingIntent(parcel)); 1752 break; 1753 case SetDrawableParameters.TAG: 1754 mActions.add(new SetDrawableParameters(parcel)); 1755 break; 1756 case ReflectionAction.TAG: 1757 mActions.add(new ReflectionAction(parcel)); 1758 break; 1759 case ViewGroupAction.TAG: 1760 mActions.add(new ViewGroupAction(parcel, mBitmapCache)); 1761 break; 1762 case ReflectionActionWithoutParams.TAG: 1763 mActions.add(new ReflectionActionWithoutParams(parcel)); 1764 break; 1765 case SetEmptyView.TAG: 1766 mActions.add(new SetEmptyView(parcel)); 1767 break; 1768 case SetPendingIntentTemplate.TAG: 1769 mActions.add(new SetPendingIntentTemplate(parcel)); 1770 break; 1771 case SetOnClickFillInIntent.TAG: 1772 mActions.add(new SetOnClickFillInIntent(parcel)); 1773 break; 1774 case SetRemoteViewsAdapterIntent.TAG: 1775 mActions.add(new SetRemoteViewsAdapterIntent(parcel)); 1776 break; 1777 case TextViewDrawableAction.TAG: 1778 mActions.add(new TextViewDrawableAction(parcel)); 1779 break; 1780 case TextViewSizeAction.TAG: 1781 mActions.add(new TextViewSizeAction(parcel)); 1782 break; 1783 case ViewPaddingAction.TAG: 1784 mActions.add(new ViewPaddingAction(parcel)); 1785 break; 1786 case BitmapReflectionAction.TAG: 1787 mActions.add(new BitmapReflectionAction(parcel)); 1788 break; 1789 case SetRemoteViewsAdapterList.TAG: 1790 mActions.add(new SetRemoteViewsAdapterList(parcel)); 1791 break; 1792 case TextViewDrawableColorFilterAction.TAG: 1793 mActions.add(new TextViewDrawableColorFilterAction(parcel)); 1794 break; 1795 default: 1796 throw new ActionException("Tag " + tag + " not found"); 1797 } 1798 } 1799 } 1800 } else { 1801 // MODE_HAS_LANDSCAPE_AND_PORTRAIT 1802 mLandscape = new RemoteViews(parcel, mBitmapCache); 1803 mPortrait = new RemoteViews(parcel, mBitmapCache); 1804 mApplication = mPortrait.mApplication; 1805 mLayoutId = mPortrait.getLayoutId(); 1806 } 1807 1808 // setup the memory usage statistics 1809 mMemoryUsageCounter = new MemoryUsageCounter(); 1810 recalculateMemoryUsage(); 1811 } 1812 1813 clone()1814 public RemoteViews clone() { 1815 Parcel p = Parcel.obtain(); 1816 writeToParcel(p, 0); 1817 p.setDataPosition(0); 1818 RemoteViews rv = new RemoteViews(p); 1819 p.recycle(); 1820 return rv; 1821 } 1822 getPackage()1823 public String getPackage() { 1824 return (mApplication != null) ? mApplication.packageName : null; 1825 } 1826 1827 /** 1828 * Reutrns the layout id of the root layout associated with this RemoteViews. In the case 1829 * that the RemoteViews has both a landscape and portrait root, this will return the layout 1830 * id associated with the portrait layout. 1831 * 1832 * @return the layout id. 1833 */ getLayoutId()1834 public int getLayoutId() { 1835 return mLayoutId; 1836 } 1837 1838 /* 1839 * This flag indicates whether this RemoteViews object is being created from a 1840 * RemoteViewsService for use as a child of a widget collection. This flag is used 1841 * to determine whether or not certain features are available, in particular, 1842 * setting on click extras and setting on click pending intents. The former is enabled, 1843 * and the latter disabled when this flag is true. 1844 */ setIsWidgetCollectionChild(boolean isWidgetCollectionChild)1845 void setIsWidgetCollectionChild(boolean isWidgetCollectionChild) { 1846 mIsWidgetCollectionChild = isWidgetCollectionChild; 1847 } 1848 1849 /** 1850 * Updates the memory usage statistics. 1851 */ recalculateMemoryUsage()1852 private void recalculateMemoryUsage() { 1853 mMemoryUsageCounter.clear(); 1854 1855 if (!hasLandscapeAndPortraitLayouts()) { 1856 // Accumulate the memory usage for each action 1857 if (mActions != null) { 1858 final int count = mActions.size(); 1859 for (int i= 0; i < count; ++i) { 1860 mActions.get(i).updateMemoryUsageEstimate(mMemoryUsageCounter); 1861 } 1862 } 1863 if (mIsRoot) { 1864 mBitmapCache.addBitmapMemory(mMemoryUsageCounter); 1865 } 1866 } else { 1867 mMemoryUsageCounter.increment(mLandscape.estimateMemoryUsage()); 1868 mMemoryUsageCounter.increment(mPortrait.estimateMemoryUsage()); 1869 mBitmapCache.addBitmapMemory(mMemoryUsageCounter); 1870 } 1871 } 1872 1873 /** 1874 * Recursively sets BitmapCache in the hierarchy and update the bitmap ids. 1875 */ setBitmapCache(BitmapCache bitmapCache)1876 private void setBitmapCache(BitmapCache bitmapCache) { 1877 mBitmapCache = bitmapCache; 1878 if (!hasLandscapeAndPortraitLayouts()) { 1879 if (mActions != null) { 1880 final int count = mActions.size(); 1881 for (int i= 0; i < count; ++i) { 1882 mActions.get(i).setBitmapCache(bitmapCache); 1883 } 1884 } 1885 } else { 1886 mLandscape.setBitmapCache(bitmapCache); 1887 mPortrait.setBitmapCache(bitmapCache); 1888 } 1889 } 1890 1891 /** 1892 * Returns an estimate of the bitmap heap memory usage for this RemoteViews. 1893 */ 1894 /** @hide */ estimateMemoryUsage()1895 public int estimateMemoryUsage() { 1896 return mMemoryUsageCounter.getMemoryUsage(); 1897 } 1898 1899 /** 1900 * Add an action to be executed on the remote side when apply is called. 1901 * 1902 * @param a The action to add 1903 */ addAction(Action a)1904 private void addAction(Action a) { 1905 if (hasLandscapeAndPortraitLayouts()) { 1906 throw new RuntimeException("RemoteViews specifying separate landscape and portrait" + 1907 " layouts cannot be modified. Instead, fully configure the landscape and" + 1908 " portrait layouts individually before constructing the combined layout."); 1909 } 1910 if (mActions == null) { 1911 mActions = new ArrayList<Action>(); 1912 } 1913 mActions.add(a); 1914 1915 // update the memory usage stats 1916 a.updateMemoryUsageEstimate(mMemoryUsageCounter); 1917 } 1918 1919 /** 1920 * Equivalent to calling {@link ViewGroup#addView(View)} after inflating the 1921 * given {@link RemoteViews}. This allows users to build "nested" 1922 * {@link RemoteViews}. In cases where consumers of {@link RemoteViews} may 1923 * recycle layouts, use {@link #removeAllViews(int)} to clear any existing 1924 * children. 1925 * 1926 * @param viewId The id of the parent {@link ViewGroup} to add child into. 1927 * @param nestedView {@link RemoteViews} that describes the child. 1928 */ addView(int viewId, RemoteViews nestedView)1929 public void addView(int viewId, RemoteViews nestedView) { 1930 addAction(new ViewGroupAction(viewId, nestedView)); 1931 } 1932 1933 /** 1934 * Equivalent to calling {@link ViewGroup#removeAllViews()}. 1935 * 1936 * @param viewId The id of the parent {@link ViewGroup} to remove all 1937 * children from. 1938 */ removeAllViews(int viewId)1939 public void removeAllViews(int viewId) { 1940 addAction(new ViewGroupAction(viewId, null)); 1941 } 1942 1943 /** 1944 * Equivalent to calling {@link AdapterViewAnimator#showNext()} 1945 * 1946 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showNext()} 1947 */ showNext(int viewId)1948 public void showNext(int viewId) { 1949 addAction(new ReflectionActionWithoutParams(viewId, "showNext")); 1950 } 1951 1952 /** 1953 * Equivalent to calling {@link AdapterViewAnimator#showPrevious()} 1954 * 1955 * @param viewId The id of the view on which to call {@link AdapterViewAnimator#showPrevious()} 1956 */ showPrevious(int viewId)1957 public void showPrevious(int viewId) { 1958 addAction(new ReflectionActionWithoutParams(viewId, "showPrevious")); 1959 } 1960 1961 /** 1962 * Equivalent to calling {@link AdapterViewAnimator#setDisplayedChild(int)} 1963 * 1964 * @param viewId The id of the view on which to call 1965 * {@link AdapterViewAnimator#setDisplayedChild(int)} 1966 */ setDisplayedChild(int viewId, int childIndex)1967 public void setDisplayedChild(int viewId, int childIndex) { 1968 setInt(viewId, "setDisplayedChild", childIndex); 1969 } 1970 1971 /** 1972 * Equivalent to calling View.setVisibility 1973 * 1974 * @param viewId The id of the view whose visibility should change 1975 * @param visibility The new visibility for the view 1976 */ setViewVisibility(int viewId, int visibility)1977 public void setViewVisibility(int viewId, int visibility) { 1978 setInt(viewId, "setVisibility", visibility); 1979 } 1980 1981 /** 1982 * Equivalent to calling TextView.setText 1983 * 1984 * @param viewId The id of the view whose text should change 1985 * @param text The new text for the view 1986 */ setTextViewText(int viewId, CharSequence text)1987 public void setTextViewText(int viewId, CharSequence text) { 1988 setCharSequence(viewId, "setText", text); 1989 } 1990 1991 /** 1992 * Equivalent to calling {@link TextView#setTextSize(int, float)} 1993 * 1994 * @param viewId The id of the view whose text size should change 1995 * @param units The units of size (e.g. COMPLEX_UNIT_SP) 1996 * @param size The size of the text 1997 */ setTextViewTextSize(int viewId, int units, float size)1998 public void setTextViewTextSize(int viewId, int units, float size) { 1999 addAction(new TextViewSizeAction(viewId, units, size)); 2000 } 2001 2002 /** 2003 * Equivalent to calling 2004 * {@link TextView#setCompoundDrawablesWithIntrinsicBounds(int, int, int, int)}. 2005 * 2006 * @param viewId The id of the view whose text should change 2007 * @param left The id of a drawable to place to the left of the text, or 0 2008 * @param top The id of a drawable to place above the text, or 0 2009 * @param right The id of a drawable to place to the right of the text, or 0 2010 * @param bottom The id of a drawable to place below the text, or 0 2011 */ setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom)2012 public void setTextViewCompoundDrawables(int viewId, int left, int top, int right, int bottom) { 2013 addAction(new TextViewDrawableAction(viewId, false, left, top, right, bottom)); 2014 } 2015 2016 /** 2017 * Equivalent to calling {@link 2018 * TextView#setCompoundDrawablesRelativeWithIntrinsicBounds(int, int, int, int)}. 2019 * 2020 * @param viewId The id of the view whose text should change 2021 * @param start The id of a drawable to place before the text (relative to the 2022 * layout direction), or 0 2023 * @param top The id of a drawable to place above the text, or 0 2024 * @param end The id of a drawable to place after the text, or 0 2025 * @param bottom The id of a drawable to place below the text, or 0 2026 */ setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom)2027 public void setTextViewCompoundDrawablesRelative(int viewId, int start, int top, int end, int bottom) { 2028 addAction(new TextViewDrawableAction(viewId, true, start, top, end, bottom)); 2029 } 2030 2031 /** 2032 * Equivalent to applying a color filter on one of the drawables in 2033 * {@link android.widget.TextView#getCompoundDrawablesRelative()}. 2034 * 2035 * @param viewId The id of the view whose text should change. 2036 * @param index The index of the drawable in the array of 2037 * {@link android.widget.TextView#getCompoundDrawablesRelative()} to set the color 2038 * filter on. Must be in [0, 3]. 2039 * @param color The color of the color filter. See 2040 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}. 2041 * @param mode The mode of the color filter. See 2042 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}. 2043 * @hide 2044 */ setTextViewCompoundDrawablesRelativeColorFilter(int viewId, int index, int color, PorterDuff.Mode mode)2045 public void setTextViewCompoundDrawablesRelativeColorFilter(int viewId, 2046 int index, int color, PorterDuff.Mode mode) { 2047 if (index < 0 || index >= 4) { 2048 throw new IllegalArgumentException("index must be in range [0, 3]."); 2049 } 2050 addAction(new TextViewDrawableColorFilterAction(viewId, true, index, color, mode)); 2051 } 2052 2053 /** 2054 * Equivalent to calling ImageView.setImageResource 2055 * 2056 * @param viewId The id of the view whose drawable should change 2057 * @param srcId The new resource id for the drawable 2058 */ setImageViewResource(int viewId, int srcId)2059 public void setImageViewResource(int viewId, int srcId) { 2060 setInt(viewId, "setImageResource", srcId); 2061 } 2062 2063 /** 2064 * Equivalent to calling ImageView.setImageURI 2065 * 2066 * @param viewId The id of the view whose drawable should change 2067 * @param uri The Uri for the image 2068 */ setImageViewUri(int viewId, Uri uri)2069 public void setImageViewUri(int viewId, Uri uri) { 2070 setUri(viewId, "setImageURI", uri); 2071 } 2072 2073 /** 2074 * Equivalent to calling ImageView.setImageBitmap 2075 * 2076 * @param viewId The id of the view whose bitmap should change 2077 * @param bitmap The new Bitmap for the drawable 2078 */ setImageViewBitmap(int viewId, Bitmap bitmap)2079 public void setImageViewBitmap(int viewId, Bitmap bitmap) { 2080 setBitmap(viewId, "setImageBitmap", bitmap); 2081 } 2082 2083 /** 2084 * Equivalent to calling AdapterView.setEmptyView 2085 * 2086 * @param viewId The id of the view on which to set the empty view 2087 * @param emptyViewId The view id of the empty view 2088 */ setEmptyView(int viewId, int emptyViewId)2089 public void setEmptyView(int viewId, int emptyViewId) { 2090 addAction(new SetEmptyView(viewId, emptyViewId)); 2091 } 2092 2093 /** 2094 * Equivalent to calling {@link Chronometer#setBase Chronometer.setBase}, 2095 * {@link Chronometer#setFormat Chronometer.setFormat}, 2096 * and {@link Chronometer#start Chronometer.start()} or 2097 * {@link Chronometer#stop Chronometer.stop()}. 2098 * 2099 * @param viewId The id of the {@link Chronometer} to change 2100 * @param base The time at which the timer would have read 0:00. This 2101 * time should be based off of 2102 * {@link android.os.SystemClock#elapsedRealtime SystemClock.elapsedRealtime()}. 2103 * @param format The Chronometer format string, or null to 2104 * simply display the timer value. 2105 * @param started True if you want the clock to be started, false if not. 2106 */ setChronometer(int viewId, long base, String format, boolean started)2107 public void setChronometer(int viewId, long base, String format, boolean started) { 2108 setLong(viewId, "setBase", base); 2109 setString(viewId, "setFormat", format); 2110 setBoolean(viewId, "setStarted", started); 2111 } 2112 2113 /** 2114 * Equivalent to calling {@link ProgressBar#setMax ProgressBar.setMax}, 2115 * {@link ProgressBar#setProgress ProgressBar.setProgress}, and 2116 * {@link ProgressBar#setIndeterminate ProgressBar.setIndeterminate} 2117 * 2118 * If indeterminate is true, then the values for max and progress are ignored. 2119 * 2120 * @param viewId The id of the {@link ProgressBar} to change 2121 * @param max The 100% value for the progress bar 2122 * @param progress The current value of the progress bar. 2123 * @param indeterminate True if the progress bar is indeterminate, 2124 * false if not. 2125 */ setProgressBar(int viewId, int max, int progress, boolean indeterminate)2126 public void setProgressBar(int viewId, int max, int progress, 2127 boolean indeterminate) { 2128 setBoolean(viewId, "setIndeterminate", indeterminate); 2129 if (!indeterminate) { 2130 setInt(viewId, "setMax", max); 2131 setInt(viewId, "setProgress", progress); 2132 } 2133 } 2134 2135 /** 2136 * Equivalent to calling 2137 * {@link android.view.View#setOnClickListener(android.view.View.OnClickListener)} 2138 * to launch the provided {@link PendingIntent}. 2139 * 2140 * When setting the on-click action of items within collections (eg. {@link ListView}, 2141 * {@link StackView} etc.), this method will not work. Instead, use {@link 2142 * RemoteViews#setPendingIntentTemplate(int, PendingIntent) in conjunction with 2143 * RemoteViews#setOnClickFillInIntent(int, Intent). 2144 * 2145 * @param viewId The id of the view that will trigger the {@link PendingIntent} when clicked 2146 * @param pendingIntent The {@link PendingIntent} to send when user clicks 2147 */ setOnClickPendingIntent(int viewId, PendingIntent pendingIntent)2148 public void setOnClickPendingIntent(int viewId, PendingIntent pendingIntent) { 2149 addAction(new SetOnClickPendingIntent(viewId, pendingIntent)); 2150 } 2151 2152 /** 2153 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 2154 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 2155 * this method should be used to set a single PendingIntent template on the collection, and 2156 * individual items can differentiate their on-click behavior using 2157 * {@link RemoteViews#setOnClickFillInIntent(int, Intent)}. 2158 * 2159 * @param viewId The id of the collection who's children will use this PendingIntent template 2160 * when clicked 2161 * @param pendingIntentTemplate The {@link PendingIntent} to be combined with extras specified 2162 * by a child of viewId and executed when that child is clicked 2163 */ setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate)2164 public void setPendingIntentTemplate(int viewId, PendingIntent pendingIntentTemplate) { 2165 addAction(new SetPendingIntentTemplate(viewId, pendingIntentTemplate)); 2166 } 2167 2168 /** 2169 * When using collections (eg. {@link ListView}, {@link StackView} etc.) in widgets, it is very 2170 * costly to set PendingIntents on the individual items, and is hence not permitted. Instead 2171 * a single PendingIntent template can be set on the collection, see {@link 2172 * RemoteViews#setPendingIntentTemplate(int, PendingIntent)}, and the individual on-click 2173 * action of a given item can be distinguished by setting a fillInIntent on that item. The 2174 * fillInIntent is then combined with the PendingIntent template in order to determine the final 2175 * intent which will be executed when the item is clicked. This works as follows: any fields 2176 * which are left blank in the PendingIntent template, but are provided by the fillInIntent 2177 * will be overwritten, and the resulting PendingIntent will be used. 2178 * 2179 * 2180 * of the PendingIntent template will then be filled in with the associated fields that are 2181 * set in fillInIntent. See {@link Intent#fillIn(Intent, int)} for more details. 2182 * 2183 * @param viewId The id of the view on which to set the fillInIntent 2184 * @param fillInIntent The intent which will be combined with the parent's PendingIntent 2185 * in order to determine the on-click behavior of the view specified by viewId 2186 */ setOnClickFillInIntent(int viewId, Intent fillInIntent)2187 public void setOnClickFillInIntent(int viewId, Intent fillInIntent) { 2188 addAction(new SetOnClickFillInIntent(viewId, fillInIntent)); 2189 } 2190 2191 /** 2192 * @hide 2193 * Equivalent to calling a combination of {@link Drawable#setAlpha(int)}, 2194 * {@link Drawable#setColorFilter(int, android.graphics.PorterDuff.Mode)}, 2195 * and/or {@link Drawable#setLevel(int)} on the {@link Drawable} of a given 2196 * view. 2197 * <p> 2198 * You can omit specific calls by marking their values with null or -1. 2199 * 2200 * @param viewId The id of the view that contains the target 2201 * {@link Drawable} 2202 * @param targetBackground If true, apply these parameters to the 2203 * {@link Drawable} returned by 2204 * {@link android.view.View#getBackground()}. Otherwise, assume 2205 * the target view is an {@link ImageView} and apply them to 2206 * {@link ImageView#getDrawable()}. 2207 * @param alpha Specify an alpha value for the drawable, or -1 to leave 2208 * unchanged. 2209 * @param colorFilter Specify a color for a 2210 * {@link android.graphics.ColorFilter} for this drawable. This will be ignored if 2211 * {@code mode} is {@code null}. 2212 * @param mode Specify a PorterDuff mode for this drawable, or null to leave 2213 * unchanged. 2214 * @param level Specify the level for the drawable, or -1 to leave 2215 * unchanged. 2216 */ setDrawableParameters(int viewId, boolean targetBackground, int alpha, int colorFilter, PorterDuff.Mode mode, int level)2217 public void setDrawableParameters(int viewId, boolean targetBackground, int alpha, 2218 int colorFilter, PorterDuff.Mode mode, int level) { 2219 addAction(new SetDrawableParameters(viewId, targetBackground, alpha, 2220 colorFilter, mode, level)); 2221 } 2222 2223 /** 2224 * @hide 2225 * Equivalent to calling {@link android.widget.ProgressBar#setProgressTintList}. 2226 * 2227 * @param viewId The id of the view whose tint should change 2228 * @param tint the tint to apply, may be {@code null} to clear tint 2229 */ setProgressTintList(int viewId, ColorStateList tint)2230 public void setProgressTintList(int viewId, ColorStateList tint) { 2231 addAction(new ReflectionAction(viewId, "setProgressTintList", 2232 ReflectionAction.COLOR_STATE_LIST, tint)); 2233 } 2234 2235 /** 2236 * @hide 2237 * Equivalent to calling {@link android.widget.ProgressBar#setProgressBackgroundTintList}. 2238 * 2239 * @param viewId The id of the view whose tint should change 2240 * @param tint the tint to apply, may be {@code null} to clear tint 2241 */ setProgressBackgroundTintList(int viewId, ColorStateList tint)2242 public void setProgressBackgroundTintList(int viewId, ColorStateList tint) { 2243 addAction(new ReflectionAction(viewId, "setProgressBackgroundTintList", 2244 ReflectionAction.COLOR_STATE_LIST, tint)); 2245 } 2246 2247 /** 2248 * @hide 2249 * Equivalent to calling {@link android.widget.ProgressBar#setIndeterminateTintList}. 2250 * 2251 * @param viewId The id of the view whose tint should change 2252 * @param tint the tint to apply, may be {@code null} to clear tint 2253 */ setProgressIndeterminateTintList(int viewId, ColorStateList tint)2254 public void setProgressIndeterminateTintList(int viewId, ColorStateList tint) { 2255 addAction(new ReflectionAction(viewId, "setIndeterminateTintList", 2256 ReflectionAction.COLOR_STATE_LIST, tint)); 2257 } 2258 2259 /** 2260 * Equivalent to calling {@link android.widget.TextView#setTextColor(int)}. 2261 * 2262 * @param viewId The id of the view whose text color should change 2263 * @param color Sets the text color for all the states (normal, selected, 2264 * focused) to be this color. 2265 */ setTextColor(int viewId, int color)2266 public void setTextColor(int viewId, int color) { 2267 setInt(viewId, "setTextColor", color); 2268 } 2269 2270 /** 2271 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2272 * 2273 * @param appWidgetId The id of the app widget which contains the specified view. (This 2274 * parameter is ignored in this deprecated method) 2275 * @param viewId The id of the {@link AdapterView} 2276 * @param intent The intent of the service which will be 2277 * providing data to the RemoteViewsAdapter 2278 * @deprecated This method has been deprecated. See 2279 * {@link android.widget.RemoteViews#setRemoteAdapter(int, Intent)} 2280 */ 2281 @Deprecated setRemoteAdapter(int appWidgetId, int viewId, Intent intent)2282 public void setRemoteAdapter(int appWidgetId, int viewId, Intent intent) { 2283 setRemoteAdapter(viewId, intent); 2284 } 2285 2286 /** 2287 * Equivalent to calling {@link android.widget.AbsListView#setRemoteViewsAdapter(Intent)}. 2288 * Can only be used for App Widgets. 2289 * 2290 * @param viewId The id of the {@link AdapterView} 2291 * @param intent The intent of the service which will be 2292 * providing data to the RemoteViewsAdapter 2293 */ setRemoteAdapter(int viewId, Intent intent)2294 public void setRemoteAdapter(int viewId, Intent intent) { 2295 addAction(new SetRemoteViewsAdapterIntent(viewId, intent)); 2296 } 2297 2298 /** 2299 * Creates a simple Adapter for the viewId specified. The viewId must point to an AdapterView, 2300 * ie. {@link ListView}, {@link GridView}, {@link StackView} or {@link AdapterViewAnimator}. 2301 * This is a simpler but less flexible approach to populating collection widgets. Its use is 2302 * encouraged for most scenarios, as long as the total memory within the list of RemoteViews 2303 * is relatively small (ie. doesn't contain large or numerous Bitmaps, see {@link 2304 * RemoteViews#setImageViewBitmap}). In the case of numerous images, the use of API is still 2305 * possible by setting image URIs instead of Bitmaps, see {@link RemoteViews#setImageViewUri}. 2306 * 2307 * This API is supported in the compatibility library for previous API levels, see 2308 * RemoteViewsCompat. 2309 * 2310 * @param viewId The id of the {@link AdapterView} 2311 * @param list The list of RemoteViews which will populate the view specified by viewId. 2312 * @param viewTypeCount The maximum number of unique layout id's used to construct the list of 2313 * RemoteViews. This count cannot change during the life-cycle of a given widget, so this 2314 * parameter should account for the maximum possible number of types that may appear in the 2315 * See {@link Adapter#getViewTypeCount()}. 2316 * 2317 * @hide 2318 */ setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount)2319 public void setRemoteAdapter(int viewId, ArrayList<RemoteViews> list, int viewTypeCount) { 2320 addAction(new SetRemoteViewsAdapterList(viewId, list, viewTypeCount)); 2321 } 2322 2323 /** 2324 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 2325 * 2326 * @param viewId The id of the view to change 2327 * @param position Scroll to this adapter position 2328 */ setScrollPosition(int viewId, int position)2329 public void setScrollPosition(int viewId, int position) { 2330 setInt(viewId, "smoothScrollToPosition", position); 2331 } 2332 2333 /** 2334 * Equivalent to calling {@link android.widget.AbsListView#smoothScrollToPosition(int, int)}. 2335 * 2336 * @param viewId The id of the view to change 2337 * @param offset Scroll by this adapter position offset 2338 */ setRelativeScrollPosition(int viewId, int offset)2339 public void setRelativeScrollPosition(int viewId, int offset) { 2340 setInt(viewId, "smoothScrollByOffset", offset); 2341 } 2342 2343 /** 2344 * Equivalent to calling {@link android.view.View#setPadding(int, int, int, int)}. 2345 * 2346 * @param viewId The id of the view to change 2347 * @param left the left padding in pixels 2348 * @param top the top padding in pixels 2349 * @param right the right padding in pixels 2350 * @param bottom the bottom padding in pixels 2351 */ setViewPadding(int viewId, int left, int top, int right, int bottom)2352 public void setViewPadding(int viewId, int left, int top, int right, int bottom) { 2353 addAction(new ViewPaddingAction(viewId, left, top, right, bottom)); 2354 } 2355 2356 /** 2357 * Call a method taking one boolean on a view in the layout for this RemoteViews. 2358 * 2359 * @param viewId The id of the view on which to call the method. 2360 * @param methodName The name of the method to call. 2361 * @param value The value to pass to the method. 2362 */ setBoolean(int viewId, String methodName, boolean value)2363 public void setBoolean(int viewId, String methodName, boolean value) { 2364 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BOOLEAN, value)); 2365 } 2366 2367 /** 2368 * Call a method taking one byte on a view in the layout for this RemoteViews. 2369 * 2370 * @param viewId The id of the view on which to call the method. 2371 * @param methodName The name of the method to call. 2372 * @param value The value to pass to the method. 2373 */ setByte(int viewId, String methodName, byte value)2374 public void setByte(int viewId, String methodName, byte value) { 2375 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BYTE, value)); 2376 } 2377 2378 /** 2379 * Call a method taking one short on a view in the layout for this RemoteViews. 2380 * 2381 * @param viewId The id of the view on which to call the method. 2382 * @param methodName The name of the method to call. 2383 * @param value The value to pass to the method. 2384 */ setShort(int viewId, String methodName, short value)2385 public void setShort(int viewId, String methodName, short value) { 2386 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.SHORT, value)); 2387 } 2388 2389 /** 2390 * Call a method taking one int on a view in the layout for this RemoteViews. 2391 * 2392 * @param viewId The id of the view on which to call the method. 2393 * @param methodName The name of the method to call. 2394 * @param value The value to pass to the method. 2395 */ setInt(int viewId, String methodName, int value)2396 public void setInt(int viewId, String methodName, int value) { 2397 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INT, value)); 2398 } 2399 2400 /** 2401 * Call a method taking one long on a view in the layout for this RemoteViews. 2402 * 2403 * @param viewId The id of the view on which to call the method. 2404 * @param methodName The name of the method to call. 2405 * @param value The value to pass to the method. 2406 */ setLong(int viewId, String methodName, long value)2407 public void setLong(int viewId, String methodName, long value) { 2408 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.LONG, value)); 2409 } 2410 2411 /** 2412 * Call a method taking one float on a view in the layout for this RemoteViews. 2413 * 2414 * @param viewId The id of the view on which to call the method. 2415 * @param methodName The name of the method to call. 2416 * @param value The value to pass to the method. 2417 */ setFloat(int viewId, String methodName, float value)2418 public void setFloat(int viewId, String methodName, float value) { 2419 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.FLOAT, value)); 2420 } 2421 2422 /** 2423 * Call a method taking one double on a view in the layout for this RemoteViews. 2424 * 2425 * @param viewId The id of the view on which to call the method. 2426 * @param methodName The name of the method to call. 2427 * @param value The value to pass to the method. 2428 */ setDouble(int viewId, String methodName, double value)2429 public void setDouble(int viewId, String methodName, double value) { 2430 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.DOUBLE, value)); 2431 } 2432 2433 /** 2434 * Call a method taking one char on a view in the layout for this RemoteViews. 2435 * 2436 * @param viewId The id of the view on which to call the method. 2437 * @param methodName The name of the method to call. 2438 * @param value The value to pass to the method. 2439 */ setChar(int viewId, String methodName, char value)2440 public void setChar(int viewId, String methodName, char value) { 2441 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR, value)); 2442 } 2443 2444 /** 2445 * Call a method taking one String on a view in the layout for this RemoteViews. 2446 * 2447 * @param viewId The id of the view on which to call the method. 2448 * @param methodName The name of the method to call. 2449 * @param value The value to pass to the method. 2450 */ setString(int viewId, String methodName, String value)2451 public void setString(int viewId, String methodName, String value) { 2452 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.STRING, value)); 2453 } 2454 2455 /** 2456 * Call a method taking one CharSequence on a view in the layout for this RemoteViews. 2457 * 2458 * @param viewId The id of the view on which to call the method. 2459 * @param methodName The name of the method to call. 2460 * @param value The value to pass to the method. 2461 */ setCharSequence(int viewId, String methodName, CharSequence value)2462 public void setCharSequence(int viewId, String methodName, CharSequence value) { 2463 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.CHAR_SEQUENCE, value)); 2464 } 2465 2466 /** 2467 * Call a method taking one Uri on a view in the layout for this RemoteViews. 2468 * 2469 * @param viewId The id of the view on which to call the method. 2470 * @param methodName The name of the method to call. 2471 * @param value The value to pass to the method. 2472 */ setUri(int viewId, String methodName, Uri value)2473 public void setUri(int viewId, String methodName, Uri value) { 2474 if (value != null) { 2475 // Resolve any filesystem path before sending remotely 2476 value = value.getCanonicalUri(); 2477 if (StrictMode.vmFileUriExposureEnabled()) { 2478 value.checkFileUriExposed("RemoteViews.setUri()"); 2479 } 2480 } 2481 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value)); 2482 } 2483 2484 /** 2485 * Call a method taking one Bitmap on a view in the layout for this RemoteViews. 2486 * @more 2487 * <p class="note">The bitmap will be flattened into the parcel if this object is 2488 * sent across processes, so it may end up using a lot of memory, and may be fairly slow.</p> 2489 * 2490 * @param viewId The id of the view on which to call the method. 2491 * @param methodName The name of the method to call. 2492 * @param value The value to pass to the method. 2493 */ setBitmap(int viewId, String methodName, Bitmap value)2494 public void setBitmap(int viewId, String methodName, Bitmap value) { 2495 addAction(new BitmapReflectionAction(viewId, methodName, value)); 2496 } 2497 2498 /** 2499 * Call a method taking one Bundle on a view in the layout for this RemoteViews. 2500 * 2501 * @param viewId The id of the view on which to call the method. 2502 * @param methodName The name of the method to call. 2503 * @param value The value to pass to the method. 2504 */ setBundle(int viewId, String methodName, Bundle value)2505 public void setBundle(int viewId, String methodName, Bundle value) { 2506 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.BUNDLE, value)); 2507 } 2508 2509 /** 2510 * Call a method taking one Intent on a view in the layout for this RemoteViews. 2511 * 2512 * @param viewId The id of the view on which to call the method. 2513 * @param methodName The name of the method to call. 2514 * @param value The {@link android.content.Intent} to pass the method. 2515 */ setIntent(int viewId, String methodName, Intent value)2516 public void setIntent(int viewId, String methodName, Intent value) { 2517 addAction(new ReflectionAction(viewId, methodName, ReflectionAction.INTENT, value)); 2518 } 2519 2520 /** 2521 * Equivalent to calling View.setContentDescription(CharSequence). 2522 * 2523 * @param viewId The id of the view whose content description should change. 2524 * @param contentDescription The new content description for the view. 2525 */ setContentDescription(int viewId, CharSequence contentDescription)2526 public void setContentDescription(int viewId, CharSequence contentDescription) { 2527 setCharSequence(viewId, "setContentDescription", contentDescription); 2528 } 2529 2530 /** 2531 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalBefore(int)}. 2532 * 2533 * @param viewId The id of the view whose before view in accessibility traversal to set. 2534 * @param nextId The id of the next in the accessibility traversal. 2535 **/ setAccessibilityTraversalBefore(int viewId, int nextId)2536 public void setAccessibilityTraversalBefore(int viewId, int nextId) { 2537 setInt(viewId, "setAccessibilityTraversalBefore", nextId); 2538 } 2539 2540 /** 2541 * Equivalent to calling {@link android.view.View#setAccessibilityTraversalAfter(int)}. 2542 * 2543 * @param viewId The id of the view whose after view in accessibility traversal to set. 2544 * @param nextId The id of the next in the accessibility traversal. 2545 **/ setAccessibilityTraversalAfter(int viewId, int nextId)2546 public void setAccessibilityTraversalAfter(int viewId, int nextId) { 2547 setInt(viewId, "setAccessibilityTraversalAfter", nextId); 2548 } 2549 2550 /** 2551 * Equivalent to calling View.setLabelFor(int). 2552 * 2553 * @param viewId The id of the view whose property to set. 2554 * @param labeledId The id of a view for which this view serves as a label. 2555 */ setLabelFor(int viewId, int labeledId)2556 public void setLabelFor(int viewId, int labeledId) { 2557 setInt(viewId, "setLabelFor", labeledId); 2558 } 2559 getRemoteViewsToApply(Context context)2560 private RemoteViews getRemoteViewsToApply(Context context) { 2561 if (hasLandscapeAndPortraitLayouts()) { 2562 int orientation = context.getResources().getConfiguration().orientation; 2563 if (orientation == Configuration.ORIENTATION_LANDSCAPE) { 2564 return mLandscape; 2565 } else { 2566 return mPortrait; 2567 } 2568 } 2569 return this; 2570 } 2571 2572 /** 2573 * Inflates the view hierarchy represented by this object and applies 2574 * all of the actions. 2575 * 2576 * <p><strong>Caller beware: this may throw</strong> 2577 * 2578 * @param context Default context to use 2579 * @param parent Parent that the resulting view hierarchy will be attached to. This method 2580 * does <strong>not</strong> attach the hierarchy. The caller should do so when appropriate. 2581 * @return The inflated view hierarchy 2582 */ apply(Context context, ViewGroup parent)2583 public View apply(Context context, ViewGroup parent) { 2584 return apply(context, parent, null); 2585 } 2586 2587 /** @hide */ apply(Context context, ViewGroup parent, OnClickHandler handler)2588 public View apply(Context context, ViewGroup parent, OnClickHandler handler) { 2589 RemoteViews rvToApply = getRemoteViewsToApply(context); 2590 2591 View result; 2592 // RemoteViews may be built by an application installed in another 2593 // user. So build a context that loads resources from that user but 2594 // still returns the current users userId so settings like data / time formats 2595 // are loaded without requiring cross user persmissions. 2596 final Context contextForResources = getContextForResources(context); 2597 Context inflationContext = new ContextWrapper(context) { 2598 @Override 2599 public Resources getResources() { 2600 return contextForResources.getResources(); 2601 } 2602 @Override 2603 public Resources.Theme getTheme() { 2604 return contextForResources.getTheme(); 2605 } 2606 }; 2607 2608 LayoutInflater inflater = (LayoutInflater) 2609 context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); 2610 2611 // Clone inflater so we load resources from correct context and 2612 // we don't add a filter to the static version returned by getSystemService. 2613 inflater = inflater.cloneInContext(inflationContext); 2614 inflater.setFilter(this); 2615 result = inflater.inflate(rvToApply.getLayoutId(), parent, false); 2616 2617 rvToApply.performApply(result, parent, handler); 2618 2619 return result; 2620 } 2621 2622 /** 2623 * Applies all of the actions to the provided view. 2624 * 2625 * <p><strong>Caller beware: this may throw</strong> 2626 * 2627 * @param v The view to apply the actions to. This should be the result of 2628 * the {@link #apply(Context,ViewGroup)} call. 2629 */ reapply(Context context, View v)2630 public void reapply(Context context, View v) { 2631 reapply(context, v, null); 2632 } 2633 2634 /** @hide */ reapply(Context context, View v, OnClickHandler handler)2635 public void reapply(Context context, View v, OnClickHandler handler) { 2636 RemoteViews rvToApply = getRemoteViewsToApply(context); 2637 2638 // In the case that a view has this RemoteViews applied in one orientation, is persisted 2639 // across orientation change, and has the RemoteViews re-applied in the new orientation, 2640 // we throw an exception, since the layouts may be completely unrelated. 2641 if (hasLandscapeAndPortraitLayouts()) { 2642 if (v.getId() != rvToApply.getLayoutId()) { 2643 throw new RuntimeException("Attempting to re-apply RemoteViews to a view that" + 2644 " that does not share the same root layout id."); 2645 } 2646 } 2647 2648 rvToApply.performApply(v, (ViewGroup) v.getParent(), handler); 2649 } 2650 performApply(View v, ViewGroup parent, OnClickHandler handler)2651 private void performApply(View v, ViewGroup parent, OnClickHandler handler) { 2652 if (mActions != null) { 2653 handler = handler == null ? DEFAULT_ON_CLICK_HANDLER : handler; 2654 final int count = mActions.size(); 2655 for (int i = 0; i < count; i++) { 2656 Action a = mActions.get(i); 2657 a.apply(v, parent, handler); 2658 } 2659 } 2660 } 2661 getContextForResources(Context context)2662 private Context getContextForResources(Context context) { 2663 if (mApplication != null) { 2664 if (context.getUserId() == UserHandle.getUserId(mApplication.uid) 2665 && context.getPackageName().equals(mApplication.packageName)) { 2666 return context; 2667 } 2668 try { 2669 return context.createApplicationContext(mApplication, 2670 Context.CONTEXT_RESTRICTED); 2671 } catch (NameNotFoundException e) { 2672 Log.e(LOG_TAG, "Package name " + mApplication.packageName + " not found"); 2673 } 2674 } 2675 2676 return context; 2677 } 2678 2679 /** 2680 * Returns the number of actions in this RemoteViews. Can be used as a sequence number. 2681 * 2682 * @hide 2683 */ getSequenceNumber()2684 public int getSequenceNumber() { 2685 return (mActions == null) ? 0 : mActions.size(); 2686 } 2687 2688 /* (non-Javadoc) 2689 * Used to restrict the views which can be inflated 2690 * 2691 * @see android.view.LayoutInflater.Filter#onLoadClass(java.lang.Class) 2692 */ onLoadClass(Class clazz)2693 public boolean onLoadClass(Class clazz) { 2694 return clazz.isAnnotationPresent(RemoteView.class); 2695 } 2696 describeContents()2697 public int describeContents() { 2698 return 0; 2699 } 2700 writeToParcel(Parcel dest, int flags)2701 public void writeToParcel(Parcel dest, int flags) { 2702 if (!hasLandscapeAndPortraitLayouts()) { 2703 dest.writeInt(MODE_NORMAL); 2704 // We only write the bitmap cache if we are the root RemoteViews, as this cache 2705 // is shared by all children. 2706 if (mIsRoot) { 2707 mBitmapCache.writeBitmapsToParcel(dest, flags); 2708 } 2709 dest.writeParcelable(mApplication, flags); 2710 dest.writeInt(mLayoutId); 2711 dest.writeInt(mIsWidgetCollectionChild ? 1 : 0); 2712 int count; 2713 if (mActions != null) { 2714 count = mActions.size(); 2715 } else { 2716 count = 0; 2717 } 2718 dest.writeInt(count); 2719 for (int i=0; i<count; i++) { 2720 Action a = mActions.get(i); 2721 a.writeToParcel(dest, 0); 2722 } 2723 } else { 2724 dest.writeInt(MODE_HAS_LANDSCAPE_AND_PORTRAIT); 2725 // We only write the bitmap cache if we are the root RemoteViews, as this cache 2726 // is shared by all children. 2727 if (mIsRoot) { 2728 mBitmapCache.writeBitmapsToParcel(dest, flags); 2729 } 2730 mLandscape.writeToParcel(dest, flags); 2731 mPortrait.writeToParcel(dest, flags); 2732 } 2733 } 2734 getApplicationInfo(String packageName, int userId)2735 private static ApplicationInfo getApplicationInfo(String packageName, int userId) { 2736 if (packageName == null) { 2737 return null; 2738 } 2739 2740 // Get the application for the passed in package and user. 2741 Application application = ActivityThread.currentApplication(); 2742 if (application == null) { 2743 throw new IllegalStateException("Cannot create remote views out of an aplication."); 2744 } 2745 2746 ApplicationInfo applicationInfo = application.getApplicationInfo(); 2747 if (UserHandle.getUserId(applicationInfo.uid) != userId 2748 || !applicationInfo.packageName.equals(packageName)) { 2749 try { 2750 Context context = application.getBaseContext().createPackageContextAsUser( 2751 packageName, 0, new UserHandle(userId)); 2752 applicationInfo = context.getApplicationInfo(); 2753 } catch (NameNotFoundException nnfe) { 2754 throw new IllegalArgumentException("No such package " + packageName); 2755 } 2756 } 2757 2758 return applicationInfo; 2759 } 2760 2761 /** 2762 * Parcelable.Creator that instantiates RemoteViews objects 2763 */ 2764 public static final Parcelable.Creator<RemoteViews> CREATOR = new Parcelable.Creator<RemoteViews>() { 2765 public RemoteViews createFromParcel(Parcel parcel) { 2766 return new RemoteViews(parcel); 2767 } 2768 2769 public RemoteViews[] newArray(int size) { 2770 return new RemoteViews[size]; 2771 } 2772 }; 2773 } 2774