From 026456eccee8390343a1cf46b8ef6bc3ded92ebd Mon Sep 17 00:00:00 2001 From: Yuri Lin Date: Tue, 28 Sep 2021 17:11:03 -0400 Subject: [PATCH] Add matchesCallFilter function to NotificationManager. This method takes a URI for a phone number that's calling and returns a boolean indicating whether the call would be allowed to interrupt the user. It's a light wrapper around the pre-existing (hidden) matchesCallFilter call that takes a Bundle with the same information. Bug: 192592755 Test: atest NotificationManagerTest Change-Id: I6e819b28b0f5047f005258bfd6044c6670628789 --- core/api/current.txt | 1 + core/api/test-current.txt | 2 +- .../android/app/INotificationManager.aidl | 1 + .../java/android/app/NotificationManager.java | 55 ++++++++++++++++++- .../NotificationManagerService.java | 7 ++- .../server/notification/ZenModeFiltering.java | 15 +++++ .../server/notification/ZenModeHelper.java | 4 ++ 7 files changed, 82 insertions(+), 3 deletions(-) diff --git a/core/api/current.txt b/core/api/current.txt index a1812528595ee..6c193ed20905c 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -6255,6 +6255,7 @@ package android.app { method public android.app.NotificationManager.Policy getNotificationPolicy(); method public boolean isNotificationListenerAccessGranted(android.content.ComponentName); method public boolean isNotificationPolicyAccessGranted(); + method @WorkerThread public boolean matchesCallFilter(@NonNull android.net.Uri); method public void notify(int, android.app.Notification); method public void notify(String, int, android.app.Notification); method public void notifyAsPackage(@NonNull String, @Nullable String, int, @NonNull android.app.Notification); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 96fa9c42dc812..aba3a9720f70b 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -303,10 +303,10 @@ package android.app { public class NotificationManager { method public void allowAssistantAdjustment(String); + method public void cleanUpCallersAfter(long); method public void disallowAssistantAdjustment(String); method public android.content.ComponentName getEffectsSuppressor(); method public boolean isNotificationPolicyAccessGrantedForPackage(@NonNull String); - method public boolean matchesCallFilter(android.os.Bundle); method @RequiresPermission(android.Manifest.permission.MANAGE_NOTIFICATION_LISTENERS) public void setNotificationListenerAccessGranted(@NonNull android.content.ComponentName, boolean, boolean); method @RequiresPermission(android.Manifest.permission.MANAGE_TOAST_RATE_LIMITING) public void setToastRateLimitingEnabled(boolean); method public void updateNotificationChannel(@NonNull String, int, @NonNull android.app.NotificationChannel); diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index 098492c8234b5..01885b27e84ba 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -175,6 +175,7 @@ interface INotificationManager ComponentName getEffectsSuppressor(); boolean matchesCallFilter(in Bundle extras); + void cleanUpCallersAfter(long timeThreshold); boolean isSystemConditionProviderEnabled(String path); boolean isNotificationListenerAccessGranted(in ComponentName listener); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index ccf1edb3fecc0..9be4adcbec753 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -25,6 +25,7 @@ import android.annotation.SuppressLint; import android.annotation.SystemApi; import android.annotation.SystemService; import android.annotation.TestApi; +import android.annotation.WorkerThread; import android.app.Notification.Builder; import android.compat.annotation.UnsupportedAppUsage; import android.content.ComponentName; @@ -1079,7 +1080,6 @@ public class NotificationManager { /** * @hide */ - @TestApi public boolean matchesCallFilter(Bundle extras) { INotificationManager service = getService(); try { @@ -1089,6 +1089,19 @@ public class NotificationManager { } } + /** + * @hide + */ + @TestApi + public void cleanUpCallersAfter(long timeThreshold) { + INotificationManager service = getService(); + try { + service.cleanUpCallersAfter(timeThreshold); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * @hide */ @@ -2544,6 +2557,46 @@ public class NotificationManager { } } + /** + * Returns whether a call from the provided URI is permitted to notify the user. + *

+ * A true return value indicates one of the following: Do Not Disturb is not currently active; + * or the caller is a repeat caller and the current policy allows interruptions from repeat + * callers; or the caller is in the user's set of contacts whose calls are allowed to interrupt + * Do Not Disturb. + *

+ *

+ * If Do Not Disturb is enabled and either no interruptions or only alarms are allowed, this + * method will return false regardless of input. + *

+ *

+ * The provided URI must meet the requirements for a URI associated with a + * {@link Person}: it may be the {@code String} representation of a + * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, or a + * mailto: or tel: schema URI matching an entry in the + * Contacts database. See also {@link Person.Builder#setUri} and + * {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} + * for more information. + *

+ *

+ * NOTE: This method calls into Contacts, which may take some time, and should not be called + * on the main thread. + *

+ * + * @param uri A URI representing a caller. Must not be null. + * @return A boolean indicating whether a call from the URI provided would be allowed to + * interrupt the user given the current filter. + */ + @WorkerThread + public boolean matchesCallFilter(@NonNull Uri uri) { + Bundle extras = new Bundle(); + ArrayList pList = new ArrayList<>(); + pList.add(new Person.Builder().setUri(uri.toString()).build()); + extras.putParcelableArrayList(Notification.EXTRA_PEOPLE_LIST, pList); + + return matchesCallFilter(extras); + } + /** @hide */ public static int zenModeToInterruptionFilter(int zen) { switch (zen) { diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index b7744c7eefeee..75d7893a4273a 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -5022,7 +5022,6 @@ public class NotificationManagerService extends SystemService { @Override public boolean matchesCallFilter(Bundle extras) { - enforceSystemOrSystemUI("INotificationManager.matchesCallFilter"); return mZenModeHelper.matchesCallFilter( Binder.getCallingUserHandle(), extras, @@ -5031,6 +5030,12 @@ public class NotificationManagerService extends SystemService { MATCHES_CALL_FILTER_TIMEOUT_AFFINITY); } + @Override + public void cleanUpCallersAfter(long timeThreshold) { + enforceSystemOrSystemUI("INotificationManager.cleanUpCallersAfter"); + mZenModeHelper.cleanUpCallersAfter(timeThreshold); + } + @Override public boolean isSystemConditionProviderEnabled(String path) { enforceSystemOrSystemUI("INotificationManager.isSystemConditionProviderEnabled"); diff --git a/services/core/java/com/android/server/notification/ZenModeFiltering.java b/services/core/java/com/android/server/notification/ZenModeFiltering.java index 4d1985590d7d0..0f526d4d63431 100644 --- a/services/core/java/com/android/server/notification/ZenModeFiltering.java +++ b/services/core/java/com/android/server/notification/ZenModeFiltering.java @@ -311,6 +311,10 @@ public class ZenModeFiltering { } } + protected void cleanUpCallersAfter(long timeThreshold) { + REPEAT_CALLERS.cleanUpCallsAfter(timeThreshold); + } + private static class RepeatCallers { // Person : time private final ArrayMap mCalls = new ArrayMap<>(); @@ -346,6 +350,17 @@ public class ZenModeFiltering { } } + // Clean up all calls that occurred after the given time. + // Used only for tests, to clean up after testing. + private synchronized void cleanUpCallsAfter(long timeThreshold) { + for (int i = mCalls.size() - 1; i >= 0; i--) { + final long time = mCalls.valueAt(i); + if (time > timeThreshold) { + mCalls.removeAt(i); + } + } + } + private void setThresholdMinutes(Context context) { if (mThresholdMinutes <= 0) { mThresholdMinutes = context.getResources().getInteger(com.android.internal.R.integer diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java index 16a0b7e39a072..93f1b4741bd6c 100644 --- a/services/core/java/com/android/server/notification/ZenModeHelper.java +++ b/services/core/java/com/android/server/notification/ZenModeHelper.java @@ -188,6 +188,10 @@ public class ZenModeHelper { mFiltering.recordCall(record); } + protected void cleanUpCallersAfter(long timeThreshold) { + mFiltering.cleanUpCallersAfter(timeThreshold); + } + public boolean shouldIntercept(NotificationRecord record) { synchronized (mConfig) { return mFiltering.shouldIntercept(mZenMode, mConsolidatedPolicy, record);