Loading core/java/android/service/notification/INotificationListener.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.service.notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.content.pm.ParceledListSlice; import android.os.UserHandle; import android.service.notification.NotificationStats; import android.service.notification.IStatusBarNotificationHolder; Loading Loading @@ -45,4 +46,5 @@ oneway interface INotificationListener // assistants only void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); void onNotificationsSeen(in List<String> keys); } core/java/android/service/notification/NotificationAssistantService.java +24 −0 Original line number Diff line number Diff line Loading @@ -148,6 +148,14 @@ public abstract class NotificationAssistantService extends NotificationListenerS onNotificationRemoved(sbn, rankingMap, reason); } /** * Implement this to know when a user has seen notifications, as triggered by * {@link #setNotificationsShown(String[])}. */ public void onNotificationsSeen(List<String> keys) { } /** * Updates a notification. N.B. this won’t cause * an existing notification to alert, but might allow a future update to Loading Loading @@ -236,11 +244,20 @@ public abstract class NotificationAssistantService extends NotificationListenerS mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_SNOOZED, args).sendToTarget(); } @Override public void onNotificationsSeen(List<String> keys) { SomeArgs args = SomeArgs.obtain(); args.arg1 = keys; mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATIONS_SEEN, args).sendToTarget(); } } private final class MyHandler extends Handler { public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1; public static final int MSG_ON_NOTIFICATION_SNOOZED = 2; public static final int MSG_ON_NOTIFICATIONS_SEEN = 3; public MyHandler(Looper looper) { super(looper, null, false); Loading Loading @@ -275,6 +292,13 @@ public abstract class NotificationAssistantService extends NotificationListenerS onNotificationSnoozedUntilContext(sbn, snoozeCriterionId); break; } case MSG_ON_NOTIFICATIONS_SEEN: { SomeArgs args = (SomeArgs) msg.obj; List<String> keys = (List<String>) args.arg1; args.recycle(); onNotificationsSeen(keys); break; } } } } Loading core/java/android/service/notification/NotificationListenerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -1331,6 +1331,12 @@ public abstract class NotificationListenerService extends Service { // no-op in the listener } @Override public void onNotificationsSeen(List<String> keys) throws RemoteException { // no-op in the listener } @Override public void onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId) Loading packages/ExtServices/src/android/ext/services/notification/AgingHelper.java 0 → 100644 +172 −0 Original line number Diff line number Diff line /** * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.ext.services.notification; import static android.app.NotificationManager.IMPORTANCE_MIN; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.ext.services.notification.NotificationCategorizer.Category; import android.net.Uri; import android.util.ArraySet; import android.util.Slog; import java.util.Set; public class AgingHelper { private final static String TAG = "AgingHelper"; private final boolean DEBUG = false; private static final String AGING_ACTION = AgingHelper.class.getSimpleName() + ".EVALUATE"; private static final int REQUEST_CODE_AGING = 1; private static final String AGING_SCHEME = "aging"; private static final String EXTRA_KEY = "key"; private static final String EXTRA_CATEGORY = "category"; private static final int HOUR_MS = 1000 * 60 * 60; private static final int TWO_HOURS_MS = 2 * HOUR_MS; private Context mContext; private NotificationCategorizer mNotificationCategorizer; private AlarmManager mAm; private Callback mCallback; // The set of keys we've scheduled alarms for private Set<String> mAging = new ArraySet<>(); public AgingHelper(Context context, NotificationCategorizer categorizer, Callback callback) { mNotificationCategorizer = categorizer; mContext = context; mAm = mContext.getSystemService(AlarmManager.class); mCallback = callback; IntentFilter filter = new IntentFilter(AGING_ACTION); filter.addDataScheme(AGING_SCHEME); mContext.registerReceiver(mBroadcastReceiver, filter); } // NAS lifecycle methods public void onNotificationSeen(NotificationEntry entry) { // user has strong opinions about this notification. we can't down rank it, so don't bother. if (entry.getChannel().isImportanceLocked()) { return; } @Category int category = mNotificationCategorizer.getCategory(entry); // already very low if (category == NotificationCategorizer.CATEGORY_MIN) { return; } if (entry.hasSeen()) { if (category == NotificationCategorizer.CATEGORY_ONGOING || category > NotificationCategorizer.CATEGORY_REMINDER) { scheduleAging(entry.getSbn().getKey(), category, TWO_HOURS_MS); } else { scheduleAging(entry.getSbn().getKey(), category, HOUR_MS); } mAging.add(entry.getSbn().getKey()); } } public void onNotificationPosted(NotificationEntry entry) { cancelAging(entry.getSbn().getKey()); } public void onNotificationRemoved(String key) { cancelAging(key); } public void onDestroy() { mContext.unregisterReceiver(mBroadcastReceiver); } // Aging private void scheduleAging(String key, @Category int category, long duration) { if (mAging.contains(key)) { // already scheduled. Don't reset aging just because the user saw the noti again. return; } final PendingIntent pi = createPendingIntent(key, category); long time = System.currentTimeMillis() + duration; if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + key + " in ms: " + duration); mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi); } private void cancelAging(String key) { final PendingIntent pi = createPendingIntent(key); mAm.cancel(pi); mAging.remove(key); } private Intent createBaseIntent(String key) { return new Intent(AGING_ACTION) .setData(new Uri.Builder().scheme(AGING_SCHEME).appendPath(key).build()); } private Intent createAgingIntent(String key, @Category int category) { Intent intent = createBaseIntent(key); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_CATEGORY, category) .putExtra(EXTRA_KEY, key); return intent; } private PendingIntent createPendingIntent(String key, @Category int category) { return PendingIntent.getBroadcast(mContext, REQUEST_CODE_AGING, createAgingIntent(key, category), PendingIntent.FLAG_UPDATE_CURRENT); } private PendingIntent createPendingIntent(String key) { return PendingIntent.getBroadcast(mContext, REQUEST_CODE_AGING, createBaseIntent(key), PendingIntent.FLAG_UPDATE_CURRENT); } private void demote(String key, @Category int category) { int newImportance = IMPORTANCE_MIN; // TODO: Change "aged" importance based on category mCallback.sendAdjustment(key, newImportance); } protected interface Callback { void sendAdjustment(String key, int newImportance); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) { Slog.d(TAG, "Reposting notification"); } if (AGING_ACTION.equals(intent.getAction())) { demote(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_CATEGORY, NotificationCategorizer.CATEGORY_EVERYTHING_ELSE)); } } }; } packages/ExtServices/src/android/ext/services/notification/Assistant.java +77 −7 Original line number Diff line number Diff line Loading @@ -18,23 +18,28 @@ package android.ext.services.notification; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.NotificationListenerService.Ranking .USER_SENTIMENT_NEGATIVE; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.AlarmManager; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.content.ContentResolver; import android.content.Context; import android.content.pm.IPackageManager; import android.database.ContentObserver; import android.ext.services.notification.AgingHelper.Callback; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.UserHandle; import android.os.storage.StorageManager; import android.provider.Settings; import android.service.notification.Adjustment; Loading Loading @@ -64,6 +69,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; /** Loading @@ -89,15 +95,17 @@ public class Assistant extends NotificationAssistantService { private int mStreakLimit; private SmartActionsHelper mSmartActionsHelper; private NotificationCategorizer mNotificationCategorizer; private AgingHelper mAgingHelper; // key : impressions tracker // TODO: prune deleted channels and apps final ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>(); // SBN key : channel id ArrayMap<String, String> mLiveNotifications = new ArrayMap<>(); private final ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>(); // SBN key : entry protected ArrayMap<String, NotificationEntry> mLiveNotifications = new ArrayMap<>(); private Ranking mFakeRanking = null; private AtomicFile mFile = null; private IPackageManager mPackageManager; protected SettingsObserver mSettingsObserver; public Assistant() { Loading @@ -108,9 +116,13 @@ public class Assistant extends NotificationAssistantService { super.onCreate(); // Contexts are correctly hooked up by the creation step, which is required for the observer // to be hooked up/initialized. mPackageManager = ActivityThread.getPackageManager(); mSettingsObserver = new SettingsObserver(mHandler); mSmartActionsHelper = new SmartActionsHelper(); mNotificationCategorizer = new NotificationCategorizer(); mAgingHelper = new AgingHelper(getContext(), mNotificationCategorizer, new AgingCallback()); } private void loadFile() { Loading Loading @@ -157,7 +169,7 @@ public class Assistant extends NotificationAssistantService { } } private void saveFile() throws IOException { private void saveFile() { AsyncTask.execute(() -> { final FileOutputStream stream; try { Loading Loading @@ -200,6 +212,9 @@ public class Assistant extends NotificationAssistantService { public Adjustment onNotificationEnqueued(StatusBarNotification sbn, NotificationChannel channel) { if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey() + " on " + channel.getId()); if (!isForCurrentUser(sbn)) { return null; } NotificationEntry entry = new NotificationEntry( ActivityThread.getPackageManager(), sbn, channel); ArrayList<Notification.Action> actions = Loading @@ -222,7 +237,7 @@ public class Assistant extends NotificationAssistantService { signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies); } if (mNotificationCategorizer.shouldSilence(entry)) { signals.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW); signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); } return new Adjustment( Loading @@ -237,8 +252,13 @@ public class Assistant extends NotificationAssistantService { public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey()); try { if (!isForCurrentUser(sbn)) { return; } Ranking ranking = getRanking(sbn.getKey(), rankingMap); if (ranking != null && ranking.getChannel() != null) { NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, ranking.getChannel()); String key = getKey( sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId()); ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, Loading @@ -248,7 +268,8 @@ public class Assistant extends NotificationAssistantService { sbn.getPackageName(), sbn.getKey(), sbn.getUserId())); } mkeyToImpressions.put(key, ci); mLiveNotifications.put(sbn.getKey(), ranking.getChannel().getId()); mLiveNotifications.put(sbn.getKey(), entry); mAgingHelper.onNotificationPosted(entry); } } catch (Throwable e) { Log.e(TAG, "Error occurred processing post", e); Loading @@ -259,8 +280,11 @@ public class Assistant extends NotificationAssistantService { public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, NotificationStats stats, int reason) { try { if (!isForCurrentUser(sbn)) { return; } boolean updatedImpressions = false; String channelId = mLiveNotifications.remove(sbn.getKey()); String channelId = mLiveNotifications.remove(sbn.getKey()).getChannel().getId(); String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId); synchronized (mkeyToImpressions) { ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, Loading Loading @@ -301,6 +325,22 @@ public class Assistant extends NotificationAssistantService { String snoozeCriterionId) { } @Override public void onNotificationsSeen(List<String> keys) { if (keys == null) { return; } for (String key : keys) { NotificationEntry entry = mLiveNotifications.get(key); if (entry != null) { entry.setSeen(); mAgingHelper.onNotificationSeen(entry); } } } @Override public void onListenerConnected() { if (DEBUG) Log.i(TAG, "CONNECTED"); Loading @@ -318,6 +358,17 @@ public class Assistant extends NotificationAssistantService { } } @Override public void onListenerDisconnected() { if (mAgingHelper != null) { mAgingHelper.onDestroy(); } } private boolean isForCurrentUser(StatusBarNotification sbn) { return sbn != null && sbn.getUserId() == UserHandle.myUserId(); } protected String getKey(String pkg, int userId, String channelId) { return pkg + "|" + userId + "|" + channelId; } Loading Loading @@ -360,6 +411,11 @@ public class Assistant extends NotificationAssistantService { mSystemContext = context; } @VisibleForTesting public void setPackageManager(IPackageManager pm) { mPackageManager = pm; } @VisibleForTesting public ChannelImpressions getImpressions(String key) { synchronized (mkeyToImpressions) { Loading @@ -380,6 +436,20 @@ public class Assistant extends NotificationAssistantService { return impressions; } protected final class AgingCallback implements Callback { @Override public void sendAdjustment(String key, int newImportance) { NotificationEntry entry = mLiveNotifications.get(key); if (entry != null) { Bundle bundle = new Bundle(); bundle.putInt(KEY_IMPORTANCE, newImportance); Adjustment adjustment = new Adjustment(entry.getSbn().getPackageName(), key, bundle, "aging", entry.getSbn().getUserId()); adjustNotification(adjustment); } } } /** * Observer for updates on blocking helper threshold values. */ Loading Loading
core/java/android/service/notification/INotificationListener.aidl +2 −0 Original line number Diff line number Diff line Loading @@ -18,6 +18,7 @@ package android.service.notification; import android.app.NotificationChannel; import android.app.NotificationChannelGroup; import android.content.pm.ParceledListSlice; import android.os.UserHandle; import android.service.notification.NotificationStats; import android.service.notification.IStatusBarNotificationHolder; Loading Loading @@ -45,4 +46,5 @@ oneway interface INotificationListener // assistants only void onNotificationEnqueuedWithChannel(in IStatusBarNotificationHolder notificationHolder, in NotificationChannel channel); void onNotificationSnoozedUntilContext(in IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId); void onNotificationsSeen(in List<String> keys); }
core/java/android/service/notification/NotificationAssistantService.java +24 −0 Original line number Diff line number Diff line Loading @@ -148,6 +148,14 @@ public abstract class NotificationAssistantService extends NotificationListenerS onNotificationRemoved(sbn, rankingMap, reason); } /** * Implement this to know when a user has seen notifications, as triggered by * {@link #setNotificationsShown(String[])}. */ public void onNotificationsSeen(List<String> keys) { } /** * Updates a notification. N.B. this won’t cause * an existing notification to alert, but might allow a future update to Loading Loading @@ -236,11 +244,20 @@ public abstract class NotificationAssistantService extends NotificationListenerS mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATION_SNOOZED, args).sendToTarget(); } @Override public void onNotificationsSeen(List<String> keys) { SomeArgs args = SomeArgs.obtain(); args.arg1 = keys; mHandler.obtainMessage(MyHandler.MSG_ON_NOTIFICATIONS_SEEN, args).sendToTarget(); } } private final class MyHandler extends Handler { public static final int MSG_ON_NOTIFICATION_ENQUEUED = 1; public static final int MSG_ON_NOTIFICATION_SNOOZED = 2; public static final int MSG_ON_NOTIFICATIONS_SEEN = 3; public MyHandler(Looper looper) { super(looper, null, false); Loading Loading @@ -275,6 +292,13 @@ public abstract class NotificationAssistantService extends NotificationListenerS onNotificationSnoozedUntilContext(sbn, snoozeCriterionId); break; } case MSG_ON_NOTIFICATIONS_SEEN: { SomeArgs args = (SomeArgs) msg.obj; List<String> keys = (List<String>) args.arg1; args.recycle(); onNotificationsSeen(keys); break; } } } } Loading
core/java/android/service/notification/NotificationListenerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -1331,6 +1331,12 @@ public abstract class NotificationListenerService extends Service { // no-op in the listener } @Override public void onNotificationsSeen(List<String> keys) throws RemoteException { // no-op in the listener } @Override public void onNotificationSnoozedUntilContext( IStatusBarNotificationHolder notificationHolder, String snoozeCriterionId) Loading
packages/ExtServices/src/android/ext/services/notification/AgingHelper.java 0 → 100644 +172 −0 Original line number Diff line number Diff line /** * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package android.ext.services.notification; import static android.app.NotificationManager.IMPORTANCE_MIN; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.ext.services.notification.NotificationCategorizer.Category; import android.net.Uri; import android.util.ArraySet; import android.util.Slog; import java.util.Set; public class AgingHelper { private final static String TAG = "AgingHelper"; private final boolean DEBUG = false; private static final String AGING_ACTION = AgingHelper.class.getSimpleName() + ".EVALUATE"; private static final int REQUEST_CODE_AGING = 1; private static final String AGING_SCHEME = "aging"; private static final String EXTRA_KEY = "key"; private static final String EXTRA_CATEGORY = "category"; private static final int HOUR_MS = 1000 * 60 * 60; private static final int TWO_HOURS_MS = 2 * HOUR_MS; private Context mContext; private NotificationCategorizer mNotificationCategorizer; private AlarmManager mAm; private Callback mCallback; // The set of keys we've scheduled alarms for private Set<String> mAging = new ArraySet<>(); public AgingHelper(Context context, NotificationCategorizer categorizer, Callback callback) { mNotificationCategorizer = categorizer; mContext = context; mAm = mContext.getSystemService(AlarmManager.class); mCallback = callback; IntentFilter filter = new IntentFilter(AGING_ACTION); filter.addDataScheme(AGING_SCHEME); mContext.registerReceiver(mBroadcastReceiver, filter); } // NAS lifecycle methods public void onNotificationSeen(NotificationEntry entry) { // user has strong opinions about this notification. we can't down rank it, so don't bother. if (entry.getChannel().isImportanceLocked()) { return; } @Category int category = mNotificationCategorizer.getCategory(entry); // already very low if (category == NotificationCategorizer.CATEGORY_MIN) { return; } if (entry.hasSeen()) { if (category == NotificationCategorizer.CATEGORY_ONGOING || category > NotificationCategorizer.CATEGORY_REMINDER) { scheduleAging(entry.getSbn().getKey(), category, TWO_HOURS_MS); } else { scheduleAging(entry.getSbn().getKey(), category, HOUR_MS); } mAging.add(entry.getSbn().getKey()); } } public void onNotificationPosted(NotificationEntry entry) { cancelAging(entry.getSbn().getKey()); } public void onNotificationRemoved(String key) { cancelAging(key); } public void onDestroy() { mContext.unregisterReceiver(mBroadcastReceiver); } // Aging private void scheduleAging(String key, @Category int category, long duration) { if (mAging.contains(key)) { // already scheduled. Don't reset aging just because the user saw the noti again. return; } final PendingIntent pi = createPendingIntent(key, category); long time = System.currentTimeMillis() + duration; if (DEBUG) Slog.d(TAG, "Scheduling evaluate for " + key + " in ms: " + duration); mAm.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, pi); } private void cancelAging(String key) { final PendingIntent pi = createPendingIntent(key); mAm.cancel(pi); mAging.remove(key); } private Intent createBaseIntent(String key) { return new Intent(AGING_ACTION) .setData(new Uri.Builder().scheme(AGING_SCHEME).appendPath(key).build()); } private Intent createAgingIntent(String key, @Category int category) { Intent intent = createBaseIntent(key); intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND) .putExtra(EXTRA_CATEGORY, category) .putExtra(EXTRA_KEY, key); return intent; } private PendingIntent createPendingIntent(String key, @Category int category) { return PendingIntent.getBroadcast(mContext, REQUEST_CODE_AGING, createAgingIntent(key, category), PendingIntent.FLAG_UPDATE_CURRENT); } private PendingIntent createPendingIntent(String key) { return PendingIntent.getBroadcast(mContext, REQUEST_CODE_AGING, createBaseIntent(key), PendingIntent.FLAG_UPDATE_CURRENT); } private void demote(String key, @Category int category) { int newImportance = IMPORTANCE_MIN; // TODO: Change "aged" importance based on category mCallback.sendAdjustment(key, newImportance); } protected interface Callback { void sendAdjustment(String key, int newImportance); } private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { if (DEBUG) { Slog.d(TAG, "Reposting notification"); } if (AGING_ACTION.equals(intent.getAction())) { demote(intent.getStringExtra(EXTRA_KEY), intent.getIntExtra(EXTRA_CATEGORY, NotificationCategorizer.CATEGORY_EVERYTHING_ELSE)); } } }; }
packages/ExtServices/src/android/ext/services/notification/Assistant.java +77 −7 Original line number Diff line number Diff line Loading @@ -18,23 +18,28 @@ package android.ext.services.notification; import static android.app.NotificationManager.IMPORTANCE_LOW; import static android.app.NotificationManager.IMPORTANCE_MIN; import static android.service.notification.Adjustment.KEY_IMPORTANCE; import static android.service.notification.NotificationListenerService.Ranking .USER_SENTIMENT_NEGATIVE; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityThread; import android.app.AlarmManager; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationChannel; import android.content.ContentResolver; import android.content.Context; import android.content.pm.IPackageManager; import android.database.ContentObserver; import android.ext.services.notification.AgingHelper.Callback; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Environment; import android.os.Handler; import android.os.UserHandle; import android.os.storage.StorageManager; import android.provider.Settings; import android.service.notification.Adjustment; Loading Loading @@ -64,6 +69,7 @@ import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.List; import java.util.Map; /** Loading @@ -89,15 +95,17 @@ public class Assistant extends NotificationAssistantService { private int mStreakLimit; private SmartActionsHelper mSmartActionsHelper; private NotificationCategorizer mNotificationCategorizer; private AgingHelper mAgingHelper; // key : impressions tracker // TODO: prune deleted channels and apps final ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>(); // SBN key : channel id ArrayMap<String, String> mLiveNotifications = new ArrayMap<>(); private final ArrayMap<String, ChannelImpressions> mkeyToImpressions = new ArrayMap<>(); // SBN key : entry protected ArrayMap<String, NotificationEntry> mLiveNotifications = new ArrayMap<>(); private Ranking mFakeRanking = null; private AtomicFile mFile = null; private IPackageManager mPackageManager; protected SettingsObserver mSettingsObserver; public Assistant() { Loading @@ -108,9 +116,13 @@ public class Assistant extends NotificationAssistantService { super.onCreate(); // Contexts are correctly hooked up by the creation step, which is required for the observer // to be hooked up/initialized. mPackageManager = ActivityThread.getPackageManager(); mSettingsObserver = new SettingsObserver(mHandler); mSmartActionsHelper = new SmartActionsHelper(); mNotificationCategorizer = new NotificationCategorizer(); mAgingHelper = new AgingHelper(getContext(), mNotificationCategorizer, new AgingCallback()); } private void loadFile() { Loading Loading @@ -157,7 +169,7 @@ public class Assistant extends NotificationAssistantService { } } private void saveFile() throws IOException { private void saveFile() { AsyncTask.execute(() -> { final FileOutputStream stream; try { Loading Loading @@ -200,6 +212,9 @@ public class Assistant extends NotificationAssistantService { public Adjustment onNotificationEnqueued(StatusBarNotification sbn, NotificationChannel channel) { if (DEBUG) Log.i(TAG, "ENQUEUED " + sbn.getKey() + " on " + channel.getId()); if (!isForCurrentUser(sbn)) { return null; } NotificationEntry entry = new NotificationEntry( ActivityThread.getPackageManager(), sbn, channel); ArrayList<Notification.Action> actions = Loading @@ -222,7 +237,7 @@ public class Assistant extends NotificationAssistantService { signals.putCharSequenceArrayList(Adjustment.KEY_SMART_REPLIES, smartReplies); } if (mNotificationCategorizer.shouldSilence(entry)) { signals.putInt(Adjustment.KEY_IMPORTANCE, IMPORTANCE_LOW); signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); } return new Adjustment( Loading @@ -237,8 +252,13 @@ public class Assistant extends NotificationAssistantService { public void onNotificationPosted(StatusBarNotification sbn, RankingMap rankingMap) { if (DEBUG) Log.i(TAG, "POSTED " + sbn.getKey()); try { if (!isForCurrentUser(sbn)) { return; } Ranking ranking = getRanking(sbn.getKey(), rankingMap); if (ranking != null && ranking.getChannel() != null) { NotificationEntry entry = new NotificationEntry(mPackageManager, sbn, ranking.getChannel()); String key = getKey( sbn.getPackageName(), sbn.getUserId(), ranking.getChannel().getId()); ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, Loading @@ -248,7 +268,8 @@ public class Assistant extends NotificationAssistantService { sbn.getPackageName(), sbn.getKey(), sbn.getUserId())); } mkeyToImpressions.put(key, ci); mLiveNotifications.put(sbn.getKey(), ranking.getChannel().getId()); mLiveNotifications.put(sbn.getKey(), entry); mAgingHelper.onNotificationPosted(entry); } } catch (Throwable e) { Log.e(TAG, "Error occurred processing post", e); Loading @@ -259,8 +280,11 @@ public class Assistant extends NotificationAssistantService { public void onNotificationRemoved(StatusBarNotification sbn, RankingMap rankingMap, NotificationStats stats, int reason) { try { if (!isForCurrentUser(sbn)) { return; } boolean updatedImpressions = false; String channelId = mLiveNotifications.remove(sbn.getKey()); String channelId = mLiveNotifications.remove(sbn.getKey()).getChannel().getId(); String key = getKey(sbn.getPackageName(), sbn.getUserId(), channelId); synchronized (mkeyToImpressions) { ChannelImpressions ci = mkeyToImpressions.getOrDefault(key, Loading Loading @@ -301,6 +325,22 @@ public class Assistant extends NotificationAssistantService { String snoozeCriterionId) { } @Override public void onNotificationsSeen(List<String> keys) { if (keys == null) { return; } for (String key : keys) { NotificationEntry entry = mLiveNotifications.get(key); if (entry != null) { entry.setSeen(); mAgingHelper.onNotificationSeen(entry); } } } @Override public void onListenerConnected() { if (DEBUG) Log.i(TAG, "CONNECTED"); Loading @@ -318,6 +358,17 @@ public class Assistant extends NotificationAssistantService { } } @Override public void onListenerDisconnected() { if (mAgingHelper != null) { mAgingHelper.onDestroy(); } } private boolean isForCurrentUser(StatusBarNotification sbn) { return sbn != null && sbn.getUserId() == UserHandle.myUserId(); } protected String getKey(String pkg, int userId, String channelId) { return pkg + "|" + userId + "|" + channelId; } Loading Loading @@ -360,6 +411,11 @@ public class Assistant extends NotificationAssistantService { mSystemContext = context; } @VisibleForTesting public void setPackageManager(IPackageManager pm) { mPackageManager = pm; } @VisibleForTesting public ChannelImpressions getImpressions(String key) { synchronized (mkeyToImpressions) { Loading @@ -380,6 +436,20 @@ public class Assistant extends NotificationAssistantService { return impressions; } protected final class AgingCallback implements Callback { @Override public void sendAdjustment(String key, int newImportance) { NotificationEntry entry = mLiveNotifications.get(key); if (entry != null) { Bundle bundle = new Bundle(); bundle.putInt(KEY_IMPORTANCE, newImportance); Adjustment adjustment = new Adjustment(entry.getSbn().getPackageName(), key, bundle, "aging", entry.getSbn().getUserId()); adjustNotification(adjustment); } } } /** * Observer for updates on blocking helper threshold values. */ Loading