From 2a128746b3466e75b6040147fa831fe12cc2ccbb Mon Sep 17 00:00:00 2001 From: Julia Reynolds Date: Mon, 28 Nov 2016 14:29:25 -0500 Subject: [PATCH] Allow notifications to be autocanceled at a time. Test: cts Change-Id: I24ebcab2c0fa2028ec5d2984dc3b7ec19391b590 --- api/current.txt | 4 +- api/system-current.txt | 4 +- api/test-current.txt | 4 +- core/java/android/app/Notification.java | 20 +++++++ .../NotificationListenerService.java | 4 +- core/res/AndroidManifest.xml | 1 + .../NotificationManagerService.java | 55 +++++++++++++++++++ .../notification/NotificationRecord.java | 2 + .../statusbartest/NotificationTestList.java | 11 ++++ 9 files changed, 100 insertions(+), 5 deletions(-) diff --git a/api/current.txt b/api/current.txt index ad28b94fa1a79..eae2c697a6623 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5016,6 +5016,7 @@ package android.app { method public android.graphics.drawable.Icon getLargeIcon(); method public android.graphics.drawable.Icon getSmallIcon(); method public java.lang.String getSortKey(); + method public long getTimeout(); method public void writeToParcel(android.os.Parcel, int); field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT; field public static final java.lang.String CATEGORY_ALARM = "alarm"; @@ -5244,6 +5245,7 @@ package android.app { method public android.app.Notification.Builder setSubText(java.lang.CharSequence); method public android.app.Notification.Builder setTicker(java.lang.CharSequence); method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews); + method public android.app.Notification.Builder setTimeout(long); method public android.app.Notification.Builder setUsesChronometer(boolean); method public android.app.Notification.Builder setVibrate(long[]); method public android.app.Notification.Builder setVisibility(int); @@ -35873,9 +35875,9 @@ package android.service.notification { field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf field public static final int REASON_SNOOZED = 18; // 0x12 + field public static final int REASON_TIMEOUT = 19; // 0x13 field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10 field public static final int REASON_USER_STOPPED = 6; // 0x6 - field public static final int REASON_USER_SWITCH = 19; // 0x13 field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1 field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2 diff --git a/api/system-current.txt b/api/system-current.txt index b9e89f61e5e02..81f2c2968bca7 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5174,6 +5174,7 @@ package android.app { method public static java.lang.Class getNotificationStyleClass(java.lang.String); method public android.graphics.drawable.Icon getSmallIcon(); method public java.lang.String getSortKey(); + method public long getTimeout(); method public void writeToParcel(android.os.Parcel, int); field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT; field public static final java.lang.String CATEGORY_ALARM = "alarm"; @@ -5404,6 +5405,7 @@ package android.app { method public android.app.Notification.Builder setSubText(java.lang.CharSequence); method public android.app.Notification.Builder setTicker(java.lang.CharSequence); method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews); + method public android.app.Notification.Builder setTimeout(long); method public android.app.Notification.Builder setUsesChronometer(boolean); method public android.app.Notification.Builder setVibrate(long[]); method public android.app.Notification.Builder setVisibility(int); @@ -38861,9 +38863,9 @@ package android.service.notification { field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf field public static final int REASON_SNOOZED = 18; // 0x12 + field public static final int REASON_TIMEOUT = 19; // 0x13 field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10 field public static final int REASON_USER_STOPPED = 6; // 0x6 - field public static final int REASON_USER_SWITCH = 19; // 0x13 field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1 field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2 diff --git a/api/test-current.txt b/api/test-current.txt index 0e6548e3c2e64..4701c9d82160c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5026,6 +5026,7 @@ package android.app { method public android.graphics.drawable.Icon getLargeIcon(); method public android.graphics.drawable.Icon getSmallIcon(); method public java.lang.String getSortKey(); + method public long getTimeout(); method public void writeToParcel(android.os.Parcel, int); field public static final android.media.AudioAttributes AUDIO_ATTRIBUTES_DEFAULT; field public static final java.lang.String CATEGORY_ALARM = "alarm"; @@ -5254,6 +5255,7 @@ package android.app { method public android.app.Notification.Builder setSubText(java.lang.CharSequence); method public android.app.Notification.Builder setTicker(java.lang.CharSequence); method public deprecated android.app.Notification.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews); + method public android.app.Notification.Builder setTimeout(long); method public android.app.Notification.Builder setUsesChronometer(boolean); method public android.app.Notification.Builder setVibrate(long[]); method public android.app.Notification.Builder setVisibility(int); @@ -35994,9 +35996,9 @@ package android.service.notification { field public static final int REASON_PACKAGE_SUSPENDED = 14; // 0xe field public static final int REASON_PROFILE_TURNED_OFF = 15; // 0xf field public static final int REASON_SNOOZED = 18; // 0x12 + field public static final int REASON_TIMEOUT = 19; // 0x13 field public static final int REASON_UNAUTOBUNDLED = 16; // 0x10 field public static final int REASON_USER_STOPPED = 6; // 0x6 - field public static final int REASON_USER_SWITCH = 19; // 0x13 field public static final java.lang.String SERVICE_INTERFACE = "android.service.notification.NotificationListenerService"; field public static final int SUPPRESSED_EFFECT_SCREEN_OFF = 1; // 0x1 field public static final int SUPPRESSED_EFFECT_SCREEN_ON = 2; // 0x2 diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 82917d27d0d6b..4172ed7e57ea2 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -1022,6 +1022,7 @@ public class Notification implements Parcelable private Icon mLargeIcon; private String mChannelId; + private long mTimeout; /** * Structure to encapsulate a named action that can be shown as part of this notification. @@ -1766,6 +1767,7 @@ public class Notification implements Parcelable if (parcel.readInt() != 0) { mChannelId = parcel.readString(); } + mTimeout = parcel.readLong(); } @Override @@ -1872,6 +1874,7 @@ public class Notification implements Parcelable that.color = this.color; that.mChannelId = this.mChannelId; + that.mTimeout = this.mTimeout; if (!heavy) { that.lightenPayload(); // will clean out extras @@ -2128,6 +2131,7 @@ public class Notification implements Parcelable } else { parcel.writeInt(0); } + parcel.writeLong(mTimeout); } /** @@ -2324,6 +2328,13 @@ public class Notification implements Parcelable return mChannelId; } + /** + * Returns the time at which this notification should be canceled, if it's not canceled already. + */ + public long getTimeout() { + return mTimeout; + } + /** * The small icon representing this notification in the status bar and content view. * @@ -2531,6 +2542,15 @@ public class Notification implements Parcelable return this; } + /** + * Specifies the time at which this notification should be canceled, if it is not already + * canceled. + */ + public Builder setTimeout(long when) { + mN.mTimeout = when; + return this; + } + /** * Add a timestamp pertaining to the notification (usually the time the event occurred). * diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 517b305e0a161..417be602be5b9 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -183,8 +183,8 @@ public abstract class NotificationListenerService extends Service { public static final int REASON_CHANNEL_BANNED = 17; /** Notification was snoozed. */ public static final int REASON_SNOOZED = 18; - /** Notification no longer visible because of user switch */ - public static final int REASON_USER_SWITCH = 19; + /** Notification was canceled due to timeout */ + public static final int REASON_TIMEOUT = 19; /** * The full trim of the StatusBarNotification including all its features. diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml index 6d48862ea132a..6b36e71a092b9 100644 --- a/core/res/AndroidManifest.xml +++ b/core/res/AndroidManifest.xml @@ -443,6 +443,7 @@ + diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 8b53b97dd79f6..45bdb9c170d78 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -32,6 +32,7 @@ import static android.service.notification.NotificationListenerService.REASON_PA import static android.service.notification.NotificationListenerService.REASON_PACKAGE_SUSPENDED; import static android.service.notification.NotificationListenerService.REASON_PROFILE_TURNED_OFF; import static android.service.notification.NotificationListenerService.REASON_SNOOZED; +import static android.service.notification.NotificationListenerService.REASON_TIMEOUT; import static android.service.notification.NotificationListenerService.REASON_UNAUTOBUNDLED; import static android.service.notification.NotificationListenerService.REASON_USER_STOPPED; import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS; @@ -50,6 +51,7 @@ import android.Manifest; import android.annotation.Nullable; import android.app.ActivityManager; import android.app.ActivityManagerInternal; +import android.app.AlarmManager; import android.app.AppGlobals; import android.app.AppOpsManager; import android.app.AutomaticZenRule; @@ -165,6 +167,7 @@ import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; @@ -229,6 +232,12 @@ public class NotificationManagerService extends SystemService { private static final long DELAY_FOR_ASSISTANT_TIME = 100; + private static final String ACTION_NOTIFICATION_TIMEOUT = + NotificationManagerService.class.getSimpleName() + ".TIMEOUT"; + private static final int REQUEST_CODE_TIMEOUT = 1; + private static final String SCHEME_TIMEOUT = "timeout"; + private static final String EXTRA_KEY = "key"; + private IActivityManager mAm; private IPackageManager mPackageManager; private PackageManager mPackageManagerClient; @@ -237,6 +246,7 @@ public class NotificationManagerService extends SystemService { @Nullable StatusBarManagerInternal mStatusBar; Vibrator mVibrator; private WindowManagerInternal mWindowManagerInternal; + private AlarmManager mAlarmManager; final IBinder mForegroundToken = new Binder(); private Handler mHandler; @@ -682,6 +692,29 @@ public class NotificationManagerService extends SystemService { updateLightsLocked(); } + private final BroadcastReceiver mNotificationTimeoutReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action == null) { + return; + } + if (ACTION_NOTIFICATION_TIMEOUT.equals(action)) { + final NotificationRecord record; + synchronized (mNotificationLock) { + record = findNotificationByKeyLocked(intent.getStringExtra(EXTRA_KEY)); + } + if (record != null) { + cancelNotification(record.sbn.getUid(), record.sbn.getInitialPid(), + record.sbn.getPackageName(), record.sbn.getTag(), + record.sbn.getId(), 0, + Notification.FLAG_FOREGROUND_SERVICE, true, record.getUserId(), + REASON_TIMEOUT, null); + } + } + } + }; + private final BroadcastReceiver mPackageIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -966,6 +999,7 @@ public class NotificationManagerService extends SystemService { mAppOps = (AppOpsManager) getContext().getSystemService(Context.APP_OPS_SERVICE); mVibrator = (Vibrator) getContext().getSystemService(Context.VIBRATOR_SERVICE); mAppUsageStats = LocalServices.getService(UsageStatsManagerInternal.class); + mAlarmManager = (AlarmManager) getContext().getSystemService(Context.ALARM_SERVICE); mHandler = new WorkerHandler(looper); mRankingThread.start(); @@ -1132,6 +1166,10 @@ public class NotificationManagerService extends SystemService { getContext().registerReceiverAsUser(mPackageIntentReceiver, UserHandle.ALL, sdFilter, null, null); + IntentFilter timeoutFilter = new IntentFilter(ACTION_NOTIFICATION_TIMEOUT); + timeoutFilter.addDataScheme(SCHEME_TIMEOUT); + getContext().registerReceiver(mNotificationTimeoutReceiver, timeoutFilter); + mSettingsObserver = new SettingsObserver(mHandler); mArchive = new Archive(resources.getInteger( @@ -3011,6 +3049,7 @@ public class NotificationManagerService extends SystemService { public void run() { synchronized (mNotificationLock) { mEnqueuedNotifications.add(r); + scheduleTimeoutLocked(r); if (mSnoozeHelper.isSnoozed(userId, r.sbn.getPackageName(), r.getKey())) { // TODO: log to event log @@ -3240,6 +3279,22 @@ public class NotificationManagerService extends SystemService { } } + @VisibleForTesting + void scheduleTimeoutLocked(NotificationRecord record) { + if (record.getNotification().getTimeout() > System.currentTimeMillis()) { + final PendingIntent pi = PendingIntent.getBroadcast(getContext(), + REQUEST_CODE_TIMEOUT, + new Intent(ACTION_NOTIFICATION_TIMEOUT) + .setData(new Uri.Builder().scheme(SCHEME_TIMEOUT) + .appendPath(record.getKey()).build()) + .addFlags(Intent.FLAG_RECEIVER_FOREGROUND) + .putExtra(EXTRA_KEY, record.getKey()), + PendingIntent.FLAG_UPDATE_CURRENT); + mAlarmManager.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, record.getNotification().getTimeout(), pi); + } + } + @VisibleForTesting void buzzBeepBlinkLocked(NotificationRecord record) { boolean buzz = false; diff --git a/services/core/java/com/android/server/notification/NotificationRecord.java b/services/core/java/com/android/server/notification/NotificationRecord.java index 2a5a25f8e8668..8998128272623 100644 --- a/services/core/java/com/android/server/notification/NotificationRecord.java +++ b/services/core/java/com/android/server/notification/NotificationRecord.java @@ -41,6 +41,7 @@ import android.service.notification.StatusBarNotification; import android.text.TextUtils; import android.util.Log; import android.util.Slog; +import android.util.TimeUtils; import com.android.internal.annotations.VisibleForTesting; import com.android.server.EventLogTags; @@ -318,6 +319,7 @@ public final class NotificationRecord { pw.println(prefix + " vibrate=" + Arrays.toString(notification.vibrate)); pw.println(prefix + String.format(" led=0x%08x onMs=%d offMs=%d", notification.ledARGB, notification.ledOnMS, notification.ledOffMS)); + pw.println(prefix + " timeout=" + TimeUtils.formatForLogging(notification.getTimeout())); if (notification.actions != null && notification.actions.length > 0) { pw.println(prefix + " actions={"); final int N = notification.actions.length; diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java index da27ea916a18a..56aad23eecc9c 100644 --- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java +++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java @@ -424,6 +424,17 @@ public class NotificationTestList extends TestActivity mNM.notify("secret", 7012, n); } }, + new Test("1 minute timeout") { + public void run() + { + Notification n = new Notification.Builder(NotificationTestList.this) + .setSmallIcon(R.drawable.icon2) + .setContentTitle("timeout in a minute") + .setTimeout(System.currentTimeMillis() + (1000 * 60)) + .build(); + mNM.notify("timeout_min", 7013, n); + } + }, new Test("Off") { public void run() { PowerManager pm = (PowerManager)NotificationTestList.this.getSystemService(Context.POWER_SERVICE);