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();