• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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