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