Add filtering to notifications sent to NLSes

Test: atest
Bug: 173052211
Change-Id: Id1b3440ff375543b546f2a599b791a70e28d8bb8
This commit is contained in:
Julia Reynolds
2020-12-14 10:41:31 -05:00
parent 655f6cdce6
commit 51582aed15
11 changed files with 809 additions and 54 deletions

View File

@@ -35,6 +35,7 @@ import android.service.notification.Condition;
import android.service.notification.IConditionListener;
import android.service.notification.IConditionProvider;
import android.service.notification.INotificationListener;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.StatusBarNotification;
import android.app.AutomaticZenRule;
import android.service.notification.ZenModeConfig;
@@ -224,4 +225,7 @@ interface INotificationManager
boolean getPrivateNotificationsAllowed();
long pullStats(long startNs, int report, boolean doAgg, out List<ParcelFileDescriptor> stats);
NotificationListenerFilter getListenerFilter(in ComponentName cn, int userId);
void setListenerFilter(in ComponentName cn, int userId, in NotificationListenerFilter nlf);
}

View File

@@ -0,0 +1,20 @@
/*
* Copyright (c) 2020, 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.service.notification;
parcelable NotificationListenerFilter;

View File

@@ -0,0 +1,102 @@
/**
* Copyright (c) 2020, The Android Open Source Project
*
* Licensed under the Apache License, 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.service.notification;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.ArraySet;
/**
* Specifies a filter for what types of notifications should be bridged to notification listeners.
* Each requested listener will have their own filter instance.
* @hide
*/
public class NotificationListenerFilter implements Parcelable {
private int mAllowedNotificationTypes;
private ArraySet<String> mDisallowedPackages;
public NotificationListenerFilter() {
mAllowedNotificationTypes = FLAG_FILTER_TYPE_CONVERSATIONS
| FLAG_FILTER_TYPE_ALERTING
| FLAG_FILTER_TYPE_SILENT;
mDisallowedPackages = new ArraySet<>();
}
public NotificationListenerFilter(int types, ArraySet<String> pkgs) {
mAllowedNotificationTypes = types;
mDisallowedPackages = pkgs;
}
/**
* @hide
*/
protected NotificationListenerFilter(Parcel in) {
mAllowedNotificationTypes = in.readInt();
mDisallowedPackages = (ArraySet<String>) in.readArraySet(String.class.getClassLoader());
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(mAllowedNotificationTypes);
dest.writeArraySet(mDisallowedPackages);
}
public static final Creator<NotificationListenerFilter> CREATOR =
new Creator<NotificationListenerFilter>() {
@Override
public NotificationListenerFilter createFromParcel(Parcel in) {
return new NotificationListenerFilter(in);
}
@Override
public NotificationListenerFilter[] newArray(int size) {
return new NotificationListenerFilter[size];
}
};
public boolean isTypeAllowed(int type) {
return (mAllowedNotificationTypes & type) != 0;
}
public boolean isPackageAllowed(String pkg) {
return !mDisallowedPackages.contains(pkg);
}
public int getTypes() {
return mAllowedNotificationTypes;
}
public ArraySet<String> getDisallowedPackages() {
return mDisallowedPackages;
}
public void setTypes(int types) {
mAllowedNotificationTypes = types;
}
public void setDisallowedPackages(ArraySet<String> pkgs) {
mDisallowedPackages = pkgs;
}
@Override
public int describeContents() {
return 0;
}
}

View File

@@ -241,6 +241,23 @@ public abstract class NotificationListenerService extends Service {
})
public @interface NotificationCancelReason{};
/**
* A flag value indicating that this notification listener can see conversation type
* notifications.
* @hide
*/
public static final int FLAG_FILTER_TYPE_CONVERSATIONS = 1;
/**
* A flag value indicating that this notification listener can see altering type notifications.
* @hide
*/
public static final int FLAG_FILTER_TYPE_ALERTING = 2;
/**
* A flag value indicating that this notification listener can see silent type notifications.
* @hide
*/
public static final int FLAG_FILTER_TYPE_SILENT = 4;
/**
* The full trim of the StatusBarNotification including all its features.
*

View File

@@ -0,0 +1,124 @@
/*
* Copyright (C) 2020 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.service.notification;
import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
import static com.google.common.truth.Truth.assertThat;
import android.app.NotificationChannel;
import android.os.Parcel;
import android.util.ArraySet;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
@SmallTest
public class NotificationListenerFilterTest {
@Test
public void testEmptyConstructor() {
NotificationListenerFilter nlf = new NotificationListenerFilter();
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isTrue();
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_ALERTING)).isTrue();
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isTrue();
assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_CONVERSATIONS
| FLAG_FILTER_TYPE_ALERTING
| FLAG_FILTER_TYPE_SILENT);
assertThat(nlf.getDisallowedPackages()).isEmpty();
assertThat(nlf.isPackageAllowed("pkg1")).isTrue();
}
@Test
public void testConstructor() {
ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse();
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_ALERTING)).isTrue();
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
assertThat(nlf.getDisallowedPackages()).contains("pkg1");
assertThat(nlf.getDisallowedPackages()).contains("pkg2");
assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
assertThat(nlf.isPackageAllowed("pkg2")).isFalse();
}
@Test
public void testSetDisallowedPackages() {
NotificationListenerFilter nlf = new NotificationListenerFilter();
ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1"});
nlf.setDisallowedPackages(pkgs);
assertThat(nlf.isPackageAllowed("pkg1")).isFalse();
}
@Test
public void testSetTypes() {
NotificationListenerFilter nlf = new NotificationListenerFilter();
nlf.setTypes(FLAG_FILTER_TYPE_ALERTING | FLAG_FILTER_TYPE_SILENT);
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse();
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_ALERTING)).isTrue();
assertThat(nlf.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isTrue();
assertThat(nlf.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING
| FLAG_FILTER_TYPE_SILENT);
}
@Test
public void testDescribeContents() {
final int expected = 0;
ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
assertThat(nlf.describeContents()).isEqualTo(expected);
}
@Test
public void testParceling() {
ArraySet<String> pkgs = new ArraySet<>(new String[] {"pkg1", "pkg2"});
NotificationListenerFilter nlf =
new NotificationListenerFilter(FLAG_FILTER_TYPE_ALERTING, pkgs);
Parcel parcel = Parcel.obtain();
nlf.writeToParcel(parcel, 0);
parcel.setDataPosition(0);
NotificationListenerFilter nlf1 =
NotificationListenerFilter.CREATOR.createFromParcel(parcel);
assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_CONVERSATIONS)).isFalse();
assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_ALERTING)).isTrue();
assertThat(nlf1.isTypeAllowed(FLAG_FILTER_TYPE_SILENT)).isFalse();
assertThat(nlf1.getTypes()).isEqualTo(FLAG_FILTER_TYPE_ALERTING);
assertThat(nlf1.getDisallowedPackages()).contains("pkg1");
assertThat(nlf1.getDisallowedPackages()).contains("pkg2");
assertThat(nlf1.isPackageAllowed("pkg1")).isFalse();
assertThat(nlf1.isPackageAllowed("pkg2")).isFalse();
}
}

View File

@@ -73,7 +73,6 @@ import com.android.server.utils.TimingsTraceAndSlog;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.IOException;
import java.io.PrintWriter;
@@ -544,7 +543,8 @@ abstract public class ManagedServices {
/**
* This is called to process tags other than {@link #TAG_MANAGED_SERVICES}.
*/
protected void readExtraTag(String tag, TypedXmlPullParser parser) throws IOException {}
protected void readExtraTag(String tag, TypedXmlPullParser parser)
throws IOException, XmlPullParserException {}
protected final void migrateToXml() {
for (UserInfo user : mUm.getUsers()) {
@@ -1613,6 +1613,7 @@ abstract public class ManagedServices {
public boolean isSystem;
public ServiceConnection connection;
public int targetSdkVersion;
public Pair<ComponentName, Integer> mKey;
public ManagedServiceInfo(IInterface service, ComponentName component,
int userid, boolean isSystem, ServiceConnection connection, int targetSdkVersion) {
@@ -1622,6 +1623,7 @@ abstract public class ManagedServices {
this.isSystem = isSystem;
this.connection = connection;
this.targetSdkVersion = targetSdkVersion;
mKey = Pair.create(component, userid);
}
public boolean isGuest(ManagedServices host) {

View File

@@ -66,6 +66,9 @@ import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_CRITICAL;
import static android.os.IServiceManager.DUMP_FLAG_PRIORITY_NORMAL;
import static android.os.UserHandle.USER_NULL;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_CALL_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_EFFECTS;
import static android.service.notification.NotificationListenerService.HINT_HOST_DISABLE_NOTIFICATION_EFFECTS;
@@ -163,6 +166,8 @@ import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ParceledListSlice;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutServiceInternal;
import android.content.pm.UserInfo;
@@ -209,6 +214,7 @@ import android.service.notification.INotificationListener;
import android.service.notification.IStatusBarNotificationHolder;
import android.service.notification.ListenersDisablingEffectsProto;
import android.service.notification.NotificationAssistantService;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationRankingUpdate;
import android.service.notification.NotificationRecordProto;
@@ -285,6 +291,7 @@ import libcore.io.IoUtils;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import java.io.ByteArrayInputStream;
@@ -301,6 +308,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
@@ -1021,7 +1029,7 @@ public class NotificationManagerService extends SystemService {
nv.recycle();
}
reportUserInteraction(r);
mAssistants.notifyAssistantActionClicked(r.getSbn(), action, generatedByAssistant);
mAssistants.notifyAssistantActionClicked(r, action, generatedByAssistant);
}
}
@@ -1110,7 +1118,7 @@ public class NotificationManagerService extends SystemService {
reportSeen(r);
}
r.setVisibility(true, nv.rank, nv.count, mNotificationRecordLogger);
mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), true);
mAssistants.notifyAssistantVisibilityChangedLocked(r, true);
boolean isHun = (nv.location
== NotificationVisibility.NotificationLocation.LOCATION_FIRST_HEADS_UP);
// hasBeenVisiblyExpanded must be called after updating the expansion state of
@@ -1129,7 +1137,7 @@ public class NotificationManagerService extends SystemService {
NotificationRecord r = mNotificationsByKey.get(nv.key);
if (r == null) continue;
r.setVisibility(false, nv.rank, nv.count, mNotificationRecordLogger);
mAssistants.notifyAssistantVisibilityChangedLocked(r.getSbn(), false);
mAssistants.notifyAssistantVisibilityChangedLocked(r, false);
nv.recycle();
}
}
@@ -1161,7 +1169,7 @@ public class NotificationManagerService extends SystemService {
reportUserInteraction(r);
}
mAssistants.notifyAssistantExpansionChangedLocked(
r.getSbn(), userAction, expanded);
r.getSbn(), r.getNotificationType(), userAction, expanded);
}
}
}
@@ -1180,7 +1188,7 @@ public class NotificationManagerService extends SystemService {
NotificationRecordLogger.NotificationEvent.NOTIFICATION_DIRECT_REPLIED,
r);
reportUserInteraction(r);
mAssistants.notifyAssistantNotificationDirectReplyLocked(r.getSbn());
mAssistants.notifyAssistantNotificationDirectReplyLocked(r);
}
}
}
@@ -1227,7 +1235,8 @@ public class NotificationManagerService extends SystemService {
// Treat clicking on a smart reply as a user interaction.
reportUserInteraction(r);
mAssistants.notifyAssistantSuggestedReplySent(
r.getSbn(), reply, r.getSuggestionsGeneratedByAssistant());
r.getSbn(), r.getNotificationType(), reply,
r.getSuggestionsGeneratedByAssistant());
}
}
}
@@ -2241,7 +2250,8 @@ public class NotificationManagerService extends SystemService {
init(handler, new RankingHandlerWorker(mRankingThread.getLooper()),
AppGlobals.getPackageManager(), getContext().getPackageManager(),
getLocalService(LightsManager.class),
new NotificationListeners(AppGlobals.getPackageManager()),
new NotificationListeners(getContext(), mNotificationLock, mUserProfiles,
AppGlobals.getPackageManager()),
new NotificationAssistants(getContext(), mNotificationLock, mUserProfiles,
AppGlobals.getPackageManager()),
new ConditionProviders(getContext(), mUserProfiles, AppGlobals.getPackageManager()),
@@ -3252,6 +3262,21 @@ public class NotificationManagerService extends SystemService {
mHistoryManager.deleteNotificationHistoryItem(pkg, uid, postedTime);
}
@Override
public NotificationListenerFilter getListenerFilter(ComponentName cn, int userId) {
checkCallerIsSystem();
return mListeners.getNotificationListenerFilter(Pair.create(cn, userId));
}
@Override
public void setListenerFilter(ComponentName cn, int userId,
NotificationListenerFilter nlf) {
checkCallerIsSystem();
mListeners.setNotificationListenerFilter(Pair.create(cn, userId), nlf);
// TODO (b/173052211): cancel notifications for listeners that can no longer see them
handleSavePolicyFile();
}
@Override
public int getPackageImportance(String pkg) {
checkCallerIsSystemOrSameApp(pkg);
@@ -4268,7 +4293,7 @@ public class NotificationManagerService extends SystemService {
: mNotificationList.get(i);
if (r == null) continue;
StatusBarNotification sbn = r.getSbn();
if (!isVisibleToListener(sbn, info)) continue;
if (!isVisibleToListener(sbn, r.getNotificationType(), info)) continue;
StatusBarNotification sbnToSend =
(trim == TRIM_FULL) ? sbn : sbn.cloneLight();
list.add(sbnToSend);
@@ -4298,7 +4323,7 @@ public class NotificationManagerService extends SystemService {
final NotificationRecord r = snoozedRecords.get(i);
if (r == null) continue;
StatusBarNotification sbn = r.getSbn();
if (!isVisibleToListener(sbn, info)) continue;
if (!isVisibleToListener(sbn, r.getNotificationType(), info)) continue;
StatusBarNotification sbnToSend =
(trim == TRIM_FULL) ? sbn : sbn.cloneLight();
list.add(sbnToSend);
@@ -6339,7 +6364,7 @@ public class NotificationManagerService extends SystemService {
cancelNotificationLocked(r, false, REASON_SNOOZED, wasPosted, null);
updateLightsLocked();
if (mSnoozeCriterionId != null) {
mAssistants.notifyAssistantSnoozedLocked(r.getSbn(), mSnoozeCriterionId);
mAssistants.notifyAssistantSnoozedLocked(r, mSnoozeCriterionId);
mSnoozeHelper.snooze(r, mSnoozeCriterionId);
} else {
mSnoozeHelper.snooze(r, mDuration);
@@ -8812,7 +8837,7 @@ public class NotificationManagerService extends SystemService {
for (int i = 0; i < N; i++) {
NotificationRecord record = mNotificationList.get(i);
if (!isVisibleToListener(record.getSbn(), info)) {
if (!isVisibleToListener(record.getSbn(), record.getNotificationType(), info)) {
continue;
}
final String key = record.getSbn().getKey();
@@ -8886,11 +8911,21 @@ public class NotificationManagerService extends SystemService {
}
@VisibleForTesting
boolean isVisibleToListener(StatusBarNotification sbn, ManagedServiceInfo listener) {
boolean isVisibleToListener(StatusBarNotification sbn, int notificationType,
ManagedServiceInfo listener) {
if (!listener.enabledAndUserMatches(sbn.getUserId())) {
return false;
}
return isInteractionVisibleToListener(listener, sbn.getUserId());
if (!isInteractionVisibleToListener(listener, sbn.getUserId())) {
return false;
}
NotificationListenerFilter nls = mListeners.getNotificationListenerFilter(listener.mKey);
if (nls != null
&& (!nls.isTypeAllowed(notificationType)
|| !nls.isPackageAllowed(sbn.getPackageName()))) {
return false;
}
return true;
}
/**
@@ -9126,7 +9161,8 @@ public class NotificationManagerService extends SystemService {
for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
ArrayList<String> keys = new ArrayList<>(records.size());
for (NotificationRecord r : records) {
boolean sbnVisible = isVisibleToListener(r.getSbn(), info)
boolean sbnVisible = isVisibleToListener(
r.getSbn(), r.getNotificationType(), info)
&& info.isSameUser(r.getUserId());
if (sbnVisible) {
keys.add(r.getKey());
@@ -9241,6 +9277,7 @@ public class NotificationManagerService extends SystemService {
final StatusBarNotification sbn = r.getSbn();
notifyAssistantLocked(
sbn,
r.getNotificationType(),
true /* sameUserOnly */,
(assistant, sbnHolder) -> {
try {
@@ -9257,14 +9294,15 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void notifyAssistantVisibilityChangedLocked(
final StatusBarNotification sbn,
final NotificationRecord r,
final boolean isVisible) {
final String key = sbn.getKey();
final String key = r.getSbn().getKey();
if (DBG) {
Slog.d(TAG, "notifyAssistantVisibilityChangedLocked: " + key);
}
notifyAssistantLocked(
sbn,
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
(assistant, sbnHolder) -> {
try {
@@ -9278,11 +9316,13 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void notifyAssistantExpansionChangedLocked(
final StatusBarNotification sbn,
final int notificationType,
final boolean isUserAction,
final boolean isExpanded) {
final String key = sbn.getKey();
notifyAssistantLocked(
sbn,
notificationType,
true /* sameUserOnly */,
(assistant, sbnHolder) -> {
try {
@@ -9295,10 +9335,11 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void notifyAssistantNotificationDirectReplyLocked(
final StatusBarNotification sbn) {
final String key = sbn.getKey();
final NotificationRecord r) {
final String key = r.getKey();
notifyAssistantLocked(
sbn,
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
(assistant, sbnHolder) -> {
try {
@@ -9311,10 +9352,12 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void notifyAssistantSuggestedReplySent(
final StatusBarNotification sbn, CharSequence reply, boolean generatedByAssistant) {
final StatusBarNotification sbn, int notificationType,
CharSequence reply, boolean generatedByAssistant) {
final String key = sbn.getKey();
notifyAssistantLocked(
sbn,
notificationType,
true /* sameUserOnly */,
(assistant, sbnHolder) -> {
try {
@@ -9332,11 +9375,12 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
void notifyAssistantActionClicked(
final StatusBarNotification sbn, Notification.Action action,
final NotificationRecord r, Notification.Action action,
boolean generatedByAssistant) {
final String key = sbn.getKey();
final String key = r.getSbn().getKey();
notifyAssistantLocked(
sbn,
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
(assistant, sbnHolder) -> {
try {
@@ -9358,9 +9402,10 @@ public class NotificationManagerService extends SystemService {
*/
@GuardedBy("mNotificationLock")
private void notifyAssistantSnoozedLocked(
final StatusBarNotification sbn, final String snoozeCriterionId) {
final NotificationRecord r, final String snoozeCriterionId) {
notifyAssistantLocked(
sbn,
r.getSbn(),
r.getNotificationType(),
true /* sameUserOnly */,
(assistant, sbnHolder) -> {
try {
@@ -9384,6 +9429,7 @@ public class NotificationManagerService extends SystemService {
@GuardedBy("mNotificationLock")
private void notifyAssistantLocked(
final StatusBarNotification sbn,
int notificationType,
boolean sameUserOnly,
BiConsumer<INotificationListener, StatusBarNotificationHolder> callback) {
TrimCache trimCache = new TrimCache(sbn);
@@ -9397,7 +9443,7 @@ public class NotificationManagerService extends SystemService {
+ sameUserOnly + "], callback = [" + callback + "]");
}
for (final ManagedServiceInfo info : NotificationAssistants.this.getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, info)
boolean sbnVisible = isVisibleToListener(sbn, notificationType, info)
&& (!sameUserOnly || info.isSameUser(sbn.getUserId()));
if (debug) {
Slog.v(TAG, "notifyAssistantLocked info=" + info + " snbVisible=" + sbnVisible);
@@ -9451,11 +9497,22 @@ public class NotificationManagerService extends SystemService {
public class NotificationListeners extends ManagedServices {
static final String TAG_ENABLED_NOTIFICATION_LISTENERS = "enabled_listeners";
static final String TAG_REQUESTED_LISTENERS = "requested_listeners";
static final String TAG_REQUESTED_LISTENER = "listener";
static final String ATT_COMPONENT = "component";
static final String ATT_TYPES = "types";
static final String ATT_PKGS = "pkgs";
static final String TAG_APPROVED = "allowed";
static final String TAG_DISALLOWED= "disallowed";
static final String XML_SEPARATOR = ",";
private final ArraySet<ManagedServiceInfo> mLightTrimListeners = new ArraySet<>();
ArrayMap<Pair<ComponentName, Integer>, NotificationListenerFilter>
mRequestedNotificationListeners = new ArrayMap<>();
public NotificationListeners(IPackageManager pm) {
super(getContext(), mNotificationLock, mUserProfiles, pm);
public NotificationListeners(Context context, Object lock, UserProfiles userProfiles,
IPackageManager pm) {
super(context, lock, userProfiles, pm);
}
@Override
@@ -9551,6 +9608,59 @@ public class NotificationManagerService extends SystemService {
mLightTrimListeners.remove(removed);
}
@Override
public void onUserRemoved(int user) {
super.onUserRemoved(user);
for (int i = mRequestedNotificationListeners.size() - 1; i >= 0; i--) {
if (mRequestedNotificationListeners.keyAt(i).second == user) {
mRequestedNotificationListeners.removeAt(i);
}
}
}
@Override
public void onUserUnlocked(int user) {
int flags = PackageManager.GET_SERVICES | PackageManager.GET_META_DATA;
final PackageManager pmWrapper = mContext.getPackageManager();
List<ResolveInfo> installedServices = pmWrapper.queryIntentServicesAsUser(
new Intent(getConfig().serviceInterface), flags, user);
for (ResolveInfo resolveInfo : installedServices) {
ServiceInfo info = resolveInfo.serviceInfo;
if (!getConfig().bindPermission.equals(info.permission)) {
continue;
}
Pair key = Pair.create(info.getComponentName(), user);
if (!mRequestedNotificationListeners.containsKey(key)) {
mRequestedNotificationListeners.put(key, new NotificationListenerFilter());
}
}
super.onUserUnlocked(user);
}
@Override
public void onPackagesChanged(boolean removingPackage, String[] pkgList, int[] uidList) {
super.onPackagesChanged(removingPackage, pkgList, uidList);
// Since the default behavior is to allow everything, we don't need to explicitly
// handle package add or update. they will be added to the xml file on next boot or
// when the user tries to change the settings.
if (removingPackage) {
for (int i = 0; i < pkgList.length; i++) {
String pkg = pkgList[i];
int userId = UserHandle.getUserId(uidList[i]);
for (int j = mRequestedNotificationListeners.size() - 1; j >= 0; j--) {
Pair<ComponentName, Integer> key = mRequestedNotificationListeners.keyAt(j);
if (key.second == userId && key.first.getPackageName().equals(pkg)) {
mRequestedNotificationListeners.removeAt(j);
}
}
}
}
}
@Override
protected String getRequiredPermission() {
return null;
@@ -9563,6 +9673,75 @@ public class NotificationManagerService extends SystemService {
return true;
}
@Override
protected void readExtraTag(String tag, TypedXmlPullParser parser)
throws IOException, XmlPullParserException {
if (TAG_REQUESTED_LISTENERS.equals(tag)) {
final int listenersOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, listenersOuterDepth)) {
if (!TAG_REQUESTED_LISTENER.equals(parser.getName())) {
continue;
}
final int userId = XmlUtils.readIntAttribute(parser, ATT_USER_ID);
final ComponentName cn = ComponentName.unflattenFromString(
XmlUtils.readStringAttribute(parser, ATT_COMPONENT));
int approved = FLAG_FILTER_TYPE_CONVERSATIONS | FLAG_FILTER_TYPE_ALERTING
| FLAG_FILTER_TYPE_SILENT;
ArraySet<String> disallowedPkgs = new ArraySet<>();
final int listenerOuterDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, listenerOuterDepth)) {
if (TAG_APPROVED.equals(parser.getName())) {
approved = XmlUtils.readIntAttribute(parser, ATT_TYPES);
} else if (TAG_DISALLOWED.equals(parser.getName())) {
String pkgs = XmlUtils.readStringAttribute(parser, ATT_PKGS);
if (!TextUtils.isEmpty(pkgs)) {
disallowedPkgs = new ArraySet<>(pkgs.split(XML_SEPARATOR));
}
}
}
NotificationListenerFilter nlf =
new NotificationListenerFilter(approved, disallowedPkgs);
mRequestedNotificationListeners.put(Pair.create(cn, userId), nlf);
}
}
}
@Override
protected void writeExtraXmlTags(TypedXmlSerializer out) throws IOException {
out.startTag(null, TAG_REQUESTED_LISTENERS);
for (Pair<ComponentName, Integer> listener : mRequestedNotificationListeners.keySet()) {
NotificationListenerFilter nlf = mRequestedNotificationListeners.get(listener);
out.startTag(null, TAG_REQUESTED_LISTENER);
XmlUtils.writeStringAttribute(
out, ATT_COMPONENT, listener.first.flattenToString());
XmlUtils.writeIntAttribute(out, ATT_USER_ID, listener.second);
out.startTag(null, TAG_APPROVED);
XmlUtils.writeIntAttribute(out, ATT_TYPES, nlf.getTypes());
out.endTag(null, TAG_APPROVED);
out.startTag(null, TAG_DISALLOWED);
XmlUtils.writeStringAttribute(
out, ATT_PKGS, String.join(XML_SEPARATOR, nlf.getDisallowedPackages()));
out.endTag(null, TAG_DISALLOWED);
out.endTag(null, TAG_REQUESTED_LISTENER);
}
out.endTag(null, TAG_REQUESTED_LISTENERS);
}
protected @Nullable NotificationListenerFilter getNotificationListenerFilter(
Pair<ComponentName, Integer> pair) {
return mRequestedNotificationListeners.get(pair);
}
protected void setNotificationListenerFilter(Pair<ComponentName, Integer> pair,
NotificationListenerFilter nlf) {
mRequestedNotificationListeners.put(pair, nlf);
}
@GuardedBy("mNotificationLock")
public void setOnNotificationPostedTrimLocked(ManagedServiceInfo info, int trim) {
if (trim == TRIM_LIGHT) {
@@ -9618,8 +9797,9 @@ public class NotificationManagerService extends SystemService {
TrimCache trimCache = new TrimCache(sbn);
for (final ManagedServiceInfo info : getServices()) {
boolean sbnVisible = isVisibleToListener(sbn, info);
boolean oldSbnVisible = (oldSbn != null) && isVisibleToListener(oldSbn, info);
boolean sbnVisible = isVisibleToListener(sbn, r. getNotificationType(), info);
boolean oldSbnVisible = (oldSbn != null)
&& isVisibleToListener(oldSbn, old.getNotificationType(), info);
// This notification hasn't been and still isn't visible -> ignore.
if (!oldSbnVisible && !sbnVisible) {
continue;
@@ -9672,7 +9852,7 @@ public class NotificationManagerService extends SystemService {
for (final NotificationRecord r : mNotificationList) {
// When granting permissions, ignore notifications which are invisible.
// When revoking permissions, all notifications are invisible, so process all.
if (grant && !isVisibleToListener(r.getSbn(), info)) {
if (grant && !isVisibleToListener(r.getSbn(), r.getNotificationType(), info)) {
continue;
}
// If the notification is hidden, permissions are not required by the listener.
@@ -9714,7 +9894,7 @@ public class NotificationManagerService extends SystemService {
// notification
final StatusBarNotification sbnLight = sbn.cloneLight();
for (final ManagedServiceInfo info : getServices()) {
if (!isVisibleToListener(sbn, info)) {
if (!isVisibleToListener(sbn, r.getNotificationType(), info)) {
continue;
}
@@ -9754,7 +9934,8 @@ public class NotificationManagerService extends SystemService {
public void notifyRankingUpdateLocked(List<NotificationRecord> changedHiddenNotifications) {
boolean isHiddenRankingUpdate = changedHiddenNotifications != null
&& changedHiddenNotifications.size() > 0;
// TODO (b/73052211): if the ranking update changed the notification type,
// cancel notifications for NLSes that can't see them anymore
for (final ManagedServiceInfo serviceInfo : getServices()) {
if (!serviceInfo.isEnabledForCurrentProfiles() || !isInteractionVisibleToListener(
serviceInfo, ActivityManager.getCurrentUser())) {
@@ -9765,7 +9946,8 @@ public class NotificationManagerService extends SystemService {
if (isHiddenRankingUpdate && serviceInfo.targetSdkVersion >=
Build.VERSION_CODES.P) {
for (NotificationRecord rec : changedHiddenNotifications) {
if (isVisibleToListener(rec.getSbn(), serviceInfo)) {
if (isVisibleToListener(
rec.getSbn(), rec.getNotificationType(), serviceInfo)) {
notifyThisListener = true;
break;
}
@@ -9979,6 +10161,7 @@ public class NotificationManagerService extends SystemService {
}
}
class RoleObserver implements OnRoleHoldersChangedListener {
// Role name : user id : list of approved packages
private ArrayMap<String, ArrayMap<Integer, ArraySet<String>>> mNonBlockableDefaultApps;

View File

@@ -1257,6 +1257,16 @@ public final class NotificationRecord {
return !Objects.equals(getSbn().getPackageName(), getSbn().getOpPkg());
}
public int getNotificationType() {
if (isConversation()) {
return NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
} else if (getImportance() >= IMPORTANCE_DEFAULT) {
return NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
} else {
return NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
}
}
/**
* @return all {@link Uri} that should have permission granted to whoever
* will be rendering it. This list has already been vetted to only

View File

@@ -0,0 +1,242 @@
/*
* Copyright (C) 2020 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 com.android.server.notification;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
import android.app.INotificationManager;
import android.content.ComponentName;
import android.content.pm.IPackageManager;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
import android.service.notification.NotificationListenerFilter;
import android.util.ArraySet;
import android.util.Pair;
import android.util.TypedXmlPullParser;
import android.util.TypedXmlSerializer;
import android.util.Xml;
import com.android.server.UiServiceTestCase;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.ArrayList;
import java.util.List;
public class NotificationListenersTest extends UiServiceTestCase {
@Mock
private PackageManager mPm;
@Mock
private IPackageManager miPm;
@Mock
NotificationManagerService mNm;
@Mock
private INotificationManager mINm;
NotificationManagerService.NotificationListeners mListeners;
private ComponentName mCn1 = new ComponentName("pkg", "pkg.cmp");
private ComponentName mCn2 = new ComponentName("pkg2", "pkg2.cmp2");
@Before
public void setUp() throws Exception {
MockitoAnnotations.initMocks(this);
getContext().setMockPackageManager(mPm);
mListeners = spy(mNm.new NotificationListeners(
mContext, new Object(), mock(ManagedServices.UserProfiles.class), miPm));
when(mNm.getBinderService()).thenReturn(mINm);
}
@Test
public void testReadExtraTag() throws Exception {
String xml = "<requested_listeners>"
+ "<listener component=\"" + mCn1.flattenToString() + "\" user=\"0\">"
+ "<allowed types=\"7\" />"
+ "<disallowed pkgs=\"\" />"
+ "</listener>"
+ "<listener component=\"" + mCn2.flattenToString() + "\" user=\"10\">"
+ "<allowed types=\"4\" />"
+ "<disallowed pkgs=\"something\" />"
+ "</listener>"
+ "</requested_listeners>";
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(xml.getBytes())), null);
parser.nextTag();
mListeners.readExtraTag("requested_listeners", parser);
validateListenersFromXml();
}
@Test
public void testWriteExtraTag() throws Exception {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
NotificationListenerFilter nlf2 =
new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
TypedXmlSerializer serializer = Xml.newFastSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.setOutput(new BufferedOutputStream(baos), "utf-8");
serializer.startDocument(null, true);
mListeners.writeExtraXmlTags(serializer);
serializer.endDocument();
serializer.flush();
TypedXmlPullParser parser = Xml.newFastPullParser();
parser.setInput(new BufferedInputStream(
new ByteArrayInputStream(baos.toByteArray())), null);
parser.nextTag();
mListeners.readExtraTag("requested_listeners", parser);
validateListenersFromXml();
}
private void validateListenersFromXml() {
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)).getTypes())
.isEqualTo(7);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))
.getDisallowedPackages())
.isEmpty();
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes())
.isEqualTo(4);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10))
.getDisallowedPackages())
.contains("something");
}
@Test
public void testOnUserRemoved() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
NotificationListenerFilter nlf2 =
new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
mListeners.onUserRemoved(0);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))).isNull();
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes())
.isEqualTo(4);
}
@Test
public void testOnUserUnlocked() {
// one exists already, say from xml
NotificationListenerFilter nlf =
new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf);
// new service exists or backfilling on upgrade to S
ServiceInfo si = new ServiceInfo();
si.permission = mListeners.getConfig().bindPermission;
si.packageName = "new";
si.name = "comp";
ResolveInfo ri = new ResolveInfo();
ri.serviceInfo = si;
// incorrect service
ServiceInfo si2 = new ServiceInfo();
ResolveInfo ri2 = new ResolveInfo();
ri2.serviceInfo = si2;
si2.packageName = "new2";
si2.name = "comp2";
List<ResolveInfo> ris = new ArrayList<>();
ris.add(ri);
ris.add(ri2);
when(mPm.queryIntentServicesAsUser(any(), anyInt(), anyInt())).thenReturn(ris);
mListeners.onUserUnlocked(0);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes())
.isEqualTo(4);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0))
.getDisallowedPackages())
.contains("something");
assertThat(mListeners.getNotificationListenerFilter(
Pair.create(si.getComponentName(), 0)).getTypes())
.isEqualTo(7);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(si.getComponentName(), 0))
.getDisallowedPackages())
.isEmpty();
assertThat(mListeners.getNotificationListenerFilter(Pair.create(si2.getComponentName(), 0)))
.isNull();
}
@Test
public void testOnPackageChanged() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
NotificationListenerFilter nlf2 =
new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 10), nlf2);
String[] pkgs = new String[] {mCn1.getPackageName()};
int[] uids = new int[] {1};
mListeners.onPackagesChanged(false, pkgs, uids);
// not removing; no change
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0)).getTypes())
.isEqualTo(7);
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 10)).getTypes())
.isEqualTo(4);
}
@Test
public void testOnPackageChanged_removing() {
NotificationListenerFilter nlf = new NotificationListenerFilter(7, new ArraySet<>());
NotificationListenerFilter nlf2 =
new NotificationListenerFilter(4, new ArraySet<>(new String[] {"something"}));
mListeners.setNotificationListenerFilter(Pair.create(mCn1, 0), nlf);
mListeners.setNotificationListenerFilter(Pair.create(mCn2, 0), nlf2);
String[] pkgs = new String[] {mCn1.getPackageName()};
int[] uids = new int[] {1};
mListeners.onPackagesChanged(true, pkgs, uids);
// only mCn1 removed
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn1, 0))).isNull();
assertThat(mListeners.getNotificationListenerFilter(Pair.create(mCn2, 0)).getTypes())
.isEqualTo(4);
}
}

View File

@@ -55,6 +55,7 @@ import static android.os.Build.VERSION_CODES.P;
import static android.os.UserHandle.USER_SYSTEM;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_USER_SENTIMENT;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
@@ -143,6 +144,7 @@ import android.provider.MediaStore;
import android.provider.Settings;
import android.service.notification.Adjustment;
import android.service.notification.ConversationChannelWrapper;
import android.service.notification.NotificationListenerFilter;
import android.service.notification.NotificationListenerService;
import android.service.notification.NotificationStats;
import android.service.notification.StatusBarNotification;
@@ -269,6 +271,8 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
@Mock
private NotificationListeners mListeners;
@Mock
private NotificationListenerFilter mNlf;
@Mock private NotificationAssistants mAssistants;
@Mock private ConditionProviders mConditionProviders;
private ManagedServices.ManagedServiceInfo mListener;
@@ -459,6 +463,10 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mPolicyFile.finishWrite(fos);
// Setup managed services
when(mNlf.isTypeAllowed(anyInt())).thenReturn(true);
when(mNlf.isPackageAllowed(anyString())).thenReturn(true);
when(mNlf.isPackageAllowed(null)).thenReturn(true);
when(mListeners.getNotificationListenerFilter(any())).thenReturn(mNlf);
mListener = mListeners.new ManagedServiceInfo(
null, new ComponentName(PKG, "test_class"),
UserHandle.getUserId(mUid), true, null, 0);
@@ -3596,7 +3604,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mNotificationDelegate.onNotificationDirectReplied(r.getKey());
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasDirectReplied());
verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r.getSbn()));
verify(mAssistants).notifyAssistantNotificationDirectReplyLocked(eq(r));
assertEquals(1, mNotificationRecordLogger.numCalls());
assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_DIRECT_REPLIED,
@@ -3610,14 +3618,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, true,
NOTIFICATION_LOCATION_UNKNOWN);
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true),
eq((true)));
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()),
eq(FLAG_FILTER_TYPE_ALERTING), eq(true), eq((true)));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), true, false,
NOTIFICATION_LOCATION_UNKNOWN);
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(true),
eq((false)));
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()),
eq(FLAG_FILTER_TYPE_ALERTING), eq(true), eq((false)));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
assertEquals(2, mNotificationRecordLogger.numCalls());
@@ -3635,14 +3643,14 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, true,
NOTIFICATION_LOCATION_UNKNOWN);
assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()), eq(false),
eq((true)));
verify(mAssistants).notifyAssistantExpansionChangedLocked(eq(r.getSbn()),
eq(FLAG_FILTER_TYPE_ALERTING), eq(false), eq((true)));
mService.mNotificationDelegate.onNotificationExpansionChanged(r.getKey(), false, false,
NOTIFICATION_LOCATION_UNKNOWN);
assertFalse(mService.getNotificationRecord(r.getKey()).getStats().hasExpanded());
verify(mAssistants).notifyAssistantExpansionChangedLocked(
eq(r.getSbn()), eq(false), eq((false)));
eq(r.getSbn()), eq(FLAG_FILTER_TYPE_ALERTING), eq(false), eq((false)));
}
@Test
@@ -3662,11 +3670,11 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
final NotificationVisibility nv = NotificationVisibility.obtain(r.getKey(), 1, 2, true);
mService.mNotificationDelegate.onNotificationVisibilityChanged(
new NotificationVisibility[] {nv}, new NotificationVisibility[]{});
verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.getSbn()), eq(true));
verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r), eq(true));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
mService.mNotificationDelegate.onNotificationVisibilityChanged(
new NotificationVisibility[] {}, new NotificationVisibility[]{nv});
verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r.getSbn()), eq(false));
verify(mAssistants).notifyAssistantVisibilityChangedLocked(eq(r), eq(false));
assertTrue(mService.getNotificationRecord(r.getKey()).getStats().hasSeen());
}
@@ -5324,7 +5332,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
r.getKey(), replyIndex, reply, NOTIFICATION_LOCATION_UNKNOWN,
modifiedBeforeSending);
verify(mAssistants).notifyAssistantSuggestedReplySent(
eq(r.getSbn()), eq(reply), eq(generatedByAssistant));
eq(r.getSbn()), eq(FLAG_FILTER_TYPE_ALERTING), eq(reply), eq(generatedByAssistant));
assertEquals(1, mNotificationRecordLogger.numCalls());
assertEquals(NotificationRecordLogger.NotificationEvent.NOTIFICATION_SMART_REPLIED,
mNotificationRecordLogger.event(0));
@@ -5346,7 +5354,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
10, 10, r.getKey(), actionIndex, action, notificationVisibility,
generatedByAssistant);
verify(mAssistants).notifyAssistantActionClicked(
eq(r.getSbn()), eq(action), eq(generatedByAssistant));
eq(r), eq(action), eq(generatedByAssistant));
assertEquals(1, mNotificationRecordLogger.numCalls());
assertEquals(
@@ -5370,7 +5378,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
10, 10, r.getKey(), actionIndex, action, notificationVisibility,
generatedByAssistant);
verify(mAssistants).notifyAssistantActionClicked(
eq(r.getSbn()), eq(action), eq(generatedByAssistant));
eq(r), eq(action), eq(generatedByAssistant));
assertEquals(1, mNotificationRecordLogger.numCalls());
assertEquals(
@@ -7249,7 +7257,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(info.enabledAndUserMatches(info.userid)).thenReturn(false);
when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
assertFalse(mService.isVisibleToListener(sbn, info));
assertFalse(mService.isVisibleToListener(sbn, 0, info));
}
@Test
@@ -7262,7 +7270,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
when(mAssistants.checkServiceTokenLocked(any())).thenReturn(null);
assertTrue(mService.isVisibleToListener(sbn, info));
assertTrue(mService.isVisibleToListener(sbn, 0, info));
}
@Test
@@ -7277,7 +7285,7 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
assertFalse(mService.isVisibleToListener(sbn, info));
assertFalse(mService.isVisibleToListener(sbn, 0, info));
}
@Test
@@ -7292,7 +7300,42 @@ public class NotificationManagerServiceTest extends UiServiceTestCase {
when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
assertTrue(mService.isVisibleToListener(sbn, info));
assertTrue(mService.isVisibleToListener(sbn, 0, info));
}
@Test
public void testIsVisibleToListener_mismatchedType() {
when(mNlf.isTypeAllowed(anyInt())).thenReturn(false);
StatusBarNotification sbn = mock(StatusBarNotification.class);
when(sbn.getUserId()).thenReturn(10);
ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
ManagedServices.ManagedServiceInfo assistant = mock(ManagedServices.ManagedServiceInfo.class);
info.userid = 10;
when(info.isSameUser(anyInt())).thenReturn(true);
when(assistant.isSameUser(anyInt())).thenReturn(true);
when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
assertFalse(mService.isVisibleToListener(sbn, 0, info));
}
@Test
public void testIsVisibleToListener_disallowedPackage() {
when(mNlf.isPackageAllowed(null)).thenReturn(false);
StatusBarNotification sbn = mock(StatusBarNotification.class);
when(sbn.getUserId()).thenReturn(10);
ManagedServices.ManagedServiceInfo info = mock(ManagedServices.ManagedServiceInfo.class);
ManagedServices.ManagedServiceInfo assistant =
mock(ManagedServices.ManagedServiceInfo.class);
info.userid = 10;
when(info.isSameUser(anyInt())).thenReturn(true);
when(assistant.isSameUser(anyInt())).thenReturn(true);
when(info.enabledAndUserMatches(info.userid)).thenReturn(true);
when(mAssistants.checkServiceTokenLocked(any())).thenReturn(assistant);
assertFalse(mService.isVisibleToListener(sbn, 0, info));
}
@Test

View File

@@ -21,6 +21,9 @@ import static android.app.NotificationManager.IMPORTANCE_HIGH;
import static android.app.NotificationManager.IMPORTANCE_LOW;
import static android.service.notification.Adjustment.KEY_IMPORTANCE;
import static android.service.notification.Adjustment.KEY_NOT_CONVERSATION;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_ALERTING;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_CONVERSATIONS;
import static android.service.notification.NotificationListenerService.FLAG_FILTER_TYPE_SILENT;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEGATIVE;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_NEUTRAL;
import static android.service.notification.NotificationListenerService.Ranking.USER_SENTIMENT_POSITIVE;
@@ -910,11 +913,13 @@ public class NotificationRecordTest extends UiServiceTestCase {
record.setAssistantImportance(IMPORTANCE_LOW);
record.calculateImportance();
assertEquals(IMPORTANCE_LOW, record.getImportance());
assertEquals(FLAG_FILTER_TYPE_SILENT, record.getNotificationType());
record.updateNotificationChannel(
new NotificationChannel(channelId, "", IMPORTANCE_DEFAULT));
assertEquals(IMPORTANCE_LOW, record.getImportance());
assertEquals(FLAG_FILTER_TYPE_SILENT, record.getNotificationType());
}
@Test
@@ -1125,6 +1130,7 @@ public class NotificationRecordTest extends UiServiceTestCase {
record.setShortcutInfo(mock(ShortcutInfo.class));
assertTrue(record.isConversation());
assertEquals(FLAG_FILTER_TYPE_CONVERSATIONS, record.getNotificationType());
}
@Test
@@ -1134,6 +1140,7 @@ public class NotificationRecordTest extends UiServiceTestCase {
record.setShortcutInfo(null);
assertTrue(record.isConversation());
assertEquals(FLAG_FILTER_TYPE_CONVERSATIONS, record.getNotificationType());
}
@Test
@@ -1144,6 +1151,7 @@ public class NotificationRecordTest extends UiServiceTestCase {
record.setHasSentValidMsg(true);
assertFalse(record.isConversation());
assertEquals(FLAG_FILTER_TYPE_ALERTING, record.getNotificationType());
}
@Test