From 335be87cf8646645b25cf76e050d5ed0f76fc054 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Fri, 27 May 2016 23:02:54 +0200 Subject: [PATCH] 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. --- app/src/main/AndroidManifest.xml | 3 + .../main/java/org/fdroid/fdroid/data/App.java | 2 + .../localrepo/CacheSwapAppsService.java | 84 +++++++++++++++++++ .../fdroid/localrepo/LocalRepoManager.java | 5 +- .../fdroid/fdroid/localrepo/SwapService.java | 34 ++++---- 5 files changed, 110 insertions(+), 18 deletions(-) create mode 100644 app/src/main/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 9fb2fe575..c1c25f546 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -465,6 +465,9 @@ + diff --git a/app/src/main/java/org/fdroid/fdroid/data/App.java b/app/src/main/java/org/fdroid/fdroid/data/App.java index 6dc0f496b..5e5356cd5 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/App.java +++ b/app/src/main/java/org/fdroid/fdroid/data/App.java @@ -309,6 +309,8 @@ public class App extends ValueObject implements Comparable { this.name = (String) appInfo.loadLabel(pm); this.icon = getIconName(packageName, packageInfo.versionCode); + this.installedVersionName = packageInfo.versionName; + this.installedVersionCode = packageInfo.versionCode; this.compatible = true; } diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java b/app/src/main/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java new file mode 100644 index 000000000..a3674f9d9 --- /dev/null +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/CacheSwapAppsService.java @@ -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. + *

+ * 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(); + } + } +} diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java b/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java index 32978983b..3cf9b79b3 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/LocalRepoManager.java @@ -257,7 +257,10 @@ public final class LocalRepoManager { public void addApp(Context context, String packageName) { App app; try { - app = new App(context.getApplicationContext(), pm, packageName); + app = SwapService.getAppFromCache(packageName); + if (app == null) { + app = new App(context.getApplicationContext(), pm, packageName); + } if (!app.isValid()) { return; } diff --git a/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java b/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java index fcadd5176..8414b05d8 100644 --- a/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java +++ b/app/src/main/java/org/fdroid/fdroid/localrepo/SwapService.java @@ -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 appsToSwap = new HashSet<>(); + /** + * A cache of parsed APKs from the file system. + */ + private static final ConcurrentHashMap 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, "")));