Toastが出なくて小一時間ハマッたのでメモ。
【Androidスマホのコツ】ウザったい通知は消しちゃえ!不要なアプリ通知を表示させない方法!
上の記事のように、ウザったい通知は消しちゃえ!とアプリ情報の「通知を表示」を無効にすると、トーストももれなく出なくなってしまいます。なんてこった・・・。
検証端末
Galaxy S3α OS4.3
Nexus5 OS5.0
Toastが表示されない原因
/**
* 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の設定をしてる。
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の設定は・・・
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を見てみる。
/**
* 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の処理は
// =======================================================================================
// 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の実装はどうなってるの。
// 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)判定のところっぽい。
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の
if (ENABLE_BLOCKED_TOASTS && !isSystemToast && !areNotificationsEnabledForPackageInt(pkg)) {
Slog.e(TAG, "Suppressing toast from package " + pkg + " by user request.");
return;
}
の部分が
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
どうしてもトースト表示させたいアプリがあったから作成したけど、通常営業時はそうゆう仕様なのでトースト出ませんで良いと思う。