Merge branch 'cr-of-299' into 'master'
Changes made during CR of 299 Here is a collection of small changes I implemented while CRing 299. The most interesting is probably the nice opportunity to use a little bit of RX. This is a good example of where it is a useful API, to "debounce" requests by 1 second (i.e. collect all requests but only respond to the last one after X time units). The `PublishSubject` sits there for the duration of the service, and passively receives events when required. However, it only emits events to the subscriber after one second because before subscribig we ask it to debounce events. See merge request !319
This commit is contained in:
commit
4e9c8e9e5e
@ -11,23 +11,10 @@ import mock.MockInstallablePackageManager;
|
|||||||
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
|
@SuppressWarnings("PMD") // TODO port this to JUnit 4 semantics
|
||||||
public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppProvider> {
|
public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppProvider> {
|
||||||
|
|
||||||
private MockInstallablePackageManager packageManager;
|
|
||||||
|
|
||||||
public InstalledAppProviderTest() {
|
public InstalledAppProviderTest() {
|
||||||
super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
|
super(InstalledAppProvider.class, InstalledAppProvider.getAuthority());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setUp() throws Exception {
|
|
||||||
super.setUp();
|
|
||||||
packageManager = new MockInstallablePackageManager();
|
|
||||||
getSwappableContext().setPackageManager(packageManager);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected MockInstallablePackageManager getPackageManager() {
|
|
||||||
return packageManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testUris() {
|
public void testUris() {
|
||||||
assertInvalidUri(InstalledAppProvider.getAuthority());
|
assertInvalidUri(InstalledAppProvider.getAuthority());
|
||||||
assertInvalidUri(RepoProvider.getContentUri());
|
assertInvalidUri(RepoProvider.getContentUri());
|
||||||
@ -130,45 +117,6 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testInsertWithBroadcast() {
|
|
||||||
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");
|
|
||||||
assertIsInstalledVersionInDb("com.example.broadcasted2", 105, "v1.05");
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testUpdateWithBroadcast() {
|
|
||||||
|
|
||||||
install("com.example.toUpgrade", 1, "v0.1");
|
|
||||||
|
|
||||||
assertResultCount(1, InstalledAppProvider.getContentUri());
|
|
||||||
assertIsInstalledVersionInDb("com.example.toUpgrade", 1, "v0.1");
|
|
||||||
|
|
||||||
install("com.example.toUpgrade", 2, "v0.2");
|
|
||||||
|
|
||||||
assertResultCount(1, InstalledAppProvider.getContentUri());
|
|
||||||
assertIsInstalledVersionInDb("com.example.toUpgrade", 2, "v0.2");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testDeleteWithBroadcast() {
|
|
||||||
|
|
||||||
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");
|
|
||||||
|
|
||||||
remove("com.example.toDelete");
|
|
||||||
|
|
||||||
assertResultCount(1, InstalledAppProvider.getContentUri());
|
|
||||||
assertIsInstalledVersionInDb("com.example.toKeep", 1, "v0.1");
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String[] getMinimalProjection() {
|
protected String[] getMinimalProjection() {
|
||||||
return new String[]{
|
return new String[]{
|
||||||
@ -202,14 +150,6 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
|
|||||||
getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values);
|
getMockContentResolver().insert(InstalledAppProvider.getContentUri(), values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void remove(String packageName) {
|
|
||||||
remove(getSwappableContext(), getPackageManager(), packageName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void install(String appId, int versionCode, String versionName) {
|
|
||||||
install(getSwappableContext(), getPackageManager(), appId, versionCode, versionName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will tell {@code pm} that we are installing {@code packageName}, and then update the
|
* Will tell {@code pm} that we are installing {@code packageName}, and then update the
|
||||||
* "installed apps" table in the database.
|
* "installed apps" table in the database.
|
||||||
@ -224,14 +164,4 @@ public class InstalledAppProviderTest extends FDroidProviderTest<InstalledAppPro
|
|||||||
InstalledAppProviderService.insertAppIntoDb(context, packageName, packageInfo);
|
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -6,17 +6,14 @@ import android.content.UriMatcher;
|
|||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.Signature;
|
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.fdroid.fdroid.Hasher;
|
|
||||||
import org.fdroid.fdroid.R;
|
import org.fdroid.fdroid.R;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
|
||||||
import java.security.NoSuchAlgorithmException;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
@ -118,21 +115,6 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
return packageName; // all else fails, return packageName
|
return packageName; // all else fails, return packageName
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String getPackageSig(PackageInfo info) {
|
|
||||||
if (info == null || info.signatures == null || info.signatures.length < 1) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
Signature sig = info.signatures[0];
|
|
||||||
String sigHash = "";
|
|
||||||
try {
|
|
||||||
Hasher hash = new Hasher("MD5", sig.toCharsString().getBytes());
|
|
||||||
sigHash = hash.getHash();
|
|
||||||
} catch (NoSuchAlgorithmException e) {
|
|
||||||
// ignore
|
|
||||||
}
|
|
||||||
return sigHash;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTableName() {
|
protected String getTableName() {
|
||||||
return DBHelper.TABLE_INSTALLED_APP;
|
return DBHelper.TABLE_INSTALLED_APP;
|
||||||
@ -201,11 +183,7 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
QuerySelection query = new QuerySelection(where, whereArgs);
|
QuerySelection query = new QuerySelection(where, whereArgs);
|
||||||
query = query.add(queryApp(uri.getLastPathSegment()));
|
query = query.add(queryApp(uri.getLastPathSegment()));
|
||||||
|
|
||||||
int count = db().delete(getTableName(), query.getSelection(), query.getArgs());
|
return db().delete(getTableName(), query.getSelection(), query.getArgs());
|
||||||
if (!isApplyingBatch()) {
|
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
|
||||||
}
|
|
||||||
return count;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -217,9 +195,6 @@ public class InstalledAppProvider extends FDroidProvider {
|
|||||||
|
|
||||||
verifyVersionNameNotNull(values);
|
verifyVersionNameNotNull(values);
|
||||||
db().replaceOrThrow(getTableName(), null, values);
|
db().replaceOrThrow(getTableName(), null, values);
|
||||||
if (!isApplyingBatch()) {
|
|
||||||
getContext().getContentResolver().notifyChange(uri, null);
|
|
||||||
}
|
|
||||||
return getAppUri(values.getAsString(DataColumns.PACKAGE_NAME));
|
return getAppUri(values.getAsString(DataColumns.PACKAGE_NAME));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,18 +6,23 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.Signature;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Process;
|
import android.os.Process;
|
||||||
|
|
||||||
|
import org.fdroid.fdroid.Hasher;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
import java.util.concurrent.ScheduledExecutorService;
|
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import rx.functions.Action1;
|
||||||
|
import rx.schedulers.Schedulers;
|
||||||
|
import rx.subjects.PublishSubject;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles all updates to {@link InstalledAppProvider}, whether checking the contents
|
* Handles all updates to {@link InstalledAppProvider}, whether checking the contents
|
||||||
* versus what Android says is installed, or processing {@link Intent}s that come
|
* versus what Android says is installed, or processing {@link Intent}s that come
|
||||||
@ -39,13 +44,34 @@ public class InstalledAppProviderService extends IntentService {
|
|||||||
|
|
||||||
private static final String EXTRA_PACKAGE_INFO = "org.fdroid.fdroid.data.extra.PACKAGE_INFO";
|
private static final String EXTRA_PACKAGE_INFO = "org.fdroid.fdroid.data.extra.PACKAGE_INFO";
|
||||||
|
|
||||||
private ScheduledExecutorService worker;
|
/**
|
||||||
private boolean notifyChangeNeedsSending;
|
* This is for notifing the users of this {@link android.content.ContentProvider}
|
||||||
|
* that the contents has changed. Since {@link Intent}s can come in slow
|
||||||
|
* or fast, and this can trigger a lot of UI updates, the actual
|
||||||
|
* notifications are rate limited to one per second.
|
||||||
|
*/
|
||||||
|
private PublishSubject<Void> notifyEvents;
|
||||||
|
|
||||||
public InstalledAppProviderService() {
|
public InstalledAppProviderService() {
|
||||||
super("InstalledAppProviderService");
|
super("InstalledAppProviderService");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCreate() {
|
||||||
|
super.onCreate();
|
||||||
|
notifyEvents = PublishSubject.create();
|
||||||
|
notifyEvents.debounce(1, TimeUnit.SECONDS)
|
||||||
|
.subscribeOn(Schedulers.newThread())
|
||||||
|
.subscribe(new Action1<Void>() {
|
||||||
|
@Override
|
||||||
|
public void call(Void voidArg) {
|
||||||
|
Utils.debugLog(TAG, "Notifying content providers (so they can update the relevant views).");
|
||||||
|
getContentResolver().notifyChange(AppProvider.getContentUri(), null);
|
||||||
|
getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Inserts an app into {@link InstalledAppProvider} based on a {@code package:} {@link Uri}.
|
* Inserts an app into {@link InstalledAppProvider} based on a {@code package:} {@link Uri}.
|
||||||
* This has no checks for whether it is inserting an exact duplicate, whatever is provided
|
* This has no checks for whether it is inserting an exact duplicate, whatever is provided
|
||||||
@ -134,7 +160,7 @@ public class InstalledAppProviderService extends IntentService {
|
|||||||
} else if (ACTION_DELETE.equals(action)) {
|
} else if (ACTION_DELETE.equals(action)) {
|
||||||
deleteAppFromDb(this, packageName);
|
deleteAppFromDb(this, packageName);
|
||||||
}
|
}
|
||||||
notifyChange();
|
notifyEvents.onNext(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -156,8 +182,7 @@ public class InstalledAppProviderService extends IntentService {
|
|||||||
contentValues.put(InstalledAppProvider.DataColumns.VERSION_NAME, packageInfo.versionName);
|
contentValues.put(InstalledAppProvider.DataColumns.VERSION_NAME, packageInfo.versionName);
|
||||||
contentValues.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL,
|
contentValues.put(InstalledAppProvider.DataColumns.APPLICATION_LABEL,
|
||||||
InstalledAppProvider.getApplicationLabel(context, packageInfo.packageName));
|
InstalledAppProvider.getApplicationLabel(context, packageInfo.packageName));
|
||||||
contentValues.put(InstalledAppProvider.DataColumns.SIGNATURE,
|
contentValues.put(InstalledAppProvider.DataColumns.SIGNATURE, getPackageSig(packageInfo));
|
||||||
InstalledAppProvider.getPackageSig(packageInfo));
|
|
||||||
contentValues.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, packageInfo.lastUpdateTime);
|
contentValues.put(InstalledAppProvider.DataColumns.LAST_UPDATE_TIME, packageInfo.lastUpdateTime);
|
||||||
|
|
||||||
String hashType = "sha256";
|
String hashType = "sha256";
|
||||||
@ -173,31 +198,19 @@ public class InstalledAppProviderService extends IntentService {
|
|||||||
context.getContentResolver().delete(uri, null, null);
|
context.getContentResolver().delete(uri, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private static String getPackageSig(PackageInfo info) {
|
||||||
* This notifies the users of this {@link android.content.ContentProvider}
|
if (info == null || info.signatures == null || info.signatures.length < 1) {
|
||||||
* that the contents has changed. Since {@link Intent}s can come in slow
|
return "";
|
||||||
* or fast, and this can trigger a lot of UI updates, the actual
|
|
||||||
* notifications are rate limited to one per second.
|
|
||||||
*/
|
|
||||||
private void notifyChange() {
|
|
||||||
notifyChangeNeedsSending = true;
|
|
||||||
Runnable task = new Runnable() {
|
|
||||||
@Override
|
|
||||||
public void run() {
|
|
||||||
if (notifyChangeNeedsSending) {
|
|
||||||
Utils.debugLog(TAG, "Notifying content providers (so they can update the relevant views).");
|
|
||||||
getContentResolver().notifyChange(AppProvider.getContentUri(), null);
|
|
||||||
getContentResolver().notifyChange(ApkProvider.getContentUri(), null);
|
|
||||||
notifyChangeNeedsSending = false;
|
|
||||||
} else {
|
|
||||||
worker.shutdown();
|
|
||||||
worker = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
if (worker == null || worker.isShutdown()) {
|
|
||||||
worker = Executors.newSingleThreadScheduledExecutor();
|
|
||||||
worker.scheduleAtFixedRate(task, 0, 1, TimeUnit.SECONDS);
|
|
||||||
}
|
}
|
||||||
|
Signature sig = info.signatures[0];
|
||||||
|
String sigHash = "";
|
||||||
|
try {
|
||||||
|
Hasher hash = new Hasher("MD5", sig.toCharsString().getBytes());
|
||||||
|
sigHash = hash.getHash();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
return sigHash;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -37,7 +37,9 @@ public class CacheSwapAppsService extends IntentService {
|
|||||||
* Parse the locally installed APK for {@code packageName} and save its XML
|
* Parse the locally installed APK for {@code packageName} and save its XML
|
||||||
* to the APK XML cache.
|
* to the APK XML cache.
|
||||||
*/
|
*/
|
||||||
public static void parseApp(Context context, Intent intent) {
|
private static void parseApp(Context context, String packageName) {
|
||||||
|
Intent intent = new Intent();
|
||||||
|
intent.setData(Utils.getPackageUri(packageName));
|
||||||
intent.setClass(context, CacheSwapAppsService.class);
|
intent.setClass(context, CacheSwapAppsService.class);
|
||||||
intent.setAction(ACTION_PARSE_APP);
|
intent.setAction(ACTION_PARSE_APP);
|
||||||
context.startService(intent);
|
context.startService(intent);
|
||||||
@ -57,9 +59,7 @@ public class CacheSwapAppsService extends IntentService {
|
|||||||
}
|
}
|
||||||
if (!indexJarFile.exists()
|
if (!indexJarFile.exists()
|
||||||
|| FileUtils.isFileNewer(new File(applicationInfo.sourceDir), indexJarFile)) {
|
|| FileUtils.isFileNewer(new File(applicationInfo.sourceDir), indexJarFile)) {
|
||||||
Intent intent = new Intent();
|
parseApp(context, applicationInfo.packageName);
|
||||||
intent.setData(Utils.getPackageUri(applicationInfo.packageName));
|
|
||||||
parseApp(context, intent);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -319,16 +319,6 @@ public final class LocalRepoManager {
|
|||||||
* Helper class to aid in constructing index.xml file.
|
* Helper class to aid in constructing index.xml file.
|
||||||
*/
|
*/
|
||||||
public static final class IndexXmlBuilder {
|
public static final class IndexXmlBuilder {
|
||||||
|
|
||||||
private static IndexXmlBuilder indexXmlBuilder;
|
|
||||||
|
|
||||||
public static IndexXmlBuilder get() throws XmlPullParserException {
|
|
||||||
if (indexXmlBuilder == null) {
|
|
||||||
indexXmlBuilder = new IndexXmlBuilder();
|
|
||||||
}
|
|
||||||
return indexXmlBuilder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final XmlSerializer serializer;
|
private final XmlSerializer serializer;
|
||||||
|
|
||||||
@ -487,7 +477,7 @@ public final class LocalRepoManager {
|
|||||||
JarOutputStream jo = new JarOutputStream(bo);
|
JarOutputStream jo = new JarOutputStream(bo);
|
||||||
JarEntry je = new JarEntry("index.xml");
|
JarEntry je = new JarEntry("index.xml");
|
||||||
jo.putNextEntry(je);
|
jo.putNextEntry(je);
|
||||||
IndexXmlBuilder.get().build(context, apps, jo);
|
new IndexXmlBuilder().build(context, apps, jo);
|
||||||
jo.close();
|
jo.close();
|
||||||
bo.close();
|
bo.close();
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user