diff --git a/api/test-current.txt b/api/test-current.txt index a39c154e7ddf2..17199fa3c7a45 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -328,8 +328,14 @@ package android.app { } public class NotificationManager { + method public void allowAssistantCapability(String); + method public void disallowAssistantCapability(String); + method @NonNull public java.util.List getAllowedAssistantCapabilities(); + method @Nullable public android.content.ComponentName getAllowedNotificationAssistant(); method public android.content.ComponentName getEffectsSuppressor(); + method public boolean isNotificationAssistantAccessGranted(@NonNull android.content.ComponentName); method public boolean matchesCallFilter(android.os.Bundle); + method public void setNotificationAssistantAccessGranted(@Nullable android.content.ComponentName, boolean); } public final class PictureInPictureParams implements android.os.Parcelable { @@ -2454,10 +2460,49 @@ package android.service.contentcapture { package android.service.notification { + public final class Adjustment implements android.os.Parcelable { + ctor public Adjustment(String, String, android.os.Bundle, CharSequence, int); + ctor public Adjustment(@NonNull String, @NonNull String, @NonNull android.os.Bundle, @NonNull CharSequence, @NonNull android.os.UserHandle); + method public int describeContents(); + method @NonNull public CharSequence getExplanation(); + method @NonNull public String getKey(); + method @NonNull public String getPackage(); + method @NonNull public android.os.Bundle getSignals(); + method public int getUser(); + method @NonNull public android.os.UserHandle getUserHandle(); + method public void writeToParcel(android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator CREATOR; + field public static final String KEY_CONTEXTUAL_ACTIONS = "key_contextual_actions"; + field public static final String KEY_IMPORTANCE = "key_importance"; + field public static final String KEY_SNOOZE_CRITERIA = "key_snooze_criteria"; + field public static final String KEY_TEXT_REPLIES = "key_text_replies"; + field public static final String KEY_USER_SENTIMENT = "key_user_sentiment"; + } + @Deprecated public abstract class ConditionProviderService extends android.app.Service { method @Deprecated public boolean isBound(); } + public abstract class NotificationAssistantService extends android.service.notification.NotificationListenerService { + ctor public NotificationAssistantService(); + method public final void adjustNotification(@NonNull android.service.notification.Adjustment); + method public final void adjustNotifications(@NonNull java.util.List); + method public void onActionInvoked(@NonNull String, @NonNull android.app.Notification.Action, int); + method @NonNull public final android.os.IBinder onBind(@Nullable android.content.Intent); + method public void onCapabilitiesChanged(); + method public void onNotificationDirectReplied(@NonNull String); + method @Nullable public abstract android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification); + method @Nullable public android.service.notification.Adjustment onNotificationEnqueued(@NonNull android.service.notification.StatusBarNotification, @NonNull android.app.NotificationChannel); + method public void onNotificationExpansionChanged(@NonNull String, boolean, boolean); + method public abstract void onNotificationSnoozedUntilContext(@NonNull android.service.notification.StatusBarNotification, @NonNull String); + method public void onNotificationsSeen(@NonNull java.util.List); + method public void onSuggestedReplySent(@NonNull String, @NonNull CharSequence, int); + method public final void unsnoozeNotification(@NonNull String); + field public static final String SERVICE_INTERFACE = "android.service.notification.NotificationAssistantService"; + field public static final int SOURCE_FROM_APP = 0; // 0x0 + field public static final int SOURCE_FROM_ASSISTANT = 1; // 0x1 + } + public abstract class NotificationListenerService extends android.app.Service { method public void onNotificationRemoved(@NonNull android.service.notification.StatusBarNotification, @NonNull android.service.notification.NotificationListenerService.RankingMap, @NonNull android.service.notification.NotificationStats, int); } diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 0bec21fcda900..d54aca89c50d7 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -1169,6 +1169,7 @@ public class NotificationManager { * @hide */ @SystemApi + @TestApi public boolean isNotificationAssistantAccessGranted(@NonNull ComponentName assistant) { INotificationManager service = getService(); try { @@ -1204,6 +1205,7 @@ public class NotificationManager { * @hide */ @SystemApi + @TestApi public @NonNull @Adjustment.Keys List getAllowedAssistantCapabilities() { INotificationManager service = getService(); try { @@ -1213,6 +1215,32 @@ public class NotificationManager { } } + /** + * @hide + */ + @TestApi + public void allowAssistantCapability(String capability) { + INotificationManager service = getService(); + try { + service.allowAssistantCapability(capability); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * @hide + */ + @TestApi + public void disallowAssistantCapability(String capability) { + INotificationManager service = getService(); + try { + service.disallowAssistantCapability(capability); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** @hide */ public boolean isNotificationPolicyAccessGrantedForPackage(String pkg) { INotificationManager service = getService(); @@ -1310,6 +1338,7 @@ public class NotificationManager { * @hide */ @SystemApi + @TestApi public void setNotificationAssistantAccessGranted(@Nullable ComponentName assistant, boolean granted) { INotificationManager service = getService(); @@ -1332,6 +1361,7 @@ public class NotificationManager { /** @hide */ @SystemApi + @TestApi public @Nullable ComponentName getAllowedNotificationAssistant() { INotificationManager service = getService(); try { diff --git a/core/java/android/service/notification/Adjustment.java b/core/java/android/service/notification/Adjustment.java index 8ba9a8357c650..e81ce7f85ac17 100644 --- a/core/java/android/service/notification/Adjustment.java +++ b/core/java/android/service/notification/Adjustment.java @@ -18,6 +18,7 @@ package android.service.notification; import android.annotation.NonNull; import android.annotation.StringDef; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Notification; import android.os.Bundle; import android.os.Parcel; @@ -40,6 +41,7 @@ import java.lang.annotation.RetentionPolicy; * @hide */ @SystemApi +@TestApi public final class Adjustment implements Parcelable { private final String mPackage; private final String mKey; @@ -130,6 +132,7 @@ public final class Adjustment implements Parcelable { * @hide */ @SystemApi + @TestApi public Adjustment(String pkg, String key, Bundle signals, CharSequence explanation, int user) { mPackage = pkg; mKey = key; @@ -212,6 +215,7 @@ public final class Adjustment implements Parcelable { /** @hide */ @SystemApi + @TestApi public int getUser() { return mUser; } diff --git a/core/java/android/service/notification/INotificationListener.aidl b/core/java/android/service/notification/INotificationListener.aidl index 22104b5089cf5..8bb5f97ca8780 100644 --- a/core/java/android/service/notification/INotificationListener.aidl +++ b/core/java/android/service/notification/INotificationListener.aidl @@ -53,4 +53,5 @@ oneway interface INotificationListener void onNotificationDirectReply(String key); void onSuggestedReplySent(String key, in CharSequence reply, int source); void onActionClicked(String key, in Notification.Action action, int source); + void onCapabilitiesChanged(); } diff --git a/core/java/android/service/notification/NotificationAssistantService.java b/core/java/android/service/notification/NotificationAssistantService.java index b81725d99d2bb..b4fd397012337 100644 --- a/core/java/android/service/notification/NotificationAssistantService.java +++ b/core/java/android/service/notification/NotificationAssistantService.java @@ -23,6 +23,7 @@ import android.annotation.NonNull; import android.annotation.Nullable; import android.annotation.SdkConstant; import android.annotation.SystemApi; +import android.annotation.TestApi; import android.app.Notification; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -65,6 +66,7 @@ import java.util.List; * @hide */ @SystemApi +@TestApi public abstract class NotificationAssistantService extends NotificationListenerService { private static final String TAG = "NotificationAssistants"; @@ -357,6 +359,11 @@ public abstract class NotificationAssistantService extends NotificationListenerS args.argi2 = source; mHandler.obtainMessage(MyHandler.MSG_ON_ACTION_INVOKED, args).sendToTarget(); } + + @Override + public void onCapabilitiesChanged() { + mHandler.obtainMessage(MyHandler.MSG_ON_CAPABILITIES_CHANGED).sendToTarget(); + } } private final class MyHandler extends Handler { @@ -367,6 +374,7 @@ public abstract class NotificationAssistantService extends NotificationListenerS public static final int MSG_ON_NOTIFICATION_DIRECT_REPLY_SENT = 5; public static final int MSG_ON_SUGGESTED_REPLY_SENT = 6; public static final int MSG_ON_ACTION_INVOKED = 7; + public static final int MSG_ON_CAPABILITIES_CHANGED = 8; public MyHandler(Looper looper) { super(looper, null, false); @@ -448,6 +456,10 @@ public abstract class NotificationAssistantService extends NotificationListenerS onActionInvoked(key, action, source); break; } + case MSG_ON_CAPABILITIES_CHANGED: { + onCapabilitiesChanged(); + break; + } } } } diff --git a/core/java/android/service/notification/NotificationListenerService.java b/core/java/android/service/notification/NotificationListenerService.java index 333868a2f08ac..016f4aacef2a3 100644 --- a/core/java/android/service/notification/NotificationListenerService.java +++ b/core/java/android/service/notification/NotificationListenerService.java @@ -1398,6 +1398,11 @@ public abstract class NotificationListenerService extends Service { // no-op in the listener } + @Override + public void onCapabilitiesChanged() { + // no-op in the listener + } + @Override public void onNotificationChannelModification(String pkgName, UserHandle user, NotificationChannel channel, diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml index ed198e60902b4..e4a93e740c4cd 100644 --- a/data/etc/privapp-permissions-platform.xml +++ b/data/etc/privapp-permissions-platform.xml @@ -309,6 +309,7 @@ applications that come with the platform + diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml index 84110e359572f..70f9778624dda 100644 --- a/packages/Shell/AndroidManifest.xml +++ b/packages/Shell/AndroidManifest.xml @@ -154,6 +154,7 @@ + diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index 1dd36eeb8a74e..dec47a1082fcd 100644 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -2869,7 +2869,7 @@ public class NotificationManagerService extends SystemService { @Override public void allowAssistantCapability(String adjustmentType) { - checkCallerIsSystemOrShell(); + checkCallerIsSystemOrSystemUiOrShell(); mAssistants.allowAdjustmentType(adjustmentType); handleSavePolicyFile(); @@ -2877,7 +2877,7 @@ public class NotificationManagerService extends SystemService { @Override public void disallowAssistantCapability(String adjustmentType) { - checkCallerIsSystemOrShell(); + checkCallerIsSystemOrSystemUiOrShell(); mAssistants.disallowAdjustmentType(adjustmentType); handleSavePolicyFile(); @@ -3811,7 +3811,7 @@ public class NotificationManagerService extends SystemService { @Override public ComponentName getAllowedNotificationAssistantForUser(int userId) { - checkCallerIsSystem(); + checkCallerIsSystemOrSystemUiOrShell(); List allowedComponents = mAssistants.getAllowedComponents(userId); if (allowedComponents.size() > 1) { throw new IllegalStateException( @@ -3894,7 +3894,7 @@ public class NotificationManagerService extends SystemService { @Override public void setNotificationAssistantAccessGrantedForUser(ComponentName assistant, int userId, boolean granted) { - checkCallerIsSystemOrShell(); + checkCallerIsSystemOrSystemUiOrShell(); mAssistants.setUserSet(userId, true); final long identity = Binder.clearCallingIdentity(); try { @@ -3929,10 +3929,6 @@ public class NotificationManagerService extends SystemService { } } if (!foundEnqueued) { - // adjustment arrived too late to apply to enqueued; apply to posted - // However, since the notification is now posted and may have alerted, - // ignore any importance related adjustments - adjustment.getSignals().remove(Adjustment.KEY_IMPORTANCE); applyAdjustmentFromAssistant(token, adjustment); } } @@ -4123,7 +4119,7 @@ public class NotificationManagerService extends SystemService { } return; } - if (mAllowedManagedServicePackages.test(assistant.getPackageName(), userId, + if (!granted || mAllowedManagedServicePackages.test(assistant.getPackageName(), userId, mAssistants.getRequiredPermission())) { mConditionProviders.setPackageOrComponentEnabled(assistant.flattenToString(), userId, false, granted); @@ -6995,6 +6991,16 @@ public class NotificationManagerService extends SystemService { throw new SecurityException("Disallowed call for uid " + Binder.getCallingUid()); } + private void checkCallerIsSystemOrSystemUiOrShell() { + if (Binder.getCallingUid() == Process.SHELL_UID) { + return; + } + if (isCallerSystemOrPhone()) { + return; + } + getContext().enforceCallingPermission(android.Manifest.permission.STATUS_BAR_SERVICE, null); + } + private void checkCallerIsSystemOrSameApp(String pkg) { if (isCallerSystemOrPhone()) { return; @@ -7302,7 +7308,7 @@ public class NotificationManagerService extends SystemService { @GuardedBy("mLock") private ArrayMap mUserSetMap = new ArrayMap<>(); - private List mAllowedAdjustments = new ArrayList<>(); + private Set mAllowedAdjustments = new ArraySet<>(); public NotificationAssistants(Context context, Object lock, UserProfiles up, IPackageManager pm) { @@ -7390,12 +7396,18 @@ public class NotificationManagerService extends SystemService { synchronized (mLock) { mAllowedAdjustments.add(type); } + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> notifyCapabilitiesChanged(info)); + } } protected void disallowAdjustmentType(String type) { synchronized (mLock) { mAllowedAdjustments.remove(type); } + for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) { + mHandler.post(() -> notifyCapabilitiesChanged(info)); + } } protected List getAllowedAssistantCapabilities() { @@ -7455,6 +7467,15 @@ public class NotificationManagerService extends SystemService { setUserSet(userId, userSet); } + private void notifyCapabilitiesChanged(final ManagedServiceInfo info) { + final INotificationListener assistant = (INotificationListener) info.service; + try { + assistant.onCapabilitiesChanged(); + } catch (RemoteException ex) { + Slog.e(TAG, "unable to notify assistant (capabilities): " + assistant, ex); + } + } + private void notifySeen(final ManagedServiceInfo info, final ArrayList keys) { final INotificationListener assistant = (INotificationListener) info.service; diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java index cf42aa8c6b256..b0788253d0a7a 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java @@ -2890,7 +2890,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { } @Test - public void testApplyEnqueuedAdjustmentFromAssistant_importance_onTime() throws Exception { + public void testApplyEnqueuedAdjustmentFromAssistant_importance() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); mService.addEnqueuedNotification(r); NotificationManagerService.WorkerHandler handler = mock( @@ -2907,25 +2907,6 @@ public class NotificationManagerServiceTest extends UiServiceTestCase { assertEquals(IMPORTANCE_LOW, r.getImportance()); } - @Test - public void testApplyEnqueuedAdjustmentFromAssistant_importance_tooLate() throws Exception { - final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel); - mService.addNotification(r); - NotificationManagerService.WorkerHandler handler = mock( - NotificationManagerService.WorkerHandler.class); - mService.setHandler(handler); - when(mAssistants.isSameUser(eq(null), anyInt())).thenReturn(true); - - Bundle signals = new Bundle(); - signals.putInt(KEY_IMPORTANCE, IMPORTANCE_LOW); - Adjustment adjustment = new Adjustment( - r.sbn.getPackageName(), r.getKey(), signals, "", r.getUser().getIdentifier()); - mBinderService.applyEnqueuedAdjustmentFromAssistant(null, adjustment); - - assertEquals(IMPORTANCE_DEFAULT, r.getImportance()); - assertFalse(r.hasAdjustment(KEY_IMPORTANCE)); - } - @Test public void testApplyEnqueuedAdjustmentFromAssistant_crossUser() throws Exception { final NotificationRecord r = generateNotificationRecord(mTestNotificationChannel);