diff --git a/services/core/java/com/android/server/notification/NotificationChannelLogger.java b/services/core/java/com/android/server/notification/NotificationChannelLogger.java new file mode 100644 index 0000000000000..83f4ebb41827c --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationChannelLogger.java @@ -0,0 +1,213 @@ +/* + * 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 android.annotation.NonNull; +import android.app.NotificationChannel; +import android.app.NotificationChannelGroup; + +import com.android.internal.logging.UiEvent; +import com.android.internal.logging.UiEventLogger; +import com.android.internal.util.FrameworkStatsLog; + +/** + * Interface for logging NotificationChannelModified statsd atoms. Provided as an interface to + * enable unit-testing - use standard implementation NotificationChannelLoggerImpl in production. + */ +public interface NotificationChannelLogger { + // The logging interface. Not anticipating a need to override these high-level methods, which by + // default forward to a lower-level interface. + + /** + * Log the creation of a notification channel. + * @param channel The channel. + * @param uid UID of app that owns the channel. + * @param pkg Package of app that owns the channel. + */ + default void logNotificationChannelCreated(@NonNull NotificationChannel channel, int uid, + String pkg) { + logNotificationChannel( + NotificationChannelEvent.getCreated(channel), + channel, uid, pkg, 0, 0); + } + + /** + * Log the deletion of a notification channel. + * @param channel The channel. + * @param uid UID of app that owns the channel. + * @param pkg Package of app that owns the channel. + */ + default void logNotificationChannelDeleted(@NonNull NotificationChannel channel, int uid, + String pkg) { + logNotificationChannel( + NotificationChannelEvent.getDeleted(channel), + channel, uid, pkg, 0, 0); + } + + /** + * Log the modification of a notification channel. + * @param channel The channel. + * @param uid UID of app that owns the channel. + * @param pkg Package of app that owns the channel. + * @param oldImportance Previous importance level of the channel. + * @param byUser True if the modification was user-specified. + */ + default void logNotificationChannelModified(@NonNull NotificationChannel channel, int uid, + String pkg, int oldImportance, boolean byUser) { + logNotificationChannel(NotificationChannelEvent.getUpdated(byUser), + channel, uid, pkg, oldImportance, channel.getImportance()); + } + + /** + * Log the creation or modification of a notification channel group. + * @param channelGroup The notification channel group. + * @param uid UID of app that owns the channel. + * @param pkg Package of app that owns the channel. + * @param isNew True if this is a creation of a new group. + * @param wasBlocked + */ + default void logNotificationChannelGroup(@NonNull NotificationChannelGroup channelGroup, + int uid, String pkg, boolean isNew, boolean wasBlocked) { + logNotificationChannelGroup(NotificationChannelEvent.getGroupUpdated(isNew), + channelGroup, uid, pkg, wasBlocked); + } + + /** + * Log the deletion of a notification channel group. + * @param channelGroup The notification channel group. + * @param uid UID of app that owns the channel. + * @param pkg Package of app that owns the channel. + */ + default void logNotificationChannelGroupDeleted(@NonNull NotificationChannelGroup channelGroup, + int uid, String pkg) { + logNotificationChannelGroup(NotificationChannelEvent.NOTIFICATION_CHANNEL_GROUP_DELETED, + channelGroup, uid, pkg, false); + } + + /** + * Low-level interface for logging events, to be implemented. + * @param event Event to log. + * @param channel Notification channel. + * @param uid UID of app that owns the channel. + * @param pkg Package of app that owns the channel. + * @param oldImportance Old importance of the channel, if applicable (0 otherwise). + * @param newImportance New importance of the channel, if applicable (0 otherwise). + */ + void logNotificationChannel(@NonNull NotificationChannelEvent event, + @NonNull NotificationChannel channel, int uid, String pkg, + int oldImportance, int newImportance); + + /** + * Low-level interface for logging channel group events, to be implemented. + * @param event Event to log. + * @param channelGroup Notification channel group. + * @param uid UID of app that owns the channel. + * @param pkg Package of app that owns the channel. + * @param wasBlocked True if the channel is being modified and was previously blocked. + */ + void logNotificationChannelGroup(@NonNull NotificationChannelEvent event, + @NonNull NotificationChannelGroup channelGroup, int uid, String pkg, + boolean wasBlocked); + + /** + * The UiEvent enums that this class can log. + */ + enum NotificationChannelEvent implements UiEventLogger.UiEventEnum { + @UiEvent(doc = "App created a new notification channel") + NOTIFICATION_CHANNEL_CREATED(219), + @UiEvent(doc = "App modified an existing notification channel") + NOTIFICATION_CHANNEL_UPDATED(220), + @UiEvent(doc = "User modified a new notification channel") + NOTIFICATION_CHANNEL_UPDATED_BY_USER(221), + @UiEvent(doc = "App deleted an existing notification channel") + NOTIFICATION_CHANNEL_DELETED(222), + @UiEvent(doc = "App created a new notification channel group") + NOTIFICATION_CHANNEL_GROUP_CREATED(223), + @UiEvent(doc = "App modified an existing notification channel group") + NOTIFICATION_CHANNEL_GROUP_UPDATED(224), + @UiEvent(doc = "App deleted an existing notification channel group") + NOTIFICATION_CHANNEL_GROUP_DELETED(226), + @UiEvent(doc = "System created a new conversation (sub-channel in a notification channel)") + NOTIFICATION_CHANNEL_CONVERSATION_CREATED(272), + @UiEvent(doc = "System deleted a new conversation (sub-channel in a notification channel)") + NOTIFICATION_CHANNEL_CONVERSATION_DELETED(274); + + + private final int mId; + NotificationChannelEvent(int id) { + mId = id; + } + @Override public int getId() { + return mId; + } + + public static NotificationChannelEvent getUpdated(boolean byUser) { + return byUser + ? NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED_BY_USER + : NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED; + } + + public static NotificationChannelEvent getCreated(@NonNull NotificationChannel channel) { + return channel.getConversationId() != null + ? NotificationChannelEvent.NOTIFICATION_CHANNEL_CONVERSATION_CREATED + : NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED; + } + + public static NotificationChannelEvent getDeleted(@NonNull NotificationChannel channel) { + return channel.getConversationId() != null + ? NotificationChannelEvent.NOTIFICATION_CHANNEL_CONVERSATION_DELETED + : NotificationChannelEvent.NOTIFICATION_CHANNEL_DELETED; + } + + public static NotificationChannelEvent getGroupUpdated(boolean isNew) { + return isNew + ? NotificationChannelEvent.NOTIFICATION_CHANNEL_GROUP_CREATED + : NotificationChannelEvent.NOTIFICATION_CHANNEL_GROUP_DELETED; + } + } + + /** + * @return Small hash of the channel ID, if present, or 0 otherwise. + */ + static int getIdHash(@NonNull NotificationChannel channel) { + return NotificationRecordLogger.smallHash(channel.getId()); + } + + /** + * @return Small hash of the channel ID, if present, or 0 otherwise. + */ + static int getIdHash(@NonNull NotificationChannelGroup group) { + return NotificationRecordLogger.smallHash(group.getId()); + } + + /** + * @return "Importance" for a channel group + */ + static int getImportance(@NonNull NotificationChannelGroup channelGroup) { + return getImportance(channelGroup.isBlocked()); + } + + /** + * @return "Importance" for a channel group, from its blocked status + */ + static int getImportance(boolean isBlocked) { + return isBlocked + ? FrameworkStatsLog.NOTIFICATION_CHANNEL_MODIFIED__IMPORTANCE__IMPORTANCE_NONE + : FrameworkStatsLog.NOTIFICATION_CHANNEL_MODIFIED__IMPORTANCE__IMPORTANCE_DEFAULT; + } + +} diff --git a/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java new file mode 100644 index 0000000000000..2f7772eec2d25 --- /dev/null +++ b/services/core/java/com/android/server/notification/NotificationChannelLoggerImpl.java @@ -0,0 +1,54 @@ +/* + * 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 android.app.NotificationChannel; +import android.app.NotificationChannelGroup; + +import com.android.internal.util.FrameworkStatsLog; + +/** + * Standard implementation of NotificationChannelLogger, which passes data through to StatsLog. + * This layer is as skinny as possible, to maximize code coverage of unit tests. Nontrivial code + * should live in the interface so it can be tested. + */ +public class NotificationChannelLoggerImpl implements NotificationChannelLogger { + @Override + public void logNotificationChannel(NotificationChannelEvent event, + NotificationChannel channel, int uid, String pkg, + int oldImportance, int newImportance) { + FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_CHANNEL_MODIFIED, + /* int event_id*/ event.getId(), + /* int uid*/ uid, + /* String package_name */ pkg, + /* int32 channel_id_hash */ NotificationChannelLogger.getIdHash(channel), + /* int old_importance*/ oldImportance, + /* int importance*/ newImportance); + } + + @Override + public void logNotificationChannelGroup(NotificationChannelEvent event, + NotificationChannelGroup channelGroup, int uid, String pkg, boolean wasBlocked) { + FrameworkStatsLog.write(FrameworkStatsLog.NOTIFICATION_CHANNEL_MODIFIED, + /* int event_id*/ event.getId(), + /* int uid*/ uid, + /* String package_name */ pkg, + /* int32 channel_id_hash */ NotificationChannelLogger.getIdHash(channelGroup), + /* int old_importance*/ NotificationChannelLogger.getImportance(wasBlocked), + /* int importance*/ NotificationChannelLogger.getImportance(channelGroup)); + } +} diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java index c40f1b65573dc..d139cd119df8d 100755 --- a/services/core/java/com/android/server/notification/NotificationManagerService.java +++ b/services/core/java/com/android/server/notification/NotificationManagerService.java @@ -1979,7 +1979,8 @@ public class NotificationManagerService extends SystemService { mPreferencesHelper = new PreferencesHelper(getContext(), mPackageManagerClient, mRankingHandler, - mZenModeHelper); + mZenModeHelper, + new NotificationChannelLoggerImpl()); mRankingHelper = new RankingHelper(getContext(), mRankingHandler, mPreferencesHelper, diff --git a/services/core/java/com/android/server/notification/NotificationRecordLogger.java b/services/core/java/com/android/server/notification/NotificationRecordLogger.java index eaca066f026ff..f4ee4616df27f 100644 --- a/services/core/java/com/android/server/notification/NotificationRecordLogger.java +++ b/services/core/java/com/android/server/notification/NotificationRecordLogger.java @@ -27,7 +27,6 @@ import android.os.Bundle; import android.service.notification.NotificationListenerService; import android.service.notification.NotificationStats; -import com.android.internal.annotations.VisibleForTesting; import com.android.internal.logging.UiEvent; import com.android.internal.logging.UiEventLogger; @@ -307,27 +306,36 @@ public interface NotificationRecordLogger { * @return Small hash of the channel ID, if present, or 0 otherwise. */ int getChannelIdHash() { - return smallHash(Objects.hashCode(r.getSbn().getNotification().getChannelId())); + return smallHash(r.getSbn().getNotification().getChannelId()); } /** * @return Small hash of the group ID, respecting group override if present. 0 otherwise. */ int getGroupIdHash() { - return smallHash(Objects.hashCode(r.getSbn().getGroup())); + return smallHash(r.getSbn().getGroup()); } - // "Small" hashes will be in the range [0, MAX_HASH). - static final int MAX_HASH = (1 << 13); - - /** - * Maps in to the range [0, MAX_HASH), keeping similar values distinct. - * @param in An arbitrary integer. - * @return in mod MAX_HASH, signs chosen to stay in the range [0, MAX_HASH). - */ - @VisibleForTesting - static int smallHash(int in) { - return Math.floorMod(in, MAX_HASH); - } } + + // "Small" hashes will be in the range [0, MAX_HASH). + int MAX_HASH = (1 << 13); + + /** + * Maps in to the range [0, MAX_HASH), keeping similar values distinct. + * @param in An arbitrary integer. + * @return in mod MAX_HASH, signs chosen to stay in the range [0, MAX_HASH). + */ + static int smallHash(int in) { + return Math.floorMod(in, MAX_HASH); + } + + /** + * @return Small hash of the string, if non-null, or 0 otherwise. + */ + static int smallHash(@Nullable String in) { + return smallHash(Objects.hashCode(in)); + } + + } diff --git a/services/core/java/com/android/server/notification/PreferencesHelper.java b/services/core/java/com/android/server/notification/PreferencesHelper.java index b8186ed814fe0..6fd09bb2fa8c8 100644 --- a/services/core/java/com/android/server/notification/PreferencesHelper.java +++ b/services/core/java/com/android/server/notification/PreferencesHelper.java @@ -145,6 +145,7 @@ public class PreferencesHelper implements RankingConfig { private final PackageManager mPm; private final RankingHandler mRankingHandler; private final ZenModeHelper mZenModeHelper; + private final NotificationChannelLogger mNotificationChannelLogger; private SparseBooleanArray mBadgingEnabled; private boolean mBubblesEnabled = DEFAULT_ALLOW_BUBBLE; @@ -161,11 +162,12 @@ public class PreferencesHelper implements RankingConfig { } public PreferencesHelper(Context context, PackageManager pm, RankingHandler rankingHandler, - ZenModeHelper zenHelper) { + ZenModeHelper zenHelper, NotificationChannelLogger notificationChannelLogger) { mContext = context; mZenModeHelper = zenHelper; mRankingHandler = rankingHandler; mPm = pm; + mNotificationChannelLogger = notificationChannelLogger; // STOPSHIP (b/142218092) this should be removed before ship if (!wasBadgingForcedTrue(context)) { @@ -654,10 +656,6 @@ public class PreferencesHelper implements RankingConfig { throw new IllegalArgumentException("Invalid package"); } final NotificationChannelGroup oldGroup = r.groups.get(group.getId()); - if (!group.equals(oldGroup)) { - // will log for new entries as well as name/description changes - MetricsLogger.action(getChannelGroupLog(group.getId(), pkg)); - } if (oldGroup != null) { group.setChannels(oldGroup.getChannels()); @@ -674,6 +672,13 @@ public class PreferencesHelper implements RankingConfig { } } } + if (!group.equals(oldGroup)) { + // will log for new entries as well as name/description changes + MetricsLogger.action(getChannelGroupLog(group.getId(), pkg)); + mNotificationChannelLogger.logNotificationChannelGroup(group, uid, pkg, + oldGroup == null, + (oldGroup != null) && oldGroup.isBlocked()); + } r.groups.put(group.getId(), group); } } @@ -685,7 +690,7 @@ public class PreferencesHelper implements RankingConfig { Objects.requireNonNull(channel); Objects.requireNonNull(channel.getId()); Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName())); - boolean needsPolicyFileChange = false; + boolean needsPolicyFileChange = false, wasUndeleted = false; synchronized (mPackagePreferences) { PackagePreferences r = getOrCreatePackagePreferencesLocked(pkg, uid); if (r == null) { @@ -698,15 +703,18 @@ public class PreferencesHelper implements RankingConfig { throw new IllegalArgumentException("Reserved id"); } NotificationChannel existing = r.channels.get(channel.getId()); - // Keep most of the existing settings if (existing != null && fromTargetApp) { + // Actually modifying an existing channel - keep most of the existing settings if (existing.isDeleted()) { + // The existing channel was deleted - undelete it. existing.setDeleted(false); needsPolicyFileChange = true; + wasUndeleted = true; // log a resurrected channel as if it's new again MetricsLogger.action(getChannelLog(channel, pkg).setType( com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN)); + mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg); } if (!Objects.equals(channel.getName().toString(), existing.getName().toString())) { @@ -756,6 +764,10 @@ public class PreferencesHelper implements RankingConfig { } updateConfig(); + if (needsPolicyFileChange && !wasUndeleted) { + mNotificationChannelLogger.logNotificationChannelModified(existing, uid, pkg, + previousExistingImportance, false); + } return needsPolicyFileChange; } @@ -806,6 +818,7 @@ public class PreferencesHelper implements RankingConfig { } MetricsLogger.action(getChannelLog(channel, pkg).setType( com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_OPEN)); + mNotificationChannelLogger.logNotificationChannelCreated(channel, uid, pkg); } return needsPolicyFileChange; @@ -867,6 +880,8 @@ public class PreferencesHelper implements RankingConfig { // only log if there are real changes MetricsLogger.action(getChannelLog(updatedChannel, pkg) .setSubtype(fromUser ? 1 : 0)); + mNotificationChannelLogger.logNotificationChannelModified(updatedChannel, uid, pkg, + channel.getImportance(), fromUser); } if (updatedChannel.canBypassDnd() != mAreChannelsBypassingDnd @@ -954,14 +969,21 @@ public class PreferencesHelper implements RankingConfig { } NotificationChannel channel = r.channels.get(channelId); if (channel != null) { - channel.setDeleted(true); - LogMaker lm = getChannelLog(channel, pkg); - lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE); - MetricsLogger.action(lm); + deleteNotificationChannelLocked(channel, pkg, uid); + } + } + } - if (mAreChannelsBypassingDnd && channel.canBypassDnd()) { - updateChannelsBypassingDnd(mContext.getUserId()); - } + private void deleteNotificationChannelLocked(NotificationChannel channel, String pkg, int uid) { + if (!channel.isDeleted()) { + channel.setDeleted(true); + LogMaker lm = getChannelLog(channel, pkg); + lm.setType(com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE); + MetricsLogger.action(lm); + mNotificationChannelLogger.logNotificationChannelDeleted(channel, uid, pkg); + + if (mAreChannelsBypassingDnd && channel.canBypassDnd()) { + updateChannelsBypassingDnd(mContext.getUserId()); } } } @@ -1158,13 +1180,17 @@ public class PreferencesHelper implements RankingConfig { return deletedChannels; } - r.groups.remove(groupId); + NotificationChannelGroup channelGroup = r.groups.remove(groupId); + if (channelGroup != null) { + mNotificationChannelLogger.logNotificationChannelGroupDeleted(channelGroup, uid, + pkg); + } int N = r.channels.size(); for (int i = 0; i < N; i++) { final NotificationChannel nc = r.channels.valueAt(i); if (groupId.equals(nc.getGroup())) { - nc.setDeleted(true); + deleteNotificationChannelLocked(nc, pkg, uid); deletedChannels.add(nc); } } @@ -1279,6 +1305,7 @@ public class PreferencesHelper implements RankingConfig { lm.setType( com.android.internal.logging.nano.MetricsProto.MetricsEvent.TYPE_CLOSE); MetricsLogger.action(lm); + mNotificationChannelLogger.logNotificationChannelDeleted(nc, uid, pkg); deletedChannelIds.add(nc.getId()); } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java new file mode 100644 index 0000000000000..b6ea063ccc146 --- /dev/null +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationChannelLoggerFake.java @@ -0,0 +1,54 @@ +/* + * 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 android.app.NotificationChannel; +import android.app.NotificationChannelGroup; + +import java.util.ArrayList; +import java.util.List; + +public class NotificationChannelLoggerFake implements NotificationChannelLogger { + static class CallRecord { + public NotificationChannelEvent event; + CallRecord(NotificationChannelEvent event) { + this.event = event; + } + } + + private List mCalls = new ArrayList<>(); + + List getCalls() { + return mCalls; + } + + CallRecord get(int index) { + return mCalls.get(index); + } + + @Override + public void logNotificationChannel(NotificationChannelEvent event, NotificationChannel channel, + int uid, String pkg, int oldImportance, int newImportance) { + mCalls.add(new CallRecord(event)); + } + + @Override + public void logNotificationChannelGroup(NotificationChannelEvent event, + NotificationChannelGroup channelGroup, int uid, String pkg, boolean wasBlocked) { + mCalls.add(new CallRecord(event)); + } +} diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java index f051fa4c22900..1e6270d78275d 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationRecordLoggerTest.java @@ -60,16 +60,16 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { @Test public void testSmallHash() { - assertEquals(0, NotificationRecordLogger.NotificationRecordPair.smallHash(0)); - final int maxHash = NotificationRecordLogger.NotificationRecordPair.MAX_HASH; + assertEquals(0, NotificationRecordLogger.smallHash(0)); + final int maxHash = NotificationRecordLogger.MAX_HASH; assertEquals(0, - NotificationRecordLogger.NotificationRecordPair.smallHash(maxHash)); + NotificationRecordLogger.smallHash(maxHash)); assertEquals(0, - NotificationRecordLogger.NotificationRecordPair.smallHash(17 * maxHash)); + NotificationRecordLogger.smallHash(17 * maxHash)); assertEquals(maxHash - 1, - NotificationRecordLogger.NotificationRecordPair.smallHash(maxHash - 1)); + NotificationRecordLogger.smallHash(maxHash - 1)); assertEquals(maxHash - 1, - NotificationRecordLogger.NotificationRecordPair.smallHash(-1)); + NotificationRecordLogger.smallHash(-1)); } @Test @@ -78,10 +78,10 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { getNotificationRecordPair(0, null).getNotificationIdHash()); assertEquals(1, getNotificationRecordPair(1, null).getNotificationIdHash()); - assertEquals(NotificationRecordLogger.NotificationRecordPair.MAX_HASH - 1, + assertEquals(NotificationRecordLogger.MAX_HASH - 1, getNotificationRecordPair(-1, null).getNotificationIdHash()); final String tag = "someTag"; - final int hash = NotificationRecordLogger.NotificationRecordPair.smallHash(tag.hashCode()); + final int hash = NotificationRecordLogger.smallHash(tag.hashCode()); assertEquals(hash, getNotificationRecordPair(0, tag).getNotificationIdHash()); // We xor the tag and hashcode together before compressing the range. The order of // operations doesn't matter if id is small. @@ -89,19 +89,19 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { getNotificationRecordPair(1, tag).getNotificationIdHash()); // But it does matter for an id with more 1 bits than fit in the small hash. assertEquals( - NotificationRecordLogger.NotificationRecordPair.smallHash(-1 ^ tag.hashCode()), + NotificationRecordLogger.smallHash(-1 ^ tag.hashCode()), getNotificationRecordPair(-1, tag).getNotificationIdHash()); assertNotEquals(-1 ^ hash, - NotificationRecordLogger.NotificationRecordPair.smallHash(-1 ^ tag.hashCode())); + NotificationRecordLogger.smallHash(-1 ^ tag.hashCode())); } @Test public void testGetChannelIdHash() { assertEquals( - NotificationRecordLogger.NotificationRecordPair.smallHash(CHANNEL_ID.hashCode()), + NotificationRecordLogger.smallHash(CHANNEL_ID.hashCode()), getNotificationRecordPair(0, null).getChannelIdHash()); assertNotEquals( - NotificationRecordLogger.NotificationRecordPair.smallHash(CHANNEL_ID.hashCode()), + NotificationRecordLogger.smallHash(CHANNEL_ID.hashCode()), CHANNEL_ID.hashCode()); } @@ -113,7 +113,7 @@ public class NotificationRecordLoggerTest extends UiServiceTestCase { final String group = "someGroup"; p.r.setOverrideGroupKey(group); assertEquals( - NotificationRecordLogger.NotificationRecordPair.smallHash(group.hashCode()), + NotificationRecordLogger.smallHash(group.hashCode()), p.getGroupIdHash()); } } diff --git a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java index 58299614efe81..af605112307f7 100644 --- a/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java +++ b/services/tests/uiservicestests/src/com/android/server/notification/PreferencesHelperTest.java @@ -134,6 +134,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { private PreferencesHelper mHelper; private AudioAttributes mAudioAttributes; + private NotificationChannelLoggerFake mLogger = new NotificationChannelLoggerFake(); @Before public void setUp() throws Exception { @@ -183,7 +184,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); resetZenModeHelper(); mAudioAttributes = new AudioAttributes.Builder() @@ -1106,6 +1107,22 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.deleteNotificationChannelGroup(PKG_N_MR1, UID_N_MR1, "does not exist"); } + @Test + public void testDoubleDeleteChannel() throws Exception { + NotificationChannel channel = getChannel(); + mHelper.createNotificationChannel(PKG_N_MR1, UID_N_MR1, channel, true, false); + mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId()); + mHelper.deleteNotificationChannel(PKG_N_MR1, UID_N_MR1, channel.getId()); + assertEquals(2, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED, + mLogger.get(0).event); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_DELETED, + mLogger.get(1).event); + // No log for the second delete of the same channel. + } + @Test public void testGetDeletedChannel() throws Exception { NotificationChannel channel = getChannel(); @@ -1444,7 +1461,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, NotificationManager.Policy.STATE_CHANNELS_BYPASSING_DND, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, times(1)).setNotificationPolicy(any()); resetZenModeHelper(); @@ -1455,7 +1472,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { // start notification policy off with mAreChannelsBypassingDnd = false mTestNotificationPolicy = new NotificationManager.Policy(0, 0, 0, 0, 0, 0); when(mMockZenModeHelper.getNotificationPolicy()).thenReturn(mTestNotificationPolicy); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); assertFalse(mHelper.areChannelsBypassingDnd()); verify(mMockZenModeHelper, never()).setNotificationPolicy(any()); resetZenModeHelper(); @@ -1525,6 +1542,11 @@ public class PreferencesHelperTest extends UiServiceTestCase { // Old settings not overridden compareChannels(channel, mHelper.getNotificationChannel(PKG_N_MR1, UID_N_MR1, newChannel.getId(), false)); + + assertEquals(1, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED, + mLogger.get(0).event); } @Test @@ -1594,6 +1616,16 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(1, mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1).size()); verify(mHandler, never()).requestSort(); + + assertEquals(7, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_GROUP_DELETED, + mLogger.get(5).event); // Next-to-last log is the deletion of the channel group. + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_DELETED, + mLogger.get(6).event); // Final log is the deletion of the channel. } @Test @@ -1739,6 +1771,11 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(ncg, mHelper.getNotificationChannelGroups(PKG_N_MR1, UID_N_MR1).iterator().next()); verify(mHandler, never()).requestSort(); + assertEquals(1, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_GROUP_CREATED, + mLogger.get(0).event); } @Test @@ -1751,6 +1788,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { fail("Created a channel with a bad group"); } catch (IllegalArgumentException e) { } + assertEquals(0, mLogger.getCalls().size()); } @Test @@ -1905,6 +1943,17 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(IMPORTANCE_DEFAULT, actual.getImportance()); verify(mHandler, times(1)).requestSort(); + assertEquals(3, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_GROUP_CREATED, + mLogger.get(0).event); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED, + mLogger.get(1).event); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_UPDATED, + mLogger.get(2).event); } @Test @@ -2189,7 +2238,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { + "content_type=\"4\" flags=\"0\" show_badge=\"true\" />\n" + "\n" + "\n"; - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadByteArrayXml(preQXml.getBytes(), true, UserHandle.USER_SYSTEM); assertEquals(PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -2201,7 +2250,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setHideSilentStatusIcons(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals(!PreferencesHelper.DEFAULT_HIDE_SILENT_STATUS_BAR_ICONS, @@ -2297,7 +2346,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setImportance(PKG_O, UID_O, IMPORTANCE_UNSPECIFIED); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -2308,7 +2357,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.setNotificationDelegate(PKG_O, UID_O, "other", 53); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); assertEquals("other", mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -2320,7 +2369,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.revokeNotificationDelegate(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); assertNull(mHelper.getNotificationDelegate(PKG_O, UID_O)); @@ -2332,7 +2381,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.toggleNotificationDelegate(PKG_O, UID_O, false); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); // appears disabled @@ -2350,7 +2399,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.revokeNotificationDelegate(PKG_O, UID_O); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); // appears disabled @@ -2368,7 +2417,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); assertTrue(mHelper.areBubblesAllowed(PKG_O, UID_O)); @@ -2383,7 +2432,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { mHelper.getAppLockedFields(PKG_O, UID_O)); ByteArrayOutputStream baos = writeXmlAndPurge(PKG_O, UID_O, false, UserHandle.USER_ALL); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); loadStreamXml(baos, false, UserHandle.USER_ALL); assertFalse(mHelper.areBubblesAllowed(PKG_O, UID_O)); @@ -2876,7 +2925,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testPlaceholderConversationId_flagOn() throws Exception { Settings.Global.putString( mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "true"); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "\n" + "\n" @@ -2896,7 +2945,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testPlaceholderConversationId_flagOff() throws Exception { Settings.Global.putString( mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "\n" + "\n" @@ -2916,7 +2965,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNormalConversationId_flagOff() throws Exception { Settings.Global.putString( mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "\n" + "\n" @@ -2936,7 +2985,7 @@ public class PreferencesHelperTest extends UiServiceTestCase { public void testNoConversationId_flagOff() throws Exception { Settings.Global.putString( mContext.getContentResolver(), NOTIF_CONVO_BYPASS_SHORTCUT_REQ, "false"); - mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper); + mHelper = new PreferencesHelper(getContext(), mPm, mHandler, mMockZenModeHelper, mLogger); final String xml = "\n" + "\n" @@ -3171,5 +3220,33 @@ public class PreferencesHelperTest extends UiServiceTestCase { assertEquals(channel, mHelper.getNotificationChannel(PKG_O, UID_O, channel.getId(), true)); assertEquals(channel2, mHelper.getNotificationChannel(PKG_O, UID_O, channel2.getId(), true)); + + assertEquals(7, mLogger.getCalls().size()); + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED, + mLogger.get(0).event); // Channel messages + assertEquals( + NotificationChannelLogger.NotificationChannelEvent.NOTIFICATION_CHANNEL_CREATED, + mLogger.get(1).event); // Channel calls + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_CONVERSATION_CREATED, + mLogger.get(2).event); // Channel channel - Conversation A person msgs + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_CONVERSATION_CREATED, + mLogger.get(3).event); // Channel noMatch - Conversation B person msgs + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_CONVERSATION_CREATED, + mLogger.get(4).event); // Channel channel2 - Conversation A person calls + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_CONVERSATION_DELETED, + mLogger.get(5).event); // Delete Channel channel - Conversation A person msgs + assertEquals( + NotificationChannelLogger.NotificationChannelEvent + .NOTIFICATION_CHANNEL_CONVERSATION_DELETED, + mLogger.get(6).event); // Delete Channel channel2 - Conversation A person calls } }