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