Merge "Send broadcast when a system app updates for the first time after device setup in LocaleManagerService" into tm-dev am: 0c0a88ea44

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/17012124

Change-Id: I4cee2c062baffd640fb715190a55b77ffb139007
This commit is contained in:
TreeHugger Robot
2022-02-28 12:34:52 +00:00
committed by Automerger Merge Worker
5 changed files with 580 additions and 7 deletions

View File

@@ -78,10 +78,20 @@ public class LocaleManagerService extends SystemService {
Process.THREAD_PRIORITY_BACKGROUND); Process.THREAD_PRIORITY_BACKGROUND);
broadcastHandlerThread.start(); broadcastHandlerThread.start();
SystemAppUpdateTracker systemAppUpdateTracker =
new SystemAppUpdateTracker(this);
broadcastHandlerThread.getThreadHandler().postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
systemAppUpdateTracker.init();
}
});
mBackupHelper = new LocaleManagerBackupHelper(this, mBackupHelper = new LocaleManagerBackupHelper(this,
mPackageManagerInternal, broadcastHandlerThread); mPackageManagerInternal, broadcastHandlerThread);
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper); mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
systemAppUpdateTracker);
mPackageMonitor.register(context, broadcastHandlerThread.getLooper(), mPackageMonitor.register(context, broadcastHandlerThread.getLooper(),
UserHandle.ALL, UserHandle.ALL,
true); 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 * <p><b>Note:</b> This is can be used by installers to deal with cases such as
* language-based APK Splits. * language-based APK Splits.
*/ */
private void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId, void notifyInstallerOfAppWhoseLocaleChanged(String appPackageName, int userId,
LocaleList locales) { LocaleList locales) {
String installingPackageName = getInstallingPackageName(appPackageName); String installingPackageName = getInstallingPackageName(appPackageName);
if (installingPackageName != null) { if (installingPackageName != null) {
@@ -271,7 +281,7 @@ public class LocaleManagerService extends SystemService {
mContext.sendBroadcastAsUser(intent, UserHandle.of(userId)); mContext.sendBroadcastAsUser(intent, UserHandle.of(userId));
} }
private static Intent createBaseIntent(String intentAction, String appPackageName, static Intent createBaseIntent(String intentAction, String appPackageName,
LocaleList locales) { LocaleList locales) {
return new Intent(intentAction) return new Intent(intentAction)
.putExtra(Intent.EXTRA_PACKAGE_NAME, appPackageName) .putExtra(Intent.EXTRA_PACKAGE_NAME, appPackageName)
@@ -406,7 +416,7 @@ public class LocaleManagerService extends SystemService {
} }
@Nullable @Nullable
private String getInstallingPackageName(String packageName) { String getInstallingPackageName(String packageName) {
try { try {
return mContext.getPackageManager() return mContext.getPackageManager()
.getInstallSourceInfo(packageName).getInstallingPackageName(); .getInstallSourceInfo(packageName).getInstallingPackageName();

View File

@@ -23,13 +23,22 @@ import com.android.internal.content.PackageMonitor;
* *
* <p> These listeners forward the call to different aspects of locale service that * <p> These listeners forward the call to different aspects of locale service that
* handle the business logic. * 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 { final class LocaleManagerServicePackageMonitor extends PackageMonitor {
private LocaleManagerBackupHelper mBackupHelper; private LocaleManagerBackupHelper mBackupHelper;
private SystemAppUpdateTracker mSystemAppUpdateTracker;
LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper) { LocaleManagerServicePackageMonitor(LocaleManagerBackupHelper localeManagerBackupHelper,
SystemAppUpdateTracker systemAppUpdateTracker) {
mBackupHelper = localeManagerBackupHelper; mBackupHelper = localeManagerBackupHelper;
mSystemAppUpdateTracker = systemAppUpdateTracker;
} }
@Override @Override
@@ -46,4 +55,9 @@ final class LocaleManagerServicePackageMonitor extends PackageMonitor {
public void onPackageRemoved(String packageName, int uid) { public void onPackageRemoved(String packageName, int uid) {
mBackupHelper.onPackageRemoved(); mBackupHelper.onPackageRemoved();
} }
@Override
public void onPackageUpdateFinished(String packageName, int uid) {
mSystemAppUpdateTracker.onPackageUpdateFinished(packageName, uid);
}
} }

View File

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

View File

@@ -107,6 +107,7 @@ public class LocaleManagerBackupRestoreTest {
private PackageManager mMockPackageManager; private PackageManager mMockPackageManager;
@Mock @Mock
private LocaleManagerService mMockLocaleManagerService; private LocaleManagerService mMockLocaleManagerService;
BroadcastReceiver mUserMonitor; BroadcastReceiver mUserMonitor;
PackageMonitor mPackageMonitor; PackageMonitor mPackageMonitor;
@@ -131,6 +132,7 @@ public class LocaleManagerBackupRestoreTest {
mMockPackageManagerInternal = mock(PackageManagerInternal.class); mMockPackageManagerInternal = mock(PackageManagerInternal.class);
mMockPackageManager = mock(PackageManager.class); mMockPackageManager = mock(PackageManager.class);
mMockLocaleManagerService = mock(LocaleManagerService.class); mMockLocaleManagerService = mock(LocaleManagerService.class);
SystemAppUpdateTracker systemAppUpdateTracker = mock(SystemAppUpdateTracker.class);
doReturn(mMockPackageManager).when(mMockContext).getPackageManager(); doReturn(mMockPackageManager).when(mMockContext).getPackageManager();
@@ -144,7 +146,8 @@ public class LocaleManagerBackupRestoreTest {
doNothing().when(mBackupHelper).notifyBackupManager(); doNothing().when(mBackupHelper).notifyBackupManager();
mUserMonitor = mBackupHelper.getUserMonitor(); mUserMonitor = mBackupHelper.getUserMonitor();
mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper); mPackageMonitor = new LocaleManagerServicePackageMonitor(mBackupHelper,
systemAppUpdateTracker);
setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS); setCurrentTimeMillis(DEFAULT_CREATION_TIME_MILLIS);
} }

View File

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