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