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

Commit a791f236 authored by Nishant Kumar Singh's avatar Nishant Kumar Singh
Browse files

Add unit tests for the app-locales backup logic.

Bug: 204986584
Test: atest FrameworksServicesTests:LocaleManagerBackupRestoreTest
Change-Id: Iac73c15f9b28aeb3bfc3f7af381cd49df443194e
parent 972120fc
Loading
Loading
Loading
Loading
+3 −8
Original line number Diff line number Diff line
@@ -77,15 +77,15 @@ public class LocaleManagerService extends SystemService {
    @VisibleForTesting
    LocaleManagerService(Context context, ActivityTaskManagerInternal activityTaskManagerInternal,
            ActivityManagerInternal activityManagerInternal,
            PackageManagerInternal packageManagerInternal) {
            PackageManagerInternal packageManagerInternal,
            LocaleManagerBackupHelper localeManagerBackupHelper) {
        super(context);
        mContext = context;
        mBinderService = new LocaleManagerBinderService();
        mActivityTaskManagerInternal = activityTaskManagerInternal;
        mActivityManagerInternal = activityManagerInternal;
        mPackageManagerInternal = packageManagerInternal;
        mBackupHelper = new LocaleManagerBackupHelper(this,
                mPackageManagerInternal);
        mBackupHelper = localeManagerBackupHelper;
    }

    @Override
@@ -143,11 +143,6 @@ public class LocaleManagerService extends SystemService {

    }

    @VisibleForTesting
    LocaleManagerBackupHelper getBackupHelper() {
        return mBackupHelper;
    }

    /**
     * Sets the current UI locales for a specified app.
     */
+199 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.locales;

import static junit.framework.Assert.assertEquals;
import static junit.framework.Assert.assertNull;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManagerInternal;
import android.os.Environment;
import android.os.LocaleList;
import android.os.RemoteException;
import android.os.SimpleClock;
import android.util.TypedXmlPullParser;
import android.util.Xml;

import androidx.test.ext.junit.runners.AndroidJUnit4;

import com.android.internal.util.XmlUtils;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.xmlpull.v1.XmlPullParserException;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.time.Clock;
import java.time.ZoneOffset;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Unit tests for the {@link LocaleManagerInternal}.
 */
@RunWith(AndroidJUnit4.class)
public class LocaleManagerBackupRestoreTest {
    private static final String DEFAULT_PACKAGE_NAME = "com.android.myapp";
    private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
    private static final String STAGE_LOCALES_XML_TAG = "locales";
    private static final int DEFAULT_USER_ID = 0;
    private static final int INVALID_UID = -1;
    private static final LocaleList DEFAULT_LOCALES =
            LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
    private static final Map<String, String> DEFAULT_PACKAGE_LOCALES_MAP = Map.of(
            DEFAULT_PACKAGE_NAME, DEFAULT_LOCALE_TAGS);

    private LocaleManagerBackupHelper mBackupHelper;
    private long mCurrentTimeMillis;

    @Mock
    private Context mMockContext;
    @Mock
    private PackageManagerInternal mMockPackageManagerInternal;
    @Mock
    private LocaleManagerService mMockLocaleManagerService;

    private final Clock mClock = new SimpleClock(ZoneOffset.UTC) {
        @Override
        public long millis() {
            return currentTimeMillis();
        }
    };

    private long currentTimeMillis() {
        return mCurrentTimeMillis;
    }

    private void setCurrentTimeMillis(long currentTimeMillis) {
        mCurrentTimeMillis = currentTimeMillis;
    }

    @Before
    public void setUp() throws Exception {
        mMockContext = mock(Context.class);
        mMockPackageManagerInternal = mock(PackageManagerInternal.class);

        mMockLocaleManagerService = mock(LocaleManagerService.class);

        mBackupHelper = new LocaleManagerBackupHelper(mMockContext, mMockLocaleManagerService,
                mMockPackageManagerInternal,
                new File(Environment.getExternalStorageDirectory(), "lmsUnitTests"),
                mClock);
    }

    @Test
    public void testBackupPayload_noAppsInstalled_returnsNull() throws Exception {
        doReturn(List.of()).when(mMockPackageManagerInternal)
                .getInstalledApplications(anyLong(), anyInt(), anyInt());

        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
    }

    @Test
    public void testBackupPayload_noAppLocalesSet_returnsNull() throws Exception {
        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, LocaleList.getEmptyLocaleList());
        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);

        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
    }

    @Test
    public void testBackupPayload_appLocalesSet_returnsNonNullBlob() throws Exception {
        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);

        byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
    }

    @Test
    public void testBackupPayload_exceptionInGetLocalesAllPackages_returnsNull() throws Exception {
        setUpDummyAppForPackageManager(DEFAULT_PACKAGE_NAME);
        doThrow(new RemoteException("mock")).when(mMockLocaleManagerService).getApplicationLocales(
                anyString(), anyInt());

        assertNull(mBackupHelper.getBackupPayload(DEFAULT_USER_ID));
    }

    @Test
    public void testBackupPayload_exceptionInGetLocalesSomePackages_appsWithExceptionNotBackedUp()
            throws Exception {
        // Set up two apps.
        ApplicationInfo defaultAppInfo = new ApplicationInfo();
        ApplicationInfo anotherAppInfo = new ApplicationInfo();
        defaultAppInfo.packageName = DEFAULT_PACKAGE_NAME;
        anotherAppInfo.packageName = "com.android.anotherapp";
        doReturn(List.of(defaultAppInfo, anotherAppInfo)).when(mMockPackageManagerInternal)
                .getInstalledApplications(anyLong(), anyInt(), anyInt());

        setUpLocalesForPackage(DEFAULT_PACKAGE_NAME, DEFAULT_LOCALES);
        // Exception when getting locales for anotherApp.
        doThrow(new RemoteException("mock")).when(mMockLocaleManagerService).getApplicationLocales(
                eq(anotherAppInfo.packageName), anyInt());

        byte[] payload = mBackupHelper.getBackupPayload(DEFAULT_USER_ID);
        verifyPayloadForAppLocales(DEFAULT_PACKAGE_LOCALES_MAP, payload);
    }

    private void setUpLocalesForPackage(String packageName, LocaleList locales) throws Exception {
        doReturn(locales).when(mMockLocaleManagerService).getApplicationLocales(
                eq(packageName), anyInt());
    }

    private void setUpDummyAppForPackageManager(String packageName) {
        ApplicationInfo dummyApp = new ApplicationInfo();
        dummyApp.packageName = packageName;
        doReturn(List.of(dummyApp)).when(mMockPackageManagerInternal)
                .getInstalledApplications(anyLong(), anyInt(), anyInt());
    }

    private void verifyPayloadForAppLocales(Map<String, String> expectedPkgLocalesMap,
            byte[] payload)
            throws IOException, XmlPullParserException {
        final ByteArrayInputStream stream = new ByteArrayInputStream(payload);
        final TypedXmlPullParser parser = Xml.newFastPullParser();
        parser.setInput(stream, StandardCharsets.UTF_8.name());

        Map<String, String> backupDataMap = new HashMap<>();
        XmlUtils.beginDocument(parser, STAGE_LOCALES_XML_TAG);
        int depth = parser.getDepth();
        while (XmlUtils.nextElementWithin(parser, depth)) {
            if (parser.getName().equals("package")) {
                String packageName = parser.getAttributeValue(null, "name");
                String languageTags = parser.getAttributeValue(null, "locales");
                backupDataMap.put(packageName, languageTags);
            }
        }

        assertEquals(expectedPkgLocalesMap, backupDataMap);
    }
}
+12 −3
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import static org.mockito.Mockito.doNothing;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.doThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;

import android.Manifest;
@@ -68,6 +69,7 @@ public class LocaleManagerServiceTest {
            /* originatingPackageName = */ null, /* installingPackageName = */ null);

    private LocaleManagerService mLocaleManagerService;
    private LocaleManagerBackupHelper mMockBackupHelper;

    @Mock
    private Context mMockContext;
@@ -104,8 +106,9 @@ public class LocaleManagerServiceTest {
                .handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
                        anyString(), anyString());

        mMockBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
        mLocaleManagerService = new LocaleManagerService(mMockContext, mMockActivityTaskManager,
                mMockActivityManager, mMockPackageManagerInternal);
                mMockActivityManager, mMockPackageManagerInternal, mMockBackupHelper);
    }

    @Test(expected = SecurityException.class)
@@ -122,6 +125,7 @@ public class LocaleManagerServiceTest {
            verify(mMockContext).enforceCallingOrSelfPermission(
                    eq(android.Manifest.permission.CHANGE_CONFIGURATION),
                    anyString());
            verify(mMockBackupHelper, times(0)).notifyBackupManager();
            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
        }
    }
@@ -133,6 +137,7 @@ public class LocaleManagerServiceTest {
                    DEFAULT_USER_ID, LocaleList.getEmptyLocaleList());
            fail("Expected NullPointerException");
        } finally {
            verify(mMockBackupHelper, times(0)).notifyBackupManager();
            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
        }
    }
@@ -146,6 +151,7 @@ public class LocaleManagerServiceTest {
                    /* locales = */ null);
            fail("Expected NullPointerException");
        } finally {
            verify(mMockBackupHelper, times(0)).notifyBackupManager();
            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
        }
    }
@@ -163,6 +169,7 @@ public class LocaleManagerServiceTest {
                DEFAULT_LOCALES);

        assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
        verify(mMockBackupHelper, times(1)).notifyBackupManager();

    }

@@ -175,6 +182,7 @@ public class LocaleManagerServiceTest {
                DEFAULT_LOCALES);

        assertEquals(DEFAULT_LOCALES, mFakePackageConfigurationUpdater.getStoredLocales());
        verify(mMockBackupHelper, times(1)).notifyBackupManager();
    }

    @Test(expected = IllegalArgumentException.class)
@@ -187,6 +195,7 @@ public class LocaleManagerServiceTest {
            fail("Expected IllegalArgumentException");
        } finally {
            assertNoLocalesStored(mFakePackageConfigurationUpdater.getStoredLocales());
            verify(mMockBackupHelper, times(0)).notifyBackupManager();
        }
    }

+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.locales;

import android.content.Context;
import android.content.pm.PackageManagerInternal;

import java.io.File;
import java.time.Clock;

/**
 * Shadow for {@link LocaleManagerBackupHelper} to enable mocking it for tests.
 *
 * <p>{@link LocaleManagerBackupHelper} is a package private class and hence not mockable directly.
 */
public class ShadowLocaleManagerBackupHelper extends LocaleManagerBackupHelper {
    ShadowLocaleManagerBackupHelper(Context context,
            LocaleManagerService localeManagerService,
            PackageManagerInternal pmInternal, File stagedLocalesDir, Clock clock) {
        super(context, localeManagerService, pmInternal, stagedLocalesDir, clock);
    }
}