new CacheSwapAppsService for caching parsed apps to be swapped

Since it takes a chunk of time to generate and write the app index.jar when
swapping apps, this service starts running in the background immediately
when SwapService starts.  It first indexes the installed apps that were not
cached, then caches apps based PACKAGE_ADDED broadcasts.  It does not index
system apps, since there are many and they are rarely swapped.
This commit is contained in:
Hans-Christoph Steiner 2016-05-27 23:02:54 +02:00
parent 944d355e29
commit 335be87cf8
5 changed files with 110 additions and 18 deletions

View File

@ -465,6 +465,9 @@
<service
android:name=".installer.InstallManagerService"
android:exported="false" />
<service
android:name=".localrepo.CacheSwapAppsService"
android:exported="false" />
</application>
</manifest>

View File

@ -309,6 +309,8 @@ public class App extends ValueObject implements Comparable<App> {
this.name = (String) appInfo.loadLabel(pm);
this.icon = getIconName(packageName, packageInfo.versionCode);
this.installedVersionName = packageInfo.versionName;
this.installedVersionCode = packageInfo.versionCode;
this.compatible = true;
}

View File

@ -0,0 +1,84 @@
package org.fdroid.fdroid.localrepo;
import android.app.IntentService;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import org.apache.commons.io.FileUtils;
import org.fdroid.fdroid.FDroidApp;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.App;
import java.io.File;
import java.io.IOException;
import java.security.cert.CertificateEncodingException;
/**
* An {@link IntentService} subclass for generating cached info about the installed APKs
* which are available for swapping. It does not cache system apps, since those are
* rarely swapped. This is meant to start running when {@link SwapService} starts.
* <p>
* This could probably be replaced by {@link org.fdroid.fdroid.data.InstalledAppProvider}
* if that contained all of the info to generate complete {@link App} and
* {@link org.fdroid.fdroid.data.Apk} instances.
*/
public class CacheSwapAppsService extends IntentService {
public static final String TAG = "CacheSwapAppsService";
private static final String ACTION_PARSE_APP = "org.fdroid.fdroid.localrepo.action.PARSE_APP";
public CacheSwapAppsService() {
super("CacheSwapAppsService");
}
/**
* Parse the locally installed APK for {@code packageName} and save its XML
* to the APK XML cache.
*/
public static void parseApp(Context context, Intent intent) {
intent.setClass(context, CacheSwapAppsService.class);
intent.setAction(ACTION_PARSE_APP);
context.startService(intent);
}
/**
* Parse all of the locally installed APKs into a memory cache, starting
* with the currently selected apps. APKs that are already parsed in the
* {@code index.jar} file will be read from that file.
*/
public static void startCaching(Context context) {
File indexJarFile = LocalRepoManager.get(context).getIndexJar();
PackageManager pm = context.getPackageManager();
for (ApplicationInfo applicationInfo : pm.getInstalledApplications(0)) {
if (applicationInfo.publicSourceDir.startsWith(FDroidApp.SYSTEM_DIR_NAME)) {
continue;
}
if (!indexJarFile.exists()
|| FileUtils.isFileNewer(new File(applicationInfo.sourceDir), indexJarFile)) {
Intent intent = new Intent();
intent.setData(Uri.parse("package:" + applicationInfo.packageName));
parseApp(context, intent);
}
}
}
@Override
protected void onHandleIntent(Intent intent) {
android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_LOWEST);
if (intent == null || !ACTION_PARSE_APP.equals(intent.getAction())) {
Utils.debugLog(TAG, "received bad Intent: " + intent);
return;
}
try {
PackageManager pm = getPackageManager();
String packageName = intent.getData().getSchemeSpecificPart();
App app = new App(this, pm, packageName);
SwapService.putAppInCache(packageName, app);
} catch (CertificateEncodingException | IOException | PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
}
}

View File

@ -257,7 +257,10 @@ public final class LocalRepoManager {
public void addApp(Context context, String packageName) {
App app;
try {
app = SwapService.getAppFromCache(packageName);
if (app == null) {
app = new App(context.getApplicationContext(), pm, packageName);
}
if (!app.isValid()) {
return;
}

View File

@ -31,6 +31,7 @@ import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.UpdateService;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.Repo;
import org.fdroid.fdroid.data.RepoProvider;
import org.fdroid.fdroid.localrepo.peers.Peer;
@ -52,6 +53,7 @@ import java.util.List;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentHashMap;
import rx.Observable;
import rx.Subscription;
@ -61,23 +63,6 @@ import rx.schedulers.Schedulers;
/**
* Central service which manages all of the different moving parts of swap which are required
* to enable p2p swapping of apps.
*
* The following UI elements don't do anything:
* + TODO: Be notified of changes to wifi state correctly, particularly from the WiFi AP (https://github.com/mvdan/accesspoint/issues/5)
* + TODO: The "?" button in the top right of the swap start screen doesn't do anything
* (This has been commented out for now, but it is still preferable to have a working help mechanism)
*
* TODO: Show "Waiting for other device to finish setting up swap" when only F-Droid shown in swap
* TODO: Handle "not connected to wifi" more gracefully. For example, Bonjour discovery falls over.
* TODO: When unable to reach the swap repo, but viewing apps to swap, show relevant feedback when attempting to download and install.
* TODO: Remove peers from list of peers when no longer "visible".
* TODO: Feedback for "Setting up (wifi|bluetooth)" in start swap view is not as immediate as I had hoped.
* TODO: Turn off bluetooth after cancelling/timing out if we turned it on.
* TODO: Disable the Scan QR button unless visible via something. Could equally show relevant feedback.
*
* TODO: Starting wifi after cancelling swap and beginning again doesn't work properly
* TODO: Scan QR hangs when updating repoo. Swapper was 2.3.3 and Swappee was 5.0
* TODO: Showing the progress bar during install doesn't work when the view is inflated again, or when the adapter is scrolled off screen and back again.
*/
public class SwapService extends Service {
@ -90,6 +75,19 @@ public class SwapService extends Service {
@NonNull
private final Set<String> appsToSwap = new HashSet<>();
/**
* A cache of parsed APKs from the file system.
*/
private static final ConcurrentHashMap<String, App> INSTALLED_APPS = new ConcurrentHashMap<>();
static App getAppFromCache(String packageName) {
return INSTALLED_APPS.get(packageName);
}
static void putAppInCache(String packageName, App app) {
INSTALLED_APPS.put(packageName, app);
}
/**
* Where relevant, the state of the swap process will be saved to disk using preferences.
* Note that this is not always useful, for example saving the "current wifi network" is
@ -516,6 +514,8 @@ public class SwapService extends Service {
Utils.debugLog(TAG, "Creating swap service.");
CacheSwapAppsService.startCaching(this);
SharedPreferences preferences = getSharedPreferences(SHARED_PREFERENCES, Context.MODE_PRIVATE);
appsToSwap.addAll(deserializePackages(preferences.getString(KEY_APPS_TO_SWAP, "")));