LoginSignup
20
15

More than 5 years have passed since last update.

Toastが出ない。まじで出ない。

Last updated at Posted at 2015-02-17

Toastが出なくて小一時間ハマッたのでメモ。

【Androidスマホのコツ】ウザったい通知は消しちゃえ!不要なアプリ通知を表示させない方法!

上の記事のように、ウザったい通知は消しちゃえ!とアプリ情報の「通知を表示」を無効にすると、トーストももれなく出なくなってしまいます。なんてこった・・・。


検証端末
Galaxy S3α OS4.3
Nexus5 OS5.0


Toastが表示されない原因

android.widget.Toast#makeText

    /**
     * Make a standard toast that just contains a text view.
     *
     * @param context  The context to use.  Usually your {@link android.app.Application}
     *                 or {@link android.app.Activity} object.
     * @param text     The text to show.  Can be formatted text.
     * @param duration How long to display the message.  Either {@link #LENGTH_SHORT} or
     *                 {@link #LENGTH_LONG}
     *
     */
 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ではToast生成と、Toastに対してViewとDurationの設定をしてる。

android.widget.Toast
 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);
    }

コンストラクタではTNを生成してる。TNは多分ToastNotificationの略。TNに対するgravityとy_offsetの設定は・・・

android.widget.Toast.TN
private static class TN extends ITransientNotification.Stub {
// 略
        private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams();
        int mGravity;
        int mX, mY;
// 略

        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;
        }
}

内部クラスのTNはToastのWindowManager.LayoutParamsを持ってる。
下準備は眺めたので次はshowを見てみる。

android.widget.Toast#show
    /**
     * Show the view for the specified duration.
     */
    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
        }
    }

INotificationManagerでenququeToastでtnを送ってる。getServiceの処理は

android.widget.Toast#getService
    // =======================================================================================
    // All the gunk below is the interaction with the Notification Service, which handles
    // the proper ordering of these system-wide.
    // =======================================================================================

    private static INotificationManager sService;

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

INotificationManagerはnotificationのServiceっぽい雰囲気をかもしてる。
ここまでの流れを簡単にまとめると、TN作成 -> INotificationManagerがTN情報表示。
端末で走ってるっぽいINotificationManagerの実装はどうなってるの。

com.android.server.NotificationManagerService#enqueueToast
   // Toasts
    // ============================================================================
    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 = ("android".equals(pkg));

        if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
            Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
            return;
        }

        synchronized (mToastQueue) {
           // 略 Toast表示
        }
    }

NotificationManagerにenqueueToastの実装部分があった。ITransientNotificationのcallbackはさっきのTN。
Toastが出ない原因はareNotificationsEnaledForPakageInt(pkg)判定のところっぽい。

com.android.server.NotificationManagerService#areNotificationsEnabledForPackageIntとか
   private HashSet<String> mBlockedPackages = new HashSet<String>();

   // Unchecked. Not exposed via Binder, but can be called in the course of enqueue*().
    private boolean areNotificationsEnabledForPackageInt(String pkg) {
        final boolean enabled = !mBlockedPackages.contains(pkg);
        if (DBG) {
            Slog.v(TAG, "notifications are " + (enabled?"en":"dis") + "abled for " + pkg);
        }
        return enabled;
    }

    public void setNotificationsEnabledForPackage(String pkg, boolean enabled) {
        checkCallerIsSystem();
        if (DBG) {
            Slog.v(TAG, (enabled?"en":"dis") + "abling notifications for " + pkg);
        }
        if (enabled) {
            mBlockedPackages.remove(pkg);
        } else {
            mBlockedPackages.add(pkg);

            // Now, cancel any outstanding notifications that are part of a just-disabled app
            if (ENABLE_BLOCKED_NOTIFICATIONS) {
                synchronized (mNotificationList) {
                    final int N = mNotificationList.size();
                    for (int i=0; i<N; i++) {
                        final NotificationRecord r = mNotificationList.get(i);
                        if (r.pkg.equals(pkg)) {
                            cancelNotificationLocked(r, false);
                        }
                    }
                }
            }
            // Don't bother canceling toasts, they'll go away soon enough.
        }
        writeBlockDb();
    }

HashSetのmBlockedPackegsはその名の通りのかな。
「通知を表示」が無効のときにToastが表示されない原因は、Toast表示の条件に!areNotificationsEnabledForPackageIntがあるからということでソースコード追うのは終わり。

こうなれば・・・

NotificationManagerServiceの

com.android.server.NotificationManagerService#enqueueToast
 if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
            Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
            return;
        }

の部分が

com.android.server.NotificationManagerService#enqueueToast
 if (ENABLE_BLOCKED_TOASTS && !isSystemToast /* && !areNotificationsEnabledForPackageInt(pkg) */ ) {
            Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
            return;
        }

で表示される気がする。


解決方法

NotificationManagerServiceが関与しない方法でToast表示するGhostというライブラリを作成した。
https://github.com/garlicG/AndroidGhost

どうしてもトースト表示させたいアプリがあったから作成したけど、通常営業時はそうゆう仕様なのでトースト出ませんで良いと思う。

20
15
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
15