From d0856f00c748b598eee3106ae7d3f85ea8c91fdc Mon Sep 17 00:00:00 2001 From: Geoffrey Pitsch Date: Thu, 16 Feb 2017 10:51:18 -0500 Subject: [PATCH] Inline Notification Controls to full spec Based off redline spec Support bundled notifications when long-press on summary Hide banned text when sent from the default channel Test: runtest systemui Change-Id: I9315b4dc12cafbdf37ba9896879bfbe23c8d6921 --- .../layout/notification_template_header.xml | 10 +- core/res/res/values/dimens.xml | 11 + .../SystemUI/res/layout/notification_info.xml | 107 +++++---- packages/SystemUI/res/values/strings.xml | 23 +- packages/SystemUI/res/values/styles.xml | 30 ++- .../systemui/statusbar/NotificationInfo.java | 140 ++++++----- .../systemui/statusbar/phone/StatusBar.java | 46 +++- .../statusbar/NotificationInfoTest.java | 223 ++++++++++++++---- 8 files changed, 406 insertions(+), 184 deletions(-) diff --git a/core/res/res/layout/notification_template_header.xml b/core/res/res/layout/notification_template_header.xml index 448cd2e30ef48..a165621b96967 100644 --- a/core/res/res/layout/notification_template_header.xml +++ b/core/res/res/layout/notification_template_header.xml @@ -20,17 +20,17 @@ android:id="@+id/notification_header" android:orientation="horizontal" android:layout_width="wrap_content" - android:layout_height="48dp" + android:layout_height="@dimen/notification_header_height" android:clipChildren="false" - android:paddingTop="10dp" - android:paddingBottom="11dp" + android:paddingTop="@dimen/notification_header_padding_top" + android:paddingBottom="@dimen/notification_header_padding_bottom" android:layout_marginBottom="5dp" android:paddingStart="@dimen/notification_content_margin_start" android:paddingEnd="16dp"> 16dp + + 48dp + 45.5dp + + 10dp + + 11dp + + + 18dp + 92dp diff --git a/packages/SystemUI/res/layout/notification_info.xml b/packages/SystemUI/res/layout/notification_info.xml index 31bd8b9e415ba..195eb9bafdab4 100644 --- a/packages/SystemUI/res/layout/notification_info.xml +++ b/packages/SystemUI/res/layout/notification_info.xml @@ -21,110 +21,113 @@ android:layout_height="wrap_content" android:id="@+id/notification_guts" android:clickable="true" - android:gravity="top|start" android:orientation="vertical" android:paddingStart="@*android:dimen/notification_content_margin_start" - android:paddingEnd="8dp" android:background="@color/notification_guts_bg_color" android:theme="@*android:style/Theme.DeviceDefault.Light"> + android:layout_height="@*android:dimen/notification_header_height" + android:clipChildren="false" + android:paddingTop="@*android:dimen/notification_header_padding_top" + android:paddingBottom="@*android:dimen/notification_header_padding_bottom" + android:gravity="center_vertical" + android:orientation="horizontal" > + android:layout_width="@*android:dimen/notification_header_icon_size" + android:layout_height="@*android:dimen/notification_header_icon_size" + android:layout_marginEnd="3dp"/> + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info" + android:layout_marginStart="3dp" + android:layout_marginEnd="2dp" + android:singleLine="true"/> + android:text="@*android:string/notification_header_divider_symbol"/> + android:textAppearance="@*android:style/TextAppearance.Material.Notification.Info" + android:layout_marginStart="2dp" + android:layout_marginEnd="2dp"/> - + - + + android:orientation="vertical"> + + + + + + + - - - - - + android:layout_marginBottom="8dp" > - + android:layout_height="match_parent" + android:layout_marginEnd="8dp" + style="@style/TextAppearance.NotificationInfo.Button"/> + style="@style/TextAppearance.NotificationInfo.Button"/> diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml index c9e7e57ecc5a0..1f88aca8ec594 100644 --- a/packages/SystemUI/res/values/strings.xml +++ b/packages/SystemUI/res/values/strings.xml @@ -1340,18 +1340,33 @@ Notifications - - " • " - You won\'t get these notifications anymore. + + %d notification categories + + defined, describing the current notification channel as "1 out of n categories from this app". --> 1 out of %d category from this app 1 out of %d categories from this app + + + %1$s, %2$s + + %1$s, %2$s, and %3$d other + %1$s, %2$s, and %3$d others + + All Categories diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml index 9168256eca89f..d6abda6bd4c94 100644 --- a/packages/SystemUI/res/values/styles.xml +++ b/packages/SystemUI/res/values/styles.xml @@ -349,38 +349,42 @@ @style/Preference.DropDown.Material - - - - - diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index 5db5498e9bb6d..a9043e4c83bb1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -35,7 +35,6 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.service.notification.NotificationListenerService; -import android.service.notification.StatusBarNotification; import android.util.AttributeSet; import android.util.Log; import android.view.View; @@ -59,6 +58,8 @@ import com.android.systemui.plugins.statusbar.NotificationMenuRowProvider.GutsIn import com.android.systemui.statusbar.NotificationGuts.OnSettingsClickListener; import com.android.systemui.statusbar.stack.StackStateAnimator; +import java.lang.IllegalArgumentException; +import java.util.List; import java.util.Set; /** @@ -68,9 +69,11 @@ public class NotificationInfo extends LinearLayout implements GutsContent { private static final String TAG = "InfoGuts"; private INotificationManager mINotificationManager; + private String mPkg; + private int mAppUid; + private List mNotificationChannels; + private NotificationChannel mSingleNotificationChannel; private int mStartingUserImportance; - private StatusBarNotification mStatusBarNotification; - private NotificationChannel mNotificationChannel; private TextView mNumChannelsView; private View mChannelDisabledView; @@ -83,36 +86,42 @@ public class NotificationInfo extends LinearLayout implements GutsContent { } public interface OnSettingsClickListener { - void onClick(View v, int appUid); + void onClick(View v, NotificationChannel channel, int appUid); } public void bindNotification(final PackageManager pm, final INotificationManager iNotificationManager, - final StatusBarNotification sbn, final NotificationChannel channel, + final String pkg, + final List notificationChannels, OnSettingsClickListener onSettingsClick, - OnClickListener onDoneClick, final Set nonBlockablePkgs) { + OnClickListener onDoneClick, final Set nonBlockablePkgs) + throws RemoteException { mINotificationManager = iNotificationManager; - mNotificationChannel = channel; - mStatusBarNotification = sbn; - mStartingUserImportance = channel.getImportance(); + mPkg = pkg; + mNotificationChannels = notificationChannels; + if (mNotificationChannels.isEmpty()) { + throw new IllegalArgumentException("bindNotification requires at least one channel"); + } else if (mNotificationChannels.size() == 1) { + mSingleNotificationChannel = mNotificationChannels.get(0); + mStartingUserImportance = mSingleNotificationChannel.getImportance(); + } else { + mSingleNotificationChannel = null; + } - final String pkg = sbn.getPackageName(); - int appUid = -1; - String appName = pkg; + String appName = mPkg; Drawable pkgicon = null; CharSequence channelNameText = ""; ApplicationInfo info = null; try { - info = pm.getApplicationInfo(pkg, + info = pm.getApplicationInfo(mPkg, PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE | PackageManager.MATCH_DIRECT_BOOT_AWARE); if (info != null) { - appUid = info.uid; + mAppUid = info.uid; appName = String.valueOf(pm.getApplicationLabel(info)); pkgicon = pm.getApplicationIcon(info); - } } catch (PackageManager.NameNotFoundException e) { // app is gone, just show package name and generic icon @@ -121,38 +130,54 @@ public class NotificationInfo extends LinearLayout implements GutsContent { ((ImageView) findViewById(R.id.pkgicon)).setImageDrawable(pkgicon); int numChannels = 1; - try { - numChannels = iNotificationManager.getNumNotificationChannelsForPackage( - pkg, appUid, false /* includeDeleted */); - } catch (RemoteException e) { - Log.e(TAG, e.toString()); - } + numChannels = iNotificationManager.getNumNotificationChannelsForPackage( + pkg, mAppUid, false /* includeDeleted */); + String channelsDescText; mNumChannelsView = (TextView) (findViewById(R.id.num_channels_desc)); - mNumChannelsView.setText(String.format(mContext.getResources().getQuantityString( - R.plurals.notification_num_channels_desc, numChannels), numChannels)); + switch (mNotificationChannels.size()) { + case 1: + channelsDescText = String.format(mContext.getResources().getQuantityString( + R.plurals.notification_num_channels_desc, numChannels), numChannels); + break; + case 2: + channelsDescText = mContext.getString(R.string.notification_channels_list_desc_2, + mNotificationChannels.get(0).getName(), + mNotificationChannels.get(1).getName()); + break; + default: + final int numOthers = mNotificationChannels.size() - 2; + channelsDescText = String.format( + mContext.getResources().getQuantityString( + R.plurals.notification_channels_list_desc_2_and_others, numOthers), + mNotificationChannels.get(0).getName(), + mNotificationChannels.get(1).getName(), + numOthers); + } + mNumChannelsView.setText(channelsDescText); - // If this is the placeholder channel, don't use our channel-specific text. - if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) { + if (mSingleNotificationChannel == null) { + // Multiple channels don't use a channel name for the title. + channelNameText = mContext.getString(R.string.notification_num_channels, + mNotificationChannels.size()); + } else if (mSingleNotificationChannel.getId() + .equals(NotificationChannel.DEFAULT_CHANNEL_ID)) { + // If this is the placeholder channel, don't use our channel-specific text. channelNameText = mContext.getString(R.string.notification_header_default_channel); } else { - channelNameText = channel.getName(); + channelNameText = mSingleNotificationChannel.getName(); } ((TextView) findViewById(R.id.pkgname)).setText(appName); ((TextView) findViewById(R.id.channel_name)).setText(channelNameText); // Set group information if this channel has an associated group. CharSequence groupName = null; - if (channel.getGroup() != null) { - try { - final NotificationChannelGroup notificationChannelGroup = - iNotificationManager.getNotificationChannelGroupForPackage( - channel.getGroup(), pkg, appUid); - if (notificationChannelGroup != null) { - groupName = notificationChannelGroup.getName(); - } - } catch (RemoteException e) { - Log.e(TAG, e.toString()); + if (mSingleNotificationChannel != null && mSingleNotificationChannel.getGroup() != null) { + final NotificationChannelGroup notificationChannelGroup = + iNotificationManager.getNotificationChannelGroupForPackage( + mSingleNotificationChannel.getGroup(), pkg, mAppUid); + if (notificationChannelGroup != null) { + groupName = notificationChannelGroup.getName(); } } TextView groupNameView = ((TextView) findViewById(R.id.group_name)); @@ -181,15 +206,15 @@ public class NotificationInfo extends LinearLayout implements GutsContent { // Top-level importance group mChannelDisabledView = findViewById(R.id.channel_disabled); - updateImportanceDisplay(); + updateSecondaryText(); // Settings button. final TextView settingsButton = (TextView) findViewById(R.id.more_settings); - if (appUid >= 0 && onSettingsClick != null) { - final int appUidF = appUid; + if (mAppUid >= 0 && onSettingsClick != null) { + final int appUidF = mAppUid; settingsButton.setOnClickListener( (View view) -> { - onSettingsClick.onClick(view, appUidF); + onSettingsClick.onClick(view, mSingleNotificationChannel, appUidF); }); if (numChannels > 1) { settingsButton.setText(R.string.notification_all_categories); @@ -208,21 +233,24 @@ public class NotificationInfo extends LinearLayout implements GutsContent { } public boolean hasImportanceChanged() { - return mStartingUserImportance != getSelectedImportance(); + return mSingleNotificationChannel != null && + mStartingUserImportance != getSelectedImportance(); } private void saveImportance() { + if (mSingleNotificationChannel == null) { + return; + } int selectedImportance = getSelectedImportance(); if (selectedImportance == mStartingUserImportance) { return; } MetricsLogger.action(mContext, MetricsEvent.ACTION_SAVE_IMPORTANCE, selectedImportance - mStartingUserImportance); - mNotificationChannel.setImportance(selectedImportance); + mSingleNotificationChannel.setImportance(selectedImportance); try { mINotificationManager.updateNotificationChannelForPackage( - mStatusBarNotification.getPackageName(), mStatusBarNotification.getUid(), - mNotificationChannel); + mPkg, mAppUid, mSingleNotificationChannel); } catch (RemoteException e) { // :( } @@ -241,26 +269,32 @@ public class NotificationInfo extends LinearLayout implements GutsContent { mChannelEnabledSwitch = (Switch) findViewById(R.id.channel_enabled_switch); mChannelEnabledSwitch.setChecked( mStartingUserImportance != NotificationManager.IMPORTANCE_NONE); - mChannelEnabledSwitch.setVisibility(nonBlockable ? View.INVISIBLE : View.VISIBLE); + final boolean visible = !nonBlockable && mSingleNotificationChannel != null; + mChannelEnabledSwitch.setVisibility(visible ? View.VISIBLE : View.INVISIBLE); // Callback when checked. mChannelEnabledSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { if (mGutsInteractionListener != null) { mGutsInteractionListener.onInteraction(NotificationInfo.this); } - updateImportanceDisplay(); + updateSecondaryText(); }); } - private void updateImportanceDisplay() { - final boolean disabled = getSelectedImportance() == NotificationManager.IMPORTANCE_NONE; - mChannelDisabledView.setVisibility(disabled ? View.VISIBLE : View.GONE); - if (disabled) { - // To be replaced by disabled text. + private void updateSecondaryText() { + final boolean defaultChannel = mSingleNotificationChannel != null && + mSingleNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID); + final boolean disabled = mSingleNotificationChannel != null && + getSelectedImportance() == NotificationManager.IMPORTANCE_NONE; + if (defaultChannel) { + // Don't show any secondary text if this is from the default channel. + mChannelDisabledView.setVisibility(View.GONE); + mNumChannelsView.setVisibility(View.GONE); + } else if (disabled) { + mChannelDisabledView.setVisibility(View.VISIBLE); mNumChannelsView.setVisibility(View.GONE); - } else if (mNotificationChannel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) { - mNumChannelsView.setVisibility(View.INVISIBLE); } else { + mChannelDisabledView.setVisibility(View.GONE); mNumChannelsView.setVisibility(View.VISIBLE); } } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java index 2c5bd3c4d8a9a..101aee4e9130a 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBar.java @@ -18,7 +18,6 @@ package com.android.systemui.statusbar.phone; - import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN; import static android.app.StatusBarManager.WINDOW_STATE_SHOWING; import static android.app.StatusBarManager.windowStateToString; @@ -196,8 +195,6 @@ import com.android.systemui.statusbar.policy.UserInfoControllerImpl; import com.android.systemui.statusbar.policy.UserSwitcherController; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout; import com.android.systemui.statusbar.stack.NotificationStackScrollLayout.OnChildLocationsChangedListener; - - import com.android.systemui.util.leak.LeakDetector; import com.android.systemui.volume.VolumeComponent; @@ -5719,11 +5716,13 @@ public class StatusBar extends SystemUI implements DemoMode, // The (i) button in the guts that links to the system notification settings for that app private void startAppNotificationSettingsActivity(String packageName, final int appUid, - final String channelId) { + final NotificationChannel channel) { final Intent intent = new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS); intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName); intent.putExtra(Settings.EXTRA_APP_UID, appUid); - intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channelId); + if (channel != null) { + intent.putExtra(EXTRA_FRAGMENT_ARG_KEY, channel.getId()); + } startNotificationGutsIntent(intent, appUid); } @@ -5776,23 +5775,23 @@ public class StatusBar extends SystemUI implements DemoMode, } if (item.gutsContent instanceof NotificationInfo) { - final NotificationChannel channel = row.getEntry().channel; + final UserHandle userHandle = sbn.getUser(); PackageManager pmUser = getPackageManagerForUser(mContext, - sbn.getUser().getIdentifier()); + userHandle.getIdentifier()); final INotificationManager iNotificationManager = INotificationManager.Stub.asInterface( ServiceManager.getService(Context.NOTIFICATION_SERVICE)); final String pkg = sbn.getPackageName(); NotificationInfo info = (NotificationInfo) item.gutsContent; final NotificationInfo.OnSettingsClickListener onSettingsClick = (View v, - int appUid) -> { + NotificationChannel channel, int appUid) -> { mMetricsLogger.action(MetricsEvent.ACTION_NOTE_INFO); guts.resetFalsingCheck(); - startAppNotificationSettingsActivity(pkg, appUid, channel.getId()); + startAppNotificationSettingsActivity(pkg, appUid, channel); }; final View.OnClickListener onDoneClick = (View v) -> { // If the user has security enabled, show challenge if the setting is changed. if (info.hasImportanceChanged() - && isLockscreenPublicMode(sbn.getUser().getIdentifier()) + && isLockscreenPublicMode(userHandle.getIdentifier()) && (mState == StatusBarState.KEYGUARD || mState == StatusBarState.SHADE_LOCKED)) { OnDismissAction dismissAction = new OnDismissAction() { @@ -5807,9 +5806,30 @@ public class StatusBar extends SystemUI implements DemoMode, saveAndCloseNotificationMenu(info, row, guts, v); } }; - info.bindNotification(pmUser, iNotificationManager, sbn, channel, onSettingsClick, - onDoneClick, - mNonBlockablePkgs); + + ArraySet channels = new ArraySet(); + channels.add(row.getEntry().channel); + if (row.isSummaryWithChildren()) { + // If this is a summary, then add in the children notification channels for the + // same user and pkg. + final List childrenRows = row.getNotificationChildren(); + final int numChildren = childrenRows.size(); + for (int i = 0; i < numChildren; i++) { + final ExpandableNotificationRow childRow = childrenRows.get(i); + final NotificationChannel childChannel = childRow.getEntry().channel; + final StatusBarNotification childSbn = childRow.getStatusBarNotification(); + if (childSbn.getUser().equals(userHandle) && + childSbn.getPackageName().equals(pkg)) { + channels.add(childChannel); + } + } + } + try { + info.bindNotification(pmUser, iNotificationManager, pkg, new ArrayList(channels), + onSettingsClick, onDoneClick, mNonBlockablePkgs); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java index 726300f79fc1f..8aca546b27302 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -56,8 +56,9 @@ import com.android.systemui.SysuiTestCase; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; - +import java.util.Arrays; import java.util.Collections; +import java.util.List; import java.util.concurrent.CountDownLatch; @SmallTest @@ -72,14 +73,12 @@ public class NotificationInfoTest extends SysuiTestCase { private final INotificationManager mMockINotificationManager = mock(INotificationManager.class); private final PackageManager mMockPackageManager = mock(PackageManager.class); private NotificationChannel mNotificationChannel; - private final StatusBarNotification mMockStatusBarNotification = - mock(StatusBarNotification.class); + private NotificationChannel mDefaultNotificationChannel; @Before public void setUp() throws Exception { // Inflate the layout - final LayoutInflater layoutInflater = - LayoutInflater.from(mContext); + final LayoutInflater layoutInflater = LayoutInflater.from(mContext); mNotificationInfo = (NotificationInfo) layoutInflater.inflate(R.layout.notification_info, null); @@ -92,30 +91,51 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockPackageManager.getApplicationInfo(anyString(), anyInt())).thenReturn( applicationInfo); - // mMockStatusBarNotification with a test channel. - mNotificationChannel = new NotificationChannel( - TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW); - when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + // Package has one channel by default. when(mMockINotificationManager.getNumNotificationChannelsForPackage( eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(1); + + // Some test channels. + mNotificationChannel = new NotificationChannel( + TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW); + mDefaultNotificationChannel = new NotificationChannel( + NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, + NotificationManager.IMPORTANCE_LOW); } private CharSequence getStringById(int resId) { return mContext.getString(resId); } - private CharSequence getNumChannelsString(int numChannels) { + private CharSequence getNumChannelsDescString(int numChannels) { return String.format( mContext.getResources().getQuantityString( R.plurals.notification_num_channels_desc, numChannels), numChannels); } + private CharSequence getChannelsListDescString(NotificationChannel... channels) { + if (channels.length == 2) { + return mContext.getString(R.string.notification_channels_list_desc_2, + channels[0].getName(), channels[1].getName()); + } else { + final int numOthers = channels.length - 2; + return String.format( + mContext.getResources().getQuantityString( + R.plurals.notification_channels_list_desc_2_and_others, numOthers), + channels[0].getName(), channels[1].getName(), numOthers); + } + } + + private CharSequence getNumChannelsString(int numChannels) { + return mContext.getString(R.string.notification_num_channels, numChannels); + } + @Test public void testBindNotification_SetsTextApplicationName() throws Exception { when(mMockPackageManager.getApplicationLabel(any())).thenReturn("App Name"); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.pkgname); assertTrue(textView.getText().toString().contains("App Name")); } @@ -126,7 +146,7 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockPackageManager.getApplicationIcon(any(ApplicationInfo.class))) .thenReturn(iconDrawable); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final ImageView iconView = (ImageView) mNotificationInfo.findViewById(R.id.pkgicon); assertEquals(iconDrawable, iconView.getDrawable()); } @@ -134,7 +154,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_GroupNameHiddenIfNoGroup() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.GONE, groupNameView.getVisibility()); final TextView groupDividerView = @@ -151,7 +171,7 @@ public class NotificationInfoTest extends SysuiTestCase { eq("test_group_id"), eq(TEST_PACKAGE_NAME), anyInt())) .thenReturn(notificationChannelGroup); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView groupNameView = (TextView) mNotificationInfo.findViewById(R.id.group_name); assertEquals(View.VISIBLE, groupNameView.getVisibility()); assertEquals("Test Group Name", groupNameView.getText()); @@ -163,7 +183,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SetsTextChannelName() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView textView = (TextView) mNotificationInfo.findViewById(R.id.channel_name); assertEquals(TEST_CHANNEL_NAME, textView.getText()); } @@ -172,8 +192,29 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsOnClickListenerForSettings() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, - (View v, int appUid) -> { latch.countDown(); }, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + (View v, NotificationChannel c, int appUid) -> { + assertEquals(mNotificationChannel, c); + latch.countDown(); + }, null, null); + + final TextView settingsButton = + (TextView) mNotificationInfo.findViewById(R.id.more_settings); + settingsButton.performClick(); + // Verify that listener was triggered. + assertEquals(0, latch.getCount()); + } + + @Test + public void testOnClickListenerPassesNullChannelForBundle() throws Exception { + final CountDownLatch latch = new CountDownLatch(1); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, + Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), + (View v, NotificationChannel c, int appUid) -> { + assertEquals(null, c); + latch.countDown(); + }, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); @@ -185,8 +226,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_SettingsTextWithOneChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, (View v, int appUid) -> {}, null, - null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + (View v, NotificationChannel c, int appUid) -> {}, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); assertEquals(getStringById(R.string.notification_more_settings), settingsButton.getText()); @@ -197,8 +238,8 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockINotificationManager.getNumNotificationChannelsForPackage( eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, (View v, int appUid) -> {}, null, - null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), + (View v, NotificationChannel c, int appUid) -> {}, null, null); final TextView settingsButton = (TextView) mNotificationInfo.findViewById(R.id.more_settings); assertEquals(getStringById(R.string.notification_all_categories), settingsButton.getText()); @@ -208,7 +249,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_SetsOnClickListenerForDone() throws Exception { final CountDownLatch latch = new CountDownLatch(1); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, (View v) -> { latch.countDown(); }, null); @@ -220,11 +261,8 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_NumChannelsTextHiddenWhenDefaultChannel() throws Exception { - final NotificationChannel defaultChannel = new NotificationChannel( - NotificationChannel.DEFAULT_CHANNEL_ID, TEST_CHANNEL_NAME, - NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, defaultChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertTrue(numChannelsView.getVisibility() != View.VISIBLE); @@ -234,11 +272,11 @@ public class NotificationInfoTest extends SysuiTestCase { public void testBindNotification_NumChannelsTextDisplaysWhenNotDefaultChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); assertEquals(numChannelsView.getVisibility(), View.VISIBLE); - assertEquals(getNumChannelsString(1), numChannelsView.getText()); + assertEquals(getNumChannelsDescString(1), numChannelsView.getText()); } @Test @@ -247,16 +285,90 @@ public class NotificationInfoTest extends SysuiTestCase { when(mMockINotificationManager.getNumNotificationChannelsForPackage( eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(2); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView numChannelsView = (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); - assertEquals(getNumChannelsString(2), numChannelsView.getText()); + assertEquals(getNumChannelsDescString(2), numChannelsView.getText()); + } + + @Test + @UiThreadTest + public void testBindNotification_NumChannelsTextListsChannelsWhenTwoInBundle() + throws Exception { + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), + null, null, null); + final TextView numChannelsView = + (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); + assertEquals(getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel), + numChannelsView.getText()); + } + + @Test + @UiThreadTest + public void testBindNotification_NumChannelsTextListsChannelsWhenThreeInBundle() + throws Exception { + NotificationChannel thirdChannel = new NotificationChannel( + "third_channel", "third_channel", NotificationManager.IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, + Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel), + null, null, null); + final TextView numChannelsView = + (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); + assertEquals( + getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel, + thirdChannel), + numChannelsView.getText()); + } + + @Test + @UiThreadTest + public void testBindNotification_NumChannelsTextListsChannelsWhenFourInBundle() + throws Exception { + NotificationChannel thirdChannel = new NotificationChannel( + "third_channel", "third_channel", NotificationManager.IMPORTANCE_LOW); + NotificationChannel fourthChannel = new NotificationChannel( + "fourth_channel", "fourth_channel", NotificationManager.IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, + Arrays.asList(mNotificationChannel, mDefaultNotificationChannel, thirdChannel, + fourthChannel), + null, null, null); + final TextView numChannelsView = + (TextView) mNotificationInfo.findViewById(R.id.num_channels_desc); + assertEquals( + getChannelsListDescString(mNotificationChannel, mDefaultNotificationChannel, + thirdChannel, fourthChannel), + numChannelsView.getText()); + } + + @Test + @UiThreadTest + public void testBindNotification_ChannelNameChangesWhenBundleFromDifferentChannels() + throws Exception { + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), + null, null, null); + final TextView channelNameView = + (TextView) mNotificationInfo.findViewById(R.id.channel_name); + assertEquals(getNumChannelsString(2), channelNameView.getText()); + } + + @Test + @UiThreadTest + public void testEnabledSwitchInvisibleIfBundleFromDifferentChannels() throws Exception { + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel, mDefaultNotificationChannel), + null, null, null); + Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); + assertEquals(View.INVISIBLE, enabledSwitch.getVisibility()); } @Test public void testbindNotification_ChannelDisabledTextGoneWhenNotDisabled() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView channelDisabledView = (TextView) mNotificationInfo.findViewById(R.id.channel_disabled); assertEquals(channelDisabledView.getVisibility(), View.GONE); @@ -266,7 +378,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testbindNotification_ChannelDisabledTextVisibleWhenDisabled() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); final TextView channelDisabledView = (TextView) mNotificationInfo.findViewById(R.id.channel_disabled); assertEquals(channelDisabledView.getVisibility(), View.VISIBLE); @@ -277,9 +389,21 @@ public class NotificationInfoTest extends SysuiTestCase { } @Test + @UiThreadTest + public void testBindNotification_ChannelDisabledTextHiddenWhenDefaultChannel() + throws Exception { + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mDefaultNotificationChannel), null, null, null); + final TextView channelDisabledView = + (TextView) mNotificationInfo.findViewById(R.id.channel_disabled); + assertTrue(channelDisabledView.getVisibility() != View.VISIBLE); + } + + @Test + @UiThreadTest public void testHasImportanceChanged_DefaultsToFalse() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); assertFalse(mNotificationInfo.hasImportanceChanged()); } @@ -287,7 +411,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testHasImportanceChanged_ReturnsTrueAfterChannelDisabled() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); // Find the high button and check it. Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); enabledSwitch.setChecked(false); @@ -297,7 +421,7 @@ public class NotificationInfoTest extends SysuiTestCase { @Test public void testBindNotification_DoesNotUpdateNotificationChannel() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( anyString(), anyInt(), any()); } @@ -306,7 +430,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testDoesNotUpdateNotificationChannelAfterImportanceChanged() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); enabledSwitch.setChecked(false); @@ -318,7 +442,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testHandleCloseControls_DoesNotUpdateNotificationChannelIfUnchanged() throws Exception { mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); mNotificationInfo.handleCloseControls(true); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -330,7 +454,7 @@ public class NotificationInfoTest extends SysuiTestCase { throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_UNSPECIFIED); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); mNotificationInfo.handleCloseControls(true); verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( @@ -341,7 +465,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchOnByDefault() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertTrue(enabledSwitch.isChecked()); @@ -351,7 +475,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledButtonOffWhenAlreadyBanned() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_NONE); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertFalse(enabledSwitch.isChecked()); @@ -361,7 +485,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchVisibleByDefault() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, null); + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, null); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertEquals(View.VISIBLE, enabledSwitch.getVisibility()); @@ -371,18 +495,29 @@ public class NotificationInfoTest extends SysuiTestCase { public void testEnabledSwitchInvisibleIfNonBlockable() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); assertEquals(View.INVISIBLE, enabledSwitch.getVisibility()); } + @Test + public void testNonBlockableAppDoesNotBecomeBlocked() throws Exception { + mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, + Collections.singleton(TEST_PACKAGE_NAME)); + mNotificationInfo.handleCloseControls(true); + verify(mMockINotificationManager, never()).updateNotificationChannelForPackage( + anyString(), anyInt(), any()); + } + @Test public void testEnabledSwitchChangedCallsUpdateNotificationChannel() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch); @@ -396,7 +531,7 @@ public class NotificationInfoTest extends SysuiTestCase { public void testCloseControlsDoesNotUpdateIfSaveIsFalse() throws Exception { mNotificationChannel.setImportance(NotificationManager.IMPORTANCE_LOW); mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, - mMockStatusBarNotification, mNotificationChannel, null, null, + TEST_PACKAGE_NAME, Arrays.asList(mNotificationChannel), null, null, Collections.singleton(TEST_PACKAGE_NAME)); Switch enabledSwitch = (Switch) mNotificationInfo.findViewById(R.id.channel_enabled_switch);