From 23822cc2474b427fc45672f15977afc6aaff463c Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Mon, 19 Mar 2018 09:46:30 +0100 Subject: [PATCH 1/4] switch English string to string resource closes #1270 --- .../fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java index 1c4a499f6..d6dea4384 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsRecyclerViewAdapter.java @@ -732,10 +732,11 @@ public class AppDetailsRecyclerViewAdapter @Override public void bindModel() { + Context context = headerView.getContext(); if (hasCompatibleApksDifferentSigs()) { - headerView.setText("No versions with compatible signature"); + headerView.setText(context.getString(R.string.app_details__no_versions__no_compatible_signatures)); } else { - headerView.setText("No versions compatible with device"); + headerView.setText(context.getString(R.string.app_details__no_versions__none_compatible_with_device)); } } From 52d09e641805087c0660f8aa454e6c80fa6e397f Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 20 Mar 2018 11:46:08 +0100 Subject: [PATCH 2/4] do not set cached APKs as ReadyToInstall if they are already installed If an APK is already installed, and it is still in the cache, it should never be marked "Ready to Install". #1271 --- .../fdroid/fdroid/AppUpdateStatusService.java | 48 +++++++++++++------ 1 file changed, 33 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java index dc5740d0e..3179c7a65 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java +++ b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java @@ -20,13 +20,15 @@ import java.util.ArrayList; import java.util.List; /** - * Scans the list of downloaded .apk files in the cache for each app which can be updated. - * If a valid .apk file is found then it will tell the {@link AppUpdateStatusManager} that it is - * {@link AppUpdateStatusManager.Status#ReadyToInstall}. This is an {@link IntentService} so as to - * run on a background thread, as it hits the disk a bit to figure out the hash of each downloaded - * file. + * Scans the list of downloaded .apk files in the cache. This info is used to + * determine if an APK is ready to install. When a valid .apk file is found, + * this checks whether that APK is already installed, and whether the user's + * install request is still active. If all those are true, then this tells the + * {@link AppUpdateStatusManager} that the APK is + * {@link AppUpdateStatusManager.Status#ReadyToInstall}. This is an + * {@link IntentService} so as to run on a background thread, as it hits the + * disk a bit to figure out the hash of each downloaded file. */ -@SuppressWarnings("LineLength") public class AppUpdateStatusService extends IntentService { private static final String TAG = "AppUpdateStatusService"; @@ -54,6 +56,7 @@ public class AppUpdateStatusService extends IntentService { if (cacheDirList == null) { return; } + PackageManager packageManager = getPackageManager(); List apksReadyToInstall = new ArrayList<>(); for (String repoDirName : cacheDirList) { File repoDir = new File(cacheDir, repoDirName); @@ -64,14 +67,23 @@ public class AppUpdateStatusService extends IntentService { for (String apkFileName : apks) { Apk apk = processDownloadedApk(new File(repoDir, apkFileName)); if (apk != null) { - Log.i(TAG, "Found downloaded apk " + apk.packageName + ". Notifying user that it should be installed."); - apksReadyToInstall.add(apk); + PackageInfo packageInfo = null; + try { + packageInfo = packageManager.getPackageInfo(apk.packageName, 0); + } catch (PackageManager.NameNotFoundException e) { + // ignored + } + if (packageInfo == null || packageInfo.versionCode != apk.versionCode) { + Utils.debugLog(TAG, "Marking downloaded apk " + apk.apkName + " as ReadyToInstall"); + apksReadyToInstall.add(apk); + } } } } if (apksReadyToInstall.size() > 0) { - AppUpdateStatusManager.getInstance(this).addApks(apksReadyToInstall, AppUpdateStatusManager.Status.ReadyToInstall); + AppUpdateStatusManager.getInstance(this).addApks(apksReadyToInstall, + AppUpdateStatusManager.Status.ReadyToInstall); InstallManagerService.managePreviouslyDownloadedApks(this); } } @@ -100,21 +112,25 @@ public class AppUpdateStatusService extends IntentService { return null; } - PackageInfo downloadedInfo = getPackageManager().getPackageArchiveInfo(apkPath.getAbsolutePath(), PackageManager.GET_GIDS); + PackageInfo downloadedInfo = getPackageManager().getPackageArchiveInfo(apkPath.getAbsolutePath(), + PackageManager.GET_GIDS); if (downloadedInfo == null) { Log.i(TAG, "Skipping " + apkPath + " because PackageManager was unable to read it."); return null; } - Utils.debugLog(TAG, "Found package for " + downloadedInfo.packageName + ", checking its hash to see if it downloaded correctly."); + Utils.debugLog(TAG, "Found package for " + downloadedInfo.packageName + ':' + downloadedInfo.versionCode + + ", checking its hash to see if it downloaded correctly."); Apk downloadedApk = findApkMatchingHash(apkPath); if (downloadedApk == null) { - Log.i(TAG, "Either the apk wasn't downloaded fully, or the repo it came from has been disabled. Either way, not notifying the user about it."); + Log.i(TAG, "Either the apk wasn't downloaded fully, or the repo it came from has been disabled. " + + "Either way, not notifying the user about it."); return null; } if (!AppUpdateStatusManager.getInstance(this).isPendingInstall(downloadedApk.hash)) { - Log.i(TAG, downloadedApk.packageName + " is NOT pending install, probably just left over from a previous install."); + Log.i(TAG, downloadedApk.packageName + ':' + downloadedApk.versionCode + + " is NOT pending install, probably just left over from a previous install."); return null; } @@ -124,14 +140,16 @@ public class AppUpdateStatusService extends IntentService { if (pathToInstalled != null && pathToInstalled.canRead() && pathToInstalled.length() == downloadedApk.size && // Check size before hash for performance. TextUtils.equals(Utils.getBinaryHash(pathToInstalled, "sha256"), downloadedApk.hash)) { - Log.i(TAG, downloadedApk.packageName + " is pending install, but we already have the correct version installed."); + Log.i(TAG, downloadedApk.packageName + + " is pending install, but we already have the correct version installed."); AppUpdateStatusManager.getInstance(this).markAsNoLongerPendingInstall(downloadedApk.getUrl()); return null; } } catch (PackageManager.NameNotFoundException ignored) { } - Utils.debugLog(TAG, downloadedApk.packageName + " is pending install, so we need to notify the user about installing it."); + Utils.debugLog(TAG, downloadedApk.packageName + ':' + downloadedApk.versionCode + + " is pending install, so we need to notify the user about installing it."); return downloadedApk; } From 7c1e2ee09cb8b8079ddb7c0e6e64f1584c8ccda9 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 20 Mar 2018 12:14:29 +0100 Subject: [PATCH 3/4] actually remove Updates entries when they are swiped away --- .../main/java/org/fdroid/fdroid/AppUpdateStatusManager.java | 2 +- .../java/org/fdroid/fdroid/views/updates/DismissResult.java | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java index fa7faeaf1..12ac7a11b 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java +++ b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java @@ -370,7 +370,7 @@ public final class AppUpdateStatusManager { public void removeApk(String key) { synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(key); + AppUpdateStatus entry = appMapping.remove(key); if (entry != null) { Utils.debugLog(LOGTAG, "Remove APK " + entry.apk.apkName); notifyRemove(entry); diff --git a/app/src/main/java/org/fdroid/fdroid/views/updates/DismissResult.java b/app/src/main/java/org/fdroid/fdroid/views/updates/DismissResult.java index c68870128..f37d58fb4 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/updates/DismissResult.java +++ b/app/src/main/java/org/fdroid/fdroid/views/updates/DismissResult.java @@ -17,10 +17,6 @@ public class DismissResult { this(null, false); } - public DismissResult(boolean requiresAdapterRefresh) { - this(null, requiresAdapterRefresh); - } - public DismissResult(@Nullable CharSequence message, boolean requiresAdapterRefresh) { this.message = message; this.requiresAdapterRefresh = requiresAdapterRefresh; From dbfac03b524f368b72113569a70c52431bea2e72 Mon Sep 17 00:00:00 2001 From: Hans-Christoph Steiner Date: Tue, 20 Mar 2018 13:46:01 +0100 Subject: [PATCH 4/4] sync AppUpdateStatusManager docs and varnames to InstallManagerService --- .../fdroid/fdroid/AppUpdateStatusManager.java | 23 +++++++++++-------- .../installer/InstallManagerService.java | 2 +- 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java index 12ac7a11b..22d9590b0 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java +++ b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java @@ -29,13 +29,16 @@ import java.util.Map; /** * Manages the state of APKs that are being installed or that have updates available. + * This also ensures the state is saved across F-Droid restarts, and repopulates + * based on {@link org.fdroid.fdroid.data.Schema.InstalledAppTable} data, APKs that + * are present in the cache, and the {@code apks-pending-install} + * {@link SharedPreferences} instance. *

- * The full URL for the APK file to download is used as the unique ID to - * represent the status of the APK throughout F-Droid. The full download URL is guaranteed - * to be unique since it points to files on a filesystem, where there cannot be multiple files with - * the same name. This provides a unique ID beyond just {@code packageName} - * and {@code versionCode} since there could be different copies of the same - * APK on different servers, signed by different keys, or even different builds. + * As defined in {@link org.fdroid.fdroid.installer.InstallManagerService}, the + * canonical URL for the APK file to download is used as the unique ID to represent + * the status of the APK throughout F-Droid. + * + * @see org.fdroid.fdroid.installer.InstallManagerService */ public final class AppUpdateStatusManager { @@ -556,8 +559,8 @@ public final class AppUpdateStatusManager { * * @see #isPendingInstall(String) */ - public void markAsPendingInstall(String uniqueKey) { - AppUpdateStatus entry = get(uniqueKey); + public void markAsPendingInstall(String urlString) { + AppUpdateStatus entry = get(urlString); if (entry != null) { Utils.debugLog(TAG, "Marking " + entry.apk.packageName + " as pending install."); apksPendingInstall.edit().putBoolean(entry.apk.hash, true).apply(); @@ -568,8 +571,8 @@ public final class AppUpdateStatusManager { * @see #markAsNoLongerPendingInstall(AppUpdateStatus) * @see #isPendingInstall(String) */ - public void markAsNoLongerPendingInstall(String uniqueKey) { - AppUpdateStatus entry = get(uniqueKey); + public void markAsNoLongerPendingInstall(String urlString) { + AppUpdateStatus entry = get(urlString); if (entry != null) { markAsNoLongerPendingInstall(entry); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java index 654bbfc4f..f10754362 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -40,7 +40,7 @@ import java.io.IOException; * and then redeliver the {@link Intent} for us, which includes all of the data needed * for {@code InstallManagerService} to do its job for the whole lifecycle of an install. *

- * The full URL for the APK file to download is also used as the unique ID to + * The canonical URL for the APK file to download is also used as the unique ID to * represent the download itself throughout F-Droid. This follows the model * of {@link Intent#setData(Uri)}, where the core data of an {@code Intent} is * a {@code Uri}. The full download URL is guaranteed to be unique since it