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