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 static com.android.internal.util.Preconditions.checkNotNull; 20 import static com.android.internal.util.Preconditions.checkState; 21 22 import android.annotation.IntDef; 23 import android.annotation.NonNull; 24 import android.annotation.Nullable; 25 import android.annotation.StringRes; 26 import android.app.INotificationManager; 27 import android.app.ITransientNotification; 28 import android.app.ITransientNotificationCallback; 29 import android.compat.Compatibility; 30 import android.compat.annotation.ChangeId; 31 import android.compat.annotation.EnabledAfter; 32 import android.compat.annotation.UnsupportedAppUsage; 33 import android.content.Context; 34 import android.content.res.Resources; 35 import android.os.Binder; 36 import android.os.Build; 37 import android.os.Handler; 38 import android.os.IBinder; 39 import android.os.Looper; 40 import android.os.Message; 41 import android.os.RemoteException; 42 import android.os.ServiceManager; 43 import android.util.Log; 44 import android.view.View; 45 import android.view.WindowManager; 46 import android.view.accessibility.IAccessibilityManager; 47 48 import com.android.internal.annotations.GuardedBy; 49 50 import java.lang.annotation.Retention; 51 import java.lang.annotation.RetentionPolicy; 52 import java.util.ArrayList; 53 import java.util.List; 54 55 /** 56 * A toast is a view containing a quick little message for the user. The toast class 57 * helps you create and show those. 58 * {@more} 59 * 60 * <p> 61 * When the view is shown to the user, appears as a floating view over the 62 * application. It will never receive focus. The user will probably be in the 63 * middle of typing something else. The idea is to be as unobtrusive as 64 * possible, while still showing the user the information you want them to see. 65 * Two examples are the volume control, and the brief message saying that your 66 * settings have been saved. 67 * <p> 68 * The easiest way to use this class is to call one of the static methods that constructs 69 * everything you need and returns a new Toast object. 70 * <p> 71 * Note that 72 * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbars</a> are 73 * preferred for brief messages while the app is in the foreground. 74 * <p> 75 * Note that toasts being sent from the background are rate limited, so avoid sending such toasts 76 * in quick succession. 77 * 78 * <div class="special reference"> 79 * <h3>Developer Guides</h3> 80 * <p>For information about creating Toast notifications, read the 81 * <a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Toast Notifications</a> developer 82 * guide.</p> 83 * </div> 84 */ 85 public class Toast { 86 static final String TAG = "Toast"; 87 static final boolean localLOGV = false; 88 89 /** @hide */ 90 @IntDef(prefix = { "LENGTH_" }, value = { 91 LENGTH_SHORT, 92 LENGTH_LONG 93 }) 94 @Retention(RetentionPolicy.SOURCE) 95 public @interface Duration {} 96 97 /** 98 * Show the view or text notification for a short period of time. This time 99 * could be user-definable. This is the default. 100 * @see #setDuration 101 */ 102 public static final int LENGTH_SHORT = 0; 103 104 /** 105 * Show the view or text notification for a long period of time. This time 106 * could be user-definable. 107 * @see #setDuration 108 */ 109 public static final int LENGTH_LONG = 1; 110 111 /** 112 * Text toasts will be rendered by SystemUI instead of in-app, so apps can't circumvent 113 * background custom toast restrictions. 114 */ 115 @ChangeId 116 @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.Q) 117 private static final long CHANGE_TEXT_TOASTS_IN_THE_SYSTEM = 147798919L; 118 119 120 private final Binder mToken; 121 private final Context mContext; 122 private final Handler mHandler; 123 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 124 final TN mTN; 125 @UnsupportedAppUsage 126 int mDuration; 127 128 /** 129 * This is also passed to {@link TN} object, where it's also accessed with itself as its own 130 * lock. 131 */ 132 @GuardedBy("mCallbacks") 133 private final List<Callback> mCallbacks; 134 135 /** 136 * View to be displayed, in case this is a custom toast (e.g. not created with {@link 137 * #makeText(Context, int, int)} or its variants). 138 */ 139 @Nullable 140 private View mNextView; 141 142 /** 143 * Text to be shown, in case this is NOT a custom toast (e.g. created with {@link 144 * #makeText(Context, int, int)} or its variants). 145 */ 146 @Nullable 147 private CharSequence mText; 148 149 /** 150 * Construct an empty Toast object. You must call {@link #setView} before you 151 * can call {@link #show}. 152 * 153 * @param context The context to use. Usually your {@link android.app.Application} 154 * or {@link android.app.Activity} object. 155 */ Toast(Context context)156 public Toast(Context context) { 157 this(context, null); 158 } 159 160 /** 161 * Constructs an empty Toast object. If looper is null, Looper.myLooper() is used. 162 * @hide 163 */ Toast(@onNull Context context, @Nullable Looper looper)164 public Toast(@NonNull Context context, @Nullable Looper looper) { 165 mContext = context; 166 mToken = new Binder(); 167 looper = getLooper(looper); 168 mHandler = new Handler(looper); 169 mCallbacks = new ArrayList<>(); 170 mTN = new TN(context, context.getPackageName(), mToken, 171 mCallbacks, looper); 172 mTN.mY = context.getResources().getDimensionPixelSize( 173 com.android.internal.R.dimen.toast_y_offset); 174 mTN.mGravity = context.getResources().getInteger( 175 com.android.internal.R.integer.config_toastDefaultGravity); 176 } 177 getLooper(@ullable Looper looper)178 private Looper getLooper(@Nullable Looper looper) { 179 if (looper != null) { 180 return looper; 181 } 182 return checkNotNull(Looper.myLooper(), 183 "Can't toast on a thread that has not called Looper.prepare()"); 184 } 185 186 /** 187 * Show the view for the specified duration. 188 */ show()189 public void show() { 190 if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { 191 checkState(mNextView != null || mText != null, "You must either set a text or a view"); 192 } else { 193 if (mNextView == null) { 194 throw new RuntimeException("setView must have been called"); 195 } 196 } 197 198 INotificationManager service = getService(); 199 String pkg = mContext.getOpPackageName(); 200 TN tn = mTN; 201 tn.mNextView = mNextView; 202 final int displayId = mContext.getDisplayId(); 203 204 try { 205 if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { 206 if (mNextView != null) { 207 // It's a custom toast 208 service.enqueueToast(pkg, mToken, tn, mDuration, displayId); 209 } else { 210 // It's a text toast 211 ITransientNotificationCallback callback = 212 new CallbackBinder(mCallbacks, mHandler); 213 service.enqueueTextToast(pkg, mToken, mText, mDuration, displayId, callback); 214 } 215 } else { 216 service.enqueueToast(pkg, mToken, tn, mDuration, displayId); 217 } 218 } catch (RemoteException e) { 219 // Empty 220 } 221 } 222 223 /** 224 * Close the view if it's showing, or don't show it if it isn't showing yet. 225 * You do not normally have to call this. Normally view will disappear on its own 226 * after the appropriate duration. 227 */ cancel()228 public void cancel() { 229 if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) 230 && mNextView == null) { 231 try { 232 getService().cancelToast(mContext.getOpPackageName(), mToken); 233 } catch (RemoteException e) { 234 // Empty 235 } 236 } else { 237 mTN.cancel(); 238 } 239 } 240 241 /** 242 * Set the view to show. 243 * 244 * @see #getView 245 * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the 246 * {@link #makeText(Context, CharSequence, int)} method, or use a 247 * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a> 248 * when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps 249 * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background 250 * will not have custom toast views displayed. 251 */ 252 @Deprecated setView(View view)253 public void setView(View view) { 254 mNextView = view; 255 } 256 257 /** 258 * Return the view. 259 * 260 * <p>Toasts constructed with {@link #Toast(Context)} that haven't called {@link #setView(View)} 261 * with a non-{@code null} view will return {@code null} here. 262 * 263 * <p>Starting from Android {@link Build.VERSION_CODES#R}, in apps targeting API level {@link 264 * Build.VERSION_CODES#R} or higher, toasts constructed with {@link #makeText(Context, 265 * CharSequence, int)} or its variants will also return {@code null} here unless they had called 266 * {@link #setView(View)} with a non-{@code null} view. If you want to be notified when the 267 * toast is shown or hidden, use {@link #addCallback(Callback)}. 268 * 269 * @see #setView 270 * @deprecated Custom toast views are deprecated. Apps can create a standard text toast with the 271 * {@link #makeText(Context, CharSequence, int)} method, or use a 272 * <a href="{@docRoot}reference/com/google/android/material/snackbar/Snackbar">Snackbar</a> 273 * when in the foreground. Starting from Android {@link Build.VERSION_CODES#R}, apps 274 * targeting API level {@link Build.VERSION_CODES#R} or higher that are in the background 275 * will not have custom toast views displayed. 276 */ 277 @Deprecated getView()278 @Nullable public View getView() { 279 return mNextView; 280 } 281 282 /** 283 * Set how long to show the view for. 284 * @see #LENGTH_SHORT 285 * @see #LENGTH_LONG 286 */ setDuration(@uration int duration)287 public void setDuration(@Duration int duration) { 288 mDuration = duration; 289 mTN.mDuration = duration; 290 } 291 292 /** 293 * Return the duration. 294 * @see #setDuration 295 */ 296 @Duration getDuration()297 public int getDuration() { 298 return mDuration; 299 } 300 301 /** 302 * Set the margins of the view. 303 * 304 * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps 305 * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when 306 * called on text toasts. 307 * 308 * @param horizontalMargin The horizontal margin, in percentage of the 309 * container width, between the container's edges and the 310 * notification 311 * @param verticalMargin The vertical margin, in percentage of the 312 * container height, between the container's edges and the 313 * notification 314 */ setMargin(float horizontalMargin, float verticalMargin)315 public void setMargin(float horizontalMargin, float verticalMargin) { 316 if (isSystemRenderedTextToast()) { 317 Log.e(TAG, "setMargin() shouldn't be called on text toasts, the values won't be used"); 318 } 319 mTN.mHorizontalMargin = horizontalMargin; 320 mTN.mVerticalMargin = verticalMargin; 321 } 322 323 /** 324 * Return the horizontal margin. 325 * 326 * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps 327 * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called 328 * on text toasts as its return value may not reflect actual value since text toasts are not 329 * rendered by the app anymore. 330 */ getHorizontalMargin()331 public float getHorizontalMargin() { 332 if (isSystemRenderedTextToast()) { 333 Log.e(TAG, "getHorizontalMargin() shouldn't be called on text toasts, the result may " 334 + "not reflect actual values."); 335 } 336 return mTN.mHorizontalMargin; 337 } 338 339 /** 340 * Return the vertical margin. 341 * 342 * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps 343 * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called 344 * on text toasts as its return value may not reflect actual value since text toasts are not 345 * rendered by the app anymore. 346 */ getVerticalMargin()347 public float getVerticalMargin() { 348 if (isSystemRenderedTextToast()) { 349 Log.e(TAG, "getVerticalMargin() shouldn't be called on text toasts, the result may not" 350 + " reflect actual values."); 351 } 352 return mTN.mVerticalMargin; 353 } 354 355 /** 356 * Set the location at which the notification should appear on the screen. 357 * 358 * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps 359 * targeting API level {@link Build.VERSION_CODES#R} or higher, this method is a no-op when 360 * called on text toasts. 361 * 362 * @see android.view.Gravity 363 * @see #getGravity 364 */ setGravity(int gravity, int xOffset, int yOffset)365 public void setGravity(int gravity, int xOffset, int yOffset) { 366 if (isSystemRenderedTextToast()) { 367 Log.e(TAG, "setGravity() shouldn't be called on text toasts, the values won't be used"); 368 } 369 mTN.mGravity = gravity; 370 mTN.mX = xOffset; 371 mTN.mY = yOffset; 372 } 373 374 /** 375 * Get the location at which the notification should appear on the screen. 376 * 377 * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps 378 * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called 379 * on text toasts as its return value may not reflect actual value since text toasts are not 380 * rendered by the app anymore. 381 * 382 * @see android.view.Gravity 383 * @see #getGravity 384 */ getGravity()385 public int getGravity() { 386 if (isSystemRenderedTextToast()) { 387 Log.e(TAG, "getGravity() shouldn't be called on text toasts, the result may not reflect" 388 + " actual values."); 389 } 390 return mTN.mGravity; 391 } 392 393 /** 394 * Return the X offset in pixels to apply to the gravity's location. 395 * 396 * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps 397 * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called 398 * on text toasts as its return value may not reflect actual value since text toasts are not 399 * rendered by the app anymore. 400 */ getXOffset()401 public int getXOffset() { 402 if (isSystemRenderedTextToast()) { 403 Log.e(TAG, "getXOffset() shouldn't be called on text toasts, the result may not reflect" 404 + " actual values."); 405 } 406 return mTN.mX; 407 } 408 409 /** 410 * Return the Y offset in pixels to apply to the gravity's location. 411 * 412 * <p><strong>Warning:</strong> Starting from Android {@link Build.VERSION_CODES#R}, for apps 413 * targeting API level {@link Build.VERSION_CODES#R} or higher, this method shouldn't be called 414 * on text toasts as its return value may not reflect actual value since text toasts are not 415 * rendered by the app anymore. 416 */ getYOffset()417 public int getYOffset() { 418 if (isSystemRenderedTextToast()) { 419 Log.e(TAG, "getYOffset() shouldn't be called on text toasts, the result may not reflect" 420 + " actual values."); 421 } 422 return mTN.mY; 423 } 424 isSystemRenderedTextToast()425 private boolean isSystemRenderedTextToast() { 426 return Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM) && mNextView == null; 427 } 428 429 /** 430 * Adds a callback to be notified when the toast is shown or hidden. 431 * 432 * Note that if the toast is blocked for some reason you won't get a call back. 433 * 434 * @see #removeCallback(Callback) 435 */ addCallback(@onNull Callback callback)436 public void addCallback(@NonNull Callback callback) { 437 checkNotNull(callback); 438 synchronized (mCallbacks) { 439 mCallbacks.add(callback); 440 } 441 } 442 443 /** 444 * Removes a callback previously added with {@link #addCallback(Callback)}. 445 */ removeCallback(@onNull Callback callback)446 public void removeCallback(@NonNull Callback callback) { 447 synchronized (mCallbacks) { 448 mCallbacks.remove(callback); 449 } 450 } 451 452 /** 453 * Gets the LayoutParams for the Toast window. 454 * @hide 455 */ 456 @UnsupportedAppUsage getWindowParams()457 @Nullable public WindowManager.LayoutParams getWindowParams() { 458 if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { 459 if (mNextView != null) { 460 // Custom toasts 461 return mTN.mParams; 462 } else { 463 // Text toasts 464 return null; 465 } 466 } else { 467 // Text and custom toasts are app-rendered 468 return mTN.mParams; 469 } 470 } 471 472 /** 473 * Make a standard toast that just contains text. 474 * 475 * @param context The context to use. Usually your {@link android.app.Application} 476 * or {@link android.app.Activity} object. 477 * @param text The text to show. Can be formatted text. 478 * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or 479 * {@link #LENGTH_LONG} 480 * 481 */ makeText(Context context, CharSequence text, @Duration int duration)482 public static Toast makeText(Context context, CharSequence text, @Duration int duration) { 483 return makeText(context, null, text, duration); 484 } 485 486 /** 487 * Make a standard toast to display using the specified looper. 488 * If looper is null, Looper.myLooper() is used. 489 * 490 * @hide 491 */ makeText(@onNull Context context, @Nullable Looper looper, @NonNull CharSequence text, @Duration int duration)492 public static Toast makeText(@NonNull Context context, @Nullable Looper looper, 493 @NonNull CharSequence text, @Duration int duration) { 494 if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { 495 Toast result = new Toast(context, looper); 496 result.mText = text; 497 result.mDuration = duration; 498 return result; 499 } else { 500 Toast result = new Toast(context, looper); 501 View v = ToastPresenter.getTextToastView(context, text); 502 result.mNextView = v; 503 result.mDuration = duration; 504 505 return result; 506 } 507 } 508 509 /** 510 * Make a standard toast that just contains text from a resource. 511 * 512 * @param context The context to use. Usually your {@link android.app.Application} 513 * or {@link android.app.Activity} object. 514 * @param resId The resource id of the string resource to use. Can be formatted text. 515 * @param duration How long to display the message. Either {@link #LENGTH_SHORT} or 516 * {@link #LENGTH_LONG} 517 * 518 * @throws Resources.NotFoundException if the resource can't be found. 519 */ makeText(Context context, @StringRes int resId, @Duration int duration)520 public static Toast makeText(Context context, @StringRes int resId, @Duration int duration) 521 throws Resources.NotFoundException { 522 return makeText(context, context.getResources().getText(resId), duration); 523 } 524 525 /** 526 * Update the text in a Toast that was previously created using one of the makeText() methods. 527 * @param resId The new text for the Toast. 528 */ setText(@tringRes int resId)529 public void setText(@StringRes int resId) { 530 setText(mContext.getText(resId)); 531 } 532 533 /** 534 * Update the text in a Toast that was previously created using one of the makeText() methods. 535 * @param s The new text for the Toast. 536 */ setText(CharSequence s)537 public void setText(CharSequence s) { 538 if (Compatibility.isChangeEnabled(CHANGE_TEXT_TOASTS_IN_THE_SYSTEM)) { 539 if (mNextView != null) { 540 throw new IllegalStateException( 541 "Text provided for custom toast, remove previous setView() calls if you " 542 + "want a text toast instead."); 543 } 544 mText = s; 545 } else { 546 if (mNextView == null) { 547 throw new RuntimeException("This Toast was not created with Toast.makeText()"); 548 } 549 TextView tv = mNextView.findViewById(com.android.internal.R.id.message); 550 if (tv == null) { 551 throw new RuntimeException("This Toast was not created with Toast.makeText()"); 552 } 553 tv.setText(s); 554 } 555 } 556 557 // ======================================================================================= 558 // All the gunk below is the interaction with the Notification Service, which handles 559 // the proper ordering of these system-wide. 560 // ======================================================================================= 561 562 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 563 private static INotificationManager sService; 564 565 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) getService()566 static private INotificationManager getService() { 567 if (sService != null) { 568 return sService; 569 } 570 sService = INotificationManager.Stub.asInterface( 571 ServiceManager.getService(Context.NOTIFICATION_SERVICE)); 572 return sService; 573 } 574 575 private static class TN extends ITransientNotification.Stub { 576 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 577 private final WindowManager.LayoutParams mParams; 578 579 private static final int SHOW = 0; 580 private static final int HIDE = 1; 581 private static final int CANCEL = 2; 582 final Handler mHandler; 583 584 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 585 int mGravity; 586 int mX; 587 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 588 int mY; 589 float mHorizontalMargin; 590 float mVerticalMargin; 591 592 593 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 594 View mView; 595 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P) 596 View mNextView; 597 int mDuration; 598 599 WindowManager mWM; 600 601 final String mPackageName; 602 final Binder mToken; 603 private final ToastPresenter mPresenter; 604 605 @GuardedBy("mCallbacks") 606 private final List<Callback> mCallbacks; 607 608 /** 609 * Creates a {@link ITransientNotification} object. 610 * 611 * The parameter {@code callbacks} is not copied and is accessed with itself as its own 612 * lock. 613 */ TN(Context context, String packageName, Binder token, List<Callback> callbacks, @Nullable Looper looper)614 TN(Context context, String packageName, Binder token, List<Callback> callbacks, 615 @Nullable Looper looper) { 616 IAccessibilityManager accessibilityManager = IAccessibilityManager.Stub.asInterface( 617 ServiceManager.getService(Context.ACCESSIBILITY_SERVICE)); 618 mPresenter = new ToastPresenter(context, accessibilityManager, getService(), 619 packageName); 620 mParams = mPresenter.getLayoutParams(); 621 mPackageName = packageName; 622 mToken = token; 623 mCallbacks = callbacks; 624 625 mHandler = new Handler(looper, null) { 626 @Override 627 public void handleMessage(Message msg) { 628 switch (msg.what) { 629 case SHOW: { 630 IBinder token = (IBinder) msg.obj; 631 handleShow(token); 632 break; 633 } 634 case HIDE: { 635 handleHide(); 636 // Don't do this in handleHide() because it is also invoked by 637 // handleShow() 638 mNextView = null; 639 break; 640 } 641 case CANCEL: { 642 handleHide(); 643 // Don't do this in handleHide() because it is also invoked by 644 // handleShow() 645 mNextView = null; 646 try { 647 getService().cancelToast(mPackageName, mToken); 648 } catch (RemoteException e) { 649 } 650 break; 651 } 652 } 653 } 654 }; 655 } 656 getCallbacks()657 private List<Callback> getCallbacks() { 658 synchronized (mCallbacks) { 659 return new ArrayList<>(mCallbacks); 660 } 661 } 662 663 /** 664 * schedule handleShow into the right thread 665 */ 666 @Override 667 @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023) show(IBinder windowToken)668 public void show(IBinder windowToken) { 669 if (localLOGV) Log.v(TAG, "SHOW: " + this); 670 mHandler.obtainMessage(SHOW, windowToken).sendToTarget(); 671 } 672 673 /** 674 * schedule handleHide into the right thread 675 */ 676 @Override hide()677 public void hide() { 678 if (localLOGV) Log.v(TAG, "HIDE: " + this); 679 mHandler.obtainMessage(HIDE).sendToTarget(); 680 } 681 cancel()682 public void cancel() { 683 if (localLOGV) Log.v(TAG, "CANCEL: " + this); 684 mHandler.obtainMessage(CANCEL).sendToTarget(); 685 } 686 handleShow(IBinder windowToken)687 public void handleShow(IBinder windowToken) { 688 if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView 689 + " mNextView=" + mNextView); 690 // If a cancel/hide is pending - no need to show - at this point 691 // the window token is already invalid and no need to do any work. 692 if (mHandler.hasMessages(CANCEL) || mHandler.hasMessages(HIDE)) { 693 return; 694 } 695 if (mView != mNextView) { 696 // remove the old view if necessary 697 handleHide(); 698 mView = mNextView; 699 mPresenter.show(mView, mToken, windowToken, mDuration, mGravity, mX, mY, 700 mHorizontalMargin, mVerticalMargin, 701 new CallbackBinder(getCallbacks(), mHandler)); 702 } 703 } 704 705 @UnsupportedAppUsage handleHide()706 public void handleHide() { 707 if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); 708 if (mView != null) { 709 checkState(mView == mPresenter.getView(), 710 "Trying to hide toast view different than the last one displayed"); 711 mPresenter.hide(new CallbackBinder(getCallbacks(), mHandler)); 712 mView = null; 713 } 714 } 715 } 716 717 /** 718 * Callback object to be called when the toast is shown or hidden. 719 * 720 * @see #makeText(Context, CharSequence, int) 721 * @see #addCallback(Callback) 722 */ 723 public abstract static class Callback { 724 /** 725 * Called when the toast is displayed on the screen. 726 */ onToastShown()727 public void onToastShown() {} 728 729 /** 730 * Called when the toast is hidden. 731 */ onToastHidden()732 public void onToastHidden() {} 733 } 734 735 private static class CallbackBinder extends ITransientNotificationCallback.Stub { 736 private final Handler mHandler; 737 738 @GuardedBy("mCallbacks") 739 private final List<Callback> mCallbacks; 740 741 /** 742 * Creates a {@link ITransientNotificationCallback} object. 743 * 744 * The parameter {@code callbacks} is not copied and is accessed with itself as its own 745 * lock. 746 */ CallbackBinder(List<Callback> callbacks, Handler handler)747 private CallbackBinder(List<Callback> callbacks, Handler handler) { 748 mCallbacks = callbacks; 749 mHandler = handler; 750 } 751 752 @Override onToastShown()753 public void onToastShown() { 754 mHandler.post(() -> { 755 for (Callback callback : getCallbacks()) { 756 callback.onToastShown(); 757 } 758 }); 759 } 760 761 @Override onToastHidden()762 public void onToastHidden() { 763 mHandler.post(() -> { 764 for (Callback callback : getCallbacks()) { 765 callback.onToastHidden(); 766 } 767 }); 768 } 769 getCallbacks()770 private List<Callback> getCallbacks() { 771 synchronized (mCallbacks) { 772 return new ArrayList<>(mCallbacks); 773 } 774 } 775 } 776 } 777