Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ca84d3d6 authored by Jiaming Liu's avatar Jiaming Liu
Browse files

Fix translation for device state notifications

Reloads notification strings when locale has changed. Previously, the
strings were loaded during boot time and did not update when device
locale was changed.

Fix: 275423060
Test: atest DeviceStateNotificationControllerTest
Change-Id: I0b8bd54a26a9c2e3dd61b991dba14559c01a7aac
parent b504082e
Loading
Loading
Loading
Loading
+121 −67
Original line number Diff line number Diff line
@@ -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,39 +223,89 @@ class DeviceStateNotificationController extends BroadcastReceiver {
        mNotificationManager.notify(NOTIFICATION_TAG, NOTIFICATION_ID, builder.build());
    }

    private SparseArray<NotificationInfo> getNotificationInfos() {
        Locale locale = mContext.getResources().getConfiguration().getLocales().get(0);
        return mNotificationInfoProvider.getNotificationInfos(locale);
    }

    @VisibleForTesting
    public static class NotificationInfoProvider {
        @NonNull
        private final Context mContext;
        private final Object mLock = new Object();

        @GuardedBy("mLock")
        @Nullable
        private SparseArray<NotificationInfo> mCachedNotificationInfos;

        @GuardedBy("mLock")
        @Nullable
        @VisibleForTesting
        Locale mCachedLocale;

        NotificationInfoProvider(@NonNull Context context) {
            mContext = context;
        }

        /**
         * 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) {
        @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 =
                context.getResources().getIntArray(
                    mContext.getResources().getIntArray(
                            R.array.device_state_notification_state_identifiers);
            final String[] names =
                context.getResources().getStringArray(R.array.device_state_notification_names);
                    mContext.getResources().getStringArray(R.array.device_state_notification_names);
            final String[] activeNotificationTitles =
                context.getResources().getStringArray(
                    mContext.getResources().getStringArray(
                            R.array.device_state_notification_active_titles);
            final String[] activeNotificationContents =
                context.getResources().getStringArray(
                    mContext.getResources().getStringArray(
                            R.array.device_state_notification_active_contents);
            final String[] thermalCriticalNotificationTitles =
                context.getResources().getStringArray(
                    mContext.getResources().getStringArray(
                            R.array.device_state_notification_thermal_titles);
            final String[] thermalCriticalNotificationContents =
                context.getResources().getStringArray(
                    mContext.getResources().getStringArray(
                            R.array.device_state_notification_thermal_contents);
            final String[] powerSaveModeNotificationTitles =
                context.getResources().getStringArray(
                    mContext.getResources().getStringArray(
                            R.array.device_state_notification_power_save_titles);
            final String[] powerSaveModeNotificationContents =
                context.getResources().getStringArray(
                    mContext.getResources().getStringArray(
                            R.array.device_state_notification_power_save_contents);


            if (stateIdentifiers.length != names.length
                    || stateIdentifiers.length != activeNotificationTitles.length
                    || stateIdentifiers.length != activeNotificationContents.length
@@ -275,16 +327,18 @@ class DeviceStateNotificationController extends BroadcastReceiver {
                notificationInfos.put(
                        identifier,
                        new NotificationInfo(
                            names[i], activeNotificationTitles[i], activeNotificationContents[i],
                                names[i],
                                activeNotificationTitles[i],
                                activeNotificationContents[i],
                                thermalCriticalNotificationTitles[i],
                                thermalCriticalNotificationContents[i],
                                powerSaveModeNotificationTitles[i],
                                powerSaveModeNotificationContents[i])
                );
            }

            return notificationInfos;
        }
    }

    /**
     * A helper function to get app name (label) using the app uid.
+37 −1
Original line number Diff line number Diff line
@@ -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);
    }
}