Notification.java revision d9273d6f289d9b55da3fd0db2f659fdfb48106a8
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 17package android.app; 18 19import com.android.internal.R; 20 21import android.annotation.IntDef; 22import android.content.Context; 23import android.content.Intent; 24import android.content.res.Resources; 25import android.graphics.Bitmap; 26import android.media.AudioManager; 27import android.net.Uri; 28import android.os.BadParcelableException; 29import android.os.Bundle; 30import android.os.Parcel; 31import android.os.Parcelable; 32import android.os.SystemClock; 33import android.os.UserHandle; 34import android.text.TextUtils; 35import android.util.Log; 36import android.util.TypedValue; 37import android.view.View; 38import android.widget.ProgressBar; 39import android.widget.RemoteViews; 40 41import java.lang.annotation.Retention; 42import java.lang.annotation.RetentionPolicy; 43import java.text.NumberFormat; 44import java.util.ArrayList; 45 46/** 47 * A class that represents how a persistent notification is to be presented to 48 * the user using the {@link android.app.NotificationManager}. 49 * 50 * <p>The {@link Notification.Builder Notification.Builder} has been added to make it 51 * easier to construct Notifications.</p> 52 * 53 * <div class="special reference"> 54 * <h3>Developer Guides</h3> 55 * <p>For a guide to creating notifications, read the 56 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Status Bar Notifications</a> 57 * developer guide.</p> 58 * </div> 59 */ 60public class Notification implements Parcelable 61{ 62 private static final String TAG = "Notification"; 63 64 /** 65 * Use all default values (where applicable). 66 */ 67 public static final int DEFAULT_ALL = ~0; 68 69 /** 70 * Use the default notification sound. This will ignore any given 71 * {@link #sound}. 72 * 73 74 * @see #defaults 75 */ 76 77 public static final int DEFAULT_SOUND = 1; 78 79 /** 80 * Use the default notification vibrate. This will ignore any given 81 * {@link #vibrate}. Using phone vibration requires the 82 * {@link android.Manifest.permission#VIBRATE VIBRATE} permission. 83 * 84 * @see #defaults 85 */ 86 87 public static final int DEFAULT_VIBRATE = 2; 88 89 /** 90 * Use the default notification lights. This will ignore the 91 * {@link #FLAG_SHOW_LIGHTS} bit, and {@link #ledARGB}, {@link #ledOffMS}, or 92 * {@link #ledOnMS}. 93 * 94 * @see #defaults 95 */ 96 97 public static final int DEFAULT_LIGHTS = 4; 98 99 /** 100 * A timestamp related to this notification, in milliseconds since the epoch. 101 * 102 * Default value: {@link System#currentTimeMillis() Now}. 103 * 104 * Choose a timestamp that will be most relevant to the user. For most finite events, this 105 * corresponds to the time the event happened (or will happen, in the case of events that have 106 * yet to occur but about which the user is being informed). Indefinite events should be 107 * timestamped according to when the activity began. 108 * 109 * Some examples: 110 * 111 * <ul> 112 * <li>Notification of a new chat message should be stamped when the message was received.</li> 113 * <li>Notification of an ongoing file download (with a progress bar, for example) should be stamped when the download started.</li> 114 * <li>Notification of a completed file download should be stamped when the download finished.</li> 115 * <li>Notification of an upcoming meeting should be stamped with the time the meeting will begin (that is, in the future).</li> 116 * <li>Notification of an ongoing stopwatch (increasing timer) should be stamped with the watch's start time. 117 * <li>Notification of an ongoing countdown timer should be stamped with the timer's end time. 118 * </ul> 119 * 120 */ 121 public long when; 122 123 /** 124 * The resource id of a drawable to use as the icon in the status bar. 125 * This is required; notifications with an invalid icon resource will not be shown. 126 */ 127 public int icon; 128 129 /** 130 * If the icon in the status bar is to have more than one level, you can set this. Otherwise, 131 * leave it at its default value of 0. 132 * 133 * @see android.widget.ImageView#setImageLevel 134 * @see android.graphics.drawable#setLevel 135 */ 136 public int iconLevel; 137 138 /** 139 * The number of events that this notification represents. For example, in a new mail 140 * notification, this could be the number of unread messages. 141 * 142 * The system may or may not use this field to modify the appearance of the notification. For 143 * example, before {@link android.os.Build.VERSION_CODES#HONEYCOMB}, this number was 144 * superimposed over the icon in the status bar. Starting with 145 * {@link android.os.Build.VERSION_CODES#HONEYCOMB}, the template used by 146 * {@link Notification.Builder} has displayed the number in the expanded notification view. 147 * 148 * If the number is 0 or negative, it is never shown. 149 */ 150 public int number; 151 152 /** 153 * The intent to execute when the expanded status entry is clicked. If 154 * this is an activity, it must include the 155 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 156 * that you take care of task management as described in the 157 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 158 * Stack</a> document. In particular, make sure to read the notification section 159 * <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html#HandlingNotifications">Handling 160 * Notifications</a> for the correct ways to launch an application from a 161 * notification. 162 */ 163 public PendingIntent contentIntent; 164 165 /** 166 * The intent to execute when the notification is explicitly dismissed by the user, either with 167 * the "Clear All" button or by swiping it away individually. 168 * 169 * This probably shouldn't be launching an activity since several of those will be sent 170 * at the same time. 171 */ 172 public PendingIntent deleteIntent; 173 174 /** 175 * An intent to launch instead of posting the notification to the status bar. 176 * 177 * @see Notification.Builder#setFullScreenIntent 178 */ 179 public PendingIntent fullScreenIntent; 180 181 /** 182 * Text to scroll across the screen when this item is added to 183 * the status bar on large and smaller devices. 184 * 185 * @see #tickerView 186 */ 187 public CharSequence tickerText; 188 189 /** 190 * The view to show as the ticker in the status bar when the notification 191 * is posted. 192 */ 193 public RemoteViews tickerView; 194 195 /** 196 * The view that will represent this notification in the expanded status bar. 197 */ 198 public RemoteViews contentView; 199 200 /** 201 * A large-format version of {@link #contentView}, giving the Notification an 202 * opportunity to show more detail. The system UI may choose to show this 203 * instead of the normal content view at its discretion. 204 */ 205 public RemoteViews bigContentView; 206 207 /** 208 * The bitmap that may escape the bounds of the panel and bar. 209 */ 210 public Bitmap largeIcon; 211 212 /** 213 * The sound to play. 214 * 215 * <p> 216 * To play the default notification sound, see {@link #defaults}. 217 * </p> 218 */ 219 public Uri sound; 220 221 /** 222 * Use this constant as the value for audioStreamType to request that 223 * the default stream type for notifications be used. Currently the 224 * default stream type is {@link AudioManager#STREAM_NOTIFICATION}. 225 */ 226 public static final int STREAM_DEFAULT = -1; 227 228 /** 229 * The audio stream type to use when playing the sound. 230 * Should be one of the STREAM_ constants from 231 * {@link android.media.AudioManager}. 232 */ 233 public int audioStreamType = STREAM_DEFAULT; 234 235 /** 236 * The pattern with which to vibrate. 237 * 238 * <p> 239 * To vibrate the default pattern, see {@link #defaults}. 240 * </p> 241 * 242 * @see android.os.Vibrator#vibrate(long[],int) 243 */ 244 public long[] vibrate; 245 246 /** 247 * The color of the led. The hardware will do its best approximation. 248 * 249 * @see #FLAG_SHOW_LIGHTS 250 * @see #flags 251 */ 252 public int ledARGB; 253 254 /** 255 * The number of milliseconds for the LED to be on while it's flashing. 256 * The hardware will do its best approximation. 257 * 258 * @see #FLAG_SHOW_LIGHTS 259 * @see #flags 260 */ 261 public int ledOnMS; 262 263 /** 264 * The number of milliseconds for the LED to be off while it's flashing. 265 * The hardware will do its best approximation. 266 * 267 * @see #FLAG_SHOW_LIGHTS 268 * @see #flags 269 */ 270 public int ledOffMS; 271 272 /** 273 * Specifies which values should be taken from the defaults. 274 * <p> 275 * To set, OR the desired from {@link #DEFAULT_SOUND}, 276 * {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. For all default 277 * values, use {@link #DEFAULT_ALL}. 278 * </p> 279 */ 280 public int defaults; 281 282 /** 283 * Bit to be bitwise-ored into the {@link #flags} field that should be 284 * set if you want the LED on for this notification. 285 * <ul> 286 * <li>To turn the LED off, pass 0 in the alpha channel for colorARGB 287 * or 0 for both ledOnMS and ledOffMS.</li> 288 * <li>To turn the LED on, pass 1 for ledOnMS and 0 for ledOffMS.</li> 289 * <li>To flash the LED, pass the number of milliseconds that it should 290 * be on and off to ledOnMS and ledOffMS.</li> 291 * </ul> 292 * <p> 293 * Since hardware varies, you are not guaranteed that any of the values 294 * you pass are honored exactly. Use the system defaults (TODO) if possible 295 * because they will be set to values that work on any given hardware. 296 * <p> 297 * The alpha channel must be set for forward compatibility. 298 * 299 */ 300 public static final int FLAG_SHOW_LIGHTS = 0x00000001; 301 302 /** 303 * Bit to be bitwise-ored into the {@link #flags} field that should be 304 * set if this notification is in reference to something that is ongoing, 305 * like a phone call. It should not be set if this notification is in 306 * reference to something that happened at a particular point in time, 307 * like a missed phone call. 308 */ 309 public static final int FLAG_ONGOING_EVENT = 0x00000002; 310 311 /** 312 * Bit to be bitwise-ored into the {@link #flags} field that if set, 313 * the audio will be repeated until the notification is 314 * cancelled or the notification window is opened. 315 */ 316 public static final int FLAG_INSISTENT = 0x00000004; 317 318 /** 319 * Bit to be bitwise-ored into the {@link #flags} field that should be 320 * set if you want the sound and/or vibration play each time the 321 * notification is sent, even if it has not been canceled before that. 322 */ 323 public static final int FLAG_ONLY_ALERT_ONCE = 0x00000008; 324 325 /** 326 * Bit to be bitwise-ored into the {@link #flags} field that should be 327 * set if the notification should be canceled when it is clicked by the 328 * user. 329 330 */ 331 public static final int FLAG_AUTO_CANCEL = 0x00000010; 332 333 /** 334 * Bit to be bitwise-ored into the {@link #flags} field that should be 335 * set if the notification should not be canceled when the user clicks 336 * the Clear all button. 337 */ 338 public static final int FLAG_NO_CLEAR = 0x00000020; 339 340 /** 341 * Bit to be bitwise-ored into the {@link #flags} field that should be 342 * set if this notification represents a currently running service. This 343 * will normally be set for you by {@link Service#startForeground}. 344 */ 345 public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; 346 347 /** 348 * Obsolete flag indicating high-priority notifications; use the priority field instead. 349 * 350 * @deprecated Use {@link #priority} with a positive value. 351 */ 352 public static final int FLAG_HIGH_PRIORITY = 0x00000080; 353 354 public int flags; 355 356 /** @hide */ 357 @IntDef({PRIORITY_DEFAULT,PRIORITY_LOW,PRIORITY_MIN,PRIORITY_HIGH,PRIORITY_MAX}) 358 @Retention(RetentionPolicy.SOURCE) 359 public @interface Priority {} 360 361 /** 362 * Default notification {@link #priority}. If your application does not prioritize its own 363 * notifications, use this value for all notifications. 364 */ 365 public static final int PRIORITY_DEFAULT = 0; 366 367 /** 368 * Lower {@link #priority}, for items that are less important. The UI may choose to show these 369 * items smaller, or at a different position in the list, compared with your app's 370 * {@link #PRIORITY_DEFAULT} items. 371 */ 372 public static final int PRIORITY_LOW = -1; 373 374 /** 375 * Lowest {@link #priority}; these items might not be shown to the user except under special 376 * circumstances, such as detailed notification logs. 377 */ 378 public static final int PRIORITY_MIN = -2; 379 380 /** 381 * Higher {@link #priority}, for more important notifications or alerts. The UI may choose to 382 * show these items larger, or at a different position in notification lists, compared with 383 * your app's {@link #PRIORITY_DEFAULT} items. 384 */ 385 public static final int PRIORITY_HIGH = 1; 386 387 /** 388 * Highest {@link #priority}, for your application's most important items that require the 389 * user's prompt attention or input. 390 */ 391 public static final int PRIORITY_MAX = 2; 392 393 /** 394 * Relative priority for this notification. 395 * 396 * Priority is an indication of how much of the user's valuable attention should be consumed by 397 * this notification. Low-priority notifications may be hidden from the user in certain 398 * situations, while the user might be interrupted for a higher-priority notification. The 399 * system will make a determination about how to interpret this priority when presenting 400 * the notification. 401 */ 402 @Priority 403 public int priority; 404 405 /** 406 * @hide 407 * Notification type: incoming call (voice or video) or similar synchronous communication request. 408 */ 409 public static final String KIND_CALL = "android.call"; 410 411 /** 412 * @hide 413 * Notification type: incoming direct message (SMS, instant message, etc.). 414 */ 415 public static final String KIND_MESSAGE = "android.message"; 416 417 /** 418 * @hide 419 * Notification type: asynchronous bulk message (email). 420 */ 421 public static final String KIND_EMAIL = "android.email"; 422 423 /** 424 * @hide 425 * Notification type: calendar event. 426 */ 427 public static final String KIND_EVENT = "android.event"; 428 429 /** 430 * @hide 431 * Notification type: promotion or advertisement. 432 */ 433 public static final String KIND_PROMO = "android.promo"; 434 435 /** 436 * @hide 437 * If this notification matches of one or more special types (see the <code>KIND_*</code> 438 * constants), add them here, best match first. 439 */ 440 public String[] kind; 441 442 /** 443 * Additional semantic data to be carried around with this Notification. 444 */ 445 public Bundle extras = new Bundle(); 446 447 // extras keys for Builder inputs 448 public static final String EXTRA_TITLE = "android.title"; 449 public static final String EXTRA_TITLE_BIG = EXTRA_TITLE + ".big"; 450 public static final String EXTRA_TEXT = "android.text"; 451 public static final String EXTRA_SUB_TEXT = "android.subText"; 452 public static final String EXTRA_INFO_TEXT = "android.infoText"; 453 public static final String EXTRA_SUMMARY_TEXT = "android.summaryText"; 454 public static final String EXTRA_SMALL_ICON = "android.icon"; 455 public static final String EXTRA_LARGE_ICON = "android.largeIcon"; 456 public static final String EXTRA_LARGE_ICON_BIG = EXTRA_LARGE_ICON + ".big"; 457 public static final String EXTRA_PROGRESS = "android.progress"; 458 public static final String EXTRA_PROGRESS_MAX = "android.progressMax"; 459 public static final String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate"; 460 public static final String EXTRA_SHOW_CHRONOMETER = "android.showChronometer"; 461 public static final String EXTRA_SHOW_WHEN = "android.showWhen"; 462 public static final String EXTRA_PICTURE = "android.picture"; 463 public static final String EXTRA_TEXT_LINES = "android.textLines"; 464 465 // extras keys for other interesting pieces of information 466 public static final String EXTRA_PEOPLE = "android.people"; 467 468 /** 469 * @hide 470 * Extra added by NotificationManagerService to indicate whether a NotificationScorer 471 * modified the Notifications's score. 472 */ 473 public static final String EXTRA_SCORE_MODIFIED = "android.scoreModified"; 474 475 /** 476 * Notification extra to specify heads up display preference. 477 * @hide 478 */ 479 public static final String EXTRA_AS_HEADS_UP = "headsup"; 480 481 /** 482 * Value for {@link #EXTRA_AS_HEADS_UP} indicating that heads up display is not appropriate. 483 * @hide 484 */ 485 public static final int HEADS_UP_NEVER = 0; 486 487 /** 488 * Default value for {@link #EXTRA_AS_HEADS_UP} indicating that heads up display is appropriate. 489 * @hide 490 */ 491 public static final int HEADS_UP_ALLOWED = 1; 492 493 /** 494 * Value for {@link #EXTRA_AS_HEADS_UP} that advocates for heads up display. 495 * @hide 496 */ 497 public static final int HEADS_UP_REQUESTED = 2; 498 499 /** 500 * Structure to encapsulate an "action", including title and icon, that can be attached to a Notification. 501 */ 502 public static class Action implements Parcelable { 503 public int icon; 504 public CharSequence title; 505 public PendingIntent actionIntent; 506 @SuppressWarnings("unused") 507 public Action() { } 508 private Action(Parcel in) { 509 icon = in.readInt(); 510 title = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); 511 if (in.readInt() == 1) { 512 actionIntent = PendingIntent.CREATOR.createFromParcel(in); 513 } 514 } 515 public Action(int icon_, CharSequence title_, PendingIntent intent_) { 516 this.icon = icon_; 517 this.title = title_; 518 this.actionIntent = intent_; 519 } 520 @Override 521 public Action clone() { 522 return new Action( 523 this.icon, 524 this.title.toString(), 525 this.actionIntent // safe to alias 526 ); 527 } 528 @Override 529 public int describeContents() { 530 return 0; 531 } 532 @Override 533 public void writeToParcel(Parcel out, int flags) { 534 out.writeInt(icon); 535 TextUtils.writeToParcel(title, out, flags); 536 if (actionIntent != null) { 537 out.writeInt(1); 538 actionIntent.writeToParcel(out, flags); 539 } else { 540 out.writeInt(0); 541 } 542 } 543 public static final Parcelable.Creator<Action> CREATOR 544 = new Parcelable.Creator<Action>() { 545 public Action createFromParcel(Parcel in) { 546 return new Action(in); 547 } 548 public Action[] newArray(int size) { 549 return new Action[size]; 550 } 551 }; 552 } 553 554 public Action[] actions; 555 556 /** 557 * Constructs a Notification object with default values. 558 * You might want to consider using {@link Builder} instead. 559 */ 560 public Notification() 561 { 562 this.when = System.currentTimeMillis(); 563 this.priority = PRIORITY_DEFAULT; 564 } 565 566 /** 567 * @hide 568 */ 569 public Notification(Context context, int icon, CharSequence tickerText, long when, 570 CharSequence contentTitle, CharSequence contentText, Intent contentIntent) 571 { 572 this.when = when; 573 this.icon = icon; 574 this.tickerText = tickerText; 575 setLatestEventInfo(context, contentTitle, contentText, 576 PendingIntent.getActivity(context, 0, contentIntent, 0)); 577 } 578 579 /** 580 * Constructs a Notification object with the information needed to 581 * have a status bar icon without the standard expanded view. 582 * 583 * @param icon The resource id of the icon to put in the status bar. 584 * @param tickerText The text that flows by in the status bar when the notification first 585 * activates. 586 * @param when The time to show in the time field. In the System.currentTimeMillis 587 * timebase. 588 * 589 * @deprecated Use {@link Builder} instead. 590 */ 591 @Deprecated 592 public Notification(int icon, CharSequence tickerText, long when) 593 { 594 this.icon = icon; 595 this.tickerText = tickerText; 596 this.when = when; 597 } 598 599 /** 600 * Unflatten the notification from a parcel. 601 */ 602 public Notification(Parcel parcel) 603 { 604 int version = parcel.readInt(); 605 606 when = parcel.readLong(); 607 icon = parcel.readInt(); 608 number = parcel.readInt(); 609 if (parcel.readInt() != 0) { 610 contentIntent = PendingIntent.CREATOR.createFromParcel(parcel); 611 } 612 if (parcel.readInt() != 0) { 613 deleteIntent = PendingIntent.CREATOR.createFromParcel(parcel); 614 } 615 if (parcel.readInt() != 0) { 616 tickerText = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(parcel); 617 } 618 if (parcel.readInt() != 0) { 619 tickerView = RemoteViews.CREATOR.createFromParcel(parcel); 620 } 621 if (parcel.readInt() != 0) { 622 contentView = RemoteViews.CREATOR.createFromParcel(parcel); 623 } 624 if (parcel.readInt() != 0) { 625 largeIcon = Bitmap.CREATOR.createFromParcel(parcel); 626 } 627 defaults = parcel.readInt(); 628 flags = parcel.readInt(); 629 if (parcel.readInt() != 0) { 630 sound = Uri.CREATOR.createFromParcel(parcel); 631 } 632 633 audioStreamType = parcel.readInt(); 634 vibrate = parcel.createLongArray(); 635 ledARGB = parcel.readInt(); 636 ledOnMS = parcel.readInt(); 637 ledOffMS = parcel.readInt(); 638 iconLevel = parcel.readInt(); 639 640 if (parcel.readInt() != 0) { 641 fullScreenIntent = PendingIntent.CREATOR.createFromParcel(parcel); 642 } 643 644 priority = parcel.readInt(); 645 646 kind = parcel.createStringArray(); // may set kind to null 647 648 extras = parcel.readBundle(); // may be null 649 650 actions = parcel.createTypedArray(Action.CREATOR); // may be null 651 652 if (parcel.readInt() != 0) { 653 bigContentView = RemoteViews.CREATOR.createFromParcel(parcel); 654 } 655 } 656 657 @Override 658 public Notification clone() { 659 Notification that = new Notification(); 660 cloneInto(that, true); 661 return that; 662 } 663 664 /** 665 * Copy all (or if heavy is false, all except Bitmaps and RemoteViews) members 666 * of this into that. 667 * @hide 668 */ 669 public void cloneInto(Notification that, boolean heavy) { 670 that.when = this.when; 671 that.icon = this.icon; 672 that.number = this.number; 673 674 // PendingIntents are global, so there's no reason (or way) to clone them. 675 that.contentIntent = this.contentIntent; 676 that.deleteIntent = this.deleteIntent; 677 that.fullScreenIntent = this.fullScreenIntent; 678 679 if (this.tickerText != null) { 680 that.tickerText = this.tickerText.toString(); 681 } 682 if (heavy && this.tickerView != null) { 683 that.tickerView = this.tickerView.clone(); 684 } 685 if (heavy && this.contentView != null) { 686 that.contentView = this.contentView.clone(); 687 } 688 if (heavy && this.largeIcon != null) { 689 that.largeIcon = Bitmap.createBitmap(this.largeIcon); 690 } 691 that.iconLevel = this.iconLevel; 692 that.sound = this.sound; // android.net.Uri is immutable 693 that.audioStreamType = this.audioStreamType; 694 695 final long[] vibrate = this.vibrate; 696 if (vibrate != null) { 697 final int N = vibrate.length; 698 final long[] vib = that.vibrate = new long[N]; 699 System.arraycopy(vibrate, 0, vib, 0, N); 700 } 701 702 that.ledARGB = this.ledARGB; 703 that.ledOnMS = this.ledOnMS; 704 that.ledOffMS = this.ledOffMS; 705 that.defaults = this.defaults; 706 707 that.flags = this.flags; 708 709 that.priority = this.priority; 710 711 final String[] thiskind = this.kind; 712 if (thiskind != null) { 713 final int N = thiskind.length; 714 final String[] thatkind = that.kind = new String[N]; 715 System.arraycopy(thiskind, 0, thatkind, 0, N); 716 } 717 718 if (this.extras != null) { 719 try { 720 that.extras = new Bundle(this.extras); 721 // will unparcel 722 that.extras.size(); 723 } catch (BadParcelableException e) { 724 Log.e(TAG, "could not unparcel extras from notification: " + this, e); 725 that.extras = null; 726 } 727 } 728 729 if (this.actions != null) { 730 that.actions = new Action[this.actions.length]; 731 for(int i=0; i<this.actions.length; i++) { 732 that.actions[i] = this.actions[i].clone(); 733 } 734 } 735 736 if (heavy && this.bigContentView != null) { 737 that.bigContentView = this.bigContentView.clone(); 738 } 739 740 if (!heavy) { 741 that.lightenPayload(); // will clean out extras 742 } 743 } 744 745 /** 746 * Removes heavyweight parts of the Notification object for archival or for sending to 747 * listeners when the full contents are not necessary. 748 * @hide 749 */ 750 public final void lightenPayload() { 751 tickerView = null; 752 contentView = null; 753 bigContentView = null; 754 largeIcon = null; 755 if (extras != null) { 756 extras.remove(Notification.EXTRA_LARGE_ICON); 757 extras.remove(Notification.EXTRA_LARGE_ICON_BIG); 758 extras.remove(Notification.EXTRA_PICTURE); 759 } 760 } 761 762 /** 763 * Make sure this CharSequence is safe to put into a bundle, which basically 764 * means it had better not be some custom Parcelable implementation. 765 * @hide 766 */ 767 public static CharSequence safeCharSequence(CharSequence cs) { 768 if (cs instanceof Parcelable) { 769 Log.e(TAG, "warning: " + cs.getClass().getCanonicalName() 770 + " instance is a custom Parcelable and not allowed in Notification"); 771 return cs.toString(); 772 } 773 774 return cs; 775 } 776 777 public int describeContents() { 778 return 0; 779 } 780 781 /** 782 * Flatten this notification from a parcel. 783 */ 784 public void writeToParcel(Parcel parcel, int flags) 785 { 786 parcel.writeInt(1); 787 788 parcel.writeLong(when); 789 parcel.writeInt(icon); 790 parcel.writeInt(number); 791 if (contentIntent != null) { 792 parcel.writeInt(1); 793 contentIntent.writeToParcel(parcel, 0); 794 } else { 795 parcel.writeInt(0); 796 } 797 if (deleteIntent != null) { 798 parcel.writeInt(1); 799 deleteIntent.writeToParcel(parcel, 0); 800 } else { 801 parcel.writeInt(0); 802 } 803 if (tickerText != null) { 804 parcel.writeInt(1); 805 TextUtils.writeToParcel(tickerText, parcel, flags); 806 } else { 807 parcel.writeInt(0); 808 } 809 if (tickerView != null) { 810 parcel.writeInt(1); 811 tickerView.writeToParcel(parcel, 0); 812 } else { 813 parcel.writeInt(0); 814 } 815 if (contentView != null) { 816 parcel.writeInt(1); 817 contentView.writeToParcel(parcel, 0); 818 } else { 819 parcel.writeInt(0); 820 } 821 if (largeIcon != null) { 822 parcel.writeInt(1); 823 largeIcon.writeToParcel(parcel, 0); 824 } else { 825 parcel.writeInt(0); 826 } 827 828 parcel.writeInt(defaults); 829 parcel.writeInt(this.flags); 830 831 if (sound != null) { 832 parcel.writeInt(1); 833 sound.writeToParcel(parcel, 0); 834 } else { 835 parcel.writeInt(0); 836 } 837 parcel.writeInt(audioStreamType); 838 parcel.writeLongArray(vibrate); 839 parcel.writeInt(ledARGB); 840 parcel.writeInt(ledOnMS); 841 parcel.writeInt(ledOffMS); 842 parcel.writeInt(iconLevel); 843 844 if (fullScreenIntent != null) { 845 parcel.writeInt(1); 846 fullScreenIntent.writeToParcel(parcel, 0); 847 } else { 848 parcel.writeInt(0); 849 } 850 851 parcel.writeInt(priority); 852 853 parcel.writeStringArray(kind); // ok for null 854 855 parcel.writeBundle(extras); // null ok 856 857 parcel.writeTypedArray(actions, 0); // null ok 858 859 if (bigContentView != null) { 860 parcel.writeInt(1); 861 bigContentView.writeToParcel(parcel, 0); 862 } else { 863 parcel.writeInt(0); 864 } 865 } 866 867 /** 868 * Parcelable.Creator that instantiates Notification objects 869 */ 870 public static final Parcelable.Creator<Notification> CREATOR 871 = new Parcelable.Creator<Notification>() 872 { 873 public Notification createFromParcel(Parcel parcel) 874 { 875 return new Notification(parcel); 876 } 877 878 public Notification[] newArray(int size) 879 { 880 return new Notification[size]; 881 } 882 }; 883 884 /** 885 * Sets the {@link #contentView} field to be a view with the standard "Latest Event" 886 * layout. 887 * 888 * <p>Uses the {@link #icon} and {@link #when} fields to set the icon and time fields 889 * in the view.</p> 890 * @param context The context for your application / activity. 891 * @param contentTitle The title that goes in the expanded entry. 892 * @param contentText The text that goes in the expanded entry. 893 * @param contentIntent The intent to launch when the user clicks the expanded notification. 894 * If this is an activity, it must include the 895 * {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} flag, which requires 896 * that you take care of task management as described in the 897 * <a href="{@docRoot}guide/topics/fundamentals/tasks-and-back-stack.html">Tasks and Back 898 * Stack</a> document. 899 * 900 * @deprecated Use {@link Builder} instead. 901 */ 902 @Deprecated 903 public void setLatestEventInfo(Context context, 904 CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) { 905 Notification.Builder builder = new Notification.Builder(context); 906 907 // First, ensure that key pieces of information that may have been set directly 908 // are preserved 909 builder.setWhen(this.when); 910 builder.setSmallIcon(this.icon); 911 builder.setPriority(this.priority); 912 builder.setTicker(this.tickerText); 913 builder.setNumber(this.number); 914 builder.mFlags = this.flags; 915 builder.setSound(this.sound, this.audioStreamType); 916 builder.setDefaults(this.defaults); 917 builder.setVibrate(this.vibrate); 918 919 // now apply the latestEventInfo fields 920 if (contentTitle != null) { 921 builder.setContentTitle(contentTitle); 922 } 923 if (contentText != null) { 924 builder.setContentText(contentText); 925 } 926 builder.setContentIntent(contentIntent); 927 builder.buildInto(this); 928 } 929 930 @Override 931 public String toString() { 932 StringBuilder sb = new StringBuilder(); 933 sb.append("Notification(pri="); 934 sb.append(priority); 935 sb.append(" contentView="); 936 if (contentView != null) { 937 sb.append(contentView.getPackage()); 938 sb.append("/0x"); 939 sb.append(Integer.toHexString(contentView.getLayoutId())); 940 } else { 941 sb.append("null"); 942 } 943 // TODO(dsandler): defaults take precedence over local values, so reorder the branches below 944 sb.append(" vibrate="); 945 if ((this.defaults & DEFAULT_VIBRATE) != 0) { 946 sb.append("default"); 947 } else if (this.vibrate != null) { 948 int N = this.vibrate.length-1; 949 sb.append("["); 950 for (int i=0; i<N; i++) { 951 sb.append(this.vibrate[i]); 952 sb.append(','); 953 } 954 if (N != -1) { 955 sb.append(this.vibrate[N]); 956 } 957 sb.append("]"); 958 } else { 959 sb.append("null"); 960 } 961 sb.append(" sound="); 962 if ((this.defaults & DEFAULT_SOUND) != 0) { 963 sb.append("default"); 964 } else if (this.sound != null) { 965 sb.append(this.sound.toString()); 966 } else { 967 sb.append("null"); 968 } 969 sb.append(" defaults=0x"); 970 sb.append(Integer.toHexString(this.defaults)); 971 sb.append(" flags=0x"); 972 sb.append(Integer.toHexString(this.flags)); 973 sb.append(" kind=["); 974 if (this.kind == null) { 975 sb.append("null"); 976 } else { 977 for (int i=0; i<this.kind.length; i++) { 978 if (i>0) sb.append(","); 979 sb.append(this.kind[i]); 980 } 981 } 982 sb.append("]"); 983 if (actions != null) { 984 sb.append(" "); 985 sb.append(actions.length); 986 sb.append(" action"); 987 if (actions.length > 1) sb.append("s"); 988 } 989 sb.append(")"); 990 return sb.toString(); 991 } 992 993 /** {@hide} */ 994 public void setUser(UserHandle user) { 995 if (user.getIdentifier() == UserHandle.USER_ALL) { 996 user = UserHandle.OWNER; 997 } 998 if (tickerView != null) { 999 tickerView.setUser(user); 1000 } 1001 if (contentView != null) { 1002 contentView.setUser(user); 1003 } 1004 if (bigContentView != null) { 1005 bigContentView.setUser(user); 1006 } 1007 } 1008 1009 /** 1010 * Builder class for {@link Notification} objects. 1011 * 1012 * Provides a convenient way to set the various fields of a {@link Notification} and generate 1013 * content views using the platform's notification layout template. If your app supports 1014 * versions of Android as old as API level 4, you can instead use 1015 * {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}, 1016 * available in the <a href="{@docRoot}tools/extras/support-library.html">Android Support 1017 * library</a>. 1018 * 1019 * <p>Example: 1020 * 1021 * <pre class="prettyprint"> 1022 * Notification noti = new Notification.Builder(mContext) 1023 * .setContentTitle("New mail from " + sender.toString()) 1024 * .setContentText(subject) 1025 * .setSmallIcon(R.drawable.new_mail) 1026 * .setLargeIcon(aBitmap) 1027 * .build(); 1028 * </pre> 1029 */ 1030 public static class Builder { 1031 private static final int MAX_ACTION_BUTTONS = 3; 1032 1033 private Context mContext; 1034 1035 private long mWhen; 1036 private int mSmallIcon; 1037 private int mSmallIconLevel; 1038 private int mNumber; 1039 private CharSequence mContentTitle; 1040 private CharSequence mContentText; 1041 private CharSequence mContentInfo; 1042 private CharSequence mSubText; 1043 private PendingIntent mContentIntent; 1044 private RemoteViews mContentView; 1045 private PendingIntent mDeleteIntent; 1046 private PendingIntent mFullScreenIntent; 1047 private CharSequence mTickerText; 1048 private RemoteViews mTickerView; 1049 private Bitmap mLargeIcon; 1050 private Uri mSound; 1051 private int mAudioStreamType; 1052 private long[] mVibrate; 1053 private int mLedArgb; 1054 private int mLedOnMs; 1055 private int mLedOffMs; 1056 private int mDefaults; 1057 private int mFlags; 1058 private int mProgressMax; 1059 private int mProgress; 1060 private boolean mProgressIndeterminate; 1061 private ArrayList<String> mKindList = new ArrayList<String>(1); 1062 private Bundle mExtras; 1063 private int mPriority; 1064 private ArrayList<Action> mActions = new ArrayList<Action>(MAX_ACTION_BUTTONS); 1065 private boolean mUseChronometer; 1066 private Style mStyle; 1067 private boolean mShowWhen = true; 1068 1069 /** 1070 * Constructs a new Builder with the defaults: 1071 * 1072 1073 * <table> 1074 * <tr><th align=right>priority</th> 1075 * <td>{@link #PRIORITY_DEFAULT}</td></tr> 1076 * <tr><th align=right>when</th> 1077 * <td>now ({@link System#currentTimeMillis()})</td></tr> 1078 * <tr><th align=right>audio stream</th> 1079 * <td>{@link #STREAM_DEFAULT}</td></tr> 1080 * </table> 1081 * 1082 1083 * @param context 1084 * A {@link Context} that will be used by the Builder to construct the 1085 * RemoteViews. The Context will not be held past the lifetime of this Builder 1086 * object. 1087 */ 1088 public Builder(Context context) { 1089 mContext = context; 1090 1091 // Set defaults to match the defaults of a Notification 1092 mWhen = System.currentTimeMillis(); 1093 mAudioStreamType = STREAM_DEFAULT; 1094 mPriority = PRIORITY_DEFAULT; 1095 } 1096 1097 /** 1098 * Add a timestamp pertaining to the notification (usually the time the event occurred). 1099 * It will be shown in the notification content view by default; use 1100 * {@link Builder#setShowWhen(boolean) setShowWhen} to control this. 1101 * 1102 * @see Notification#when 1103 */ 1104 public Builder setWhen(long when) { 1105 mWhen = when; 1106 return this; 1107 } 1108 1109 /** 1110 * Control whether the timestamp set with {@link Builder#setWhen(long) setWhen} is shown 1111 * in the content view. 1112 */ 1113 public Builder setShowWhen(boolean show) { 1114 mShowWhen = show; 1115 return this; 1116 } 1117 1118 /** 1119 * Show the {@link Notification#when} field as a stopwatch. 1120 * 1121 * Instead of presenting <code>when</code> as a timestamp, the notification will show an 1122 * automatically updating display of the minutes and seconds since <code>when</code>. 1123 * 1124 * Useful when showing an elapsed time (like an ongoing phone call). 1125 * 1126 * @see android.widget.Chronometer 1127 * @see Notification#when 1128 */ 1129 public Builder setUsesChronometer(boolean b) { 1130 mUseChronometer = b; 1131 return this; 1132 } 1133 1134 /** 1135 * Set the small icon resource, which will be used to represent the notification in the 1136 * status bar. 1137 * 1138 1139 * The platform template for the expanded view will draw this icon in the left, unless a 1140 * {@link #setLargeIcon(Bitmap) large icon} has also been specified, in which case the small 1141 * icon will be moved to the right-hand side. 1142 * 1143 1144 * @param icon 1145 * A resource ID in the application's package of the drawable to use. 1146 * @see Notification#icon 1147 */ 1148 public Builder setSmallIcon(int icon) { 1149 mSmallIcon = icon; 1150 return this; 1151 } 1152 1153 /** 1154 * A variant of {@link #setSmallIcon(int) setSmallIcon(int)} that takes an additional 1155 * level parameter for when the icon is a {@link android.graphics.drawable.LevelListDrawable 1156 * LevelListDrawable}. 1157 * 1158 * @param icon A resource ID in the application's package of the drawable to use. 1159 * @param level The level to use for the icon. 1160 * 1161 * @see Notification#icon 1162 * @see Notification#iconLevel 1163 */ 1164 public Builder setSmallIcon(int icon, int level) { 1165 mSmallIcon = icon; 1166 mSmallIconLevel = level; 1167 return this; 1168 } 1169 1170 /** 1171 * Set the first line of text in the platform notification template. 1172 */ 1173 public Builder setContentTitle(CharSequence title) { 1174 mContentTitle = safeCharSequence(title); 1175 return this; 1176 } 1177 1178 /** 1179 * Set the second line of text in the platform notification template. 1180 */ 1181 public Builder setContentText(CharSequence text) { 1182 mContentText = safeCharSequence(text); 1183 return this; 1184 } 1185 1186 /** 1187 * Set the third line of text in the platform notification template. 1188 * Don't use if you're also using {@link #setProgress(int, int, boolean)}; they occupy the 1189 * same location in the standard template. 1190 */ 1191 public Builder setSubText(CharSequence text) { 1192 mSubText = safeCharSequence(text); 1193 return this; 1194 } 1195 1196 /** 1197 * Set the large number at the right-hand side of the notification. This is 1198 * equivalent to setContentInfo, although it might show the number in a different 1199 * font size for readability. 1200 */ 1201 public Builder setNumber(int number) { 1202 mNumber = number; 1203 return this; 1204 } 1205 1206 /** 1207 * A small piece of additional information pertaining to this notification. 1208 * 1209 * The platform template will draw this on the last line of the notification, at the far 1210 * right (to the right of a smallIcon if it has been placed there). 1211 */ 1212 public Builder setContentInfo(CharSequence info) { 1213 mContentInfo = safeCharSequence(info); 1214 return this; 1215 } 1216 1217 /** 1218 * Set the progress this notification represents. 1219 * 1220 * The platform template will represent this using a {@link ProgressBar}. 1221 */ 1222 public Builder setProgress(int max, int progress, boolean indeterminate) { 1223 mProgressMax = max; 1224 mProgress = progress; 1225 mProgressIndeterminate = indeterminate; 1226 return this; 1227 } 1228 1229 /** 1230 * Supply a custom RemoteViews to use instead of the platform template. 1231 * 1232 * @see Notification#contentView 1233 */ 1234 public Builder setContent(RemoteViews views) { 1235 mContentView = views; 1236 return this; 1237 } 1238 1239 /** 1240 * Supply a {@link PendingIntent} to be sent when the notification is clicked. 1241 * 1242 * As of {@link android.os.Build.VERSION_CODES#HONEYCOMB}, if this field is unset and you 1243 * have specified a custom RemoteViews with {@link #setContent(RemoteViews)}, you can use 1244 * {@link RemoteViews#setOnClickPendingIntent RemoteViews.setOnClickPendingIntent(int,PendingIntent)} 1245 * to assign PendingIntents to individual views in that custom layout (i.e., to create 1246 * clickable buttons inside the notification view). 1247 * 1248 * @see Notification#contentIntent Notification.contentIntent 1249 */ 1250 public Builder setContentIntent(PendingIntent intent) { 1251 mContentIntent = intent; 1252 return this; 1253 } 1254 1255 /** 1256 * Supply a {@link PendingIntent} to send when the notification is cleared explicitly by the user. 1257 * 1258 * @see Notification#deleteIntent 1259 */ 1260 public Builder setDeleteIntent(PendingIntent intent) { 1261 mDeleteIntent = intent; 1262 return this; 1263 } 1264 1265 /** 1266 * An intent to launch instead of posting the notification to the status bar. 1267 * Only for use with extremely high-priority notifications demanding the user's 1268 * <strong>immediate</strong> attention, such as an incoming phone call or 1269 * alarm clock that the user has explicitly set to a particular time. 1270 * If this facility is used for something else, please give the user an option 1271 * to turn it off and use a normal notification, as this can be extremely 1272 * disruptive. 1273 * 1274 * @param intent The pending intent to launch. 1275 * @param highPriority Passing true will cause this notification to be sent 1276 * even if other notifications are suppressed. 1277 * 1278 * @see Notification#fullScreenIntent 1279 */ 1280 public Builder setFullScreenIntent(PendingIntent intent, boolean highPriority) { 1281 mFullScreenIntent = intent; 1282 setFlag(FLAG_HIGH_PRIORITY, highPriority); 1283 return this; 1284 } 1285 1286 /** 1287 * Set the "ticker" text which is displayed in the status bar when the notification first 1288 * arrives. 1289 * 1290 * @see Notification#tickerText 1291 */ 1292 public Builder setTicker(CharSequence tickerText) { 1293 mTickerText = safeCharSequence(tickerText); 1294 return this; 1295 } 1296 1297 /** 1298 * Set the text that is displayed in the status bar when the notification first 1299 * arrives, and also a RemoteViews object that may be displayed instead on some 1300 * devices. 1301 * 1302 * @see Notification#tickerText 1303 * @see Notification#tickerView 1304 */ 1305 public Builder setTicker(CharSequence tickerText, RemoteViews views) { 1306 mTickerText = safeCharSequence(tickerText); 1307 mTickerView = views; 1308 return this; 1309 } 1310 1311 /** 1312 * Add a large icon to the notification (and the ticker on some devices). 1313 * 1314 * In the platform template, this image will be shown on the left of the notification view 1315 * in place of the {@link #setSmallIcon(int) small icon} (which will move to the right side). 1316 * 1317 * @see Notification#largeIcon 1318 */ 1319 public Builder setLargeIcon(Bitmap icon) { 1320 mLargeIcon = icon; 1321 return this; 1322 } 1323 1324 /** 1325 * Set the sound to play. 1326 * 1327 * It will be played on the {@link #STREAM_DEFAULT default stream} for notifications. 1328 * 1329 * @see Notification#sound 1330 */ 1331 public Builder setSound(Uri sound) { 1332 mSound = sound; 1333 mAudioStreamType = STREAM_DEFAULT; 1334 return this; 1335 } 1336 1337 /** 1338 * Set the sound to play, along with a specific stream on which to play it. 1339 * 1340 * See {@link android.media.AudioManager} for the <code>STREAM_</code> constants. 1341 * 1342 * @see Notification#sound 1343 */ 1344 public Builder setSound(Uri sound, int streamType) { 1345 mSound = sound; 1346 mAudioStreamType = streamType; 1347 return this; 1348 } 1349 1350 /** 1351 * Set the vibration pattern to use. 1352 * 1353 1354 * See {@link android.os.Vibrator#vibrate(long[], int)} for a discussion of the 1355 * <code>pattern</code> parameter. 1356 * 1357 1358 * @see Notification#vibrate 1359 */ 1360 public Builder setVibrate(long[] pattern) { 1361 mVibrate = pattern; 1362 return this; 1363 } 1364 1365 /** 1366 * Set the desired color for the indicator LED on the device, as well as the 1367 * blink duty cycle (specified in milliseconds). 1368 * 1369 1370 * Not all devices will honor all (or even any) of these values. 1371 * 1372 1373 * @see Notification#ledARGB 1374 * @see Notification#ledOnMS 1375 * @see Notification#ledOffMS 1376 */ 1377 public Builder setLights(int argb, int onMs, int offMs) { 1378 mLedArgb = argb; 1379 mLedOnMs = onMs; 1380 mLedOffMs = offMs; 1381 return this; 1382 } 1383 1384 /** 1385 * Set whether this is an "ongoing" notification. 1386 * 1387 1388 * Ongoing notifications cannot be dismissed by the user, so your application or service 1389 * must take care of canceling them. 1390 * 1391 1392 * They are typically used to indicate a background task that the user is actively engaged 1393 * with (e.g., playing music) or is pending in some way and therefore occupying the device 1394 * (e.g., a file download, sync operation, active network connection). 1395 * 1396 1397 * @see Notification#FLAG_ONGOING_EVENT 1398 * @see Service#setForeground(boolean) 1399 */ 1400 public Builder setOngoing(boolean ongoing) { 1401 setFlag(FLAG_ONGOING_EVENT, ongoing); 1402 return this; 1403 } 1404 1405 /** 1406 * Set this flag if you would only like the sound, vibrate 1407 * and ticker to be played if the notification is not already showing. 1408 * 1409 * @see Notification#FLAG_ONLY_ALERT_ONCE 1410 */ 1411 public Builder setOnlyAlertOnce(boolean onlyAlertOnce) { 1412 setFlag(FLAG_ONLY_ALERT_ONCE, onlyAlertOnce); 1413 return this; 1414 } 1415 1416 /** 1417 * Make this notification automatically dismissed when the user touches it. The 1418 * PendingIntent set with {@link #setDeleteIntent} will be sent when this happens. 1419 * 1420 * @see Notification#FLAG_AUTO_CANCEL 1421 */ 1422 public Builder setAutoCancel(boolean autoCancel) { 1423 setFlag(FLAG_AUTO_CANCEL, autoCancel); 1424 return this; 1425 } 1426 1427 /** 1428 * Set which notification properties will be inherited from system defaults. 1429 * <p> 1430 * The value should be one or more of the following fields combined with 1431 * bitwise-or: 1432 * {@link #DEFAULT_SOUND}, {@link #DEFAULT_VIBRATE}, {@link #DEFAULT_LIGHTS}. 1433 * <p> 1434 * For all default values, use {@link #DEFAULT_ALL}. 1435 */ 1436 public Builder setDefaults(int defaults) { 1437 mDefaults = defaults; 1438 return this; 1439 } 1440 1441 /** 1442 * Set the priority of this notification. 1443 * 1444 * @see Notification#priority 1445 */ 1446 public Builder setPriority(@Priority int pri) { 1447 mPriority = pri; 1448 return this; 1449 } 1450 1451 /** 1452 * @hide 1453 * 1454 * Add a kind (category) to this notification. Optional. 1455 * 1456 * @see Notification#kind 1457 */ 1458 public Builder addKind(String k) { 1459 mKindList.add(k); 1460 return this; 1461 } 1462 1463 /** 1464 * Add metadata to this notification. 1465 * 1466 * A reference to the Bundle is held for the lifetime of this Builder, and the Bundle's 1467 * current contents are copied into the Notification each time {@link #build()} is 1468 * called. 1469 * 1470 * @see Notification#extras 1471 */ 1472 public Builder setExtras(Bundle bag) { 1473 mExtras = bag; 1474 return this; 1475 } 1476 1477 /** 1478 * Add an action to this notification. Actions are typically displayed by 1479 * the system as a button adjacent to the notification content. 1480 * <br> 1481 * A notification displays up to 3 actions, from left to right in the order they were added. 1482 * 1483 * @param icon Resource ID of a drawable that represents the action. 1484 * @param title Text describing the action. 1485 * @param intent PendingIntent to be fired when the action is invoked. 1486 */ 1487 public Builder addAction(int icon, CharSequence title, PendingIntent intent) { 1488 mActions.add(new Action(icon, safeCharSequence(title), intent)); 1489 return this; 1490 } 1491 1492 /** 1493 * Add a rich notification style to be applied at build time. 1494 * 1495 * @param style Object responsible for modifying the notification style. 1496 */ 1497 public Builder setStyle(Style style) { 1498 if (mStyle != style) { 1499 mStyle = style; 1500 if (mStyle != null) { 1501 mStyle.setBuilder(this); 1502 } 1503 } 1504 return this; 1505 } 1506 1507 private void setFlag(int mask, boolean value) { 1508 if (value) { 1509 mFlags |= mask; 1510 } else { 1511 mFlags &= ~mask; 1512 } 1513 } 1514 1515 private RemoteViews applyStandardTemplate(int resId, boolean fitIn1U) { 1516 RemoteViews contentView = new RemoteViews(mContext.getPackageName(), resId); 1517 boolean showLine3 = false; 1518 boolean showLine2 = false; 1519 int smallIconImageViewId = R.id.icon; 1520 if (mLargeIcon != null) { 1521 contentView.setImageViewBitmap(R.id.icon, mLargeIcon); 1522 smallIconImageViewId = R.id.right_icon; 1523 } 1524 if (mPriority < PRIORITY_LOW) { 1525 contentView.setInt(R.id.icon, 1526 "setBackgroundResource", R.drawable.notification_template_icon_low_bg); 1527 contentView.setInt(R.id.status_bar_latest_event_content, 1528 "setBackgroundResource", R.drawable.notification_bg_low); 1529 } 1530 if (mSmallIcon != 0) { 1531 contentView.setImageViewResource(smallIconImageViewId, mSmallIcon); 1532 contentView.setViewVisibility(smallIconImageViewId, View.VISIBLE); 1533 } else { 1534 contentView.setViewVisibility(smallIconImageViewId, View.GONE); 1535 } 1536 if (mContentTitle != null) { 1537 contentView.setTextViewText(R.id.title, mContentTitle); 1538 } 1539 if (mContentText != null) { 1540 contentView.setTextViewText(R.id.text, mContentText); 1541 showLine3 = true; 1542 } 1543 if (mContentInfo != null) { 1544 contentView.setTextViewText(R.id.info, mContentInfo); 1545 contentView.setViewVisibility(R.id.info, View.VISIBLE); 1546 showLine3 = true; 1547 } else if (mNumber > 0) { 1548 final int tooBig = mContext.getResources().getInteger( 1549 R.integer.status_bar_notification_info_maxnum); 1550 if (mNumber > tooBig) { 1551 contentView.setTextViewText(R.id.info, mContext.getResources().getString( 1552 R.string.status_bar_notification_info_overflow)); 1553 } else { 1554 NumberFormat f = NumberFormat.getIntegerInstance(); 1555 contentView.setTextViewText(R.id.info, f.format(mNumber)); 1556 } 1557 contentView.setViewVisibility(R.id.info, View.VISIBLE); 1558 showLine3 = true; 1559 } else { 1560 contentView.setViewVisibility(R.id.info, View.GONE); 1561 } 1562 1563 // Need to show three lines? 1564 if (mSubText != null) { 1565 contentView.setTextViewText(R.id.text, mSubText); 1566 if (mContentText != null) { 1567 contentView.setTextViewText(R.id.text2, mContentText); 1568 contentView.setViewVisibility(R.id.text2, View.VISIBLE); 1569 showLine2 = true; 1570 } else { 1571 contentView.setViewVisibility(R.id.text2, View.GONE); 1572 } 1573 } else { 1574 contentView.setViewVisibility(R.id.text2, View.GONE); 1575 if (mProgressMax != 0 || mProgressIndeterminate) { 1576 contentView.setProgressBar( 1577 R.id.progress, mProgressMax, mProgress, mProgressIndeterminate); 1578 contentView.setViewVisibility(R.id.progress, View.VISIBLE); 1579 showLine2 = true; 1580 } else { 1581 contentView.setViewVisibility(R.id.progress, View.GONE); 1582 } 1583 } 1584 if (showLine2) { 1585 if (fitIn1U) { 1586 // need to shrink all the type to make sure everything fits 1587 final Resources res = mContext.getResources(); 1588 final float subTextSize = res.getDimensionPixelSize( 1589 R.dimen.notification_subtext_size); 1590 contentView.setTextViewTextSize(R.id.text, TypedValue.COMPLEX_UNIT_PX, subTextSize); 1591 } 1592 // vertical centering 1593 contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); 1594 } 1595 1596 if (mWhen != 0 && mShowWhen) { 1597 if (mUseChronometer) { 1598 contentView.setViewVisibility(R.id.chronometer, View.VISIBLE); 1599 contentView.setLong(R.id.chronometer, "setBase", 1600 mWhen + (SystemClock.elapsedRealtime() - System.currentTimeMillis())); 1601 contentView.setBoolean(R.id.chronometer, "setStarted", true); 1602 } else { 1603 contentView.setViewVisibility(R.id.time, View.VISIBLE); 1604 contentView.setLong(R.id.time, "setTime", mWhen); 1605 } 1606 } else { 1607 contentView.setViewVisibility(R.id.time, View.GONE); 1608 } 1609 1610 contentView.setViewVisibility(R.id.line3, showLine3 ? View.VISIBLE : View.GONE); 1611 contentView.setViewVisibility(R.id.overflow_divider, showLine3 ? View.VISIBLE : View.GONE); 1612 return contentView; 1613 } 1614 1615 private RemoteViews applyStandardTemplateWithActions(int layoutId) { 1616 RemoteViews big = applyStandardTemplate(layoutId, false); 1617 1618 int N = mActions.size(); 1619 if (N > 0) { 1620 // Log.d("Notification", "has actions: " + mContentText); 1621 big.setViewVisibility(R.id.actions, View.VISIBLE); 1622 big.setViewVisibility(R.id.action_divider, View.VISIBLE); 1623 if (N>MAX_ACTION_BUTTONS) N=MAX_ACTION_BUTTONS; 1624 big.removeAllViews(R.id.actions); 1625 for (int i=0; i<N; i++) { 1626 final RemoteViews button = generateActionButton(mActions.get(i)); 1627 //Log.d("Notification", "adding action " + i + ": " + mActions.get(i).title); 1628 big.addView(R.id.actions, button); 1629 } 1630 } 1631 return big; 1632 } 1633 1634 private RemoteViews makeContentView() { 1635 if (mContentView != null) { 1636 return mContentView; 1637 } else { 1638 return applyStandardTemplate(R.layout.notification_template_base, true); // no more special large_icon flavor 1639 } 1640 } 1641 1642 private RemoteViews makeTickerView() { 1643 if (mTickerView != null) { 1644 return mTickerView; 1645 } else { 1646 if (mContentView == null) { 1647 return applyStandardTemplate(mLargeIcon == null 1648 ? R.layout.status_bar_latest_event_ticker 1649 : R.layout.status_bar_latest_event_ticker_large_icon, true); 1650 } else { 1651 return null; 1652 } 1653 } 1654 } 1655 1656 private RemoteViews makeBigContentView() { 1657 if (mActions.size() == 0) return null; 1658 1659 return applyStandardTemplateWithActions(R.layout.notification_template_big_base); 1660 } 1661 1662 private RemoteViews generateActionButton(Action action) { 1663 final boolean tombstone = (action.actionIntent == null); 1664 RemoteViews button = new RemoteViews(mContext.getPackageName(), 1665 tombstone ? R.layout.notification_action_tombstone 1666 : R.layout.notification_action); 1667 button.setTextViewCompoundDrawables(R.id.action0, action.icon, 0, 0, 0); 1668 button.setTextViewText(R.id.action0, action.title); 1669 if (!tombstone) { 1670 button.setOnClickPendingIntent(R.id.action0, action.actionIntent); 1671 } 1672 button.setContentDescription(R.id.action0, action.title); 1673 return button; 1674 } 1675 1676 /** 1677 * Apply the unstyled operations and return a new {@link Notification} object. 1678 */ 1679 private Notification buildUnstyled() { 1680 Notification n = new Notification(); 1681 n.when = mWhen; 1682 n.icon = mSmallIcon; 1683 n.iconLevel = mSmallIconLevel; 1684 n.number = mNumber; 1685 n.contentView = makeContentView(); 1686 n.contentIntent = mContentIntent; 1687 n.deleteIntent = mDeleteIntent; 1688 n.fullScreenIntent = mFullScreenIntent; 1689 n.tickerText = mTickerText; 1690 n.tickerView = makeTickerView(); 1691 n.largeIcon = mLargeIcon; 1692 n.sound = mSound; 1693 n.audioStreamType = mAudioStreamType; 1694 n.vibrate = mVibrate; 1695 n.ledARGB = mLedArgb; 1696 n.ledOnMS = mLedOnMs; 1697 n.ledOffMS = mLedOffMs; 1698 n.defaults = mDefaults; 1699 n.flags = mFlags; 1700 n.bigContentView = makeBigContentView(); 1701 if (mLedOnMs != 0 || mLedOffMs != 0) { 1702 n.flags |= FLAG_SHOW_LIGHTS; 1703 } 1704 if ((mDefaults & DEFAULT_LIGHTS) != 0) { 1705 n.flags |= FLAG_SHOW_LIGHTS; 1706 } 1707 if (mKindList.size() > 0) { 1708 n.kind = new String[mKindList.size()]; 1709 mKindList.toArray(n.kind); 1710 } else { 1711 n.kind = null; 1712 } 1713 n.priority = mPriority; 1714 if (mActions.size() > 0) { 1715 n.actions = new Action[mActions.size()]; 1716 mActions.toArray(n.actions); 1717 } 1718 1719 return n; 1720 } 1721 1722 /** 1723 * Capture, in the provided bundle, semantic information used in the construction of 1724 * this Notification object. 1725 * @hide 1726 */ 1727 public void addExtras(Bundle extras) { 1728 // Store original information used in the construction of this object 1729 extras.putCharSequence(EXTRA_TITLE, mContentTitle); 1730 extras.putCharSequence(EXTRA_TEXT, mContentText); 1731 extras.putCharSequence(EXTRA_SUB_TEXT, mSubText); 1732 extras.putCharSequence(EXTRA_INFO_TEXT, mContentInfo); 1733 extras.putInt(EXTRA_SMALL_ICON, mSmallIcon); 1734 extras.putInt(EXTRA_PROGRESS, mProgress); 1735 extras.putInt(EXTRA_PROGRESS_MAX, mProgressMax); 1736 extras.putBoolean(EXTRA_PROGRESS_INDETERMINATE, mProgressIndeterminate); 1737 extras.putBoolean(EXTRA_SHOW_CHRONOMETER, mUseChronometer); 1738 extras.putBoolean(EXTRA_SHOW_WHEN, mShowWhen); 1739 if (mLargeIcon != null) { 1740 extras.putParcelable(EXTRA_LARGE_ICON, mLargeIcon); 1741 } 1742 } 1743 1744 /** 1745 * @deprecated Use {@link #build()} instead. 1746 */ 1747 @Deprecated 1748 public Notification getNotification() { 1749 return build(); 1750 } 1751 1752 /** 1753 * Combine all of the options that have been set and return a new {@link Notification} 1754 * object. 1755 */ 1756 public Notification build() { 1757 final Notification n; 1758 1759 if (mStyle != null) { 1760 n = mStyle.build(); 1761 } else { 1762 n = buildUnstyled(); 1763 } 1764 1765 n.extras = mExtras != null ? new Bundle(mExtras) : new Bundle(); 1766 1767 addExtras(n.extras); 1768 if (mStyle != null) { 1769 mStyle.addExtras(n.extras); 1770 } 1771 1772 return n; 1773 } 1774 1775 /** 1776 * Apply this Builder to an existing {@link Notification} object. 1777 * 1778 * @hide 1779 */ 1780 public Notification buildInto(Notification n) { 1781 build().cloneInto(n, true); 1782 return n; 1783 } 1784 } 1785 1786 /** 1787 * An object that can apply a rich notification style to a {@link Notification.Builder} 1788 * object. 1789 */ 1790 public static abstract class Style 1791 { 1792 private CharSequence mBigContentTitle; 1793 private CharSequence mSummaryText = null; 1794 private boolean mSummaryTextSet = false; 1795 1796 protected Builder mBuilder; 1797 1798 /** 1799 * Overrides ContentTitle in the big form of the template. 1800 * This defaults to the value passed to setContentTitle(). 1801 */ 1802 protected void internalSetBigContentTitle(CharSequence title) { 1803 mBigContentTitle = title; 1804 } 1805 1806 /** 1807 * Set the first line of text after the detail section in the big form of the template. 1808 */ 1809 protected void internalSetSummaryText(CharSequence cs) { 1810 mSummaryText = cs; 1811 mSummaryTextSet = true; 1812 } 1813 1814 public void setBuilder(Builder builder) { 1815 if (mBuilder != builder) { 1816 mBuilder = builder; 1817 if (mBuilder != null) { 1818 mBuilder.setStyle(this); 1819 } 1820 } 1821 } 1822 1823 protected void checkBuilder() { 1824 if (mBuilder == null) { 1825 throw new IllegalArgumentException("Style requires a valid Builder object"); 1826 } 1827 } 1828 1829 protected RemoteViews getStandardView(int layoutId) { 1830 checkBuilder(); 1831 1832 if (mBigContentTitle != null) { 1833 mBuilder.setContentTitle(mBigContentTitle); 1834 } 1835 1836 RemoteViews contentView = mBuilder.applyStandardTemplateWithActions(layoutId); 1837 1838 if (mBigContentTitle != null && mBigContentTitle.equals("")) { 1839 contentView.setViewVisibility(R.id.line1, View.GONE); 1840 } else { 1841 contentView.setViewVisibility(R.id.line1, View.VISIBLE); 1842 } 1843 1844 // The last line defaults to the subtext, but can be replaced by mSummaryText 1845 final CharSequence overflowText = 1846 mSummaryTextSet ? mSummaryText 1847 : mBuilder.mSubText; 1848 if (overflowText != null) { 1849 contentView.setTextViewText(R.id.text, overflowText); 1850 contentView.setViewVisibility(R.id.overflow_divider, View.VISIBLE); 1851 contentView.setViewVisibility(R.id.line3, View.VISIBLE); 1852 } else { 1853 contentView.setViewVisibility(R.id.overflow_divider, View.GONE); 1854 contentView.setViewVisibility(R.id.line3, View.GONE); 1855 } 1856 1857 return contentView; 1858 } 1859 1860 /** 1861 * @hide 1862 */ 1863 public void addExtras(Bundle extras) { 1864 if (mSummaryTextSet) { 1865 extras.putCharSequence(EXTRA_SUMMARY_TEXT, mSummaryText); 1866 } 1867 if (mBigContentTitle != null) { 1868 extras.putCharSequence(EXTRA_TITLE_BIG, mBigContentTitle); 1869 } 1870 } 1871 1872 public abstract Notification build(); 1873 } 1874 1875 /** 1876 * Helper class for generating large-format notifications that include a large image attachment. 1877 * 1878 * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: 1879 * <pre class="prettyprint"> 1880 * Notification noti = new Notification.BigPictureStyle( 1881 * new Notification.Builder() 1882 * .setContentTitle("New photo from " + sender.toString()) 1883 * .setContentText(subject) 1884 * .setSmallIcon(R.drawable.new_post) 1885 * .setLargeIcon(aBitmap)) 1886 * .bigPicture(aBigBitmap) 1887 * .build(); 1888 * </pre> 1889 * 1890 * @see Notification#bigContentView 1891 */ 1892 public static class BigPictureStyle extends Style { 1893 private Bitmap mPicture; 1894 private Bitmap mBigLargeIcon; 1895 private boolean mBigLargeIconSet = false; 1896 1897 public BigPictureStyle() { 1898 } 1899 1900 public BigPictureStyle(Builder builder) { 1901 setBuilder(builder); 1902 } 1903 1904 /** 1905 * Overrides ContentTitle in the big form of the template. 1906 * This defaults to the value passed to setContentTitle(). 1907 */ 1908 public BigPictureStyle setBigContentTitle(CharSequence title) { 1909 internalSetBigContentTitle(safeCharSequence(title)); 1910 return this; 1911 } 1912 1913 /** 1914 * Set the first line of text after the detail section in the big form of the template. 1915 */ 1916 public BigPictureStyle setSummaryText(CharSequence cs) { 1917 internalSetSummaryText(safeCharSequence(cs)); 1918 return this; 1919 } 1920 1921 /** 1922 * Provide the bitmap to be used as the payload for the BigPicture notification. 1923 */ 1924 public BigPictureStyle bigPicture(Bitmap b) { 1925 mPicture = b; 1926 return this; 1927 } 1928 1929 /** 1930 * Override the large icon when the big notification is shown. 1931 */ 1932 public BigPictureStyle bigLargeIcon(Bitmap b) { 1933 mBigLargeIconSet = true; 1934 mBigLargeIcon = b; 1935 return this; 1936 } 1937 1938 private RemoteViews makeBigContentView() { 1939 RemoteViews contentView = getStandardView(R.layout.notification_template_big_picture); 1940 1941 contentView.setImageViewBitmap(R.id.big_picture, mPicture); 1942 1943 return contentView; 1944 } 1945 1946 /** 1947 * @hide 1948 */ 1949 public void addExtras(Bundle extras) { 1950 super.addExtras(extras); 1951 1952 if (mBigLargeIconSet) { 1953 extras.putParcelable(EXTRA_LARGE_ICON_BIG, mBigLargeIcon); 1954 } 1955 extras.putParcelable(EXTRA_PICTURE, mPicture); 1956 } 1957 1958 @Override 1959 public Notification build() { 1960 checkBuilder(); 1961 Notification wip = mBuilder.buildUnstyled(); 1962 if (mBigLargeIconSet ) { 1963 mBuilder.mLargeIcon = mBigLargeIcon; 1964 } 1965 wip.bigContentView = makeBigContentView(); 1966 return wip; 1967 } 1968 } 1969 1970 /** 1971 * Helper class for generating large-format notifications that include a lot of text. 1972 * 1973 * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: 1974 * <pre class="prettyprint"> 1975 * Notification noti = new Notification.BigTextStyle( 1976 * new Notification.Builder() 1977 * .setContentTitle("New mail from " + sender.toString()) 1978 * .setContentText(subject) 1979 * .setSmallIcon(R.drawable.new_mail) 1980 * .setLargeIcon(aBitmap)) 1981 * .bigText(aVeryLongString) 1982 * .build(); 1983 * </pre> 1984 * 1985 * @see Notification#bigContentView 1986 */ 1987 public static class BigTextStyle extends Style { 1988 private CharSequence mBigText; 1989 1990 public BigTextStyle() { 1991 } 1992 1993 public BigTextStyle(Builder builder) { 1994 setBuilder(builder); 1995 } 1996 1997 /** 1998 * Overrides ContentTitle in the big form of the template. 1999 * This defaults to the value passed to setContentTitle(). 2000 */ 2001 public BigTextStyle setBigContentTitle(CharSequence title) { 2002 internalSetBigContentTitle(safeCharSequence(title)); 2003 return this; 2004 } 2005 2006 /** 2007 * Set the first line of text after the detail section in the big form of the template. 2008 */ 2009 public BigTextStyle setSummaryText(CharSequence cs) { 2010 internalSetSummaryText(safeCharSequence(cs)); 2011 return this; 2012 } 2013 2014 /** 2015 * Provide the longer text to be displayed in the big form of the 2016 * template in place of the content text. 2017 */ 2018 public BigTextStyle bigText(CharSequence cs) { 2019 mBigText = safeCharSequence(cs); 2020 return this; 2021 } 2022 2023 /** 2024 * @hide 2025 */ 2026 public void addExtras(Bundle extras) { 2027 super.addExtras(extras); 2028 2029 extras.putCharSequence(EXTRA_TEXT, mBigText); 2030 } 2031 2032 private RemoteViews makeBigContentView() { 2033 // Remove the content text so line3 only shows if you have a summary 2034 final boolean hadThreeLines = (mBuilder.mContentText != null && mBuilder.mSubText != null); 2035 mBuilder.mContentText = null; 2036 2037 RemoteViews contentView = getStandardView(R.layout.notification_template_big_text); 2038 2039 if (hadThreeLines) { 2040 // vertical centering 2041 contentView.setViewPadding(R.id.line1, 0, 0, 0, 0); 2042 } 2043 2044 contentView.setTextViewText(R.id.big_text, mBigText); 2045 contentView.setViewVisibility(R.id.big_text, View.VISIBLE); 2046 contentView.setViewVisibility(R.id.text2, View.GONE); 2047 2048 return contentView; 2049 } 2050 2051 @Override 2052 public Notification build() { 2053 checkBuilder(); 2054 Notification wip = mBuilder.buildUnstyled(); 2055 wip.bigContentView = makeBigContentView(); 2056 2057 wip.extras.putCharSequence(EXTRA_TEXT, mBigText); 2058 2059 return wip; 2060 } 2061 } 2062 2063 /** 2064 * Helper class for generating large-format notifications that include a list of (up to 5) strings. 2065 * 2066 * This class is a "rebuilder": It consumes a Builder object and modifies its behavior, like so: 2067 * <pre class="prettyprint"> 2068 * Notification noti = new Notification.InboxStyle( 2069 * new Notification.Builder() 2070 * .setContentTitle("5 New mails from " + sender.toString()) 2071 * .setContentText(subject) 2072 * .setSmallIcon(R.drawable.new_mail) 2073 * .setLargeIcon(aBitmap)) 2074 * .addLine(str1) 2075 * .addLine(str2) 2076 * .setContentTitle("") 2077 * .setSummaryText("+3 more") 2078 * .build(); 2079 * </pre> 2080 * 2081 * @see Notification#bigContentView 2082 */ 2083 public static class InboxStyle extends Style { 2084 private ArrayList<CharSequence> mTexts = new ArrayList<CharSequence>(5); 2085 2086 public InboxStyle() { 2087 } 2088 2089 public InboxStyle(Builder builder) { 2090 setBuilder(builder); 2091 } 2092 2093 /** 2094 * Overrides ContentTitle in the big form of the template. 2095 * This defaults to the value passed to setContentTitle(). 2096 */ 2097 public InboxStyle setBigContentTitle(CharSequence title) { 2098 internalSetBigContentTitle(safeCharSequence(title)); 2099 return this; 2100 } 2101 2102 /** 2103 * Set the first line of text after the detail section in the big form of the template. 2104 */ 2105 public InboxStyle setSummaryText(CharSequence cs) { 2106 internalSetSummaryText(safeCharSequence(cs)); 2107 return this; 2108 } 2109 2110 /** 2111 * Append a line to the digest section of the Inbox notification. 2112 */ 2113 public InboxStyle addLine(CharSequence cs) { 2114 mTexts.add(safeCharSequence(cs)); 2115 return this; 2116 } 2117 2118 /** 2119 * @hide 2120 */ 2121 public void addExtras(Bundle extras) { 2122 super.addExtras(extras); 2123 CharSequence[] a = new CharSequence[mTexts.size()]; 2124 extras.putCharSequenceArray(EXTRA_TEXT_LINES, mTexts.toArray(a)); 2125 } 2126 2127 private RemoteViews makeBigContentView() { 2128 // Remove the content text so line3 disappears unless you have a summary 2129 mBuilder.mContentText = null; 2130 RemoteViews contentView = getStandardView(R.layout.notification_template_inbox); 2131 2132 contentView.setViewVisibility(R.id.text2, View.GONE); 2133 2134 int[] rowIds = {R.id.inbox_text0, R.id.inbox_text1, R.id.inbox_text2, R.id.inbox_text3, 2135 R.id.inbox_text4, R.id.inbox_text5, R.id.inbox_text6}; 2136 2137 // Make sure all rows are gone in case we reuse a view. 2138 for (int rowId : rowIds) { 2139 contentView.setViewVisibility(rowId, View.GONE); 2140 } 2141 2142 2143 int i=0; 2144 while (i < mTexts.size() && i < rowIds.length) { 2145 CharSequence str = mTexts.get(i); 2146 if (str != null && !str.equals("")) { 2147 contentView.setViewVisibility(rowIds[i], View.VISIBLE); 2148 contentView.setTextViewText(rowIds[i], str); 2149 } 2150 i++; 2151 } 2152 2153 contentView.setViewVisibility(R.id.inbox_end_pad, 2154 mTexts.size() > 0 ? View.VISIBLE : View.GONE); 2155 2156 contentView.setViewVisibility(R.id.inbox_more, 2157 mTexts.size() > rowIds.length ? View.VISIBLE : View.GONE); 2158 2159 return contentView; 2160 } 2161 2162 @Override 2163 public Notification build() { 2164 checkBuilder(); 2165 Notification wip = mBuilder.buildUnstyled(); 2166 wip.bigContentView = makeBigContentView(); 2167 2168 return wip; 2169 } 2170 } 2171} 2172