Merge "Send broadcast when a system app updates for the first time after device setup in LocaleManagerService" into tm-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
0c0a88ea44
@@ -78,10 +78,20 @@ public class LocaleManagerService extends SystemService {
|
||||
Process.THREAD_PRIORITY_BACKGROUND);
|
||||
broadcastHandlerThread.start();
|
||||
|
||||
SystemAppUpdateTracker systemAppUpdateTracker =
|
||||
new SystemAppUpdateTracker(this);
|
||||
broadcastHandlerThread.getThreadHandler().postAtFrontOfQueue(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
systemAppUpdateTracker.init();
|
||||
}
|
||||
});
|
||||
|
||||
mBackupHelper = new LocaleManagerBackupHelper(this,
|
||||
mPackageManagerInternal, broadcastHandlerThread);
|
||||
|
||||
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper);
|
||||
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
|
||||
systemAppUpdateTracker);
|
||||
mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
|
||||
UserHandle.ALL,
|
||||
true);
|
||||
@@ -246,7 +256,7 @@ public class LocaleManagerService extends SystemService {
|
||||
* <p><b>Note:</b> This is can be used by installers to deal with cases such as
|
||||
* language-based APK Splits.
|
||||
*/
|
||||
private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
|
||||
void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
|
||||
LocaleList locales) {
|
||||
String installingPackageName = getInstallingPackageName(appPackageName);
|
||||
if (installingPackageName != null) {
|
||||
@@ -271,7 +281,7 @@ public class LocaleManagerService extends SystemService {
|
||||
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
|
||||
}
|
||||
|
||||
private static Intent createBaseIntent(String intentAction, String appPackageName,
|
||||
static Intent createBaseIntent(String intentAction, String appPackageName,
|
||||
LocaleList locales) {
|
||||
return new Intent(intentAction)
|
||||
.putExtra(Intent.EXTRA_PACKAGE_NAME, appPackageName)
|
||||
@@ -406,7 +416,7 @@ public class LocaleManagerService extends SystemService {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private String getInstallingPackageName(String packageName) {
|
||||
String getInstallingPackageName(String packageName) {
|
||||
try {
|
||||
return mContext.getPackageManager()
|
||||
.getInstallSourceInfo(packageName).getInstallingPackageName();
|
||||
|
||||
@@ -23,13 +23,22 @@ import com.android.internal.content.PackageMonitor;
|
||||
*
|
||||
* <p> These listeners forward the call to different aspects of locale service that
|
||||
* handle the business logic.
|
||||
* <p> We're interested in package added, package data cleared and package removed events.
|
||||
* <p> We're interested in the following events:
|
||||
* <ul>
|
||||
* <li> Package added
|
||||
* <li> Package data cleared
|
||||
* <li> Package removed
|
||||
* <li> Package Updated
|
||||
* </ul>
|
||||
*/
|
||||
final class LocaleManagerServicePackageMonitor extends PackageMonitor {
|
||||
private LocaleManagerBackupHelper mBackupHelper;
|
||||
private SystemAppUpdateTracker mSystemAppUpdateTracker;
|
||||
|
||||
LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper) {
|
||||
LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper,
|
||||
SystemAppUpdateTracker systemAppUpdateTracker) {
|
||||
mBackupHelper = localeManagerBackupHelper;
|
||||
mSystemAppUpdateTracker = systemAppUpdateTracker;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -46,4 +55,9 @@ final class LocaleManagerServicePackageMonitor extends PackageMonitor {
|
||||
public void onPackageRemoved(String packageName, int uid) {
|
||||
mBackupHelper.onPackageRemoved();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPackageUpdateFinished(String packageName, int uid) {
|
||||
mSystemAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 com.android.server.locales.LocaleManagerService.DEBUG;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.Environment;
|
||||
import android.os.LocaleList;
|
||||
import android.os.RemoteException;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.Slog;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.TypedXmlSerializer;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Track if a system app is being updated for the first time after the user completed device setup.
|
||||
*
|
||||
* <p> The entire operation is being done on a background thread from {@link LocaleManagerService}.
|
||||
* If it is the first time that a system app is being updated, then it fetches the app-specific
|
||||
* locales and sends a broadcast to the newly set installer of the app. It maintains a file to store
|
||||
* the name of the apps that have been updated.
|
||||
*/
|
||||
public class SystemAppUpdateTracker {
|
||||
private static final String TAG = "SystemAppUpdateTracker";
|
||||
private static final String PACKAGE_XML_TAG = "package";
|
||||
private static final String ATTR_NAME = "name";
|
||||
private static final String SYSTEM_APPS_XML_TAG = "system_apps";
|
||||
|
||||
private final Context mContext;
|
||||
private final LocaleManagerService mLocaleManagerService;
|
||||
private final AtomicFile mUpdatedAppsFile;
|
||||
|
||||
// Lock used while writing to the file.
|
||||
private final Object mFileLock = new Object();
|
||||
|
||||
// In-memory list of all the system apps that have been updated once after device setup.
|
||||
// We do not need to store the userid->packages mapping because when updating a system app on
|
||||
// one user updates for all users.
|
||||
private final Set<String> mUpdatedApps = new HashSet<>();
|
||||
|
||||
SystemAppUpdateTracker(LocaleManagerService localeManagerService) {
|
||||
this(localeManagerService.mContext, localeManagerService, new AtomicFile(
|
||||
new File(Environment.getDataSystemDirectory(),
|
||||
/* child = */ "locale_manager_service_updated_system_apps.xml")));
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
SystemAppUpdateTracker(Context context, LocaleManagerService localeManagerService,
|
||||
AtomicFile file) {
|
||||
mContext = context;
|
||||
mLocaleManagerService = localeManagerService;
|
||||
mUpdatedAppsFile = file;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the info of updated system apps from the file.
|
||||
*
|
||||
* <p> Invoked once during device boot from {@link LocaleManagerService} by a background thread.
|
||||
*/
|
||||
void init() {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "Loading the app info from storage. ");
|
||||
}
|
||||
loadUpdatedSystemApps();
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the XML stored in the {@link #mUpdatedAppsFile} and populates it in the in-memory list
|
||||
* {@link #mUpdatedApps}.
|
||||
*/
|
||||
private void loadUpdatedSystemApps() {
|
||||
if (!mUpdatedAppsFile.getBaseFile().exists()) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "loadUpdatedSystemApps: File does not exist.");
|
||||
}
|
||||
return;
|
||||
}
|
||||
InputStream updatedAppNamesInputStream = null;
|
||||
try {
|
||||
updatedAppNamesInputStream = mUpdatedAppsFile.openRead();
|
||||
readFromXml(updatedAppNamesInputStream);
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
Slog.e(TAG, "loadUpdatedSystemApps: Could not parse storage file ", e);
|
||||
} finally {
|
||||
IoUtils.closeQuietly(updatedAppNamesInputStream);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the update data from the serialized XML input stream.
|
||||
*/
|
||||
private void readFromXml(InputStream updateInfoInputStream)
|
||||
throws XmlPullParserException, IOException {
|
||||
final TypedXmlPullParser parser = Xml.newFastPullParser();
|
||||
parser.setInput(updateInfoInputStream, StandardCharsets.UTF_8.name());
|
||||
XmlUtils.beginDocument(parser, SYSTEM_APPS_XML_TAG);
|
||||
int depth = parser.getDepth();
|
||||
while (XmlUtils.nextElementWithin(parser, depth)) {
|
||||
if (parser.getName().equals(PACKAGE_XML_TAG)) {
|
||||
String packageName = parser.getAttributeValue(/* namespace= */ null,
|
||||
ATTR_NAME);
|
||||
if (!TextUtils.isEmpty(packageName)) {
|
||||
mUpdatedApps.add(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends a broadcast to the newly set installer with app-locales if it is a system app being
|
||||
* updated for the first time.
|
||||
*
|
||||
* <p><b>Note:</b> Invoked by service's common monitor
|
||||
* {@link LocaleManagerServicePackageMonitor#onPackageUpdateFinished} when a package updated.
|
||||
*/
|
||||
void onPackageUpdateFinished(String packageName, int uid) {
|
||||
try {
|
||||
if ((!mUpdatedApps.contains(packageName)) && isUpdatedSystemApp(packageName)) {
|
||||
// If a system app is updated, verify that it has an installer-on-record.
|
||||
String installingPackageName = mLocaleManagerService.getInstallingPackageName(
|
||||
packageName);
|
||||
if (installingPackageName == null) {
|
||||
// We want to broadcast the locales info to the installer.
|
||||
// If this app does not have an installer then do nothing.
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
int userId = UserHandle.getUserId(uid);
|
||||
// Fetch the app-specific locales.
|
||||
// If non-empty then send the info to the installer.
|
||||
LocaleList appLocales = mLocaleManagerService.getApplicationLocales(
|
||||
packageName, userId);
|
||||
if (!appLocales.isEmpty()) {
|
||||
// The broadcast would be sent to the newly set installer of the
|
||||
// updated system app.
|
||||
mLocaleManagerService.notifyInstallerOfAppWhoseLocaleChanged(packageName,
|
||||
userId, appLocales);
|
||||
}
|
||||
} catch (RemoteException e) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "onPackageUpdateFinished: Error in fetching app locales");
|
||||
}
|
||||
}
|
||||
updateBroadcastedAppsList(packageName);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Exception in onPackageUpdateFinished.", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes in-memory data {@link #mUpdatedApps} to the storage file in a synchronized manner.
|
||||
*/
|
||||
private void updateBroadcastedAppsList(String packageName) {
|
||||
synchronized (mFileLock) {
|
||||
mUpdatedApps.add(packageName);
|
||||
writeUpdatedAppsFileLocked();
|
||||
}
|
||||
}
|
||||
|
||||
private void writeUpdatedAppsFileLocked() {
|
||||
FileOutputStream stream = null;
|
||||
try {
|
||||
stream = mUpdatedAppsFile.startWrite();
|
||||
writeToXmlLocked(stream);
|
||||
mUpdatedAppsFile.finishWrite(stream);
|
||||
} catch (IOException e) {
|
||||
mUpdatedAppsFile.failWrite(stream);
|
||||
Slog.e(TAG, "Failed to persist the updated apps list", e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the list of updated app data into a serialized xml stream.
|
||||
*/
|
||||
private void writeToXmlLocked(OutputStream stream) throws IOException {
|
||||
final TypedXmlSerializer xml = Xml.newFastSerializer();
|
||||
xml.setOutput(stream, StandardCharsets.UTF_8.name());
|
||||
xml.startDocument(/* encoding= */ null, /* standalone= */ true);
|
||||
xml.startTag(/* namespace= */ null, SYSTEM_APPS_XML_TAG);
|
||||
|
||||
for (String packageName : mUpdatedApps) {
|
||||
xml.startTag(/* namespace= */ null, PACKAGE_XML_TAG);
|
||||
xml.attribute(/* namespace= */ null, ATTR_NAME, packageName);
|
||||
xml.endTag(/* namespace= */ null, PACKAGE_XML_TAG);
|
||||
}
|
||||
|
||||
xml.endTag(null, SYSTEM_APPS_XML_TAG);
|
||||
xml.endDocument();
|
||||
}
|
||||
|
||||
private boolean isUpdatedSystemApp(String packageName) {
|
||||
ApplicationInfo appInfo = null;
|
||||
try {
|
||||
appInfo = mContext.getPackageManager().getApplicationInfo(packageName,
|
||||
PackageManager.ApplicationInfoFlags.of(PackageManager.MATCH_SYSTEM_ONLY));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
if (DEBUG) {
|
||||
Slog.d(TAG, "isUpdatedSystemApp: Package not found " + packageName);
|
||||
}
|
||||
}
|
||||
if (appInfo == null) {
|
||||
return false;
|
||||
}
|
||||
return (appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
Set<String> getUpdatedApps() {
|
||||
return mUpdatedApps;
|
||||
}
|
||||
}
|
||||
@@ -107,6 +107,7 @@ public class LocaleManagerBackupRestoreTest {
|
||||
private PackageManager mMockPackageManager;
|
||||
@Mock
|
||||
private LocaleManagerService mMockLocaleManagerService;
|
||||
|
||||
BroadcastReceiver mUserMonitor;
|
||||
PackageMonitor mPackageMonitor;
|
||||
|
||||
@@ -131,6 +132,7 @@ public class LocaleManagerBackupRestoreTest {
|
||||
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
|
||||
mMockPackageManager = mock(PackageManager.class);
|
||||
mMockLocaleManagerService = mock(LocaleManagerService.class);
|
||||
SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
|
||||
|
||||
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
|
||||
|
||||
@@ -144,7 +146,8 @@ public class LocaleManagerBackupRestoreTest {
|
||||
doNothing().when(mBackupHelper).notifyBackupManager();
|
||||
|
||||
mUserMonitor = mBackupHelper.getUserMonitor();
|
||||
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper);
|
||||
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
|
||||
systemAppUpdateTracker);
|
||||
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,297 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.assertTrue;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyBoolean;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.verifyZeroInteractions;
|
||||
|
||||
import android.app.ActivityManagerInternal;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.InstallSourceInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.os.Binder;
|
||||
import android.os.Environment;
|
||||
import android.os.LocaleList;
|
||||
import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.TypedXmlPullParser;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.internal.content.PackageMonitor;
|
||||
import com.android.internal.util.XmlUtils;
|
||||
import com.android.server.wm.ActivityTaskManagerInternal;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link SystemAppUpdateTracker}.
|
||||
*/
|
||||
public class SystemAppUpdateTrackerTest {
|
||||
private static final String DEFAULT_PACKAGE_NAME_1 = "com.android.myapp1";
|
||||
private static final String DEFAULT_PACKAGE_NAME_2 = "com.android.myapp2";
|
||||
private static final String DEFAULT_LOCALE_TAGS = "en-XC,ar-XB";
|
||||
private static final LocaleList DEFAULT_LOCALES =
|
||||
LocaleList.forLanguageTags(DEFAULT_LOCALE_TAGS);
|
||||
private static final String PACKAGE_XML_TAG = "package";
|
||||
private static final String ATTR_NAME = "name";
|
||||
private static final String SYSTEM_APPS_XML_TAG = "system_apps";
|
||||
private static final int DEFAULT_USER_ID = 0;
|
||||
|
||||
private AtomicFile mStoragefile;
|
||||
private static final String DEFAULT_INSTALLER_PACKAGE_NAME = "com.android.myapp.installer";
|
||||
private static final InstallSourceInfo DEFAULT_INSTALL_SOURCE_INFO = new InstallSourceInfo(
|
||||
/* initiatingPackageName = */ null, /* initiatingPackageSigningInfo = */ null,
|
||||
/* originatingPackageName = */ null,
|
||||
/* installingPackageName = */ DEFAULT_INSTALLER_PACKAGE_NAME,
|
||||
/* packageSource = */ PackageInstaller.PACKAGE_SOURCE_UNSPECIFIED);
|
||||
|
||||
@Mock
|
||||
private Context mMockContext;
|
||||
@Mock
|
||||
PackageManager mMockPackageManager;
|
||||
@Mock
|
||||
private PackageManagerInternal mMockPackageManagerInternal;
|
||||
@Mock
|
||||
private ActivityTaskManagerInternal mMockActivityTaskManager;
|
||||
@Mock
|
||||
private ActivityManagerInternal mMockActivityManager;
|
||||
@Mock
|
||||
private LocaleManagerBackupHelper mMockLocaleManagerBackupHelper;
|
||||
@Mock
|
||||
PackageMonitor mMockPackageMonitor;
|
||||
|
||||
private LocaleManagerService mLocaleManagerService;
|
||||
|
||||
// Object under test.
|
||||
private SystemAppUpdateTracker mSystemAppUpdateTracker;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
mMockContext = mock(Context.class);
|
||||
mMockActivityTaskManager = mock(ActivityTaskManagerInternal.class);
|
||||
mMockActivityManager = mock(ActivityManagerInternal.class);
|
||||
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
|
||||
mMockPackageMonitor = mock(PackageMonitor.class);
|
||||
mMockLocaleManagerBackupHelper = mock(ShadowLocaleManagerBackupHelper.class);
|
||||
mLocaleManagerService = new LocaleManagerService(mMockContext,
|
||||
mMockActivityTaskManager, mMockActivityManager,
|
||||
mMockPackageManagerInternal, mMockLocaleManagerBackupHelper, mMockPackageMonitor);
|
||||
|
||||
doReturn(DEFAULT_USER_ID).when(mMockActivityManager)
|
||||
.handleIncomingUser(anyInt(), anyInt(), eq(DEFAULT_USER_ID), anyBoolean(), anyInt(),
|
||||
anyString(), anyString());
|
||||
|
||||
mMockPackageManager = mock(PackageManager.class);
|
||||
doReturn(DEFAULT_INSTALL_SOURCE_INFO).when(mMockPackageManager)
|
||||
.getInstallSourceInfo(anyString());
|
||||
doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
|
||||
|
||||
mStoragefile = new AtomicFile(new File(
|
||||
Environment.getExternalStorageDirectory(), "systemUpdateUnitTests.xml"));
|
||||
|
||||
mSystemAppUpdateTracker = new SystemAppUpdateTracker(mMockContext,
|
||||
mLocaleManagerService, mStoragefile);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() {
|
||||
mStoragefile.delete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInit_loadsCorrectly() throws Exception {
|
||||
doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1,
|
||||
/* isUpdatedSystemApp = */ true))
|
||||
.when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
|
||||
|
||||
// Updates the app once so that it writes to the file.
|
||||
mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
|
||||
Binder.getCallingUid());
|
||||
// Clear the in-memory data of updated apps
|
||||
mSystemAppUpdateTracker.getUpdatedApps().clear();
|
||||
// Invoke init to verify if it correctly populates in-memory set.
|
||||
mSystemAppUpdateTracker.init();
|
||||
|
||||
assertEquals(Set.of(DEFAULT_PACKAGE_NAME_1), mSystemAppUpdateTracker.getUpdatedApps());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnPackageUpdatedFinished_systemAppFirstUpdate_writesToFile() throws Exception {
|
||||
doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1,
|
||||
/* isUpdatedSystemApp = */ true))
|
||||
.when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
|
||||
doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
|
||||
DEFAULT_LOCALES)).when(mMockActivityTaskManager)
|
||||
.getApplicationConfig(anyString(), anyInt());
|
||||
|
||||
mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
|
||||
Binder.getCallingUid());
|
||||
|
||||
assertBroadcastSentToInstaller(DEFAULT_PACKAGE_NAME_1, DEFAULT_LOCALES);
|
||||
Set<String> expectedAppList = Set.of(DEFAULT_PACKAGE_NAME_1);
|
||||
assertEquals(expectedAppList, mSystemAppUpdateTracker.getUpdatedApps());
|
||||
verifyStorageFileContents(expectedAppList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnPackageUpdatedFinished_systemAppSecondUpdate_doesNothing() throws Exception {
|
||||
doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1,
|
||||
/* isUpdatedSystemApp = */ true))
|
||||
.when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
|
||||
doReturn(new ActivityTaskManagerInternal.PackageConfig(/* nightMode = */ 0,
|
||||
DEFAULT_LOCALES)).when(mMockActivityTaskManager)
|
||||
.getApplicationConfig(anyString(), anyInt());
|
||||
|
||||
// first update
|
||||
mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
|
||||
Binder.getCallingUid());
|
||||
|
||||
assertBroadcastSentToInstaller(DEFAULT_PACKAGE_NAME_1, DEFAULT_LOCALES);
|
||||
Set<String> expectedAppList = Set.of(DEFAULT_PACKAGE_NAME_1);
|
||||
assertEquals(expectedAppList, mSystemAppUpdateTracker.getUpdatedApps());
|
||||
verifyStorageFileContents(expectedAppList);
|
||||
|
||||
// second update
|
||||
mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
|
||||
Binder.getCallingUid());
|
||||
// getApplicationLocales should be invoked only once on the first update.
|
||||
verify(mMockActivityTaskManager, times(1))
|
||||
.getApplicationConfig(anyString(), anyInt());
|
||||
// Broadcast should be sent only once on first update.
|
||||
verify(mMockContext, times(1)).sendBroadcastAsUser(any(), any());
|
||||
// Verify that the content remains the same.
|
||||
verifyStorageFileContents(expectedAppList);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnPackageUpdatedFinished_notSystemApp_doesNothing() throws Exception {
|
||||
doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_2,
|
||||
/* isUpdatedSystemApp = */false))
|
||||
.when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_2), any());
|
||||
|
||||
mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_2,
|
||||
Binder.getCallingUid());
|
||||
|
||||
assertTrue(!mSystemAppUpdateTracker.getUpdatedApps().contains(DEFAULT_PACKAGE_NAME_2));
|
||||
// getApplicationLocales should be never be invoked if not a system app.
|
||||
verifyZeroInteractions(mMockActivityTaskManager);
|
||||
// Broadcast should be never sent if not a system app.
|
||||
verify(mMockContext, never()).sendBroadcastAsUser(any(), any());
|
||||
// It shouldn't write to the file if not a system app.
|
||||
assertTrue(!mStoragefile.getBaseFile().isFile());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOnPackageUpdatedFinished_noInstaller_doesNothing() throws Exception {
|
||||
doReturn(createApplicationInfoForApp(DEFAULT_PACKAGE_NAME_1,
|
||||
/* isUpdatedSystemApp = */ true))
|
||||
.when(mMockPackageManager).getApplicationInfo(eq(DEFAULT_PACKAGE_NAME_1), any());
|
||||
doReturn(null).when(mMockPackageManager).getInstallSourceInfo(anyString());
|
||||
|
||||
mSystemAppUpdateTracker.onPackageUpdateFinished(DEFAULT_PACKAGE_NAME_1,
|
||||
Binder.getCallingUid());
|
||||
|
||||
// getApplicationLocales should be never be invoked if not installer is not present.
|
||||
verifyZeroInteractions(mMockActivityTaskManager);
|
||||
// Broadcast should be never sent if installer is not present.
|
||||
verify(mMockContext, never()).sendBroadcastAsUser(any(), any());
|
||||
// It shouldn't write to file if no installer present.
|
||||
assertTrue(!mStoragefile.getBaseFile().isFile());
|
||||
}
|
||||
|
||||
private void verifyStorageFileContents(Set<String> expectedAppList)
|
||||
throws IOException, XmlPullParserException {
|
||||
assertTrue(mStoragefile.getBaseFile().isFile());
|
||||
try (InputStream storageInputStream = mStoragefile.openRead()) {
|
||||
assertEquals(expectedAppList, readFromXml(storageInputStream));
|
||||
} catch (IOException | XmlPullParserException e) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> readFromXml(InputStream storageInputStream)
|
||||
throws XmlPullParserException, IOException {
|
||||
Set<String> outputList = new HashSet<>();
|
||||
final TypedXmlPullParser parser = Xml.newFastPullParser();
|
||||
parser.setInput(storageInputStream, StandardCharsets.UTF_8.name());
|
||||
XmlUtils.beginDocument(parser, SYSTEM_APPS_XML_TAG);
|
||||
int depth = parser.getDepth();
|
||||
while (XmlUtils.nextElementWithin(parser, depth)) {
|
||||
if (parser.getName().equals(PACKAGE_XML_TAG)) {
|
||||
String packageName = parser.getAttributeValue(/* namespace= */ null,
|
||||
ATTR_NAME);
|
||||
if (!TextUtils.isEmpty(packageName)) {
|
||||
outputList.add(packageName);
|
||||
}
|
||||
}
|
||||
}
|
||||
return outputList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the broadcast sent to the installer of the updated app.
|
||||
*/
|
||||
private void assertBroadcastSentToInstaller(String packageName, LocaleList locales) {
|
||||
ArgumentCaptor<Intent> captor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mMockContext).sendBroadcastAsUser(captor.capture(), any(UserHandle.class));
|
||||
for (Intent intent : captor.getAllValues()) {
|
||||
assertTrue(Intent.ACTION_APPLICATION_LOCALE_CHANGED.equals(intent.getAction()));
|
||||
assertTrue(DEFAULT_INSTALLER_PACKAGE_NAME.equals(intent.getPackage()));
|
||||
assertTrue(packageName.equals(intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)));
|
||||
assertTrue(locales.equals(intent.getParcelableExtra(Intent.EXTRA_LOCALE_LIST)));
|
||||
}
|
||||
}
|
||||
|
||||
private ApplicationInfo createApplicationInfoForApp(String packageName,
|
||||
boolean isUpdatedSystemApp) {
|
||||
ApplicationInfo applicationInfo = new ApplicationInfo();
|
||||
applicationInfo.packageName = packageName;
|
||||
if (isUpdatedSystemApp) {
|
||||
applicationInfo.flags = ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
|
||||
}
|
||||
return applicationInfo;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user