diff --git a/api/current.txt b/api/current.txt index f1243a6824d94..a47cb761e4a7c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -5482,6 +5482,7 @@ package android.app { public final class NotificationChannel implements android.os.Parcelable { ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int); + ctor public NotificationChannel(java.lang.String, int, int); ctor protected NotificationChannel(android.os.Parcel); method public boolean canBypassDnd(); method public boolean canShowBadge(); @@ -5495,6 +5496,7 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public java.lang.CharSequence getName(); + method public int getNameResId(); method public android.net.Uri getSound(); method public long[] getVibrationPattern(); method public void setBypassDnd(boolean); diff --git a/api/system-current.txt b/api/system-current.txt index caccb0f20e95f..e6fb17d65e914 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5658,6 +5658,7 @@ package android.app { public final class NotificationChannel implements android.os.Parcelable { ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int); + ctor public NotificationChannel(java.lang.String, int, int); ctor protected NotificationChannel(android.os.Parcel); method public boolean canBypassDnd(); method public boolean canShowBadge(); @@ -5671,6 +5672,7 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public java.lang.CharSequence getName(); + method public int getNameResId(); method public android.net.Uri getSound(); method public int getUserLockedFields(); method public long[] getVibrationPattern(); diff --git a/api/test-current.txt b/api/test-current.txt index 81718d5028622..904aa40d07f0c 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -5492,6 +5492,7 @@ package android.app { public final class NotificationChannel implements android.os.Parcelable { ctor public NotificationChannel(java.lang.String, java.lang.CharSequence, int); + ctor public NotificationChannel(java.lang.String, int, int); ctor protected NotificationChannel(android.os.Parcel); method public boolean canBypassDnd(); method public boolean canShowBadge(); @@ -5505,6 +5506,7 @@ package android.app { method public int getLightColor(); method public int getLockscreenVisibility(); method public java.lang.CharSequence getName(); + method public int getNameResId(); method public android.net.Uri getSound(); method public long[] getVibrationPattern(); method public void setBypassDnd(boolean); diff --git a/core/java/android/app/NotificationChannel.java b/core/java/android/app/NotificationChannel.java index 1a516087a6e5f..85e6b85c2beb1 100644 --- a/core/java/android/app/NotificationChannel.java +++ b/core/java/android/app/NotificationChannel.java @@ -20,8 +20,10 @@ import org.json.JSONObject; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlSerializer; +import android.annotation.NonNull; +import android.annotation.Nullable; +import android.annotation.StringRes; import android.annotation.SystemApi; -import android.graphics.Color; import android.media.AudioAttributes; import android.net.Uri; import android.os.Parcel; @@ -45,6 +47,7 @@ public final class NotificationChannel implements Parcelable { private static final String TAG_CHANNEL = "channel"; private static final String ATT_NAME = "name"; + private static final String ATT_NAME_RES_ID = "name_res_id"; private static final String ATT_ID = "id"; private static final String ATT_DELETED = "deleted"; private static final String ATT_PRIORITY = "priority"; @@ -138,6 +141,7 @@ public final class NotificationChannel implements Parcelable { private final String mId; private CharSequence mName; + private int mNameResId = 0; private int mImportance = DEFAULT_IMPORTANCE; private boolean mBypassDnd; private int mLockscreenVisibility = DEFAULT_VISIBILITY; @@ -156,7 +160,9 @@ public final class NotificationChannel implements Parcelable { * Creates a notification channel. * * @param id The id of the channel. Must be unique per package. - * @param name The user visible name of the channel. + * @param name The user visible name of the channel. Unchangeable once created; use this + * constructor if the channel represents a user-defined category that does not + * need to be translated. * @param importance The importance of the channel. This controls how interruptive notifications * posted to this channel are. See e.g. * {@link NotificationManager#IMPORTANCE_DEFAULT}. @@ -167,6 +173,21 @@ public final class NotificationChannel implements Parcelable { this.mImportance = importance; } + /** + * Creates a notification channel. + * + * @param id The id of the channel. Must be unique per package. + * @param nameResId The resource id of the string containing the channel name. + * @param importance The importance of the channel. This controls how interruptive notifications + * posted to this channel are. See e.g. + * {@link NotificationManager#IMPORTANCE_DEFAULT}. + */ + public NotificationChannel(String id, @StringRes int nameResId, int importance) { + this.mId = id; + this.mNameResId = nameResId; + this.mImportance = importance; + } + protected NotificationChannel(Parcel in) { if (in.readByte() != 0) { mId = in.readString(); @@ -174,6 +195,7 @@ public final class NotificationChannel implements Parcelable { mId = null; } mName = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in); + mNameResId = in.readInt(); mImportance = in.readInt(); mBypassDnd = in.readByte() != 0; mLockscreenVisibility = in.readInt(); @@ -206,6 +228,7 @@ public final class NotificationChannel implements Parcelable { dest.writeByte((byte) 0); } TextUtils.writeToParcel(mName, dest, flags); + dest.writeInt(mNameResId); dest.writeInt(mImportance); dest.writeByte(mBypassDnd ? (byte) 1 : (byte) 0); dest.writeInt(mLockscreenVisibility); @@ -382,10 +405,17 @@ public final class NotificationChannel implements Parcelable { /** * Returns the user visible name of this channel. */ - public CharSequence getName() { + public @Nullable CharSequence getName() { return mName; } + /** + * Returns the resource id of the user visible name of this channel. + */ + public int getNameResId() { + return mNameResId; + } + /** * Returns the user specified importance {e.g. @link NotificationManager#IMPORTANCE_LOW} for * notifications posted to this channel. @@ -516,7 +546,10 @@ public final class NotificationChannel implements Parcelable { public void writeXml(XmlSerializer out) throws IOException { out.startTag(null, TAG_CHANNEL); out.attribute(null, ATT_ID, getId()); - out.attribute(null, ATT_NAME, getName().toString()); + if (getName() != null) { + out.attribute(null, ATT_NAME, getName().toString()); + } + out.attribute(null, ATT_NAME_RES_ID, Integer.toString(getNameResId())); if (getImportance() != DEFAULT_IMPORTANCE) { out.attribute( null, ATT_IMPORTANCE, Integer.toString(getImportance())); @@ -574,6 +607,7 @@ public final class NotificationChannel implements Parcelable { JSONObject record = new JSONObject(); record.put(ATT_ID, getId()); record.put(ATT_NAME, getName()); + record.put(ATT_NAME_RES_ID, getNameResId()); if (getImportance() != DEFAULT_IMPORTANCE) { record.put(ATT_IMPORTANCE, NotificationListenerService.Ranking.importanceToString(getImportance())); @@ -691,6 +725,7 @@ public final class NotificationChannel implements Parcelable { NotificationChannel that = (NotificationChannel) o; + if (getNameResId() != that.getNameResId()) return false; if (getImportance() != that.getImportance()) return false; if (mBypassDnd != that.mBypassDnd) return false; if (getLockscreenVisibility() != that.getLockscreenVisibility()) return false; @@ -720,6 +755,7 @@ public final class NotificationChannel implements Parcelable { public int hashCode() { int result = getId() != null ? getId().hashCode() : 0; result = 31 * result + (getName() != null ? getName().hashCode() : 0); + result = 31 * result + getNameResId(); result = 31 * result + getImportance(); result = 31 * result + (mBypassDnd ? 1 : 0); result = 31 * result + getLockscreenVisibility(); @@ -741,6 +777,7 @@ public final class NotificationChannel implements Parcelable { return "NotificationChannel{" + "mId='" + mId + '\'' + ", mName=" + mName + + ", mNameResId=" + mNameResId + ", mImportance=" + mImportance + ", mBypassDnd=" + mBypassDnd + ", mLockscreenVisibility=" + mLockscreenVisibility + diff --git a/core/java/com/android/internal/notification/SystemNotificationChannels.java b/core/java/com/android/internal/notification/SystemNotificationChannels.java index c840f26a0d538..ec3aac21cb19f 100644 --- a/core/java/com/android/internal/notification/SystemNotificationChannels.java +++ b/core/java/com/android/internal/notification/SystemNotificationChannels.java @@ -48,12 +48,12 @@ public class SystemNotificationChannels { List channelsList = new ArrayList(); channelsList.add(new NotificationChannel( VIRTUAL_KEYBOARD, - context.getString(R.string.notification_channel_virtual_keyboard), + R.string.notification_channel_virtual_keyboard, NotificationManager.IMPORTANCE_LOW)); final NotificationChannel physicalKeyboardChannel = new NotificationChannel( PHYSICAL_KEYBOARD, - context.getString(R.string.notification_channel_physical_keyboard), + R.string.notification_channel_physical_keyboard, NotificationManager.IMPORTANCE_DEFAULT); physicalKeyboardChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT); @@ -61,32 +61,32 @@ public class SystemNotificationChannels { channelsList.add(new NotificationChannel( SECURITY, - context.getString(R.string.notification_channel_security), + R.string.notification_channel_security, NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( CAR_MODE, - context.getString(R.string.notification_channel_car_mode), + R.string.notification_channel_car_mode, NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( DEVELOPER, - context.getString(R.string.notification_channel_developer), + R.string.notification_channel_developer, NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( UPDATES, - context.getString(R.string.notification_channel_updates), + R.string.notification_channel_updates, NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( NETWORK_STATUS, - context.getString(R.string.notification_channel_network_status), + R.string.notification_channel_network_status, NotificationManager.IMPORTANCE_LOW)); final NotificationChannel networkAlertsChannel = new NotificationChannel( NETWORK_ALERTS, - context.getString(R.string.notification_channel_network_alerts), + R.string.notification_channel_network_alerts, NotificationManager.IMPORTANCE_HIGH); networkAlertsChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT); @@ -94,17 +94,17 @@ public class SystemNotificationChannels { channelsList.add(new NotificationChannel( VPN, - context.getString(R.string.notification_channel_vpn), + R.string.notification_channel_vpn, NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( DEVICE_ADMIN, - context.getString(R.string.notification_channel_device_admin), + R.string.notification_channel_device_admin, NotificationManager.IMPORTANCE_LOW)); final NotificationChannel alertsChannel = new NotificationChannel( ALERTS, - context.getString(R.string.notification_channel_alerts), + R.string.notification_channel_alerts, NotificationManager.IMPORTANCE_DEFAULT); alertsChannel.setSound(Settings.System.DEFAULT_NOTIFICATION_URI, Notification.AUDIO_ATTRIBUTES_DEFAULT); @@ -112,12 +112,12 @@ public class SystemNotificationChannels { channelsList.add(new NotificationChannel( RETAIL_MODE, - context.getString(R.string.notification_channel_retail_mode), + R.string.notification_channel_retail_mode, NotificationManager.IMPORTANCE_LOW)); channelsList.add(new NotificationChannel( USB, - context.getString(R.string.notification_channel_usb), + R.string.notification_channel_usb, NotificationManager.IMPORTANCE_MIN)); nm.createNotificationChannels(channelsList); @@ -128,7 +128,7 @@ public class SystemNotificationChannels { final NotificationManager nm = context.getSystemService(NotificationManager.class); nm.createNotificationChannelsForPackage(pkg, Arrays.asList(new NotificationChannel( ACCOUNT, - context.getString(R.string.notification_channel_account), + R.string.notification_channel_account, NotificationManager.IMPORTANCE_LOW))); } diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java index 970323523904b..807d902e4eaf1 100644 --- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java +++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationInfo.java @@ -72,11 +72,6 @@ public class NotificationInfo extends LinearLayout implements GutsContent { private StatusBarNotification mStatusBarNotification; private NotificationChannel mNotificationChannel; - private ImageView mAutoButton; - private TextView mImportanceSummary; - private TextView mImportanceTitle; - private boolean mAuto; - private TextView mNumChannelsView; private View mChannelDisabledView; private Switch mChannelEnabledSwitch; @@ -105,8 +100,10 @@ public class NotificationInfo extends LinearLayout implements GutsContent { int appUid = -1; String appName = pkg; Drawable pkgicon = null; + CharSequence channelNameText = ""; + ApplicationInfo info = null; try { - final ApplicationInfo info = pm.getApplicationInfo(pkg, + info = pm.getApplicationInfo(pkg, PackageManager.MATCH_UNINSTALLED_PACKAGES | PackageManager.MATCH_DISABLED_COMPONENTS | PackageManager.MATCH_DIRECT_BOOT_UNAWARE @@ -115,6 +112,7 @@ public class NotificationInfo extends LinearLayout implements GutsContent { appUid = 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 @@ -135,11 +133,15 @@ public class NotificationInfo extends LinearLayout implements GutsContent { R.plurals.notification_num_channels_desc, numChannels), numChannels)); // If this is the placeholder channel, don't use our channel-specific text. - CharSequence channelNameText; if (channel.getId().equals(NotificationChannel.DEFAULT_CHANNEL_ID)) { channelNameText = mContext.getString(R.string.notification_header_default_channel); } else { - channelNameText = channel.getName(); + if (info != null && channel.getNameResId() != 0) { + channelNameText = pm.getText(pkg, channel.getNameResId(), info); + } + if (channel.getName() != null) { + channelNameText = channel.getName(); + } } ((TextView) findViewById(R.id.pkgname)).setText(appName); ((TextView) findViewById(R.id.channel_name)).setText(channelNameText); @@ -171,8 +173,8 @@ public class NotificationInfo extends LinearLayout implements GutsContent { boolean nonBlockable = false; try { - final PackageInfo info = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); - nonBlockable = Utils.isSystemPackage(getResources(), pm, info); + final PackageInfo pkgInfo = pm.getPackageInfo(pkg, PackageManager.GET_SIGNATURES); + nonBlockable = Utils.isSystemPackage(getResources(), pm, pkgInfo); } catch (PackageManager.NameNotFoundException e) { // unlikely. } diff --git a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java index 15ad0ce5b51b8..59117665b5da4 100644 --- a/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java +++ b/packages/SystemUI/src/com/android/systemui/util/NotificationChannels.java @@ -36,19 +36,19 @@ public class NotificationChannels extends SystemUI { nm.createNotificationChannels(Arrays.asList( new NotificationChannel( ALERTS, - context.getString(R.string.notification_channel_alerts), + R.string.notification_channel_alerts, NotificationManager.IMPORTANCE_HIGH), new NotificationChannel( SCREENSHOTS, - context.getString(R.string.notification_channel_screenshot), + R.string.notification_channel_screenshot, NotificationManager.IMPORTANCE_LOW), new NotificationChannel( GENERAL, - context.getString(R.string.notification_channel_general), + R.string.notification_channel_general, NotificationManager.IMPORTANCE_MIN), new NotificationChannel( STORAGE, - context.getString(R.string.notification_channel_storage), + R.string.notification_channel_storage, NotificationManager.IMPORTANCE_LOW) )); } 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 0491fc40a865c..9a3fabbbdfaac 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationInfoTest.java @@ -19,6 +19,8 @@ package com.android.systemui.statusbar; import static junit.framework.Assert.assertEquals; import static junit.framework.Assert.assertFalse; import static junit.framework.Assert.assertTrue; + +import static org.mockito.Matchers.anyObject; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyBoolean; import static org.mockito.Mockito.anyInt; @@ -98,6 +100,9 @@ public class NotificationInfoTest extends SysuiTestCase { mNotificationChannel = new NotificationChannel( TEST_CHANNEL, TEST_CHANNEL_NAME, NotificationManager.IMPORTANCE_LOW); when(mMockStatusBarNotification.getPackageName()).thenReturn(TEST_PACKAGE_NAME); + when(mMockPackageManager.getText(eq(TEST_PACKAGE_NAME), + eq(R.string.notification_menu_accessibility), anyObject())).thenReturn( + getContext().getString(R.string.notification_menu_accessibility)); when(mMockINotificationManager.getNumNotificationChannelsForPackage( eq(TEST_PACKAGE_NAME), anyInt(), anyBoolean())).thenReturn(1); @@ -176,6 +181,19 @@ public class NotificationInfoTest extends SysuiTestCase { assertEquals(TEST_CHANNEL_NAME, textView.getText()); } + @Test + @UiThreadTest + public void testBindNotification_SetsTextChannelName_resId() throws Exception { + NotificationChannel notificationChannelResId = new NotificationChannel( + TEST_CHANNEL, R.string.notification_menu_accessibility, + NotificationManager.IMPORTANCE_LOW); + mNotificationInfo.bindNotification(mMockPackageManager, mMockINotificationManager, + mMockStatusBarNotification, notificationChannelResId, null, null, null); + final TextView textView = mNotificationInfo.findViewById(R.id.channel_name); + assertEquals(getContext().getString(R.string.notification_menu_accessibility), + textView.getText()); + } + @Test @UiThreadTest public void testBindNotification_SetsOnClickListenerForSettings() throws Exception { diff --git a/services/core/java/com/android/server/notification/RankingHelper.java b/services/core/java/com/android/server/notification/RankingHelper.java index 867af9a4dd2fa..e72f7ffea8e39 100644 --- a/services/core/java/com/android/server/notification/RankingHelper.java +++ b/services/core/java/com/android/server/notification/RankingHelper.java @@ -66,6 +66,7 @@ public class RankingHelper implements RankingConfig { private static final String ATT_VERSION = "version"; private static final String ATT_NAME = "name"; + private static final String ATT_NAME_RES_ID = "name_res_id"; private static final String ATT_UID = "uid"; private static final String ATT_ID = "id"; private static final String ATT_PRIORITY = "priority"; @@ -201,12 +202,19 @@ public class RankingHelper implements RankingConfig { if (TAG_CHANNEL.equals(tagName)) { String id = parser.getAttributeValue(null, ATT_ID); CharSequence channelName = parser.getAttributeValue(null, ATT_NAME); + int channelNameRes = safeInt(parser, ATT_NAME_RES_ID, -1); int channelImportance = safeInt(parser, ATT_IMPORTANCE, DEFAULT_IMPORTANCE); if (!TextUtils.isEmpty(id)) { - final NotificationChannel channel = new NotificationChannel(id, - channelName, channelImportance); + NotificationChannel channel; + if (channelName != null) { + channel = new NotificationChannel(id, channelName, + channelImportance); + } else { + channel = new NotificationChannel(id, channelNameRes, + channelImportance); + } channel.populateFromXml(parser); r.channels.put(id, channel); } @@ -286,7 +294,7 @@ public class RankingHelper implements RankingConfig { NotificationChannel channel; channel = new NotificationChannel( NotificationChannel.DEFAULT_CHANNEL_ID, - mContext.getString(R.string.default_notification_channel_label), + R.string.default_notification_channel_label, r.importance); channel.setBypassDnd(r.priority == Notification.PRIORITY_MAX); channel.setLockscreenVisibility(r.visibility); @@ -480,7 +488,8 @@ public class RankingHelper implements RankingConfig { Preconditions.checkNotNull(pkg); Preconditions.checkNotNull(channel); Preconditions.checkNotNull(channel.getId()); - Preconditions.checkNotNull(channel.getName()); + Preconditions.checkArgument(!TextUtils.isEmpty(channel.getName()) + || channel.getNameResId() != 0); Record r = getOrCreateRecord(pkg, uid); if (r == null) { throw new IllegalArgumentException("Invalid package"); diff --git a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java index 62126265e1175..ffb0a9e75d8ab 100644 --- a/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java +++ b/services/tests/notification/src/com/android/server/notification/RankingHelperTest.java @@ -196,6 +196,7 @@ public class RankingHelperTest { private void compareChannels(NotificationChannel expected, NotificationChannel actual) { assertEquals(expected.getId(), actual.getId()); assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.getNameResId(), actual.getNameResId()); assertEquals(expected.shouldVibrate(), actual.shouldVibrate()); assertEquals(expected.shouldShowLights(), actual.shouldShowLights()); assertEquals(expected.getImportance(), actual.getImportance()); @@ -260,11 +261,13 @@ public class RankingHelperTest { @Test public void testChannelXml() throws Exception { + int nameResId = 924896; + NotificationChannelGroup ncg = new NotificationChannelGroup("1", "2"); NotificationChannel channel1 = new NotificationChannel("id1", "name1", NotificationManager.IMPORTANCE_HIGH); NotificationChannel channel2 = - new NotificationChannel("id2", "name2", IMPORTANCE_LOW); + new NotificationChannel("id2", nameResId, IMPORTANCE_LOW); channel2.setSound(new Uri.Builder().scheme("test").build(), mAudioAttributes); channel2.enableLights(true); channel2.setBypassDnd(true);