Merge "Fix translation for device state notifications" into udc-dev

This commit is contained in:
Jiaming Liu
2023-04-15 01:38:06 +00:00
committed by Android (Google) Code Review
2 changed files with 156 additions and 66 deletions

View File

@@ -37,8 +37,11 @@ import android.util.Slog;
import android.util.SparseArray;
import com.android.internal.R;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import java.util.Locale;
/**
* Manages the user-visible device state notifications.
*/
@@ -56,15 +59,14 @@ class DeviceStateNotificationController extends BroadcastReceiver {
private final NotificationManager mNotificationManager;
private final PackageManager mPackageManager;
// Stores the notification title and content indexed with the device state identifier.
private final SparseArray<NotificationInfo> mNotificationInfos;
// The callback when a device state is requested to be canceled.
private final Runnable mCancelStateRunnable;
private final NotificationInfoProvider mNotificationInfoProvider;
DeviceStateNotificationController(@NonNull Context context, @NonNull Handler handler,
@NonNull Runnable cancelStateRunnable) {
this(context, handler, cancelStateRunnable, getNotificationInfos(context),
this(context, handler, cancelStateRunnable, new NotificationInfoProvider(context),
context.getPackageManager(), context.getSystemService(NotificationManager.class));
}
@@ -72,13 +74,13 @@ class DeviceStateNotificationController extends BroadcastReceiver {
DeviceStateNotificationController(
@NonNull Context context, @NonNull Handler handler,
@NonNull Runnable cancelStateRunnable,
@NonNull SparseArray<NotificationInfo> notificationInfos,
@NonNull NotificationInfoProvider notificationInfoProvider,
@NonNull PackageManager packageManager,
@NonNull NotificationManager notificationManager) {
mContext = context;
mHandler = handler;
mCancelStateRunnable = cancelStateRunnable;
mNotificationInfos = notificationInfos;
mNotificationInfoProvider = notificationInfoProvider;
mPackageManager = packageManager;
mNotificationManager = notificationManager;
mContext.registerReceiver(
@@ -97,7 +99,7 @@ class DeviceStateNotificationController extends BroadcastReceiver {
* @param requestingAppUid the uid of the requesting app used to retrieve the app name.
*/
void showStateActiveNotificationIfNeeded(int state, int requestingAppUid) {
NotificationInfo info = mNotificationInfos.get(state);
NotificationInfo info = getNotificationInfos().get(state);
if (info == null || !info.hasActiveNotification()) {
return;
}
@@ -127,7 +129,7 @@ class DeviceStateNotificationController extends BroadcastReceiver {
* @param state the identifier of the device state being canceled.
*/
void showThermalCriticalNotificationIfNeeded(int state) {
NotificationInfo info = mNotificationInfos.get(state);
NotificationInfo info = getNotificationInfos().get(state);
if (info == null || !info.hasThermalCriticalNotification()) {
return;
}
@@ -148,7 +150,7 @@ class DeviceStateNotificationController extends BroadcastReceiver {
* @param state the identifier of the device state being canceled.
*/
void showPowerSaveNotificationIfNeeded(int state) {
NotificationInfo info = mNotificationInfos.get(state);
NotificationInfo info = getNotificationInfos().get(state);
if (info == null || !info.hasPowerSaveModeNotification()) {
return;
}
@@ -170,7 +172,7 @@ class DeviceStateNotificationController extends BroadcastReceiver {
* @param state the device state identifier.
*/
void cancelNotification(int state) {
if (!mNotificationInfos.contains(state)) {
if (getNotificationInfos().get(state) == null) {
return;
}
mNotificationManager.cancel(NOTIFICATION_TAG, NOTIFICATION_ID);
@@ -221,69 +223,121 @@ class DeviceStateNotificationController extends BroadcastReceiver {
mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
}
/**
* Loads the resources for the notifications. The device state identifiers and strings are
* stored in arrays. All the string arrays must have the same length and same order as the
* identifier array.
*/
private static SparseArray<NotificationInfo> getNotificationInfos(Context context) {
final SparseArray<NotificationInfo> notificationInfos = new SparseArray<>();
private SparseArray<NotificationInfo> getNotificationInfos() {
Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
return mNotificationInfoProvider.getNotificationInfos(locale);
}
final int[] stateIdentifiers =
context.getResources().getIntArray(
R.array.device_state_notification_state_identifiers);
final String[] names =
context.getResources().getStringArray(R.array.device_state_notification_names);
final String[] activeNotificationTitles =
context.getResources().getStringArray(
R.array.device_state_notification_active_titles);
final String[] activeNotificationContents =
context.getResources().getStringArray(
R.array.device_state_notification_active_contents);
final String[] thermalCriticalNotificationTitles =
context.getResources().getStringArray(
R.array.device_state_notification_thermal_titles);
final String[] thermalCriticalNotificationContents =
context.getResources().getStringArray(
R.array.device_state_notification_thermal_contents);
final String[] powerSaveModeNotificationTitles =
context.getResources().getStringArray(
R.array.device_state_notification_power_save_titles);
final String[] powerSaveModeNotificationContents =
context.getResources().getStringArray(
R.array.device_state_notification_power_save_contents);
@VisibleForTesting
public static class NotificationInfoProvider {
@NonNull
private final Context mContext;
private final Object mLock = new Object();
@GuardedBy("mLock")
@Nullable
private SparseArray<NotificationInfo> mCachedNotificationInfos;
if (stateIdentifiers.length != names.length
|| stateIdentifiers.length != activeNotificationTitles.length
|| stateIdentifiers.length != activeNotificationContents.length
|| stateIdentifiers.length != thermalCriticalNotificationTitles.length
|| stateIdentifiers.length != thermalCriticalNotificationContents.length
|| stateIdentifiers.length != powerSaveModeNotificationTitles.length
|| stateIdentifiers.length != powerSaveModeNotificationContents.length
) {
throw new IllegalStateException(
"The length of state identifiers and notification texts must match!");
@GuardedBy("mLock")
@Nullable
@VisibleForTesting
Locale mCachedLocale;
NotificationInfoProvider(@NonNull Context context) {
mContext = context;
}
for (int i = 0; i < stateIdentifiers.length; i++) {
int identifier = stateIdentifiers[i];
if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
continue;
/**
* Loads the resources for the notifications. The device state identifiers and strings are
* stored in arrays. All the string arrays must have the same length and same order as the
* identifier array.
*/
@NonNull
public SparseArray<NotificationInfo> getNotificationInfos(@NonNull Locale locale) {
synchronized (mLock) {
if (!locale.equals(mCachedLocale)) {
refreshNotificationInfos(locale);
}
return mCachedNotificationInfos;
}
}
@VisibleForTesting
Locale getCachedLocale() {
synchronized (mLock) {
return mCachedLocale;
}
}
@VisibleForTesting
public void refreshNotificationInfos(Locale locale) {
synchronized (mLock) {
mCachedLocale = locale;
mCachedNotificationInfos = loadNotificationInfos();
}
}
@VisibleForTesting
public SparseArray<NotificationInfo> loadNotificationInfos() {
final SparseArray<NotificationInfo> notificationInfos = new SparseArray<>();
final int[] stateIdentifiers =
mContext.getResources().getIntArray(
R.array.device_state_notification_state_identifiers);
final String[] names =
mContext.getResources().getStringArray(R.array.device_state_notification_names);
final String[] activeNotificationTitles =
mContext.getResources().getStringArray(
R.array.device_state_notification_active_titles);
final String[] activeNotificationContents =
mContext.getResources().getStringArray(
R.array.device_state_notification_active_contents);
final String[] thermalCriticalNotificationTitles =
mContext.getResources().getStringArray(
R.array.device_state_notification_thermal_titles);
final String[] thermalCriticalNotificationContents =
mContext.getResources().getStringArray(
R.array.device_state_notification_thermal_contents);
final String[] powerSaveModeNotificationTitles =
mContext.getResources().getStringArray(
R.array.device_state_notification_power_save_titles);
final String[] powerSaveModeNotificationContents =
mContext.getResources().getStringArray(
R.array.device_state_notification_power_save_contents);
if (stateIdentifiers.length != names.length
|| stateIdentifiers.length != activeNotificationTitles.length
|| stateIdentifiers.length != activeNotificationContents.length
|| stateIdentifiers.length != thermalCriticalNotificationTitles.length
|| stateIdentifiers.length != thermalCriticalNotificationContents.length
|| stateIdentifiers.length != powerSaveModeNotificationTitles.length
|| stateIdentifiers.length != powerSaveModeNotificationContents.length
) {
throw new IllegalStateException(
"The length of state identifiers and notification texts must match!");
}
notificationInfos.put(
identifier,
new NotificationInfo(
names[i], activeNotificationTitles[i], activeNotificationContents[i],
thermalCriticalNotificationTitles[i],
thermalCriticalNotificationContents[i],
powerSaveModeNotificationTitles[i],
powerSaveModeNotificationContents[i])
);
}
for (int i = 0; i < stateIdentifiers.length; i++) {
int identifier = stateIdentifiers[i];
if (identifier == DeviceStateManager.INVALID_DEVICE_STATE) {
continue;
}
return notificationInfos;
notificationInfos.put(
identifier,
new NotificationInfo(
names[i],
activeNotificationTitles[i],
activeNotificationContents[i],
thermalCriticalNotificationTitles[i],
thermalCriticalNotificationContents[i],
powerSaveModeNotificationTitles[i],
powerSaveModeNotificationContents[i])
);
}
return notificationInfos;
}
}
/**

View File

@@ -17,8 +17,13 @@
package com.android.server.devicestate;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.clearInvocations;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -41,6 +46,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.ArgumentMatchers;
import org.mockito.Mockito;
import java.util.Locale;
/**
* Unit tests for {@link DeviceStateNotificationController}.
* <p/>
@@ -77,6 +84,8 @@ public class DeviceStateNotificationControllerTest {
Notification.class);
private final NotificationManager mNotificationManager = mock(NotificationManager.class);
private DeviceStateNotificationController.NotificationInfoProvider mNotificationInfoProvider;
@Before
public void setup() throws Exception {
Context context = InstrumentationRegistry.getInstrumentation().getContext();
@@ -97,6 +106,11 @@ public class DeviceStateNotificationControllerTest {
THERMAL_TITLE_2, THERMAL_CONTENT_2,
POWER_SAVE_TITLE_2, POWER_SAVE_CONTENT_2));
mNotificationInfoProvider =
new DeviceStateNotificationController.NotificationInfoProvider(context);
mNotificationInfoProvider = spy(mNotificationInfoProvider);
doReturn(notificationInfos).when(mNotificationInfoProvider).loadNotificationInfos();
when(packageManager.getNameForUid(VALID_APP_UID)).thenReturn(VALID_APP_NAME);
when(packageManager.getNameForUid(INVALID_APP_UID)).thenReturn(INVALID_APP_NAME);
when(packageManager.getApplicationInfo(eq(VALID_APP_NAME), ArgumentMatchers.any()))
@@ -106,7 +120,7 @@ public class DeviceStateNotificationControllerTest {
when(applicationInfo.loadLabel(eq(packageManager))).thenReturn(VALID_APP_LABEL);
mController = new DeviceStateNotificationController(
context, handler, cancelStateRunnable, notificationInfos,
context, handler, cancelStateRunnable, mNotificationInfoProvider,
packageManager, mNotificationManager);
}
@@ -223,4 +237,26 @@ public class DeviceStateNotificationControllerTest {
eq(DeviceStateNotificationController.NOTIFICATION_ID),
mNotificationCaptor.capture());
}
@Test
public void test_notificationInfoProvider() {
assertNull(mNotificationInfoProvider.getCachedLocale());
mNotificationInfoProvider.getNotificationInfos(Locale.ENGLISH);
verify(mNotificationInfoProvider).refreshNotificationInfos(eq(Locale.ENGLISH));
assertEquals(Locale.ENGLISH, mNotificationInfoProvider.getCachedLocale());
clearInvocations(mNotificationInfoProvider);
// If the same locale is used again, the provider uses the cached value, so it won't refresh
mNotificationInfoProvider.getNotificationInfos(Locale.ENGLISH);
verify(mNotificationInfoProvider, never()).refreshNotificationInfos(eq(Locale.ENGLISH));
assertEquals(Locale.ENGLISH, mNotificationInfoProvider.getCachedLocale());
clearInvocations(mNotificationInfoProvider);
// If a different locale is used, the provider refreshes.
mNotificationInfoProvider.getNotificationInfos(Locale.ITALY);
verify(mNotificationInfoProvider).refreshNotificationInfos(eq(Locale.ITALY));
assertEquals(Locale.ITALY, mNotificationInfoProvider.getCachedLocale());
clearInvocations(mNotificationInfoProvider);
}
}