Analysis and solution of two problems about Toast

Posted by fqservers on Mon, 03 Jan 2022 08:09:32 +0100

Tip: after the article is written, the directory can be generated automatically. Please refer to the help document on the right for how to generate it

preface

I really can't write a preface, so I go straight to the point

Tip: This article is based on Android API 25

1, WindowManager BadTokenException

Toast has a chance to throw this exception on Android API 25

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_toast_window_manager_bad_token_exception)

        Toast.makeText(this, "test Toast Exception", Toast.LENGTH_SHORT).apply {
            fixWindowManagerBadTokenExceptionIn(this)
        }.show()
		
		// Here sleep 2 seconds
        TimeUnit.SECONDS.sleep(2)

        LogUtils.i(TAG, "Print after hibernation")
    }

The above code must show this exception on the device on Android API 25

android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@cdadaf1 is not valid; is your activity running?
    at android.view.ViewRootImpl.setView(ViewRootImpl.java:990)
    at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:533)
    at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:95)
    at android.widget.Toast$TN.handleShow(Toast.java:504)
    at android.widget.Toast$TN$2.handleMessage(Toast.java:387)
    at android.os.Handler.dispatchMessage(Handler.java:102)
    at com.study.ToastWindowManagerBadTokenExceptionActivity$SafelyHandlerWrapper.dispatchMessage(ToastWindowManagerBadTokenExceptionActivity.kt:64)
    at android.os.Looper.loop(Looper.java:159)
    at android.app.ActivityThread.main(ActivityThread.java:6385)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1096)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:883)

Exception description Android view. WindowManager$BadTokenException: Unable to add window – token android. os. BinderProxy@cdadaf1 is not valid; is your activity running? Let's analyze the reasons

    public static Toast makeText(Context context, CharSequence text, @Duration int duration) {
        Toast result = new Toast(context);

        LayoutInflater inflate = (LayoutInflater)
                context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
        View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null);
        TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message);
        tv.setText(text);
        
        result.mNextView = v;
        result.mDuration = duration;

        return result;
    }

makeText is to create a Toast object and inflate a View

    public Toast(Context context) {
        mContext = context;
        mTN = new TN();
        mTN.mY = context.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.toast_y_offset);
        mTN.mGravity = context.getResources().getInteger(
                com.android.internal.R.integer.config_toastDefaultGravity);
    }

Create TN object in Toast construction method

        TN() {
            // XXX This should be changed to use a Dialog, with a Theme.Toast
            // defined that sets up the layout params appropriately.
            final WindowManager.LayoutParams params = mParams;
            params.height = WindowManager.LayoutParams.WRAP_CONTENT;
            params.width = WindowManager.LayoutParams.WRAP_CONTENT;
            params.format = PixelFormat.TRANSLUCENT;
            params.windowAnimations = com.android.internal.R.style.Animation_Toast;
            params.type = WindowManager.LayoutParams.TYPE_TOAST;
            params.setTitle("Toast");
            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON
                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
        }

In the construction method of TN, only a WindowManager is created The layoutparams object type is WindowManager LayoutParams. TYPE_ Toast, look back at the show method

    public void show() {
        if (mNextView == null) {
            throw new RuntimeException("setView must have been called");
        }

        INotificationManager service = getService();
        String pkg = mContext.getOpPackageName();
        TN tn = mTN;
        tn.mNextView = mNextView;

        try {
            service.enqueueToast(pkg, tn, mDuration);
        } catch (RemoteException e) {
            // Empty
        }
    }

Continue to look at the getService method

    private static INotificationManager sService;

    static private INotificationManager getService() {
        if (sService != null) {
            return sService;
        }
        sService = INotificationManager.Stub.asInterface(ServiceManager.getService("notification"));
        return sService;
    }

Here is the implementation of AIDL. The specific implementation is com android. server. notification. Notificationmanagerservice#mservice calls its enqueueToast method

public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            // Omit part of the code and analyze the second problem in this part

            synchronized (mToastQueue) {
                int callingPid = Binder.getCallingPid();
                long callingId = Binder.clearCallingIdentity();
                try {
                    ToastRecord record;
                    int index = indexOfToastLocked(pkg, callback);
                    if (index >= 0) {
                        // to update
                        record = mToastQueue.get(index);
                        record.update(duration);
                    } else {
                        if (!isSystemToast) {
                            int count = 0;
                            final int N = mToastQueue.size();
                            for (int i=0; i<N; i++) {
                                 final ToastRecord r = mToastQueue.get(i);
                                 if (r.pkg.equals(pkg)) {
                                     count++;
                                     // The maximum number of Toast lists for non system applications is 50
                                     if (count >= MAX_PACKAGE_NOTIFICATIONS) {
                                         Slog.e(TAG, "Package has already posted " + count
                                                + " toasts. Not showing more. Package=" + pkg);
                                         return;
                                     }
                                 }
                            }
                        }

                        Binder token = new Binder();
                        // Create a token and store it in a HashMap
                        mWindowManagerInternal.addWindowToken(token,
                                WindowManager.LayoutParams.TYPE_TOAST);
                        record = new ToastRecord(callingPid, pkg, callback, duration, token);
                        mToastQueue.add(record);
                        index = mToastQueue.size() - 1;
                        keepProcessAliveIfNeededLocked(callingPid);
                    }
                    
                    // Show next Toast 
                    if (index == 0) {
                        showNextToastLocked();
                    }
                } finally {
                    Binder.restoreCallingIdentity(callingId);
                }
            }
        }

The above mWindowManagerInternal implementation is com android. server. wm. WindowManagerService. LocalService

    public void addWindowToken(IBinder token, int type) {
        WindowManagerService.this.addWindowToken(token, type);
    }

    public void addWindowToken(IBinder token, int type) {
        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                "addWindowToken()")) {
            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
        }

        synchronized(mWindowMap) {
            WindowToken wtoken = mTokenMap.get(token);
            if (wtoken != null) {
                Slog.w(TAG_WM, "Attempted to add existing input method token: " + token);
                return;
            }
            // Create WindowToken object
            wtoken = new WindowToken(this, token, type, true);
            // Add to HashMap
            mTokenMap.put(token, wtoken);
            if (type == TYPE_WALLPAPER) {
                mWallpaperControllerLocked.addWallpaperToken(wtoken);
            }
        }
    }

Take another look at the method showNextToastLocked that displays the next Toast

    void showNextToastLocked() {
        ToastRecord record = mToastQueue.get(0);
        while (record != null) {
            if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback);
            try {
            	// The callback here is the TN object passed
                record.callback.show(record.token);
                // Send delay message to remove Toast 
                scheduleTimeoutLocked(record);
                return;
            } catch (RemoteException e) {
				// Omit some codes
            }
        }
    }

Let's take a look at TN's show method first

        public void show(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "SHOW: " + this);
            mHandler.obtainMessage(0, windowToken).sendToTarget();
        }
        
        final Handler mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                IBinder token = (IBinder) msg.obj;
                handleShow(token);
            }
        };

        public void handleShow(IBinder windowToken) {
            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView
                    + " mNextView=" + mNextView);
            if (mView != mNextView) {
                // remove the old view if necessary
                handleHide();
                mView = mNextView;
                Context context = mView.getContext().getApplicationContext();
                String packageName = mView.getContext().getOpPackageName();
                if (context == null) {
                    context = mView.getContext();
                }
                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
                // We can resolve the Gravity here by using the Locale for getting
                // the layout direction
                final Configuration config = mView.getContext().getResources().getConfiguration();
                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());
                mParams.gravity = gravity;
                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
                    mParams.horizontalWeight = 1.0f;
                }
                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
                    mParams.verticalWeight = 1.0f;
                }
                mParams.x = mX;
                mParams.y = mY;
                mParams.verticalMargin = mVerticalMargin;
                mParams.horizontalMargin = mHorizontalMargin;
                mParams.packageName = packageName;
                mParams.hideTimeoutMilliseconds = mDuration ==
                    Toast.LENGTH_LONG ? LONG_DURATION_TIMEOUT : SHORT_DURATION_TIMEOUT;
                mParams.token = windowToken;
                if (mView.getParent() != null) {
                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                    mWM.removeView(mView);
                }
                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);
                // Through WindowManager addView, the mWM here is Android view. WindowManagerImpl
                mWM.addView(mView, mParams);
                trySendAccessibilityEvent();
            }
        }

Take a look at the addView method of WindowManagerImpl

    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        // Omit some codes    

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
        	// Omit some codes

			// Create a ViewRootImpl object. ViewRootImpl is not a View, but it is the parent of the top-level View
			// When all views are refreshed, they are refreshed layer by layer by finding ViewRootImpl through getParent
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
            // Call setView of ViewRootImpl
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }
    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        // mWindowSession is created here. The specific implementation is com android. server. wm. Session
        mWindowSession = WindowManagerGlobal.getWindowSession();
        // Omit some codes
    }

Next, look at the setView method of ViewRootImpl

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                // Omit some codes    
                
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

                // Omit some codes

                if (res < WindowManagerGlobal.ADD_OKAY) {
                    mAttachInfo.mRootView = null;
                    mAdded = false;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    switch (res) {
                        // According to the exception information thrown above, this case must have been triggered 
                        case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                        case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not valid; is your activity running?");
                        case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- token " + attrs.token
                                    + " is not for an application");
                        case WindowManagerGlobal.ADD_APP_EXITING:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- app for token " + attrs.token
                                    + " is exiting");
                        case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                            throw new WindowManager.BadTokenException(
                                    "Unable to add window -- window " + mWindow
                                    + " has already been added");
                        case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                            // Silently ignore -- we would have just removed it
                            // right away, anyway.
                            return;
                        case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- another window of type "
                                    + mWindowAttributes.type + " already exists");
                        case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                            throw new WindowManager.BadTokenException("Unable to add window "
                                    + mWindow + " -- permission denied for window type "
                                    + mWindowAttributes.type);
                        case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified display can not be found");
                        case WindowManagerGlobal.ADD_INVALID_TYPE:
                            throw new WindowManager.InvalidDisplayException("Unable to add window "
                                    + mWindow + " -- the specified window type "
                                    + mWindowAttributes.type + " is not valid");
                    }
                    throw new RuntimeException(
                            "Unable to add window -- unknown error code " + res);
                }

                // Omit some codes
            }
        }
    }

Keep looking

    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outOutsets, outInputChannel);
    }

com.android.server.wm.WindowManagerService#addWindow

    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
        // Omit some codes    
        synchronized(mWindowMap) {
            // Omit some codes

            boolean addToken = false;
            WindowToken token = mTokenMap.get(attrs.token);
            AppWindowToken atoken = null;
            boolean addToastWindowRequiresToken = false;

            if (token == null) {
                if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
                    Slog.w(TAG_WM, "Attempted to add application window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_INPUT_METHOD) {
                    Slog.w(TAG_WM, "Attempted to add input method window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_VOICE_INTERACTION) {
                    Slog.w(TAG_WM, "Attempted to add voice interaction window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_WALLPAPER) {
                    Slog.w(TAG_WM, "Attempted to add wallpaper window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_DREAM) {
                    Slog.w(TAG_WM, "Attempted to add Dream window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_QS_DIALOG) {
                    Slog.w(TAG_WM, "Attempted to add QS dialog window with unknown token "
                          + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_ACCESSIBILITY_OVERLAY) {
                    Slog.w(TAG_WM, "Attempted to add Accessibility overlay window with unknown token "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (type == TYPE_TOAST) {
                    // Apps targeting SDK above N MR1 cannot arbitrary add toast windows.
                    if (doesAddToastWindowRequireToken(attrs.packageName, callingUid,
                            attachedWindow)) {
                        Slog.w(TAG_WM, "Attempted to add a toast window with unknown token "
                                + attrs.token + ".  Aborting.");
                        return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                    }
                }
                token = new WindowToken(this, attrs.token, -1, false);
                addToken = true;
            } 
            // Omit some codes

        }

        // Omit some codes   

        return res;
    }

Here we should go to the branch where the token is equal to null. As mentioned above, we will add the token to the HashMap. Why is it null here? Let's return to the method of sending delay messages, scheduleTimeoutLocked

    private void scheduleTimeoutLocked(ToastRecord r)
    {
        mHandler.removeCallbacksAndMessages(r);
        Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r);
        // Here, the delay time is set according to the passed in duration
        // Toast.LENGTH_LONG corresponds to LONG_DELAY 
        // Toast.LENGTH_SHORT corresponds to SHORT_DELAY
        long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY;
        mHandler.sendMessageDelayed(m, delay);
    }
    
// com.android.server.notification.NotificationManagerService
static final int LONG_DELAY = PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
static final int SHORT_DELAY = 2000;

// com.android.server.policy.PhoneWindowManager
/** Amount of time (in milliseconds) a toast window can be shown. */
public static final int TOAST_WINDOW_TIMEOUT = 3500; // 3.5 seconds

So you can know LENGTH_LONG display duration 3500 MS length_ The display time of short is 2000 ms, so the above 2000 ms sleep will trigger this delay message

    private final class WorkerHandler extends Handler
    {
        @Override
        public void handleMessage(Message msg)
        {
            switch (msg.what)
            {
                case MESSAGE_TIMEOUT:
                    handleTimeout((ToastRecord)msg.obj);
                    break;
				// Omit some codes	
            }
        }

    }

    private void handleTimeout(ToastRecord record)
    {
        if (DBG) Slog.d(TAG, "Timeout pkg=" + record.pkg + " callback=" + record.callback);
        synchronized (mToastQueue) {
            int index = indexOfToastLocked(record.pkg, record.callback);
            if (index >= 0) {
                cancelToastLocked(index);
            }
        }
    }

    void cancelToastLocked(int index) {
        ToastRecord record = mToastQueue.get(index);
        try {
            record.callback.hide();
        } catch (RemoteException e) {
			// Omit some codes
        }

		// Remove current toast
        ToastRecord lastToast = mToastQueue.remove(index);
        // The token will be removed here 
        mWindowManagerInternal.removeWindowToken(lastToast.token, true);

        keepProcessAliveIfNeededLocked(record.pid);
        // Show next Toast 
        if (mToastQueue.size() > 0) {
            // Show the next one. If the callback fails, this will remove
            // it from the list, so don't assume that the list hasn't changed
            // after this point.
            showNextToastLocked();
        }
    }

    public void hide() {
        if (localLOGV) Log.v(TAG, "HIDE: " + this);
        mHandler.post(mHide);
    }

    final Runnable mHide = new Runnable() {
        @Override
        public void run() {
            handleHide();
            // Don't do this in handleHide() because it is also invoked by handleShow()
            mNextView = null;
        }
    };

    public void handleHide() {
        if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView);
        if (mView != null) {
            // note: checking parent() just to make sure the view has
            // been added...  i have seen cases where we get here when
            // the view isn't yet added, so let's try not to crash.
            if (mView.getParent() != null) {
                if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);
                // Remove view
                mWM.removeViewImmediate(mView);
            }

            mView = null;
        }
    }

Look at the code of removeWindowToken

    public void removeWindowToken(IBinder token) {
        if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS,
                "removeWindowToken()")) {
            throw new SecurityException("Requires MANAGE_APP_TOKENS permission");
        }

        final long origId = Binder.clearCallingIdentity();
        synchronized(mWindowMap) {
            DisplayContent displayContent = null;
            // Remove token
            WindowToken wtoken = mTokenMap.remove(token);
        }
        // Omit some codes
    }

You can see that the token is removed here, so it is obtained again, so null is returned. To sum up, this exception is thrown when a message blocking in the UI thread causes TN's show method to execute after the hide method. This exception will only occur in Android API 25, because google handled this problem later

Here is the difference between Android API 25 and Android API 26. You can see that this exception is caught directly. This exception cannot be caught directly where the toast pops up, because the exception is thrown in the message loop. There are two ways to repair. One is to replace TN's mHandler through reflection and then add Tay catch to handleMessage. The advantage is that it is relatively simple, but the disadvantage is that the granularity is too coarse. This exception may be caught in other scenarios. The code is as follows:

    // Fix android.com on Android API 25 view. WindowManager$BadTokenException
    private fun fixWindowManagerBadTokenExceptionIn(toast: Toast) {
        if (Build.VERSION.SDK_INT != Build.VERSION_CODES.N_MR1) {
            return
        }

        try {
            val mTnField = Toast::class.java.getDeclaredField("mTN")
            mTnField.isAccessible = true
            val mTn = mTnField.get(toast)

            val mHandlerField = mTn.javaClass.getDeclaredField("mHandler")
            mHandlerField.isAccessible = true
            val mHandler = mHandlerField.get(mTn) as? Handler ?: return

            mHandlerField.set(mTn, SafelyHandlerWrapper(mHandler))
        } catch (e: Exception) {
            e.printStackTrace()
        }

The other is to use reflection, but instead of the mContext object in the view, and then obtain various services through the mContext, it will call the getSystemService of the context to judge whether it is context WINDOW_ service can wrap the originally returned WindowManager in a layer, and then try catch the addView method of the WindowManager. The code will not be pasted Github address

2, Toast cannot pop up after some devices close the notification permission

The user may turn off the notification permission of the App. The user's expectation may just want to block push messages, not Toast. When various Toast pop-up needs to be popped up, it will be strange that the user experience is too poor. First analyze the cause of the problem, and then look back

    private final IBinder mService = new INotificationManager.Stub() {

        @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }

            final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());
			// When all three conditions are met, the process ends
		    // noteNotificationOp is used to detect whether there is notification permission
            if (ENABLE_BLOCKED_TOASTS && (!noteNotificationOp(pkg, Binder.getCallingUid())
                    || isPackageSuspended)) {
                if (!isSystemToast) {
                    Slog.e(TAG, "Suppressing toast from package " + pkg
                            + (isPackageSuspended
                                    ? " due to package suspended by administrator."
                                    : " by user request."));
                    return;
                }
            }
        }
    }    

You can see that if the process ends without notification permission, Toast will not be added to the list and will not be displayed. The solutions are as follows:
The first is to avoid the NotificationManagerService handling the display and hiding of view s through WindowManager. The disadvantage is that when there is no floating window permission, it can only be displayed on the current page. The specific implementation is as follows: ToastUtils
The second is to set the Toast view to the dialog's contentView through dialog. The specific implementation is as follows: smart-show The disadvantage of this scheme is that it can only be displayed on the current page
The third is through the specific analysis of Snackbar About Toast and Snackbar Because there is no code, you can't see the specific effect. The disadvantage is that it's too troublesome to use Toast
The fourth method is to add Toast to the list through dynamic proxy

    private final IBinder mService = new INotificationManager.Stub() {

        @Override
        public void enqueueToast(String pkg, ITransientNotification callback, int duration)
        {
            if (DBG) {
                Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback
                        + " duration=" + duration);
            }

            if (pkg == null || callback == null) {
                Slog.e(TAG, "Not doing toast. pkg=" + pkg + " callback=" + callback);
                return ;
            }

			// The value of isSystemToast depends on isCallerSystem() and "Android" equals(pkg)
			// Therefore, if the value of pkg is "Android" and issystemtoast is true, it can pass the verification
            final boolean isSystemToast = isCallerSystem() || ("android".equals(pkg));
            final boolean isPackageSuspended =
                    isPackageSuspendedForUser(pkg, Binder.getCallingUid());
			// When all three conditions are met, the process ends
		    // noteNotificationOp is used to detect whether there is notification permission
            if (ENABLE_BLOCKED_TOASTS && (!noteNotificationOp(pkg, Binder.getCallingUid())
                    || isPackageSuspended)) {
                // If isSystemToast returns true, it can pass the verification    
                if (!isSystemToast) {
                    Slog.e(TAG, "Suppressing toast from package " + pkg
                            + (isPackageSuspended
                                    ? " due to package suspended by administrator."
                                    : " by user request."));
                    return;
                }
            }
        }
    }    

According to the above analysis, just change the value of the first parameter pkg to "true". The specific code is here The notification permission of some Android phones is closed, and Toast cannot be played

Reference and thanks

Tip: the links listed above will not be repeated
WindowManager$BadTokenException(WindowManager source code analysis)

WindowManager$BadTokenException - solution

Topics: Android Android Studio