Allow translatable channel names.

Bug: 35336590
Test: runtest sytemui runtest systemui-notification, manual
Change-Id: I1e6ebbcc36cab22ab9b2dfde505a68513544ef52
This commit is contained in:
Julia Reynolds
2017-03-01 16:33:44 -05:00
parent c20082bd46
commit 5a31193497
10 changed files with 112 additions and 37 deletions

View File

@@ -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);

View File

@@ -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();

View File

@@ -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);

View File

@@ -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 +

View File

@@ -48,12 +48,12 @@ public class SystemNotificationChannels {
List<NotificationChannel> channelsList = new ArrayList<NotificationChannel>();
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)));
}

View File

@@ -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.
}

View File

@@ -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)
));
}

View File

@@ -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 {

View File

@@ -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");

View File

@@ -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);