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