InstalledAppProviderService to replace InstalledAppCacheUpdater

InstalledAppCacheUpdater was a custom Service-like thing with some
threading issues.  InstalledAppProviderService is an IntentService that
relies on the built-in queue and threading of the IntentService to make
sure that things are processed nicely in the background and one at a time.

This changes the announcing so that each app added/changed/deleted triggers
a new annoucement.  This keeps the UI more updated, and makes the Installed
tab show something as soon as possible, rather than waiting for the all of
the install apps to be processed.  This becomes more important as more
stuff is added to InstalledAppProvider, like the hash of the APK.

This also strips down and simplifies the related BroadcastReceivers.
BroadcastReceivers work on the UI thread, so they should do as little work
as possible. PackageManagerReceiver just rebadges the incoming Intent and
sends it off to InstalledAppProviderService for processing.
This commit is contained in:
Hans-Christoph Steiner
2016-05-27 16:51:35 +02:00
parent 677db72bb3
commit d734e584f6
15 changed files with 270 additions and 534 deletions

View File

@@ -98,6 +98,11 @@ public abstract class ProviderTestCase2MockContext<T extends ContentProvider> ex
public Context getApplicationContext() {
return this;
}
@Override
public String getPackageName() {
return "org.fdroid.fdroid";
}
}
/**

View File

@@ -5,6 +5,9 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import java.io.File;
import java.io.IOException;
@SuppressLint("ParcelCreator")
public class MockApplicationInfo extends ApplicationInfo {
@@ -12,6 +15,11 @@ public class MockApplicationInfo extends ApplicationInfo {
public MockApplicationInfo(PackageInfo info) {
this.info = info;
try {
this.publicSourceDir = File.createTempFile(info.packageName, "apk").getAbsolutePath();
} catch (IOException e) {
this.publicSourceDir = "/data/app/" + info.packageName + "-4.apk";
}
}
@Override

View File

@@ -37,6 +37,7 @@ public class MockInstallablePackageManager extends MockPackageManager {
p.packageName = id;
p.versionCode = version;
p.versionName = versionName;
p.applicationInfo = new MockApplicationInfo(p);
info.add(p);
}
}

View File

@@ -4,7 +4,6 @@ import android.app.Instrumentation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.support.annotation.Nullable;
@@ -15,9 +14,6 @@ import junit.framework.AssertionFailedError;
import org.fdroid.fdroid.data.ApkProvider;
import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.FDroidProviderTest;
import org.fdroid.fdroid.receiver.PackageAddedReceiver;
import org.fdroid.fdroid.receiver.PackageRemovedReceiver;
import org.fdroid.fdroid.receiver.PackageUpgradedReceiver;
import java.io.File;
import java.io.FileOutputStream;
@@ -28,9 +24,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import mock.MockContextSwappableComponents;
import mock.MockInstallablePackageManager;
public class TestUtils {
private static final String TAG = "TestUtils";
@@ -146,54 +139,6 @@ public class TestUtils {
return providerTest.getMockContentResolver().insert(uri, values);
}
/**
* Will tell {@code pm} that we are installing {@code appId}, and then alert the
* {@link org.fdroid.fdroid.receiver.PackageAddedReceiver}. This will in turn update the
* "installed apps" table in the database.
*/
public static void installAndBroadcast(MockContextSwappableComponents context,
MockInstallablePackageManager pm, String appId,
int versionCode, String versionName) {
context.setPackageManager(pm);
pm.install(appId, versionCode, versionName);
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_ADDED);
installIntent.setData(Utils.getPackageUri(appId));
new PackageAddedReceiver().onReceive(context, installIntent);
}
/**
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
*/
public static void upgradeAndBroadcast(MockContextSwappableComponents context,
MockInstallablePackageManager pm, String appId,
int versionCode, String versionName) {
/*
removeAndBroadcast(context, pm, appId);
installAndBroadcast(context, pm, appId, versionCode, versionName);
*/
context.setPackageManager(pm);
pm.install(appId, versionCode, versionName);
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_CHANGED);
installIntent.setData(Utils.getPackageUri(appId));
new PackageUpgradedReceiver().onReceive(context, installIntent);
}
/**
* @see org.fdroid.fdroid.TestUtils#installAndBroadcast(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
*/
public static void removeAndBroadcast(MockContextSwappableComponents context, MockInstallablePackageManager pm, String appId) {
context.setPackageManager(pm);
pm.remove(appId);
Intent installIntent = new Intent(Intent.ACTION_PACKAGE_REMOVED);
installIntent.setData(Utils.getPackageUri(appId));
new PackageRemovedReceiver().onReceive(context, installIntent);
}
@Nullable
public static File copyAssetToDir(Context context, String assetName, File directory) {
File tempFile;

View File

@@ -2,6 +2,7 @@ package org.fdroid.fdroid.data;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import android.content.res.Resources;
import android.database.Cursor;
@@ -56,24 +57,27 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
insertApp("com.example.app1000", "App 1000");
for (int i = 0; i < 50; i++) {
pm.install("com.example.app" + i, 1, "v" + 1);
String packageName = "com.example.app" + i;
pm.install(packageName, 1, "v" + 1);
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo);
}
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(1, AppProvider.getInstalledUri());
for (int i = 50; i < 500; i++) {
pm.install("com.example.app" + i, 1, "v" + 1);
String packageName = "com.example.app" + i;
pm.install(packageName, 1, "v" + 1);
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo);
}
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(2, AppProvider.getInstalledUri());
for (int i = 500; i < 1100; i++) {
pm.install("com.example.app" + i, 1, "v" + 1);
String packageName = "com.example.app" + i;
pm.install(packageName, 1, "v" + 1);
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
InstalledAppProviderService.insertAppIntoDb(getSwappableContext(), packageName, packageInfo);
}
InstalledAppCacheUpdater.updateInForeground(getMockContext());
assertResultCount(3, AppProvider.getInstalledUri());
}
@@ -127,7 +131,7 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
values.put(AppProvider.DataColumns.IGNORE_THISUPDATE, ignoreVercode);
insertApp(id, "App: " + id, values);
TestUtils.installAndBroadcast(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode);
InstalledAppProviderTest.install(getSwappableContext(), packageManager, id, installedVercode, "v" + installedVercode);
}
public void testCanUpdate() {
@@ -247,7 +251,7 @@ public class AppProviderTest extends FDroidProviderTest<AppProvider> {
assertResultCount(0, AppProvider.getInstalledUri());
for (int i = 10; i < 20; i++) {
TestUtils.installAndBroadcast(getSwappableContext(), pm, "com.example.test." + i, i, "v1");
InstalledAppProviderTest.install(getSwappableContext(), pm, "com.example.test." + i, i, "v1");
}
assertResultCount(10, AppProvider.getInstalledUri());

View File

@@ -1,9 +1,9 @@
package org.fdroid.fdroid.data;
import android.content.ContentValues;
import android.content.pm.PackageInfo;
import org.fdroid.fdroid.TestUtils;
import mock.MockContextSwappableComponents;
import mock.MockInstallablePackageManager;
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
@@ -96,9 +96,8 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
}
public void testInsertWithBroadcast() {
installAndBroadcast("com.example.broadcasted1", 10, "v1.0");
installAndBroadcast("com.example.broadcasted2", 105, "v1.05");
install("com.example.broadcasted1", 10, "v1.0");
install("com.example.broadcasted2", 105, "v1.05");
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.broadcasted1", 10, "v1.0");
@@ -107,12 +106,12 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
public void testUpdateWithBroadcast() {
installAndBroadcast("com.example.toUpgrade", 1, "v0.1");
install("com.example.toUpgrade", 1, "v0.1");
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toUpgrade", 1, "v0.1");
upgradeAndBroadcast("com.example.toUpgrade", 2, "v0.2");
install("com.example.toUpgrade", 2, "v0.2");
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toUpgrade", 2, "v0.2");
@@ -121,14 +120,14 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
public void testDeleteWithBroadcast() {
installAndBroadcast("com.example.toKeep", 1, "v0.1");
installAndBroadcast("com.example.toDelete", 1, "v0.1");
install("com.example.toKeep", 1, "v0.1");
install("com.example.toDelete", 1, "v0.1");
assertResultCount(2, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
assertIsInstalledVersionInDb("com.example.toDelete", 1, "v0.1");
removeAndBroadcast("com.example.toDelete");
remove("com.example.toDelete");
assertResultCount(1, InstalledAppProvider.getContentUri());
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
@@ -137,7 +136,7 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
@Override
protected String[] getMinimalProjection() {
return new String[] {
return new String[]{
InstalledAppProvider.DataColumns.PACKAGE_NAME,
InstalledAppProvider.DataColumns.VERSION_CODE,
InstalledAppProvider.DataColumns.VERSION_NAME,
@@ -165,16 +164,36 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values);
}
private void removeAndBroadcast(String appId) {
TestUtils.removeAndBroadcast(getSwappableContext(), getPackageManager(), appId);
private void remove(String packageName) {
remove(getSwappableContext(), getPackageManager(), packageName);
}
private void upgradeAndBroadcast(String appId, int versionCode, String versionName) {
TestUtils.upgradeAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
private void install(String appId, int versionCode, String versionName) {
install(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
}
private void installAndBroadcast(String appId, int versionCode, String versionName) {
TestUtils.installAndBroadcast(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
/**
* Will tell {@code pm} that we are installing {@code packageName}, and then update the
* "installed apps" table in the database.
*/
public static void install(MockContextSwappableComponents context,
MockInstallablePackageManager pm, String packageName,
int versionCode, String versionName) {
context.setPackageManager(pm);
pm.install(packageName, versionCode, versionName);
PackageInfo packageInfo = pm.getPackageInfo(packageName, 0);
InstalledAppProviderService.insertAppIntoDb(context, packageName, packageInfo);
}
/**
* @see #install(mock.MockContextSwappableComponents, mock.MockInstallablePackageManager, String, int, String)
*/
public static void remove(MockContextSwappableComponents context, MockInstallablePackageManager pm, String packageName) {
context.setPackageManager(pm);
pm.remove(packageName);
InstalledAppProviderService.deleteAppFromDb(context, packageName);
}
}