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

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

[【Androidスマホのコツ】ウザったい通知は消しちゃえ！不要なアプリ通知を表示させない方法！](http://octoba.net/archives/20140111-android-feature-notification.html)

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

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

---
### Toastが表示されない原因

```java: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の設定をしてる。

```java: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の設定は・・・


```java: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を見てみる。


```java: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の処理は

```java: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の実装はどうなってるの。

```java: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)判定のところっぽい。

```java: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の

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

```java: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

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