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