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