Loading core/res/res/values/config.xml +1 −0 Original line number Diff line number Diff line Loading @@ -2491,6 +2491,7 @@ <item>com.android.server.notification.VisibilityExtractor</item> <!-- Depends on ZenModeExtractor --> <item>com.android.server.notification.BadgeExtractor</item> <item>com.android.server.notification.CriticalNotificationExtractor</item> </string-array> Loading services/core/java/com/android/server/notification/CriticalNotificationExtractor.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import android.app.Notification; import android.content.Context; import android.content.pm.PackageManager; import android.util.Slog; /** * Sets the criticality of a notification record. This is used to allow a bypass to all other * ranking signals. It is required in the automotive use case to facilitate placing emergency and * warning notifications above all others. It does not process notifications unless the system * has the automotive feature flag set. * <p> * Note: it is up to the notification ranking system to determine the effect of criticality values * on a notification record * */ public class CriticalNotificationExtractor implements NotificationSignalExtractor { private static final String TAG = "CriticalNotificationExt"; private static final boolean DBG = false; private boolean mSupportsCriticalNotifications = false; /** * Intended to bypass all other ranking, notification should be placed above all others. * In the automotive case, the notification would be used to tell a driver to pull over * immediately */ static final int CRITICAL = 0; /** * Indicates a notification should be place above all notifications except those marked as * critical. In the automotive case this is a check engine light. */ static final int CRITICAL_LOW = 1; /** Normal notification. */ static final int NORMAL = 2; @Override public void initialize(Context context, NotificationUsageStats usageStats) { if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + "."); mSupportsCriticalNotifications = supportsCriticalNotifications(context); } private boolean supportsCriticalNotifications(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0); } @Override public RankingReconsideration process(NotificationRecord record) { if (!mSupportsCriticalNotifications) { if (DBG) Slog.d(TAG, "skipping since system does not support critical notification"); return null; } if (record == null || record.getNotification() == null) { if (DBG) Slog.d(TAG, "skipping empty notification"); return null; } // Note: The use of both CATEGORY_CAR_EMERGENCY and CATEGORY_CAR_WARNING is restricted to // System apps if (record.isCategory(Notification.CATEGORY_CAR_EMERGENCY)) { record.setCriticality(CRITICAL); } else if (record.isCategory(Notification.CATEGORY_CAR_WARNING)) { record.setCriticality(CRITICAL_LOW); } else { record.setCriticality(NORMAL); } return null; } @Override public void setConfig(RankingConfig config) { } @Override public void setZenHelper(ZenModeHelper helper) { } } services/core/java/com/android/server/notification/NotificationRecord.java +15 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,8 @@ public final class NotificationRecord { private int mPackageVisibility; private int mUserImportance = IMPORTANCE_UNSPECIFIED; private int mImportance = IMPORTANCE_UNSPECIFIED; // Field used in global sort key to bypass normal notifications private int mCriticality = CriticalNotificationExtractor.NORMAL; private CharSequence mImportanceExplanation = null; private int mSuppressedVisualEffects = 0; Loading Loading @@ -746,6 +748,19 @@ public final class NotificationRecord { return mIntercept; } /** * Set to affect global sort key. * * @param criticality used in a string based sort thus 0 is the most critical */ public void setCriticality(int criticality) { mCriticality = criticality; } public int getCriticality() { return mCriticality; } public boolean isIntercepted() { return mIntercept; } Loading services/core/java/com/android/server/notification/RankingHelper.java +4 −1 Original line number Diff line number Diff line Loading @@ -106,6 +106,8 @@ public class RankingHelper { synchronized (mProxyByGroupTmp) { // record individual ranking result and nominate proxies for each group // Note: iteration is done backwards such that the index can be used as a sort key // in a string compare below for (int i = N - 1; i >= 0; i--) { final NotificationRecord record = notificationList.get(i); record.setAuthoritativeRank(i); Loading Loading @@ -138,7 +140,8 @@ public class RankingHelper { boolean isGroupSummary = record.getNotification().isGroupSummary(); record.setGlobalSortKey( String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", String.format("crtcl=0x%04x:intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", record.getCriticality(), record.isRecentlyIntrusive() && record.getImportance() > NotificationManager.IMPORTANCE_MIN ? '0' : '1', Loading services/tests/uiservicestests/src/com/android/server/notification/CriticalNotificationExtractorTest.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import static android.app.NotificationManager.IMPORTANCE_LOW; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; import android.content.pm.PackageManager; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.TestableContext; import com.android.server.UiServiceTestCase; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class CriticalNotificationExtractorTest extends UiServiceTestCase { @Mock private PackageManager mPackageManagerClient; private TestableContext mContext = spy(getContext()); private CriticalNotificationExtractor mExtractor; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext.setMockPackageManager(mPackageManagerClient); mExtractor = new CriticalNotificationExtractor(); } /** confirm there is no affect on notifcations if the automotive feature flag is not set */ @Test public void testExtractCritically_nonsupporting() throws Exception { when(mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) .thenReturn(false); mExtractor.initialize(mContext, null); assertCriticality(Notification.CATEGORY_CAR_EMERGENCY, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CAR_WARNING, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CAR_INFORMATION, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CALL, CriticalNotificationExtractor.NORMAL); } @Test public void testExtractCritically() throws Exception { when(mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) .thenReturn(true); mExtractor.initialize(mContext, null); assertCriticality(Notification.CATEGORY_CAR_EMERGENCY, CriticalNotificationExtractor.CRITICAL); assertCriticality(Notification.CATEGORY_CAR_WARNING, CriticalNotificationExtractor.CRITICAL_LOW); assertCriticality(Notification.CATEGORY_CAR_INFORMATION, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CALL, CriticalNotificationExtractor.NORMAL); } private void assertCriticality(String cat, int criticality) { NotificationRecord info = generateRecord(cat); mExtractor.process(info); assertThat(info.getCriticality(), is(criticality)); } private NotificationRecord generateRecord(String category) { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); final Notification.Builder builder = new Notification.Builder(getContext()) .setContentTitle("foo") .setCategory(category) .setSmallIcon(android.R.drawable.sym_def_app_icon); Notification n = builder.build(); StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0, 0, n, UserHandle.ALL, null, System.currentTimeMillis()); return new NotificationRecord(getContext(), sbn, channel); } } Loading
core/res/res/values/config.xml +1 −0 Original line number Diff line number Diff line Loading @@ -2491,6 +2491,7 @@ <item>com.android.server.notification.VisibilityExtractor</item> <!-- Depends on ZenModeExtractor --> <item>com.android.server.notification.BadgeExtractor</item> <item>com.android.server.notification.CriticalNotificationExtractor</item> </string-array> Loading
services/core/java/com/android/server/notification/CriticalNotificationExtractor.java 0 → 100644 +93 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import android.app.Notification; import android.content.Context; import android.content.pm.PackageManager; import android.util.Slog; /** * Sets the criticality of a notification record. This is used to allow a bypass to all other * ranking signals. It is required in the automotive use case to facilitate placing emergency and * warning notifications above all others. It does not process notifications unless the system * has the automotive feature flag set. * <p> * Note: it is up to the notification ranking system to determine the effect of criticality values * on a notification record * */ public class CriticalNotificationExtractor implements NotificationSignalExtractor { private static final String TAG = "CriticalNotificationExt"; private static final boolean DBG = false; private boolean mSupportsCriticalNotifications = false; /** * Intended to bypass all other ranking, notification should be placed above all others. * In the automotive case, the notification would be used to tell a driver to pull over * immediately */ static final int CRITICAL = 0; /** * Indicates a notification should be place above all notifications except those marked as * critical. In the automotive case this is a check engine light. */ static final int CRITICAL_LOW = 1; /** Normal notification. */ static final int NORMAL = 2; @Override public void initialize(Context context, NotificationUsageStats usageStats) { if (DBG) Slog.d(TAG, "Initializing " + getClass().getSimpleName() + "."); mSupportsCriticalNotifications = supportsCriticalNotifications(context); } private boolean supportsCriticalNotifications(Context context) { return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0); } @Override public RankingReconsideration process(NotificationRecord record) { if (!mSupportsCriticalNotifications) { if (DBG) Slog.d(TAG, "skipping since system does not support critical notification"); return null; } if (record == null || record.getNotification() == null) { if (DBG) Slog.d(TAG, "skipping empty notification"); return null; } // Note: The use of both CATEGORY_CAR_EMERGENCY and CATEGORY_CAR_WARNING is restricted to // System apps if (record.isCategory(Notification.CATEGORY_CAR_EMERGENCY)) { record.setCriticality(CRITICAL); } else if (record.isCategory(Notification.CATEGORY_CAR_WARNING)) { record.setCriticality(CRITICAL_LOW); } else { record.setCriticality(NORMAL); } return null; } @Override public void setConfig(RankingConfig config) { } @Override public void setZenHelper(ZenModeHelper helper) { } }
services/core/java/com/android/server/notification/NotificationRecord.java +15 −0 Original line number Diff line number Diff line Loading @@ -144,6 +144,8 @@ public final class NotificationRecord { private int mPackageVisibility; private int mUserImportance = IMPORTANCE_UNSPECIFIED; private int mImportance = IMPORTANCE_UNSPECIFIED; // Field used in global sort key to bypass normal notifications private int mCriticality = CriticalNotificationExtractor.NORMAL; private CharSequence mImportanceExplanation = null; private int mSuppressedVisualEffects = 0; Loading Loading @@ -746,6 +748,19 @@ public final class NotificationRecord { return mIntercept; } /** * Set to affect global sort key. * * @param criticality used in a string based sort thus 0 is the most critical */ public void setCriticality(int criticality) { mCriticality = criticality; } public int getCriticality() { return mCriticality; } public boolean isIntercepted() { return mIntercept; } Loading
services/core/java/com/android/server/notification/RankingHelper.java +4 −1 Original line number Diff line number Diff line Loading @@ -106,6 +106,8 @@ public class RankingHelper { synchronized (mProxyByGroupTmp) { // record individual ranking result and nominate proxies for each group // Note: iteration is done backwards such that the index can be used as a sort key // in a string compare below for (int i = N - 1; i >= 0; i--) { final NotificationRecord record = notificationList.get(i); record.setAuthoritativeRank(i); Loading Loading @@ -138,7 +140,8 @@ public class RankingHelper { boolean isGroupSummary = record.getNotification().isGroupSummary(); record.setGlobalSortKey( String.format("intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", String.format("crtcl=0x%04x:intrsv=%c:grnk=0x%04x:gsmry=%c:%s:rnk=0x%04x", record.getCriticality(), record.isRecentlyIntrusive() && record.getImportance() > NotificationManager.IMPORTANCE_MIN ? '0' : '1', Loading
services/tests/uiservicestests/src/com/android/server/notification/CriticalNotificationExtractorTest.java 0 → 100644 +109 −0 Original line number Diff line number Diff line /* * Copyright (C) 2018 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.server.notification; import static android.app.NotificationManager.IMPORTANCE_LOW; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.app.Notification; import android.app.NotificationChannel; import android.content.pm.PackageManager; import android.os.UserHandle; import android.service.notification.StatusBarNotification; import android.testing.TestableContext; import com.android.server.UiServiceTestCase; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; import org.mockito.MockitoAnnotations; public class CriticalNotificationExtractorTest extends UiServiceTestCase { @Mock private PackageManager mPackageManagerClient; private TestableContext mContext = spy(getContext()); private CriticalNotificationExtractor mExtractor; @Before public void setUp() { MockitoAnnotations.initMocks(this); mContext.setMockPackageManager(mPackageManagerClient); mExtractor = new CriticalNotificationExtractor(); } /** confirm there is no affect on notifcations if the automotive feature flag is not set */ @Test public void testExtractCritically_nonsupporting() throws Exception { when(mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) .thenReturn(false); mExtractor.initialize(mContext, null); assertCriticality(Notification.CATEGORY_CAR_EMERGENCY, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CAR_WARNING, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CAR_INFORMATION, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CALL, CriticalNotificationExtractor.NORMAL); } @Test public void testExtractCritically() throws Exception { when(mPackageManagerClient.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, 0)) .thenReturn(true); mExtractor.initialize(mContext, null); assertCriticality(Notification.CATEGORY_CAR_EMERGENCY, CriticalNotificationExtractor.CRITICAL); assertCriticality(Notification.CATEGORY_CAR_WARNING, CriticalNotificationExtractor.CRITICAL_LOW); assertCriticality(Notification.CATEGORY_CAR_INFORMATION, CriticalNotificationExtractor.NORMAL); assertCriticality(Notification.CATEGORY_CALL, CriticalNotificationExtractor.NORMAL); } private void assertCriticality(String cat, int criticality) { NotificationRecord info = generateRecord(cat); mExtractor.process(info); assertThat(info.getCriticality(), is(criticality)); } private NotificationRecord generateRecord(String category) { NotificationChannel channel = new NotificationChannel("a", "a", IMPORTANCE_LOW); final Notification.Builder builder = new Notification.Builder(getContext()) .setContentTitle("foo") .setCategory(category) .setSmallIcon(android.R.drawable.sym_def_app_icon); Notification n = builder.build(); StatusBarNotification sbn = new StatusBarNotification("", "", 0, "", 0, 0, n, UserHandle.ALL, null, System.currentTimeMillis()); return new NotificationRecord(getContext(), sbn, channel); } }