From c02fc886157ba0283dbf8c229799e763be4bf70c Mon Sep 17 00:00:00 2001 From: Christopher Tate Date: Fri, 19 Feb 2021 11:14:05 -0800 Subject: [PATCH] Make FGS notification deferral control tri-state Apps can now request either immediate visibility or deferral explicitly, versus the previous iteration's immediate-or-default only. Bug: 179290175 Test: ApiDemos foreground service exercise Test: atest CtsAppTestCases:ServiceTest Test: atest CtsAppTestCases:NotificationManagerTest Change-Id: I0b4d11a7483d2407758c810cf4a77a2e45bb737f --- core/api/current.txt | 6 +- core/api/test-current.txt | 4 + core/java/android/app/Notification.java | 139 +++++++++++++++--- .../screenrecord/RecordingService.java | 2 +- .../com/android/server/am/ActiveServices.java | 38 +++-- 5 files changed, 156 insertions(+), 33 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index bb501e89b3b35..e95f3bb164f85 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -5714,6 +5714,9 @@ package android.app { field public static final int FLAG_ONGOING_EVENT = 2; // 0x2 field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8 field @Deprecated public static final int FLAG_SHOW_LIGHTS = 1; // 0x1 + field public static final int FOREGROUND_SERVICE_DEFAULT = 0; // 0x0 + field public static final int FOREGROUND_SERVICE_DEFERRED = 2; // 0x2 + field public static final int FOREGROUND_SERVICE_IMMEDIATE = 1; // 0x1 field public static final int GROUP_ALERT_ALL = 0; // 0x0 field public static final int GROUP_ALERT_CHILDREN = 2; // 0x2 field public static final int GROUP_ALERT_SUMMARY = 1; // 0x1 @@ -5917,6 +5920,7 @@ package android.app { method @NonNull public android.app.Notification.Builder setDeleteIntent(android.app.PendingIntent); method @NonNull public android.app.Notification.Builder setExtras(android.os.Bundle); method @NonNull public android.app.Notification.Builder setFlag(int, boolean); + method @NonNull public android.app.Notification.Builder setForegroundServiceBehavior(int); method @NonNull public android.app.Notification.Builder setFullScreenIntent(android.app.PendingIntent, boolean); method @NonNull public android.app.Notification.Builder setGroup(String); method @NonNull public android.app.Notification.Builder setGroupAlertBehavior(int); @@ -5935,7 +5939,7 @@ package android.app { method @NonNull public android.app.Notification.Builder setRemoteInputHistory(CharSequence[]); method @NonNull public android.app.Notification.Builder setSettingsText(CharSequence); method @NonNull public android.app.Notification.Builder setShortcutId(String); - method @NonNull public android.app.Notification.Builder setShowForegroundImmediately(boolean); + method @Deprecated @NonNull public android.app.Notification.Builder setShowForegroundImmediately(boolean); method @NonNull public android.app.Notification.Builder setShowWhen(boolean); method @NonNull public android.app.Notification.Builder setSmallIcon(@DrawableRes int); method @NonNull public android.app.Notification.Builder setSmallIcon(@DrawableRes int, int); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 5f78bc5b4e1e7..65a2e39674cbe 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -270,6 +270,10 @@ package android.app { method public abstract void onHomeVisibilityChanged(boolean); } + public class Notification implements android.os.Parcelable { + method public boolean shouldShowForegroundImmediately(); + } + public final class NotificationChannel implements android.os.Parcelable { method public int getOriginalImportance(); method public boolean isBlockable(); diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 4eda6fefd188a..7063a58f99b38 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -38,6 +38,7 @@ import android.annotation.SdkConstant.SdkConstantType; import android.annotation.StringRes; import android.annotation.SuppressLint; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.compat.annotation.UnsupportedAppUsage; import android.content.ContentResolver; import android.content.Context; @@ -130,6 +131,52 @@ public class Notification implements Parcelable { private static final String TAG = "Notification"; + /** + * @hide + */ + @Retention(RetentionPolicy.SOURCE) + @IntDef({ + FOREGROUND_SERVICE_DEFAULT, + FOREGROUND_SERVICE_IMMEDIATE, + FOREGROUND_SERVICE_DEFERRED + }) + public @interface ServiceNotificationPolicy {}; + + /** + * If the Notification associated with starting a foreground service has been + * built using setForegroundServiceBehavior() with this behavior, display of + * the notification will usually be suppressed for a short time to avoid visual + * disturbances to the user. + * @see Notification.Builder#setForegroundServiceBehavior(int) + * @see #FOREGROUND_SERVICE_IMMEDIATE + * @see #FOREGROUND_SERVICE_DEFERRED + */ + public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFAULT = 0; + + /** + * If the Notification associated with starting a foreground service has been + * built using setForegroundServiceBehavior() with this behavior, display of + * the notification will be immediate even if the default behavior would be + * to defer visibility for a short time. + * @see Notification.Builder#setForegroundServiceBehavior(int) + * @see #FOREGROUND_SERVICE_DEFAULT + * @see #FOREGROUND_SERVICE_DEFERRED + */ + public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_IMMEDIATE = 1; + + /** + * If the Notification associated with starting a foreground service has been + * built using setForegroundServiceBehavior() with this behavior, display of + * the notification will usually be suppressed for a short time to avoid visual + * disturbances to the user. + * @see Notification.Builder#setForegroundServiceBehavior(int) + * @see #FOREGROUND_SERVICE_DEFAULT + * @see #FOREGROUND_SERVICE_IMMEDIATE + */ + public static final @ServiceNotificationPolicy int FOREGROUND_SERVICE_DEFERRED = 2; + + private int mFgsDeferBehavior; + /** * An activity that provides a user interface for adjusting notification preferences for its * containing application. @@ -645,11 +692,6 @@ public class Notification implements Parcelable */ public static final int FLAG_BUBBLE = 0x00001000; - /** - * @hide - */ - public static final int FLAG_IMMEDIATE_FGS_DISPLAY = 0x00002000; - private static final List> PLATFORM_STYLE_CLASSES = Arrays.asList( BigTextStyle.class, BigPictureStyle.class, InboxStyle.class, MediaStyle.class, DecoratedCustomViewStyle.class, DecoratedMediaCustomViewStyle.class, @@ -659,8 +701,7 @@ public class Notification implements Parcelable @IntDef(flag = true, prefix = { "FLAG_" }, value = {FLAG_SHOW_LIGHTS, FLAG_ONGOING_EVENT, FLAG_INSISTENT, FLAG_ONLY_ALERT_ONCE, FLAG_AUTO_CANCEL, FLAG_NO_CLEAR, FLAG_FOREGROUND_SERVICE, FLAG_HIGH_PRIORITY, - FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE, - FLAG_IMMEDIATE_FGS_DISPLAY}) + FLAG_LOCAL_ONLY, FLAG_GROUP_SUMMARY, FLAG_AUTOGROUP_SUMMARY, FLAG_BUBBLE}) @Retention(RetentionPolicy.SOURCE) public @interface NotificationFlags{}; @@ -2550,6 +2591,8 @@ public class Notification implements Parcelable } mAllowSystemGeneratedContextualActions = parcel.readBoolean(); + + mFgsDeferBehavior = parcel.readInt(); } @Override @@ -2665,6 +2708,7 @@ public class Notification implements Parcelable that.mBadgeIcon = this.mBadgeIcon; that.mSettingsText = this.mSettingsText; that.mGroupAlertBehavior = this.mGroupAlertBehavior; + that.mFgsDeferBehavior = this.mFgsDeferBehavior; that.mBubbleMetadata = this.mBubbleMetadata; that.mAllowSystemGeneratedContextualActions = this.mAllowSystemGeneratedContextualActions; @@ -3064,6 +3108,8 @@ public class Notification implements Parcelable parcel.writeBoolean(mAllowSystemGeneratedContextualActions); + parcel.writeInt(mFgsDeferBehavior); + // mUsesStandardHeader is not written because it should be recomputed in listeners } @@ -4532,10 +4578,40 @@ public class Notification implements Parcelable * foreground service is shown as soon as the service's {@code startForeground()} * method is called, even if the system's UI policy might otherwise defer * its visibility to a later time. + * @deprecated Use setForegroundServiceBehavior(int) instead */ + @Deprecated @NonNull public Builder setShowForegroundImmediately(boolean showImmediately) { - setFlag(FLAG_IMMEDIATE_FGS_DISPLAY, showImmediately); + setForegroundServiceBehavior(showImmediately + ? FOREGROUND_SERVICE_IMMEDIATE + : FOREGROUND_SERVICE_DEFAULT); + return this; + } + + /** + * Specify a desired visibility policy for a Notification associated with a + * foreground service. By default, the system can choose to defer + * visibility of the notification for a short time after the service is + * started. Pass + * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE BEHAVIOR_IMMEDIATE_DISPLAY} + * to this method in order to guarantee that visibility is never deferred. Pass + * {@link Notification#FOREGROUND_SERVICE_DEFERRED BEHAVIOR_DEFERRED_DISPLAY} + * to request that visibility is deferred whenever possible. + * + *

Note that deferred visibility is not guaranteed. There + * may be some circumstances under which the system will show the foreground + * service's associated Notification immediately even when the app has used + * this method to explicitly request deferred display.

+ * @param behavior One of + * {@link Notification#FOREGROUND_SERVICE_DEFAULT BEHAVIOR_DEFAULT}, + * {@link Notification#FOREGROUND_SERVICE_IMMEDIATE BEHAVIOR_IMMEDIATE_DISPLAY}, + * or {@link Notification#FOREGROUND_SERVICE_DEFERRED BEHAVIOR_DEFERRED_DISPLAY} + * @return + */ + @NonNull + public Builder setForegroundServiceBehavior(int behavior) { + mN.mFgsDeferBehavior = behavior; return this; } @@ -6717,30 +6793,59 @@ public class Notification implements Parcelable * immediately when tied to a foreground service, even if the system might generally * avoid showing the notifications for short-lived foreground service lifetimes. * - * Immediate visibility of the Notification is recommended when: + * Immediate visibility of the Notification is indicated when: * - * @return whether this notification should always be displayed immediately when + * + * If the app has specified + * {@code NotificationBuilder.setForegroundServiceBehavior(BEHAVIOR_DEFERRED_DISPLAY)} + * then this method will return {@code false} and notification visibility will be + * deferred following the service's transition to the foreground state even in the + * circumstances described above. + * + * @return whether this notification should be displayed immediately when * its associated service transitions to the foreground state * @hide */ + @TestApi public boolean shouldShowForegroundImmediately() { - if ((flags & Notification.FLAG_IMMEDIATE_FGS_DISPLAY) != 0 - || isMediaNotification() || hasMediaSession() - || CATEGORY_CALL.equals(category) - || CATEGORY_NAVIGATION.equals(category) - || (actions != null && actions.length > 0)) { + // Has the app demanded immediate display? + if (mFgsDeferBehavior == FOREGROUND_SERVICE_IMMEDIATE) { return true; } + + // Has the app demanded deferred display? + if (mFgsDeferBehavior == FOREGROUND_SERVICE_DEFERRED) { + return false; + } + + // We show these sorts of notifications immediately in the absence of + // any explicit app declaration + if (isMediaNotification() || hasMediaSession() + || CATEGORY_CALL.equals(category) + || CATEGORY_NAVIGATION.equals(category) + || (actions != null && actions.length > 0)) { + return true; + } + + // No extenuating circumstances: defer visibility return false; } + /** + * Has forced deferral for FGS purposes been specified? + * @hide + */ + public boolean isForegroundDisplayForceDeferred() { + return FOREGROUND_SERVICE_DEFERRED == mFgsDeferBehavior; + } + /** * @return whether this notification has a media session attached * @hide diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java index bd46ffec759b5..4300d3734a204 100644 --- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java +++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingService.java @@ -300,7 +300,7 @@ public class RecordingService extends Service implements MediaRecorder.OnInfoLis .setColorized(true) .setColor(getResources().getColor(R.color.GM2_red_700)) .setOngoing(true) - .setShowForegroundImmediately(true) + .setForegroundServiceBehavior(Notification.FOREGROUND_SERVICE_IMMEDIATE) .setContentIntent( PendingIntent.getService(this, REQUEST_CODE, stopIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE)) diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java index d2fd8ff3890e7..a85f2b9f3b8eb 100644 --- a/services/core/java/com/android/server/am/ActiveServices.java +++ b/services/core/java/com/android/server/am/ActiveServices.java @@ -1949,20 +1949,30 @@ public final class ActiveServices { && isLegacyApp; } if (!showNow) { - // is the notification such that it should show right away? - showNow = r.foregroundNoti.shouldShowForegroundImmediately(); - // or is this an type of FGS that always shows immediately? - if (!showNow) { - switch (r.foregroundServiceType) { - case ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK: - case ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL: - case ServiceInfo.FOREGROUND_SERVICE_TYPE_CONNECTED_DEVICE: - case ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION: - if (DEBUG_FOREGROUND_SERVICE) { - Slog.d(TAG_SERVICE, "FGS " + r - + " type gets immediate display"); - } - showNow = true; + // has the app forced deferral? + if (!r.foregroundNoti.isForegroundDisplayForceDeferred()) { + // is the notification such that it should show right away? + showNow = r.foregroundNoti.shouldShowForegroundImmediately(); + if (DEBUG_FOREGROUND_SERVICE && showNow) { + Slog.d(TAG_SERVICE, "FGS " + r + + " notification policy says show immediately"); + } + // or is this an type of FGS that always shows immediately? + if (!showNow) { + switch (r.foregroundServiceType) { + case ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK: + case ServiceInfo.FOREGROUND_SERVICE_TYPE_PHONE_CALL: + case ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION: + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "FGS " + r + + " type gets immediate display"); + } + showNow = true; + } + } + } else { + if (DEBUG_FOREGROUND_SERVICE) { + Slog.d(TAG_SERVICE, "FGS " + r + " notification is app deferred"); } } }