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