From 60c39b3bc8bf6ec8c288773d4eaf4f7ee6883c28 Mon Sep 17 00:00:00 2001 From: Bernardo Rufino Date: Fri, 8 Nov 2019 16:11:15 +0000 Subject: [PATCH] [Roll Forward] Block background custom toasts Previous CL (ag/9699132) broke CTS ToastTest and was reverted (ag/9754500). This was because the test was posting a background toast and verifying it wasn't clickable. Test has been updated to use a foreground toast in ag/9754274 and I will also look into having it be executed in presubmit.(b/144810971). -- Previous CL description (updated "Test:" lines) To assess app compatibility problems early on, this CL blocks background custom toasts in a non-secure way. We create a new method in the AIDL called enqueueTextToast() that now contain the same parameters as enqueueToast(), receiving the view created inside the app's process. But this method will, in the future, contain a CharSequence instead of ITransientNotification, for window creation inside the system. We use PlatformCompat infrastructure to check if the change is enabled. I learned with this change that if user blocks notifications for an app, it won't be able to show background toasts, allowing me to re-use the foreground check that was already in place. This change will display a text toast (from the system) in case the app's custom toast gets blocked, this is temporary to get feedback from dogfood. Test: With sample app, verify that: 1. Posting text toast in bg works 2. Posting custom toast in fg works 3. Posting custom toast in bg does NOT work Test: atest CtsToastTestCases CtsWindowManagerDeviceTestCases:ToastTest CtsWidgetTestCases:ToastTest CtsToastLegacyTestCases Test: Working on CTS Bug: 128611929 Change-Id: I25c6339c6adeb907878596332f57e2fa229bfda9 --- .../android/app/INotificationManager.aidl | 2 + core/java/android/widget/Toast.java | 10 ++- services/core/Android.bp | 4 + .../NotificationManagerService.java | 74 ++++++++++++++++--- 4 files changed, 79 insertions(+), 11 deletions(-) diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index ac8b604ad103f..9aca22360fd81 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -44,6 +44,8 @@ interface INotificationManager void cancelAllNotifications(String pkg, int userId); void clearData(String pkg, int uid, boolean fromApp); + // TODO: Replace parameter (ITransientNotification callback) with (CharSequence text) + void enqueueTextToast(String pkg, ITransientNotification callback, int duration, int displayId); @UnsupportedAppUsage void enqueueToast(String pkg, ITransientNotification callback, int duration, int displayId); @UnsupportedAppUsage diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index d037337d4546f..bdc2f9af27759 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -100,6 +100,8 @@ public class Toast { @UnsupportedAppUsage int mDuration; View mNextView; + // TODO(b/128611929): Remove this and check for null view when toast creation is in the system + boolean mIsCustomToast = false; /** * Construct an empty Toast object. You must call {@link #setView} before you @@ -140,7 +142,11 @@ public class Toast { final int displayId = mContext.getDisplayId(); try { - service.enqueueToast(pkg, tn, mDuration, displayId); + if (mIsCustomToast) { + service.enqueueToast(pkg, tn, mDuration, displayId); + } else { + service.enqueueTextToast(pkg, tn, mDuration, displayId); + } } catch (RemoteException e) { // Empty } @@ -160,6 +166,7 @@ public class Toast { * @see #getView */ public void setView(View view) { + mIsCustomToast = true; mNextView = view; } @@ -168,6 +175,7 @@ public class Toast { * @see #setView */ public View getView() { + mIsCustomToast = true; return mNextView; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 770de09382fe3..3f5bb153591ee 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -117,6 +117,10 @@ java_library_static { "dnsresolver_aidl_interface-V2-java", "netd_event_listener_interface-java", ], + + plugins: [ + "compat-changeid-annotation-processor", + ], } java_genrule { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 5e764012f46d9..70fbd9832cfc7 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -131,6 +131,7 @@ import android.app.role.RoleManager; import android.app.usage.UsageEvents; import android.app.usage.UsageStatsManagerInternal; import android.companion.ICompanionDeviceManager; +import android.compat.annotation.ChangeId; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentProvider; @@ -216,6 +217,7 @@ import android.widget.Toast; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.compat.IPlatformCompat; import com.android.internal.config.sysui.SystemUiDeviceConfigFlags; import com.android.internal.logging.MetricsLogger; import com.android.internal.logging.nano.MetricsProto; @@ -236,6 +238,7 @@ import com.android.server.EventLogTags; import com.android.server.IoThread; import com.android.server.LocalServices; import com.android.server.SystemService; +import com.android.server.UiThread; import com.android.server.lights.Light; import com.android.server.lights.LightsManager; import com.android.server.notification.ManagedServices.ManagedServiceInfo; @@ -355,6 +358,15 @@ public class NotificationManagerService extends SystemService { private static final int REQUEST_CODE_TIMEOUT = 1; private static final String SCHEME_TIMEOUT = "timeout"; private static final String EXTRA_KEY = "key"; + + /** + * Apps targeting R+ that post custom toasts in the background will have those blocked. Apps can + * still post toasts created with {@link Toast#makeText(Context, CharSequence, int)} and its + * variants while in the background. + */ + @ChangeId + private static final long CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK = 128611929L; + private IActivityManager mAm; private ActivityManager mActivityManager; private IPackageManager mPackageManager; @@ -372,9 +384,11 @@ public class NotificationManagerService extends SystemService { private UriGrantsManagerInternal mUgmInternal; private RoleObserver mRoleObserver; private UserManager mUm; + private IPlatformCompat mPlatformCompat; final IBinder mForegroundToken = new Binder(); private WorkerHandler mHandler; + private Handler mUiHandler; private final HandlerThread mRankingThread = new HandlerThread("ranker", Process.THREAD_PRIORITY_BACKGROUND); @@ -1773,8 +1787,11 @@ public class NotificationManagerService extends SystemService { ServiceManager.getService(Context.DEVICE_IDLE_CONTROLLER)); mDpm = dpm; mUm = userManager; + mPlatformCompat = IPlatformCompat.Stub.asInterface( + ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE)); mHandler = new WorkerHandler(looper); + mUiHandler = new Handler(UiThread.get().getLooper()); String[] extractorNames; try { extractorNames = resources.getStringArray(R.array.config_notificationSignalExtractors); @@ -2433,10 +2450,20 @@ public class NotificationManagerService extends SystemService { // Toasts // ============================================================================ + @Override + public void enqueueTextToast(String pkg, ITransientNotification callback, int duration, + int displayId) { + enqueueToast(pkg, callback, duration, displayId, false); + } + @Override public void enqueueToast(String pkg, ITransientNotification callback, int duration, - int displayId) - { + int displayId) { + enqueueToast(pkg, callback, duration, displayId, true); + } + + private void enqueueToast(String pkg, ITransientNotification callback, int duration, + int displayId, boolean isCustomToast) { if (DBG) { Slog.i(TAG, "enqueueToast pkg=" + pkg + " callback=" + callback + " duration=" + duration + " displayId=" + displayId); @@ -2448,27 +2475,54 @@ public class NotificationManagerService extends SystemService { } final int callingUid = Binder.getCallingUid(); + final UserHandle callingUser = Binder.getCallingUserHandle(); final boolean isSystemToast = isCallerSystemOrPhone() || PackageManagerService.PLATFORM_PACKAGE_NAME.equals(pkg); final boolean isPackageSuspended = isPackagePaused(pkg); final boolean notificationsDisabledForPackage = !areNotificationsEnabledForPackage(pkg, callingUid); + final boolean appIsForeground; long callingIdentity = Binder.clearCallingIdentity(); try { - final boolean appIsForeground = mActivityManager.getUidImportance(callingUid) + appIsForeground = mActivityManager.getUidImportance(callingUid) == IMPORTANCE_FOREGROUND; - if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage - && !appIsForeground) || isPackageSuspended)) { - Slog.e(TAG, "Suppressing toast from package " + pkg - + (isPackageSuspended ? " due to package suspended." - : " by user request.")); - return; - } } finally { Binder.restoreCallingIdentity(callingIdentity); } + if (ENABLE_BLOCKED_TOASTS && !isSystemToast && ((notificationsDisabledForPackage + && !appIsForeground) || isPackageSuspended)) { + Slog.e(TAG, "Suppressing toast from package " + pkg + + (isPackageSuspended ? " due to package suspended." + : " by user request.")); + return; + } + + if (isCustomToast && !appIsForeground && !isSystemToast) { + boolean block; + try { + block = mPlatformCompat.isChangeEnabledByPackageName( + CHANGE_BACKGROUND_CUSTOM_TOAST_BLOCK, pkg, + callingUser.getIdentifier()); + } catch (RemoteException e) { + // Shouldn't happen have since it's a local local + Slog.e(TAG, "Unexpected exception while checking block background custom toasts" + + " change", e); + block = false; + } + if (block) { + // TODO(b/144152069): Remove informative toast + mUiHandler.post(() -> Toast.makeText(getContext(), + "Background custom toast blocked for package " + pkg + ".\n" + + "See go/r-toast-block.", + Toast.LENGTH_SHORT).show()); + Slog.w(TAG, "Blocking custom toast from package " + pkg + + " due to package not in the foreground"); + return; + } + } + synchronized (mToastQueue) { int callingPid = Binder.getCallingPid(); long callingId = Binder.clearCallingIdentity();