diff --git a/app/src/androidTest/java/org/fdroid/fdroid/net/HttpDownloaderTest.java b/app/src/androidTest/java/org/fdroid/fdroid/net/HttpDownloaderTest.java index 57238613d..4356f4564 100644 --- a/app/src/androidTest/java/org/fdroid/fdroid/net/HttpDownloaderTest.java +++ b/app/src/androidTest/java/org/fdroid/fdroid/net/HttpDownloaderTest.java @@ -70,7 +70,7 @@ public class HttpDownloaderTest { final HttpDownloader httpDownloader = new HttpDownloader(uri, destFile); httpDownloader.setListener(new ProgressListener() { @Override - public void onProgress(String urlString, long bytesRead, long totalBytes) { + public void onProgress(long bytesRead, long totalBytes) { receivedProgress = true; } }); @@ -132,7 +132,7 @@ public class HttpDownloaderTest { final HttpDownloader httpDownloader = new HttpDownloader(uri, destFile); httpDownloader.setListener(new ProgressListener() { @Override - public void onProgress(String urlString, long bytesRead, long totalBytes) { + public void onProgress(long bytesRead, long totalBytes) { receivedProgress = true; latch.countDown(); } diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapAppsView.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapAppsView.java index 839cf8c5b..076d4ec45 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapAppsView.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapAppsView.java @@ -314,10 +314,8 @@ public class SwapAppsView extends ListView implements } if (apk != null) { - String urlString = apk.getUrl(); - // TODO unregister receivers? or will they just die with this instance - IntentFilter downloadFilter = DownloaderService.getIntentFilter(urlString); + IntentFilter downloadFilter = DownloaderService.getIntentFilter(apk.getCanonicalUrl()); localBroadcastManager.registerReceiver(downloadReceiver, downloadFilter); } diff --git a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java index e3b1cb276..c55d773b6 100644 --- a/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java +++ b/app/src/full/java/org/fdroid/fdroid/views/swap/SwapWorkflowActivity.java @@ -836,9 +836,8 @@ public class SwapWorkflowActivity extends AppCompatActivity { } public void install(@NonNull final App app, @NonNull final Apk apk) { - Uri downloadUri = Uri.parse(apk.getUrl()); localBroadcastManager.registerReceiver(installReceiver, - Installer.getInstallIntentFilter(downloadUri)); + Installer.getInstallIntentFilter(apk.getCanonicalUrl())); InstallManagerService.queue(this, app, apk); } diff --git a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java index 020a817dd..4db8c3ca2 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java +++ b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusManager.java @@ -72,9 +72,7 @@ public final class AppUpdateStatusManager { */ public static final String BROADCAST_APPSTATUS_REMOVED = "org.fdroid.fdroid.installer.appstatus.appchange.remove"; - public static final String EXTRA_APK_URL = "urlstring"; public static final String EXTRA_STATUS = "status"; - public static final String EXTRA_REASON_FOR_CHANGE = "reason"; public static final String REASON_READY_TO_INSTALL = "readytoinstall"; @@ -129,11 +127,11 @@ public final class AppUpdateStatusManager { /** * @return the unique ID used to represent this specific package's install process - * also known as {@code urlString}. + * also known as {@code canonicalUrl}. * @see org.fdroid.fdroid.installer.InstallManagerService */ - public String getUniqueKey() { - return apk.getUrl(); + public String getCanonicalUrl() { + return apk.getCanonicalUrl(); } /** @@ -225,9 +223,9 @@ public final class AppUpdateStatusManager { } @Nullable - public AppUpdateStatus get(String key) { + public AppUpdateStatus get(String canonicalUrl) { synchronized (appMapping) { - return appMapping.get(key); + return appMapping.get(canonicalUrl); } } @@ -264,7 +262,7 @@ public final class AppUpdateStatusManager { notifyChange(entry, isStatusUpdate); if (status == Status.Installed) { - InstallManagerService.removePendingInstall(context, entry.getUniqueKey()); + InstallManagerService.removePendingInstall(context, entry.getCanonicalUrl()); } } @@ -272,11 +270,11 @@ public final class AppUpdateStatusManager { Utils.debugLog(LOGTAG, "Add APK " + apk.apkName + " with state " + status.name()); AppUpdateStatus entry = createAppEntry(apk, status, intent); setEntryContentIntentIfEmpty(entry); - appMapping.put(entry.getUniqueKey(), entry); + appMapping.put(entry.getCanonicalUrl(), entry); notifyAdd(entry); if (status == Status.Installed) { - InstallManagerService.removePendingInstall(context, entry.getUniqueKey()); + InstallManagerService.removePendingInstall(context, entry.getCanonicalUrl()); } } @@ -291,7 +289,7 @@ public final class AppUpdateStatusManager { private void notifyAdd(AppUpdateStatus entry) { if (!isBatchUpdating) { Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_ADDED); - broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey()); + broadcastIntent.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, entry.getCanonicalUrl()); broadcastIntent.putExtra(EXTRA_STATUS, entry.copy()); localBroadcastManager.sendBroadcast(broadcastIntent); } @@ -300,7 +298,7 @@ public final class AppUpdateStatusManager { private void notifyChange(AppUpdateStatus entry, boolean isStatusUpdate) { if (!isBatchUpdating) { Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_CHANGED); - broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey()); + broadcastIntent.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, entry.getCanonicalUrl()); broadcastIntent.putExtra(EXTRA_STATUS, entry.copy()); broadcastIntent.putExtra(EXTRA_IS_STATUS_UPDATE, isStatusUpdate); localBroadcastManager.sendBroadcast(broadcastIntent); @@ -310,7 +308,7 @@ public final class AppUpdateStatusManager { private void notifyRemove(AppUpdateStatus entry) { if (!isBatchUpdating) { Intent broadcastIntent = new Intent(BROADCAST_APPSTATUS_REMOVED); - broadcastIntent.putExtra(EXTRA_APK_URL, entry.getUniqueKey()); + broadcastIntent.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, entry.getCanonicalUrl()); broadcastIntent.putExtra(EXTRA_STATUS, entry.copy()); localBroadcastManager.sendBroadcast(broadcastIntent); } @@ -321,7 +319,7 @@ public final class AppUpdateStatusManager { ContentResolver resolver = context.getContentResolver(); App app = AppProvider.Helper.findSpecificApp(resolver, apk.packageName, apk.repoId); AppUpdateStatus ret = new AppUpdateStatus(app, apk, status, intent); - appMapping.put(apk.getUrl(), ret); + appMapping.put(apk.getCanonicalUrl(), ret); return ret; } } @@ -347,7 +345,7 @@ public final class AppUpdateStatusManager { } synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(apk.getUrl()); + AppUpdateStatus entry = appMapping.get(apk.getCanonicalUrl()); if (entry != null) { updateApkInternal(entry, status, pendingIntent); } else { @@ -359,9 +357,9 @@ public final class AppUpdateStatusManager { /** * @param pendingIntent Action when notification is clicked. Can be null for default action(s) */ - public void updateApk(String key, @NonNull Status status, @Nullable PendingIntent pendingIntent) { + public void updateApk(String canonicalUrl, @NonNull Status status, @Nullable PendingIntent pendingIntent) { synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(key); + AppUpdateStatus entry = appMapping.get(canonicalUrl); if (entry != null) { updateApkInternal(entry, status, pendingIntent); } @@ -369,9 +367,9 @@ public final class AppUpdateStatusManager { } @Nullable - public Apk getApk(String key) { + public Apk getApk(String canonicalUrl) { synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(key); + AppUpdateStatus entry = appMapping.get(canonicalUrl); if (entry != null) { return entry.apk; } @@ -382,13 +380,13 @@ public final class AppUpdateStatusManager { /** * Remove an APK from being tracked, since it is now considered {@link Status#Installed} * - * @param key the unique ID for the install process, also called {@code urlString} + * @param canonicalUrl the unique ID for the install process * @see org.fdroid.fdroid.installer.InstallManagerService */ - public void removeApk(String key) { + public void removeApk(String canonicalUrl) { synchronized (appMapping) { - InstallManagerService.removePendingInstall(context, key); - AppUpdateStatus entry = appMapping.remove(key); + InstallManagerService.removePendingInstall(context, canonicalUrl); + AppUpdateStatus entry = appMapping.remove(canonicalUrl); if (entry != null) { Utils.debugLog(LOGTAG, "Remove APK " + entry.apk.apkName); notifyRemove(entry); @@ -396,9 +394,9 @@ public final class AppUpdateStatusManager { } } - public void refreshApk(String key) { + public void refreshApk(String canonicalUrl) { synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(key); + AppUpdateStatus entry = appMapping.get(canonicalUrl); if (entry != null) { Utils.debugLog(LOGTAG, "Refresh APK " + entry.apk.apkName); notifyChange(entry, true); @@ -406,9 +404,9 @@ public final class AppUpdateStatusManager { } } - public void updateApkProgress(String key, long max, long current) { + public void updateApkProgress(String canonicalUrl, long max, long current) { synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(key); + AppUpdateStatus entry = appMapping.get(canonicalUrl); if (entry != null) { entry.progressMax = max; entry.progressCurrent = current; @@ -420,22 +418,22 @@ public final class AppUpdateStatusManager { /** * @param errorText If null, then it is likely because the user cancelled the download. */ - public void setDownloadError(String url, @Nullable String errorText) { + public void setDownloadError(String canonicalUrl, @Nullable String errorText) { synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(url); + AppUpdateStatus entry = appMapping.get(canonicalUrl); if (entry != null) { entry.status = Status.DownloadInterrupted; entry.errorText = errorText; entry.intent = null; notifyChange(entry, true); - removeApk(url); + removeApk(canonicalUrl); } } } public void setApkError(Apk apk, String errorText) { synchronized (appMapping) { - AppUpdateStatus entry = appMapping.get(apk.getUrl()); + AppUpdateStatus entry = appMapping.get(apk.getCanonicalUrl()); if (entry == null) { entry = createAppEntry(apk, Status.InstallError, null); } @@ -444,7 +442,7 @@ public final class AppUpdateStatusManager { entry.intent = getAppErrorIntent(entry); notifyChange(entry, false); - InstallManagerService.removePendingInstall(context, entry.getUniqueKey()); + InstallManagerService.removePendingInstall(context, entry.getCanonicalUrl()); } } diff --git a/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java b/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java index d4ad76cc1..3a267144a 100644 --- a/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java +++ b/app/src/main/java/org/fdroid/fdroid/IndexUpdater.java @@ -216,7 +216,7 @@ public class IndexUpdater { JarFile jarFile = new JarFile(downloadedFile, true); JarEntry indexEntry = (JarEntry) jarFile.getEntry(IndexUpdater.DATA_FILE_NAME); indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry), - processIndexListener, repo.address, (int) indexEntry.getSize()); + processIndexListener, (int) indexEntry.getSize()); // Process the index... SAXParserFactory factory = SAXParserFactory.newInstance(); @@ -254,14 +254,14 @@ public class IndexUpdater { protected final ProgressListener downloadListener = new ProgressListener() { @Override - public void onProgress(String urlString, long bytesRead, long totalBytes) { + public void onProgress(long bytesRead, long totalBytes) { UpdateService.reportDownloadProgress(context, IndexUpdater.this, bytesRead, totalBytes); } }; protected final ProgressListener processIndexListener = new ProgressListener() { @Override - public void onProgress(String urlString, long bytesRead, long totalBytes) { + public void onProgress(long bytesRead, long totalBytes) { UpdateService.reportProcessIndexProgress(context, IndexUpdater.this, bytesRead, totalBytes); } }; diff --git a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java index 518b23016..44405fb43 100644 --- a/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java +++ b/app/src/main/java/org/fdroid/fdroid/IndexV1Updater.java @@ -198,7 +198,7 @@ public class IndexV1Updater extends IndexUpdater { JarFile jarFile = new JarFile(outputFile, true); JarEntry indexEntry = (JarEntry) jarFile.getEntry(DATA_FILE_NAME); InputStream indexInputStream = new ProgressBufferedInputStream(jarFile.getInputStream(indexEntry), - processIndexListener, repo.address, (int) indexEntry.getSize()); + processIndexListener, (int) indexEntry.getSize()); processIndexV1(indexInputStream, indexEntry, cacheTag); } diff --git a/app/src/main/java/org/fdroid/fdroid/NotificationBroadcastReceiver.java b/app/src/main/java/org/fdroid/fdroid/NotificationBroadcastReceiver.java index d562a0484..5f041ae57 100644 --- a/app/src/main/java/org/fdroid/fdroid/NotificationBroadcastReceiver.java +++ b/app/src/main/java/org/fdroid/fdroid/NotificationBroadcastReceiver.java @@ -14,7 +14,7 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { AppUpdateStatusManager manager = AppUpdateStatusManager.getInstance(context); - String notificationKey = intent.getStringExtra(NotificationHelper.EXTRA_NOTIFICATION_KEY); + String canonicalUrl = intent.getStringExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL); switch (intent.getAction()) { case NotificationHelper.BROADCAST_NOTIFICATIONS_ALL_UPDATES_CLEARED: manager.clearAllUpdates(); @@ -25,13 +25,13 @@ public class NotificationBroadcastReceiver extends BroadcastReceiver { case NotificationHelper.BROADCAST_NOTIFICATIONS_UPDATE_CLEARED: // If clearing apps in state "InstallError" (like when auto-cancelling) we // remove them from the status manager entirely. - AppUpdateStatusManager.AppUpdateStatus appUpdateStatus = manager.get(notificationKey); + AppUpdateStatusManager.AppUpdateStatus appUpdateStatus = manager.get(canonicalUrl); if (appUpdateStatus != null && appUpdateStatus.status == AppUpdateStatusManager.Status.InstallError) { - manager.removeApk(notificationKey); + manager.removeApk(canonicalUrl); } break; case NotificationHelper.BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED: - manager.removeApk(notificationKey); + manager.removeApk(canonicalUrl); break; } } diff --git a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java index a7639ecc9..d9c1c7377 100644 --- a/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java +++ b/app/src/main/java/org/fdroid/fdroid/NotificationHelper.java @@ -46,13 +46,6 @@ class NotificationHelper { private static final int MAX_UPDATES_TO_SHOW = 5; private static final int MAX_INSTALLED_TO_SHOW = 10; - /** - * Unique ID used to represent this specific package's install process, - * including {@link Notification}s, also known as {@code urlString}. - * - * @see org.fdroid.fdroid.installer.InstallManagerService - */ - static final String EXTRA_NOTIFICATION_KEY = "key"; private static final String GROUP_UPDATES = "updates"; private static final String GROUP_INSTALLED = "installed"; @@ -93,14 +86,14 @@ class NotificationHelper { case AppUpdateStatusManager.BROADCAST_APPSTATUS_ADDED: updateStatusLists(); createSummaryNotifications(); - url = intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL); + url = intent.getStringExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL); entry = appUpdateStatusManager.get(url); if (entry != null) { createNotification(entry); } break; case AppUpdateStatusManager.BROADCAST_APPSTATUS_CHANGED: - url = intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL); + url = intent.getStringExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL); entry = appUpdateStatusManager.get(url); updateStatusLists(); if (entry != null) { @@ -111,7 +104,7 @@ class NotificationHelper { } break; case AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED: - url = intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL); + url = intent.getStringExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL); notificationManager.cancel(url, NOTIFY_ID_INSTALLED); notificationManager.cancel(url, NOTIFY_ID_UPDATES); updateStatusLists(); @@ -164,8 +157,8 @@ class NotificationHelper { private void createNotification(AppUpdateStatusManager.AppUpdateStatus entry) { if (shouldIgnoreEntry(entry)) { - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_UPDATES); - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_INSTALLED); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_UPDATES); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_INSTALLED); return; } @@ -177,23 +170,23 @@ class NotificationHelper { if (entry.status == AppUpdateStatusManager.Status.Installed) { if (useStackedNotifications()) { notification = createInstalledNotification(entry); - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_UPDATES); - notificationManager.notify(entry.getUniqueKey(), NOTIFY_ID_INSTALLED, notification); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_UPDATES); + notificationManager.notify(entry.getCanonicalUrl(), NOTIFY_ID_INSTALLED, notification); } else if (installed.size() == 1) { notification = createInstalledNotification(entry); - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_UPDATES); - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_INSTALLED); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_UPDATES); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_INSTALLED); notificationManager.notify(GROUP_INSTALLED, NOTIFY_ID_INSTALLED, notification); } } else { if (useStackedNotifications()) { notification = createUpdateNotification(entry); - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_INSTALLED); - notificationManager.notify(entry.getUniqueKey(), NOTIFY_ID_UPDATES, notification); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_INSTALLED); + notificationManager.notify(entry.getCanonicalUrl(), NOTIFY_ID_UPDATES, notification); } else if (updates.size() == 1) { notification = createUpdateNotification(entry); - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_UPDATES); - notificationManager.cancel(entry.getUniqueKey(), NOTIFY_ID_INSTALLED); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_UPDATES); + notificationManager.cancel(entry.getCanonicalUrl(), NOTIFY_ID_INSTALLED); notificationManager.notify(GROUP_UPDATES, NOTIFY_ID_UPDATES, notification); } } @@ -346,7 +339,7 @@ class NotificationHelper { } Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_UPDATE_CLEARED); - intentDeleted.putExtra(EXTRA_NOTIFICATION_KEY, entry.getUniqueKey()); + intentDeleted.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, entry.getCanonicalUrl()); intentDeleted.setClass(context, NotificationBroadcastReceiver.class); PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, PendingIntent.FLAG_UPDATE_CURRENT); builder.setDeleteIntent(piDeleted); @@ -435,7 +428,7 @@ class NotificationHelper { } Intent intentDeleted = new Intent(BROADCAST_NOTIFICATIONS_INSTALLED_CLEARED); - intentDeleted.putExtra(EXTRA_NOTIFICATION_KEY, entry.getUniqueKey()); + intentDeleted.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, entry.getCanonicalUrl()); intentDeleted.setClass(context, NotificationBroadcastReceiver.class); PendingIntent piDeleted = PendingIntent.getBroadcast(context, 0, intentDeleted, PendingIntent.FLAG_UPDATE_CURRENT); builder.setDeleteIntent(piDeleted); @@ -540,7 +533,7 @@ class NotificationHelper { public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { // Need to check that the notification is still valid, and also that the image // is indeed cached now, so we won't get stuck in an endless loop. - AppUpdateStatusManager.AppUpdateStatus oldEntry = appUpdateStatusManager.get(entry.getUniqueKey()); + AppUpdateStatusManager.AppUpdateStatus oldEntry = appUpdateStatusManager.get(entry.getCanonicalUrl()); if (oldEntry != null && oldEntry.app != null && oldEntry.app.iconUrl != null diff --git a/app/src/main/java/org/fdroid/fdroid/ProgressBufferedInputStream.java b/app/src/main/java/org/fdroid/fdroid/ProgressBufferedInputStream.java index 1340877fd..25aad6dc9 100644 --- a/app/src/main/java/org/fdroid/fdroid/ProgressBufferedInputStream.java +++ b/app/src/main/java/org/fdroid/fdroid/ProgressBufferedInputStream.java @@ -9,7 +9,6 @@ import java.io.InputStream; class ProgressBufferedInputStream extends BufferedInputStream { private final ProgressListener progressListener; - private final String urlString; private final int totalBytes; private int currentBytes; @@ -18,10 +17,9 @@ class ProgressBufferedInputStream extends BufferedInputStream { * Reports progress to the specified {@link ProgressListener}, with the * progress based on the {@code totalBytes}. */ - ProgressBufferedInputStream(InputStream in, ProgressListener progressListener, String urlString, int totalBytes) { + ProgressBufferedInputStream(InputStream in, ProgressListener progressListener, int totalBytes) { super(in); this.progressListener = progressListener; - this.urlString = urlString; this.totalBytes = totalBytes; } @@ -33,7 +31,7 @@ class ProgressBufferedInputStream extends BufferedInputStream { * the digits changing because it looks pretty, < 9000 since the reads won't * line up exactly */ if (currentBytes % 333333 < 9000) { - progressListener.onProgress(urlString, currentBytes, totalBytes); + progressListener.onProgress(currentBytes, totalBytes); } } return super.read(buffer, byteOffset, byteCount); diff --git a/app/src/main/java/org/fdroid/fdroid/ProgressListener.java b/app/src/main/java/org/fdroid/fdroid/ProgressListener.java index dc9fbcbdb..e45cb7356 100644 --- a/app/src/main/java/org/fdroid/fdroid/ProgressListener.java +++ b/app/src/main/java/org/fdroid/fdroid/ProgressListener.java @@ -19,6 +19,6 @@ import java.net.URL; */ public interface ProgressListener { - void onProgress(String urlString, long bytesRead, long totalBytes); + void onProgress(long bytesRead, long totalBytes); } diff --git a/app/src/main/java/org/fdroid/fdroid/data/Apk.java b/app/src/main/java/org/fdroid/fdroid/data/Apk.java index 0cdae110c..b70506203 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/Apk.java +++ b/app/src/main/java/org/fdroid/fdroid/data/Apk.java @@ -263,8 +263,17 @@ public class Apk extends ValueObject implements Comparable, Parcelable { } } + /** + * Get the URL that points to the canonical download source for this + * package. This is also used as the unique ID for tracking downloading, + * progress, and notifications throughout the whole install process. It + * is guaranteed to uniquely represent this file since it points to a file + * on the file system of the canonical webserver. + * + * @see org.fdroid.fdroid.installer.InstallManagerService + */ @JsonIgnore // prevent tests from failing due to nulls in checkRepoAddress() - public String getUrl() { + public String getCanonicalUrl() { checkRepoAddress(); return repoAddress + "/" + apkName.replace(" ", "%20"); } @@ -527,7 +536,7 @@ public class Apk extends ValueObject implements Comparable, Parcelable { public File getMediaInstallPath(Context context) { File path = Environment.getExternalStoragePublicDirectory( Environment.DIRECTORY_DOWNLOADS); // Default for all other non-apk/media files - String fileExtension = MimeTypeMap.getFileExtensionFromUrl(this.getUrl()); + String fileExtension = MimeTypeMap.getFileExtensionFromUrl(this.getCanonicalUrl()); if (TextUtils.isEmpty(fileExtension)) return path; MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); String[] mimeType = mimeTypeMap.getMimeTypeFromExtension(fileExtension).split("/"); diff --git a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java index 9915bf9e1..e827edca7 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java @@ -237,7 +237,7 @@ public class InstalledAppProviderService extends JobIntentService { PackageInfo packageInfo = getPackageInfo(intent, packageName); if (packageInfo != null) { for (AppUpdateStatusManager.AppUpdateStatus status : ausm.getByPackageName(packageName)) { - ausm.updateApk(status.getUniqueKey(), AppUpdateStatusManager.Status.Installed, null); + ausm.updateApk(status.getCanonicalUrl(), AppUpdateStatusManager.Status.Installed, null); } File apk = getPathToInstalledApk(packageInfo); if (apk == null) { @@ -258,7 +258,7 @@ public class InstalledAppProviderService extends JobIntentService { } else if (ACTION_DELETE.equals(action)) { deleteAppFromDb(this, packageName); for (AppUpdateStatusManager.AppUpdateStatus status : ausm.getByPackageName(packageName)) { - ausm.updateApk(status.getUniqueKey(), AppUpdateStatusManager.Status.InstallError, null); + ausm.updateApk(status.getCanonicalUrl(), AppUpdateStatusManager.Status.InstallError, null); } } packageChangeNotifier.onNext(packageName); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java b/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java index c1c15d117..747fcd7c9 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/ApkCache.java @@ -113,10 +113,16 @@ public class ApkCache { } /** - * Get the full path for where an APK URL will be downloaded into. + * Get the full path for where an package URL will be downloaded into. */ public static SanitizedFile getApkDownloadPath(Context context, String urlString) { - Uri uri = Uri.parse(urlString); + return getApkDownloadPath(context, Uri.parse(urlString)); + } + + /** + * Get the full path for where an package URL will be downloaded into. + */ + public static SanitizedFile getApkDownloadPath(Context context, Uri uri) { File dir = new File(getApkCacheDir(context), uri.getHost() + "-" + uri.getPort()); if (!dir.exists()) { dir.mkdirs(); diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java index 70c23e054..9a164e703 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstaller.java @@ -43,11 +43,11 @@ public class DefaultInstaller extends Installer { } @Override - protected void installPackageInternal(Uri localApkUri, Uri downloadUri) { + protected void installPackageInternal(Uri localApkUri, Uri canonicalUri) { Intent installIntent = new Intent(context, DefaultInstallerActivity.class); installIntent.setAction(DefaultInstallerActivity.ACTION_INSTALL_PACKAGE); - installIntent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri); + installIntent.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, canonicalUri); installIntent.putExtra(Installer.EXTRA_APK, apk); installIntent.setData(localApkUri); @@ -57,7 +57,7 @@ public class DefaultInstaller extends Installer { installIntent, PendingIntent.FLAG_UPDATE_CURRENT); - sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_USER_INTERACTION, + sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java index 322350c29..60367f4bb 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/DefaultInstallerActivity.java @@ -48,7 +48,10 @@ public class DefaultInstallerActivity extends FragmentActivity { private static final int REQUEST_CODE_INSTALL = 0; private static final int REQUEST_CODE_UNINSTALL = 1; - private Uri downloadUri; + /** + * @see InstallManagerService + */ + private Uri canonicalUri; // for the broadcasts private DefaultInstaller installer; @@ -63,7 +66,7 @@ public class DefaultInstallerActivity extends FragmentActivity { installer = new DefaultInstaller(this, apk); if (ACTION_INSTALL_PACKAGE.equals(action)) { Uri localApkUri = intent.getData(); - downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); + canonicalUri = intent.getParcelableExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL); installPackage(localApkUri); } else if (ACTION_UNINSTALL_PACKAGE.equals(action)) { uninstallPackage(apk.packageName); @@ -120,7 +123,7 @@ public class DefaultInstallerActivity extends FragmentActivity { startActivityForResult(intent, REQUEST_CODE_INSTALL); } catch (ActivityNotFoundException e) { Log.e(TAG, "ActivityNotFoundException", e); - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED, "This Android rom does not support ACTION_INSTALL_PACKAGE!"); finish(); } @@ -169,23 +172,23 @@ public class DefaultInstallerActivity extends FragmentActivity { * never executed on Androids < 4.0 */ if (Build.VERSION.SDK_INT < 14) { - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE); + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_COMPLETE); break; } switch (resultCode) { case Activity.RESULT_OK: - installer.sendBroadcastInstall(downloadUri, + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_COMPLETE); break; case Activity.RESULT_CANCELED: - installer.sendBroadcastInstall(downloadUri, + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED); break; case Activity.RESULT_FIRST_USER: default: // AOSP returns Activity.RESULT_FIRST_USER on error - installer.sendBroadcastInstall(downloadUri, + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED, getString(R.string.install_error_unknown)); break; diff --git a/app/src/main/java/org/fdroid/fdroid/installer/FileInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/FileInstaller.java index 01bf38d3d..7a2f5cd41 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/FileInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/FileInstaller.java @@ -23,7 +23,6 @@ import android.app.PendingIntent; import android.content.Context; import android.content.Intent; import android.net.Uri; - import android.support.annotation.NonNull; import org.fdroid.fdroid.data.Apk; @@ -44,15 +43,15 @@ public class FileInstaller extends Installer { } @Override - public void installPackage(Uri localApkUri, Uri downloadUri) { - installPackageInternal(localApkUri, downloadUri); + public void installPackage(Uri localApkUri, Uri canonicalUri) { + installPackageInternal(localApkUri, canonicalUri); } @Override - protected void installPackageInternal(Uri localApkUri, Uri downloadUri) { + protected void installPackageInternal(Uri localApkUri, Uri canonicalUri) { Intent installIntent = new Intent(context, FileInstallerActivity.class); installIntent.setAction(FileInstallerActivity.ACTION_INSTALL_FILE); - installIntent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri); + installIntent.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, canonicalUri); installIntent.putExtra(Installer.EXTRA_APK, apk); installIntent.setData(localApkUri); @@ -62,7 +61,7 @@ public class FileInstaller extends Installer { installIntent, PendingIntent.FLAG_UPDATE_CURRENT); - sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_USER_INTERACTION, + sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_USER_INTERACTION, installPendingIntent); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java b/app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java index 9bc3db7b8..42a4cdbd6 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/FileInstallerActivity.java @@ -13,7 +13,6 @@ import android.support.v4.content.ContextCompat; import android.support.v7.app.AlertDialog; import android.view.ContextThemeWrapper; import android.widget.Toast; - import org.apache.commons.io.FileUtils; import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.R; @@ -40,7 +39,10 @@ public class FileInstallerActivity extends FragmentActivity { private Apk apk; private Uri localApkUri; - private Uri downloadUri; + /** + * @see InstallManagerService + */ + private Uri canonicalUri; private int act = 0; @@ -51,12 +53,12 @@ public class FileInstallerActivity extends FragmentActivity { Intent intent = getIntent(); String action = intent.getAction(); localApkUri = intent.getData(); - downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); + canonicalUri = intent.getParcelableExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL); apk = intent.getParcelableExtra(Installer.EXTRA_APK); installer = new FileInstaller(this, apk); if (ACTION_INSTALL_FILE.equals(action)) { if (hasStoragePermission()) { - installPackage(localApkUri, downloadUri, apk); + installPackage(localApkUri, canonicalUri, apk); } else { requestPermission(); act = 1; @@ -110,7 +112,7 @@ public class FileInstallerActivity extends FragmentActivity { .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { if (act == 1) { - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED); } else if (act == 2) { installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED); } @@ -129,13 +131,13 @@ public class FileInstallerActivity extends FragmentActivity { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { if (act == 1) { - installPackage(localApkUri, downloadUri, apk); + installPackage(localApkUri, canonicalUri, apk); } else if (act == 2) { uninstallPackage(apk); } } else { if (act == 1) { - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED); } else if (act == 2) { installer.sendBroadcastUninstall(Installer.ACTION_UNINSTALL_INTERRUPTED); } @@ -144,7 +146,7 @@ public class FileInstallerActivity extends FragmentActivity { } } - private void installPackage(Uri localApkUri, Uri downloadUri, Apk apk) { + private void installPackage(Uri localApkUri, Uri canonicalUri, Apk apk) { Utils.debugLog(TAG, "Installing: " + localApkUri.getPath()); File path = apk.getMediaInstallPath(activity.getApplicationContext()); path.mkdirs(); @@ -152,15 +154,15 @@ public class FileInstallerActivity extends FragmentActivity { FileUtils.copyFileToDirectory(new File(localApkUri.getPath()), path); } catch (IOException e) { Utils.debugLog(TAG, "Failed to copy: " + e.getMessage()); - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED); } if (apk.isMediaInstalled(activity.getApplicationContext())) { // Copying worked Utils.debugLog(TAG, "Copying worked: " + localApkUri.getPath()); Toast.makeText(this, String.format(this.getString(R.string.app_installed_media), path.toString()), Toast.LENGTH_LONG).show(); - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_COMPLETE); + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_COMPLETE); } else { - installer.sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED); + installer.sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED); } finish(); } 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 3ba2a632a..03b863010 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallManagerService.java @@ -11,7 +11,6 @@ import android.content.pm.PackageInfo; import android.net.Uri; import android.os.IBinder; import android.support.annotation.NonNull; -import android.support.annotation.Nullable; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; @@ -66,9 +65,10 @@ import java.io.IOException; * APK on different servers, signed by different keys, or even different builds. *

* The implementations of {@link Uri#toString()} and {@link Intent#getDataString()} both * include caching of the generated {@code String}, so it should be plenty fast. @@ -147,24 +147,25 @@ public class InstallManagerService extends Service { public int onStartCommand(Intent intent, int flags, int startId) { Utils.debugLog(TAG, "onStartCommand " + intent); - String urlString = intent.getDataString(); - if (TextUtils.isEmpty(urlString)) { - Utils.debugLog(TAG, "empty urlString, nothing to do"); + String canonicalUrl = intent.getDataString(); + if (TextUtils.isEmpty(canonicalUrl)) { + Utils.debugLog(TAG, "empty canonicalUrl, nothing to do"); return START_NOT_STICKY; } String action = intent.getAction(); if (ACTION_CANCEL.equals(action)) { - DownloaderService.cancel(this, urlString); - Apk apk = appUpdateStatusManager.getApk(urlString); + DownloaderService.cancel(this, canonicalUrl); + Apk apk = appUpdateStatusManager.getApk(canonicalUrl); if (apk != null) { + Utils.debugLog(TAG, "also canceling OBB downloads"); DownloaderService.cancel(this, apk.getPatchObbUrl()); DownloaderService.cancel(this, apk.getMainObbUrl()); } return START_NOT_STICKY; } else if (ACTION_INSTALL.equals(action)) { - if (!isPendingInstall(urlString)) { + if (!isPendingInstall(canonicalUrl)) { Log.i(TAG, "Ignoring INSTALL that is not Pending Install: " + intent); return START_NOT_STICKY; } @@ -174,14 +175,14 @@ public class InstallManagerService extends Service { } if (!intent.hasExtra(EXTRA_APP) || !intent.hasExtra(EXTRA_APK)) { - Utils.debugLog(TAG, urlString + " did not include both an App and Apk instance, ignoring"); + Utils.debugLog(TAG, canonicalUrl + " did not include both an App and Apk instance, ignoring"); return START_NOT_STICKY; } if ((flags & START_FLAG_REDELIVERY) == START_FLAG_REDELIVERY - && !DownloaderService.isQueuedOrActive(urlString)) { - Utils.debugLog(TAG, urlString + " finished downloading while InstallManagerService was killed."); - appUpdateStatusManager.removeApk(urlString); + && !DownloaderService.isQueuedOrActive(canonicalUrl)) { + Utils.debugLog(TAG, canonicalUrl + " finished downloading while InstallManagerService was killed."); + appUpdateStatusManager.removeApk(canonicalUrl); return START_NOT_STICKY; } @@ -205,23 +206,23 @@ public class InstallManagerService extends Service { appUpdateStatusManager.addApk(apk, AppUpdateStatusManager.Status.Downloading, null); - registerPackageDownloaderReceivers(urlString); - getObb(urlString, apk.getMainObbUrl(), apk.getMainObbFile(), apk.obbMainFileSha256); - getObb(urlString, apk.getPatchObbUrl(), apk.getPatchObbFile(), apk.obbPatchFileSha256); + registerPackageDownloaderReceivers(canonicalUrl); + getMainObb(canonicalUrl, apk); + getPatchObb(canonicalUrl, apk); - File apkFilePath = ApkCache.getApkDownloadPath(this, apk.getUrl()); + File apkFilePath = ApkCache.getApkDownloadPath(this, apk.getCanonicalUrl()); long apkFileSize = apkFilePath.length(); if (!apkFilePath.exists() || apkFileSize < apk.size) { - Utils.debugLog(TAG, "download " + urlString + " " + apkFilePath); - DownloaderService.queue(this, switchUrlToNewMirror(urlString, apk.repoId), apk.repoId, urlString); + Utils.debugLog(TAG, "download " + canonicalUrl + " " + apkFilePath); + DownloaderService.queueUsingRandomMirror(this, apk.repoId, canonicalUrl); } else if (ApkCache.apkIsCached(apkFilePath, apk)) { - Utils.debugLog(TAG, "skip download, we have it, straight to install " + urlString + " " + apkFilePath); + Utils.debugLog(TAG, "skip download, we have it, straight to install " + canonicalUrl + " " + apkFilePath); sendBroadcast(intent.getData(), Downloader.ACTION_STARTED, apkFilePath); sendBroadcast(intent.getData(), Downloader.ACTION_COMPLETE, apkFilePath); } else { - Utils.debugLog(TAG, "delete and download again " + urlString + " " + apkFilePath); + Utils.debugLog(TAG, "delete and download again " + canonicalUrl + " " + apkFilePath); apkFilePath.delete(); - DownloaderService.queue(this, switchUrlToNewMirror(urlString, apk.repoId), apk.repoId, urlString); + DownloaderService.queueUsingRandomMirror(this, apk.repoId, canonicalUrl); } return START_REDELIVER_INTENT; // if killed before completion, retry Intent @@ -234,22 +235,12 @@ public class InstallManagerService extends Service { localBroadcastManager.sendBroadcast(intent); } - /** - * Tries to return a version of {@code urlString} from a mirror, if there - * is an error, it just returns {@code urlString}. - * - * @see FDroidApp#getNewMirrorOnError(String, org.fdroid.fdroid.data.Repo) - */ - public String getNewMirrorOnError(@Nullable String urlString, long repoId) { - try { - return FDroidApp.getNewMirrorOnError(urlString, RepoProvider.Helper.findById(this, repoId)); - } catch (IOException e) { - return urlString; - } + private void getMainObb(final String canonicalUrl, Apk apk) { + getObb(canonicalUrl, apk.getMainObbUrl(), apk.getMainObbFile(), apk.obbMainFileSha256, apk.repoId); } - public String switchUrlToNewMirror(@Nullable String urlString, long repoId) { - return FDroidApp.switchUrlToNewMirror(urlString, RepoProvider.Helper.findById(this, repoId)); + private void getPatchObb(final String canonicalUrl, Apk apk) { + getObb(canonicalUrl, apk.getPatchObbUrl(), apk.getPatchObbFile(), apk.obbPatchFileSha256, apk.repoId); } /** @@ -259,8 +250,8 @@ public class InstallManagerService extends Service { * * @see APK Expansion Files */ - private void getObb(final String urlString, String obbUrlString, - final File obbDestFile, final String hash) { + private void getObb(final String canonicalUrl, String obbUrlString, + final File obbDestFile, final String hash, final long repoId) { if (obbDestFile == null || obbDestFile.exists() || TextUtils.isEmpty(obbUrlString)) { return; } @@ -278,7 +269,7 @@ public class InstallManagerService extends Service { long bytesRead = intent.getLongExtra(Downloader.EXTRA_BYTES_READ, 0); long totalBytes = intent.getLongExtra(Downloader.EXTRA_TOTAL_BYTES, 0); - appUpdateStatusManager.updateApkProgress(urlString, totalBytes, bytesRead); + appUpdateStatusManager.updateApkProgress(canonicalUrl, totalBytes, bytesRead); } else if (Downloader.ACTION_COMPLETE.equals(action)) { localBroadcastManager.unregisterReceiver(this); File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH)); @@ -310,22 +301,22 @@ public class InstallManagerService extends Service { } else if (Downloader.ACTION_INTERRUPTED.equals(action)) { localBroadcastManager.unregisterReceiver(this); } else if (Downloader.ACTION_CONNECTION_FAILED.equals(action)) { - DownloaderService.queue(context, getNewMirrorOnError(urlString, 0), 0, urlString); + DownloaderService.queueUsingDifferentMirror(context, repoId, canonicalUrl); } else { throw new RuntimeException("intent action not handled!"); } } }; - DownloaderService.queue(this, switchUrlToNewMirror(obbUrlString, 0), 0, obbUrlString); + DownloaderService.queueUsingRandomMirror(this, repoId, obbUrlString); localBroadcastManager.registerReceiver(downloadReceiver, DownloaderService.getIntentFilter(obbUrlString)); } /** * Register a {@link BroadcastReceiver} for tracking download progress for a - * give {@code urlString}. There can be multiple of these registered at a time. + * give {@code canonicalUrl}. There can be multiple of these registered at a time. */ - private void registerPackageDownloaderReceivers(String urlString) { + private void registerPackageDownloaderReceivers(String canonicalUrl) { BroadcastReceiver downloadReceiver = new BroadcastReceiver() { @Override @@ -334,52 +325,58 @@ public class InstallManagerService extends Service { localBroadcastManager.unregisterReceiver(this); return; } - Uri downloadUri = intent.getData(); - String urlString = downloadUri.toString(); + Uri canonicalUri = intent.getData(); + String canonicalUrl = intent.getDataString(); long repoId = intent.getLongExtra(Downloader.EXTRA_REPO_ID, 0); - String mirrorUrlString = intent.getStringExtra(Downloader.EXTRA_MIRROR_URL); switch (intent.getAction()) { case Downloader.ACTION_STARTED: // App should currently be in the "PendingDownload" state, so this changes it to "Downloading". Intent intentObject = new Intent(context, InstallManagerService.class); intentObject.setAction(ACTION_CANCEL); - intentObject.setData(downloadUri); + intentObject.setData(canonicalUri); PendingIntent action = PendingIntent.getService(context, 0, intentObject, 0); - appUpdateStatusManager.updateApk(urlString, AppUpdateStatusManager.Status.Downloading, action); + appUpdateStatusManager.updateApk(canonicalUrl, + AppUpdateStatusManager.Status.Downloading, action); break; case Downloader.ACTION_PROGRESS: long bytesRead = intent.getLongExtra(Downloader.EXTRA_BYTES_READ, 0); long totalBytes = intent.getLongExtra(Downloader.EXTRA_TOTAL_BYTES, 0); - appUpdateStatusManager.updateApkProgress(urlString, totalBytes, bytesRead); + appUpdateStatusManager.updateApkProgress(canonicalUrl, totalBytes, bytesRead); break; case Downloader.ACTION_COMPLETE: File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH)); Uri localApkUri = Uri.fromFile(localFile); - Utils.debugLog(TAG, "download completed of " + mirrorUrlString + " to " + localApkUri); - appUpdateStatusManager.updateApk(urlString, AppUpdateStatusManager.Status.ReadyToInstall, null); + Utils.debugLog(TAG, "download completed of " + + intent.getStringExtra(Downloader.EXTRA_MIRROR_URL) + " to " + localApkUri); + appUpdateStatusManager.updateApk(canonicalUrl, + AppUpdateStatusManager.Status.ReadyToInstall, null); localBroadcastManager.unregisterReceiver(this); - registerInstallReceiver(downloadUri); + registerInstallReceiver(canonicalUrl); - Apk apk = appUpdateStatusManager.getApk(urlString); + Apk apk = appUpdateStatusManager.getApk(canonicalUrl); if (apk != null) { - InstallerService.install(context, localApkUri, downloadUri, apk); + InstallerService.install(context, localApkUri, canonicalUri, apk); } break; case Downloader.ACTION_INTERRUPTED: - appUpdateStatusManager.setDownloadError(urlString, intent.getStringExtra(Downloader.EXTRA_ERROR_MESSAGE)); + appUpdateStatusManager.setDownloadError(canonicalUrl, + intent.getStringExtra(Downloader.EXTRA_ERROR_MESSAGE)); localBroadcastManager.unregisterReceiver(this); break; case Downloader.ACTION_CONNECTION_FAILED: + // TODO move this logic into DownloaderService to hide the mirror URL stuff from this class try { - String currentUrlString = FDroidApp.getNewMirrorOnError(mirrorUrlString, + String currentUrlString = FDroidApp.getNewMirrorOnError( + intent.getStringExtra(Downloader.EXTRA_MIRROR_URL), RepoProvider.Helper.findById(InstallManagerService.this, repoId)); - DownloaderService.queue(context, currentUrlString, repoId, urlString); + DownloaderService.queue(context, currentUrlString, repoId, canonicalUrl); DownloaderService.setTimeout(FDroidApp.getTimeout()); } catch (IOException e) { - appUpdateStatusManager.setDownloadError(urlString, intent.getStringExtra(Downloader.EXTRA_ERROR_MESSAGE)); + appUpdateStatusManager.setDownloadError(canonicalUrl, + intent.getStringExtra(Downloader.EXTRA_ERROR_MESSAGE)); localBroadcastManager.unregisterReceiver(this); } break; @@ -390,14 +387,14 @@ public class InstallManagerService extends Service { }; localBroadcastManager.registerReceiver(downloadReceiver, - DownloaderService.getIntentFilter(urlString)); + DownloaderService.getIntentFilter(canonicalUrl)); } /** * Register a {@link BroadcastReceiver} for tracking install progress for a * give {@link Uri}. There can be multiple of these registered at a time. */ - private void registerInstallReceiver(Uri downloadUri) { + private void registerInstallReceiver(String canonicalUrl) { BroadcastReceiver installReceiver = new BroadcastReceiver() { @Override @@ -406,15 +403,17 @@ public class InstallManagerService extends Service { localBroadcastManager.unregisterReceiver(this); return; } - String downloadUrl = intent.getDataString(); + String canonicalUrl = intent.getDataString(); Apk apk; switch (intent.getAction()) { case Installer.ACTION_INSTALL_STARTED: - appUpdateStatusManager.updateApk(downloadUrl, AppUpdateStatusManager.Status.Installing, null); + appUpdateStatusManager.updateApk(canonicalUrl, + AppUpdateStatusManager.Status.Installing, null); break; case Installer.ACTION_INSTALL_COMPLETE: - appUpdateStatusManager.updateApk(downloadUrl, AppUpdateStatusManager.Status.Installed, null); - Apk apkComplete = appUpdateStatusManager.getApk(downloadUrl); + appUpdateStatusManager.updateApk(canonicalUrl, + AppUpdateStatusManager.Status.Installed, null); + Apk apkComplete = appUpdateStatusManager.getApk(canonicalUrl); if (apkComplete != null && apkComplete.isApk()) { try { @@ -432,7 +431,7 @@ public class InstallManagerService extends Service { if (!TextUtils.isEmpty(errorMessage)) { appUpdateStatusManager.setApkError(apk, errorMessage); } else { - appUpdateStatusManager.removeApk(downloadUrl); + appUpdateStatusManager.removeApk(canonicalUrl); } localBroadcastManager.unregisterReceiver(this); break; @@ -448,7 +447,7 @@ public class InstallManagerService extends Service { }; localBroadcastManager.registerReceiver(installReceiver, - Installer.getInstallIntentFilter(downloadUri)); + Installer.getInstallIntentFilter(canonicalUrl)); } /** @@ -464,23 +463,23 @@ public class InstallManagerService extends Service { * @param context this app's {@link Context} */ public static void queue(Context context, App app, @NonNull Apk apk) { - String urlString = apk.getUrl(); + String canonicalUrl = apk.getCanonicalUrl(); AppUpdateStatusManager.getInstance(context).addApk(apk, AppUpdateStatusManager.Status.PendingInstall, null); - putPendingInstall(context, urlString, apk.packageName); - Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + urlString); + putPendingInstall(context, canonicalUrl, apk.packageName); + Utils.debugLog(TAG, "queue " + app.packageName + " " + apk.versionCode + " from " + canonicalUrl); Intent intent = new Intent(context, InstallManagerService.class); intent.setAction(ACTION_INSTALL); - intent.setData(Uri.parse(urlString)); + intent.setData(Uri.parse(canonicalUrl)); intent.putExtra(EXTRA_APP, app); intent.putExtra(EXTRA_APK, apk); context.startService(intent); } - public static void cancel(Context context, String urlString) { - removePendingInstall(context, urlString); + public static void cancel(Context context, String canonicalUrl) { + removePendingInstall(context, canonicalUrl); Intent intent = new Intent(context, InstallManagerService.class); intent.setAction(ACTION_CANCEL); - intent.setData(Uri.parse(urlString)); + intent.setData(Uri.parse(canonicalUrl)); context.startService(intent); } @@ -491,29 +490,29 @@ public class InstallManagerService extends Service { * completed, or the device lost power in the middle of the install * process. */ - public boolean isPendingInstall(String urlString) { - return pendingInstalls.contains(urlString); + public boolean isPendingInstall(String canonicalUrl) { + return pendingInstalls.contains(canonicalUrl); } /** * Mark a given APK as in the process of being installed, with - * the {@code urlString} of the download used as the unique ID, + * the {@code canonicalUrl} of the download used as the unique ID, * and the file hash used to verify that things are the same. * * @see #isPendingInstall(String) */ - public static void putPendingInstall(Context context, String urlString, String packageName) { + public static void putPendingInstall(Context context, String canonicalUrl, String packageName) { if (pendingInstalls == null) { pendingInstalls = getPendingInstalls(context); } - pendingInstalls.edit().putString(urlString, packageName).apply(); + pendingInstalls.edit().putString(canonicalUrl, packageName).apply(); } - public static void removePendingInstall(Context context, String urlString) { + public static void removePendingInstall(Context context, String canonicalUrl) { if (pendingInstalls == null) { pendingInstalls = getPendingInstalls(context); } - pendingInstalls.edit().remove(urlString).apply(); + pendingInstalls.edit().remove(canonicalUrl).apply(); } private static SharedPreferences getPendingInstalls(Context context) { diff --git a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java index 40b2e1775..7c3e7e761 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/Installer.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/Installer.java @@ -64,15 +64,6 @@ public abstract class Installer { public static final String ACTION_UNINSTALL_INTERRUPTED = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_INTERRUPTED"; public static final String ACTION_UNINSTALL_USER_INTERACTION = "org.fdroid.fdroid.installer.Installer.action.UNINSTALL_USER_INTERACTION"; - /** - * The URI where the APK was originally downloaded from. This is also used - * as the unique ID representing this in the whole install process in - * {@link InstallManagerService}, there is is generally known as the - * "download URL" since it is the URL used to download the APK. - * - * @see Intent#EXTRA_ORIGINATING_URI - */ - static final String EXTRA_DOWNLOAD_URI = "org.fdroid.fdroid.installer.Installer.extra.DOWNLOAD_URI"; public static final String EXTRA_APK = "org.fdroid.fdroid.installer.Installer.extra.APK"; public static final String EXTRA_USER_INTERACTION_PI = "org.fdroid.fdroid.installer.Installer.extra.USER_INTERACTION_PI"; public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.installer.Installer.extra.ERROR_MESSAGE"; @@ -165,23 +156,23 @@ public abstract class Installer { return intent; } - void sendBroadcastInstall(Uri downloadUri, String action, PendingIntent pendingIntent) { - sendBroadcastInstall(context, downloadUri, action, apk, pendingIntent, null); + void sendBroadcastInstall(Uri canonicalUri, String action, PendingIntent pendingIntent) { + sendBroadcastInstall(context, canonicalUri, action, apk, pendingIntent, null); } - void sendBroadcastInstall(Uri downloadUri, String action) { - sendBroadcastInstall(context, downloadUri, action, apk, null, null); + void sendBroadcastInstall(Uri canonicalUri, String action) { + sendBroadcastInstall(context, canonicalUri, action, apk, null, null); } - void sendBroadcastInstall(Uri downloadUri, String action, String errorMessage) { - sendBroadcastInstall(context, downloadUri, action, apk, null, errorMessage); + void sendBroadcastInstall(Uri canonicalUri, String action, String errorMessage) { + sendBroadcastInstall(context, canonicalUri, action, apk, null, errorMessage); } static void sendBroadcastInstall(Context context, - Uri downloadUri, String action, Apk apk, + Uri canonicalUri, String action, Apk apk, PendingIntent pendingIntent, String errorMessage) { Intent intent = new Intent(action); - intent.setData(downloadUri); + intent.setData(canonicalUri); intent.putExtra(Installer.EXTRA_USER_INTERACTION_PI, pendingIntent); intent.putExtra(Installer.EXTRA_APK, apk); if (!TextUtils.isEmpty(errorMessage)) { @@ -226,20 +217,34 @@ public abstract class Installer { /** * Gets an {@link IntentFilter} for matching events from the install - * process based on the original download URL as a {@link Uri}. + * process based on {@code canonicalUri}, which is the global unique + * ID for a package going through the install process. + * + * @see InstallManagerService for more about {@code canonicalUri} */ - public static IntentFilter getInstallIntentFilter(Uri uri) { + public static IntentFilter getInstallIntentFilter(Uri canonicalUri) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Installer.ACTION_INSTALL_STARTED); intentFilter.addAction(Installer.ACTION_INSTALL_COMPLETE); intentFilter.addAction(Installer.ACTION_INSTALL_INTERRUPTED); intentFilter.addAction(Installer.ACTION_INSTALL_USER_INTERACTION); - intentFilter.addDataScheme(uri.getScheme()); - intentFilter.addDataAuthority(uri.getHost(), String.valueOf(uri.getPort())); - intentFilter.addDataPath(uri.getPath(), PatternMatcher.PATTERN_LITERAL); + intentFilter.addDataScheme(canonicalUri.getScheme()); + intentFilter.addDataAuthority(canonicalUri.getHost(), String.valueOf(canonicalUri.getPort())); + intentFilter.addDataPath(canonicalUri.getPath(), PatternMatcher.PATTERN_LITERAL); return intentFilter; } + /** + * Gets an {@link IntentFilter} for matching events from the install + * process based on {@code canonicalUrl}, which is the global unique + * ID for a package going through the install process. + * + * @see InstallManagerService for more about {@code canonicalUrl} + */ + public static IntentFilter getInstallIntentFilter(String canonicalUrl) { + return getInstallIntentFilter(Uri.parse(canonicalUrl)); + } + public static IntentFilter getUninstallIntentFilter(String packageName) { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Installer.ACTION_UNINSTALL_STARTED); @@ -262,20 +267,20 @@ public abstract class Installer { * is prompted with the system installer dialog, which shows all the * permissions that the APK is requesting. * - * @param localApkUri points to the local copy of the APK to be installed - * @param downloadUri serves as the unique ID for all actions related to the - * installation of that specific APK + * @param localApkUri points to the local copy of the APK to be installed + * @param canonicalUri serves as the unique ID for all actions related to the + * installation of that specific APK * @see InstallManagerService * @see ACTION_INSTALL_PACKAGE Fails For Any Possible Uri */ - public void installPackage(Uri localApkUri, Uri downloadUri) { + public void installPackage(Uri localApkUri, Uri canonicalUri) { Uri sanitizedUri; try { sanitizedUri = ApkFileProvider.getSafeUri(context, localApkUri, apk); } catch (IOException e) { Utils.debugLog(TAG, e.getMessage(), e); - sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage()); + sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage()); return; } @@ -285,7 +290,7 @@ public abstract class Installer { apkVerifier.verifyApk(); } catch (ApkVerifier.ApkVerificationException e) { Utils.debugLog(TAG, e.getMessage(), e); - sendBroadcastInstall(downloadUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage()); + sendBroadcastInstall(canonicalUri, Installer.ACTION_INSTALL_INTERRUPTED, e.getMessage()); return; } catch (ApkVerifier.ApkPermissionUnequalException e) { // if permissions of apk are not the ones listed in the repo @@ -295,15 +300,15 @@ public abstract class Installer { Utils.debugLog(TAG, e.getMessage(), e); Utils.debugLog(TAG, "Falling back to AOSP DefaultInstaller!"); DefaultInstaller defaultInstaller = new DefaultInstaller(context, apk); - defaultInstaller.installPackageInternal(sanitizedUri, downloadUri); + defaultInstaller.installPackageInternal(sanitizedUri, canonicalUri); return; } } - installPackageInternal(sanitizedUri, downloadUri); + installPackageInternal(sanitizedUri, canonicalUri); } - protected abstract void installPackageInternal(Uri localApkUri, Uri downloadUri); + protected abstract void installPackageInternal(Uri localApkUri, Uri canonicalUri); /** * Uninstall app as defined by {@link Installer#apk} in diff --git a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java index 496d46d5d..ac927d392 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/InstallerService.java @@ -28,10 +28,10 @@ import android.support.annotation.NonNull; import android.support.v4.app.JobIntentService; import org.apache.commons.io.FileUtils; import org.apache.commons.io.filefilter.WildcardFileFilter; -import org.fdroid.fdroid.views.AppDetailsActivity; import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.App; +import org.fdroid.fdroid.views.AppDetailsActivity; import java.io.File; import java.io.FileFilter; @@ -74,8 +74,8 @@ public class InstallerService extends JobIntentService { if (ACTION_INSTALL.equals(intent.getAction())) { Uri uri = intent.getData(); - Uri downloadUri = intent.getParcelableExtra(Installer.EXTRA_DOWNLOAD_URI); - installer.installPackage(uri, downloadUri); + Uri canonicalUri = intent.getParcelableExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL); + installer.installPackage(uri, canonicalUri); } else if (ACTION_UNINSTALL.equals(intent.getAction())) { installer.uninstallPackage(); new Thread() { @@ -111,19 +111,20 @@ public class InstallerService extends JobIntentService { * {@link #uninstall(Context, Apk)} since this is called in one place where * the input has already been validated. * - * @param context this app's {@link Context} - * @param localApkUri {@link Uri} pointing to (downloaded) local apk file - * @param downloadUri {@link Uri} where the apk has been downloaded from - * @param apk apk object of app that should be installed + * @param context this app's {@link Context} + * @param localApkUri {@link Uri} pointing to (downloaded) local apk file + * @param canonicalUri {@link Uri} used as the global unique ID for the package + * @param apk apk object of app that should be installed * @see #uninstall(Context, Apk) + * @see InstallManagerService */ - public static void install(Context context, Uri localApkUri, Uri downloadUri, Apk apk) { - Installer.sendBroadcastInstall(context, downloadUri, Installer.ACTION_INSTALL_STARTED, apk, + public static void install(Context context, Uri localApkUri, Uri canonicalUri, Apk apk) { + Installer.sendBroadcastInstall(context, canonicalUri, Installer.ACTION_INSTALL_STARTED, apk, null, null); Intent intent = new Intent(context, InstallerService.class); intent.setAction(ACTION_INSTALL); intent.setData(localApkUri); - intent.putExtra(Installer.EXTRA_DOWNLOAD_URI, downloadUri); + intent.putExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL, canonicalUri); intent.putExtra(Installer.EXTRA_APK, apk); enqueueWork(context, intent); } diff --git a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java index a4908e156..51e5ee8f4 100644 --- a/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java +++ b/app/src/main/java/org/fdroid/fdroid/installer/PrivilegedInstaller.java @@ -308,7 +308,7 @@ public class PrivilegedInstaller extends Installer { } @Override - protected void installPackageInternal(final Uri localApkUri, final Uri downloadUri) { + protected void installPackageInternal(final Uri localApkUri, final Uri canonicalUri) { ServiceConnection mServiceConnection = new ServiceConnection() { public void onServiceConnected(ComponentName name, IBinder service) { IPrivilegedService privService = IPrivilegedService.Stub.asInterface(service); @@ -317,9 +317,9 @@ public class PrivilegedInstaller extends Installer { @Override public void handleResult(String packageName, int returnCode) throws RemoteException { if (returnCode == INSTALL_SUCCEEDED) { - sendBroadcastInstall(downloadUri, ACTION_INSTALL_COMPLETE); + sendBroadcastInstall(canonicalUri, ACTION_INSTALL_COMPLETE); } else { - sendBroadcastInstall(downloadUri, ACTION_INSTALL_INTERRUPTED, + sendBroadcastInstall(canonicalUri, ACTION_INSTALL_INTERRUPTED, "Error " + returnCode + ": " + INSTALL_RETURN_CODES.get(returnCode)); } @@ -329,7 +329,7 @@ public class PrivilegedInstaller extends Installer { try { boolean hasPermissions = privService.hasPrivilegedPermissions(); if (!hasPermissions) { - sendBroadcastInstall(downloadUri, ACTION_INSTALL_INTERRUPTED, + sendBroadcastInstall(canonicalUri, ACTION_INSTALL_INTERRUPTED, context.getString(R.string.system_install_denied_permissions)); return; } @@ -338,7 +338,7 @@ public class PrivilegedInstaller extends Installer { null, callback); } catch (RemoteException e) { Log.e(TAG, "RemoteException", e); - sendBroadcastInstall(downloadUri, ACTION_INSTALL_INTERRUPTED, + sendBroadcastInstall(canonicalUri, ACTION_INSTALL_INTERRUPTED, "connecting to privileged service failed"); } } diff --git a/app/src/main/java/org/fdroid/fdroid/net/Downloader.java b/app/src/main/java/org/fdroid/fdroid/net/Downloader.java index 7a7ebb232..7acde08c6 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/Downloader.java +++ b/app/src/main/java/org/fdroid/fdroid/net/Downloader.java @@ -29,9 +29,16 @@ public abstract class Downloader { public static final String EXTRA_BYTES_READ = "org.fdroid.fdroid.net.Downloader.extra.BYTES_READ"; public static final String EXTRA_TOTAL_BYTES = "org.fdroid.fdroid.net.Downloader.extra.TOTAL_BYTES"; public static final String EXTRA_ERROR_MESSAGE = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MESSAGE"; - public static final String EXTRA_REPO_ID = "org.fdroid.fdroid.net.Downloader.extra.ERROR_REPO_ID"; - public static final String EXTRA_CANONICAL_URL = "org.fdroid.fdroid.net.Downloader.extra.ERROR_CANONICAL_URL"; - public static final String EXTRA_MIRROR_URL = "org.fdroid.fdroid.net.Downloader.extra.ERROR_MIRROR_URL"; + public static final String EXTRA_REPO_ID = "org.fdroid.fdroid.net.Downloader.extra.REPO_ID"; + public static final String EXTRA_MIRROR_URL = "org.fdroid.fdroid.net.Downloader.extra.MIRROR_URL"; + /** + * Unique ID used to represent this specific package's install process, + * including {@link android.app.Notification}s, also known as {@code canonicalUrl}. + * + * @see org.fdroid.fdroid.installer.InstallManagerService + * @see android.content.Intent#EXTRA_ORIGINATING_URI + */ + public static final String EXTRA_CANONICAL_URL = "org.fdroid.fdroid.net.Downloader.extra.CANONICAL_URL"; public static final int DEFAULT_TIMEOUT = 10000; public static final int SECOND_TIMEOUT = (int) DateUtils.MINUTE_IN_MILLIS; @@ -204,10 +211,16 @@ public abstract class Downloader { * Send progress updates on a timer to avoid flooding receivers with pointless events. */ private final TimerTask progressTask = new TimerTask() { + private long lastBytesRead = Long.MIN_VALUE; + private long lastTotalBytes = Long.MIN_VALUE; + @Override public void run() { - if (downloaderProgressListener != null) { - downloaderProgressListener.onProgress(urlString, bytesRead, totalBytes); + if (downloaderProgressListener != null + && (bytesRead != lastBytesRead || totalBytes != lastTotalBytes)) { + downloaderProgressListener.onProgress(bytesRead, totalBytes); + lastBytesRead = bytesRead; + lastTotalBytes = totalBytes; } } }; diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java b/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java index 30b8e4410..cca7e76ee 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java +++ b/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java @@ -32,9 +32,13 @@ import android.os.Process; import android.support.v4.content.LocalBroadcastManager; import android.text.TextUtils; import android.util.Log; +import android.util.LogPrinter; +import org.fdroid.fdroid.BuildConfig; +import org.fdroid.fdroid.FDroidApp; import org.fdroid.fdroid.ProgressListener; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; +import org.fdroid.fdroid.data.RepoProvider; import org.fdroid.fdroid.data.SanitizedFile; import org.fdroid.fdroid.installer.ApkCache; @@ -73,17 +77,18 @@ import java.net.UnknownHostException; * long as necessary (and will not block the application's main loop), but * only one request will be processed at a time. *

- * The full URL for the file to download is also used as the unique ID to + * The Canonical URL for the 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}. For places that need an {@code int} ID, * {@link String#hashCode()} should be used to get a reproducible, unique {@code int} - * from any {@code urlString}. The full URL is guaranteed to be unique since + * from any {@code canonicalUrl}. That full URL is guaranteed to be unique since * it points to a file on a filesystem. This is more important with media files * than with APKs since there is not reliable standard for a unique ID for * media files, unlike APKs with {@code packageName} and {@code versionCode}. * * @see android.app.IntentService + * @see org.fdroid.fdroid.installer.InstallManagerService */ public class DownloaderService extends Service { private static final String TAG = "DownloaderService"; @@ -94,10 +99,13 @@ public class DownloaderService extends Service { private volatile Looper serviceLooper; private static volatile ServiceHandler serviceHandler; private static volatile Downloader downloader; + private static volatile String activeCanonicalUrl; private LocalBroadcastManager localBroadcastManager; private static volatile int timeout; private final class ServiceHandler extends Handler { + static final String TAG = "ServiceHandler"; + ServiceHandler(Looper looper) { super(looper); } @@ -119,6 +127,9 @@ public class DownloaderService extends Service { thread.start(); serviceLooper = thread.getLooper(); + if (BuildConfig.DEBUG) { + serviceLooper.setMessageLogging(new LogPrinter(Log.DEBUG, ServiceHandler.TAG)); + } serviceHandler = new ServiceHandler(serviceLooper); localBroadcastManager = LocalBroadcastManager.getInstance(this); } @@ -131,21 +142,27 @@ public class DownloaderService extends Service { return START_NOT_STICKY; } - String uriString = intent.getDataString(); - if (uriString == null) { + String downloadUrl = intent.getDataString(); + if (downloadUrl == null) { Utils.debugLog(TAG, "Received Intent with no URI: " + intent); return START_NOT_STICKY; } + String canonicalUrl = intent.getStringExtra(Downloader.EXTRA_CANONICAL_URL); + if (canonicalUrl == null) { + Utils.debugLog(TAG, "Received Intent with no EXTRA_CANONICAL_URL: " + intent); + return START_NOT_STICKY; + } if (ACTION_CANCEL.equals(intent.getAction())) { - Utils.debugLog(TAG, "Cancelling download of " + uriString); - Integer whatToRemove = uriString.hashCode(); + Utils.debugLog(TAG, "Cancelling download of " + canonicalUrl.hashCode() + "/" + canonicalUrl + + " downloading from " + downloadUrl); + Integer whatToRemove = canonicalUrl.hashCode(); if (serviceHandler.hasMessages(whatToRemove)) { Utils.debugLog(TAG, "Removing download with ID of " + whatToRemove + " from service handler, then sending interrupted event."); serviceHandler.removeMessages(whatToRemove); - sendBroadcast(intent.getData(), Downloader.ACTION_INTERRUPTED); - } else if (isActive(uriString)) { + sendCancelledBroadcast(intent.getData(), canonicalUrl); + } else if (isActive(canonicalUrl)) { downloader.cancelDownload(); } else { Utils.debugLog(TAG, "ACTION_CANCEL called on something not queued or running" @@ -155,9 +172,10 @@ public class DownloaderService extends Service { Message msg = serviceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; - msg.what = uriString.hashCode(); + msg.what = canonicalUrl.hashCode(); serviceHandler.sendMessage(msg); - Utils.debugLog(TAG, "Queued download of " + uriString); + Utils.debugLog(TAG, "Queued download of " + canonicalUrl.hashCode() + "/" + canonicalUrl + + " using " + downloadUrl); } else { Utils.debugLog(TAG, "Received Intent with unknown action: " + intent); } @@ -198,18 +216,19 @@ public class DownloaderService extends Service { */ private void handleIntent(Intent intent) { final Uri uri = intent.getData(); - long repoId = intent.getLongExtra(Downloader.EXTRA_REPO_ID, 0); - String canonicalUrlString = intent.getStringExtra(Downloader.EXTRA_CANONICAL_URL); - final SanitizedFile localFile = ApkCache.getApkDownloadPath(this, canonicalUrlString); - sendBroadcast(uri, Downloader.ACTION_STARTED, localFile, repoId, canonicalUrlString); + final long repoId = intent.getLongExtra(Downloader.EXTRA_REPO_ID, 0); + final Uri canonicalUrl = Uri.parse(intent.getStringExtra(Downloader.EXTRA_CANONICAL_URL)); + final SanitizedFile localFile = ApkCache.getApkDownloadPath(this, canonicalUrl); + sendBroadcast(uri, Downloader.ACTION_STARTED, localFile, repoId, canonicalUrl); try { + activeCanonicalUrl = canonicalUrl.toString(); downloader = DownloaderFactory.create(this, uri, localFile); downloader.setListener(new ProgressListener() { @Override - public void onProgress(String urlString, long bytesRead, long totalBytes) { + public void onProgress(long bytesRead, long totalBytes) { Intent intent = new Intent(Downloader.ACTION_PROGRESS); - intent.setData(uri); + intent.setData(canonicalUrl); intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead); intent.putExtra(Downloader.EXTRA_TOTAL_BYTES, totalBytes); localBroadcastManager.sendBroadcast(intent); @@ -219,47 +238,44 @@ public class DownloaderService extends Service { downloader.download(); if (downloader.isNotFound()) { sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile, getString(R.string.download_404), - repoId, canonicalUrlString); + repoId, canonicalUrl); } else { - sendBroadcast(uri, Downloader.ACTION_COMPLETE, localFile, repoId, canonicalUrlString); + sendBroadcast(uri, Downloader.ACTION_COMPLETE, localFile, repoId, canonicalUrl); } } catch (InterruptedException e) { - sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile, repoId, canonicalUrlString); + sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile, repoId, canonicalUrl); } catch (ConnectException | HttpRetryException | NoRouteToHostException | SocketTimeoutException | SSLHandshakeException | SSLKeyException | SSLPeerUnverifiedException | SSLProtocolException | ProtocolException | UnknownHostException e) { // if the above list of exceptions changes, also change it in IndexV1Updater.update() Log.e(TAG, e.getLocalizedMessage()); - sendBroadcast(uri, Downloader.ACTION_CONNECTION_FAILED, localFile, repoId, canonicalUrlString); + sendBroadcast(uri, Downloader.ACTION_CONNECTION_FAILED, localFile, repoId, canonicalUrl); } catch (IOException e) { e.printStackTrace(); sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile, - e.getLocalizedMessage(), repoId, canonicalUrlString); + e.getLocalizedMessage(), repoId, canonicalUrl); } finally { if (downloader != null) { downloader.close(); } } downloader = null; + activeCanonicalUrl = null; } - private void sendBroadcast(Uri uri, String action) { - sendBroadcast(uri, action, null, null); + private void sendCancelledBroadcast(Uri uri, String canonicalUrl) { + sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, null, 0, Uri.parse(canonicalUrl)); } - private void sendBroadcast(Uri uri, String action, File file, long repoId, String originalUrlString) { - sendBroadcast(uri, action, file, null, repoId, originalUrlString); - } - - private void sendBroadcast(Uri uri, String action, File file, String errorMessage) { - sendBroadcast(uri, action, file, errorMessage, 0, null); + private void sendBroadcast(Uri uri, String action, File file, long repoId, Uri canonicalUrl) { + sendBroadcast(uri, action, file, null, repoId, canonicalUrl); } private void sendBroadcast(Uri uri, String action, File file, String errorMessage, long repoId, - String originalUrlString) { + Uri canonicalUrl) { Intent intent = new Intent(action); - if (originalUrlString != null) { - intent.setData(Uri.parse(originalUrlString)); + if (canonicalUrl != null) { + intent.setData(canonicalUrl); } if (file != null) { intent.putExtra(Downloader.EXTRA_DOWNLOAD_PATH, file.getAbsolutePath()); @@ -277,42 +293,73 @@ public class DownloaderService extends Service { *

* All notifications are sent as an {@link Intent} via local broadcasts to be received by * - * @param context this app's {@link Context} - * @param mirrorUrlString The URL to add to the download queue - * @param repoId the database ID number representing one repo - * @param urlString the URL used as the unique ID throughout F-Droid + * @param context this app's {@link Context} + * @param mirrorUrl The URL to add to the download queue + * @param repoId the database ID number representing one repo + * @param canonicalUrl the URL used as the unique ID throughout F-Droid * @see #cancel(Context, String) */ - public static void queue(Context context, String mirrorUrlString, long repoId, String urlString) { - if (TextUtils.isEmpty(mirrorUrlString)) { + public static void queue(Context context, String mirrorUrl, long repoId, String canonicalUrl) { + if (TextUtils.isEmpty(mirrorUrl)) { return; } - Utils.debugLog(TAG, "Preparing " + mirrorUrlString + " to go into the download queue"); + Utils.debugLog(TAG, "Queue download " + canonicalUrl.hashCode() + "/" + canonicalUrl + + " using " + mirrorUrl); Intent intent = new Intent(context, DownloaderService.class); intent.setAction(ACTION_QUEUE); - intent.setData(Uri.parse(mirrorUrlString)); + intent.setData(Uri.parse(mirrorUrl)); intent.putExtra(Downloader.EXTRA_REPO_ID, repoId); - intent.putExtra(Downloader.EXTRA_CANONICAL_URL, urlString); + intent.putExtra(Downloader.EXTRA_CANONICAL_URL, canonicalUrl); context.startService(intent); } + /** + * Add a package to the download queue, choosing a random mirror to + * download from. + * + * @param canonicalUrl the URL used as the unique ID throughout F-Droid, + * needed here to support canceling active downloads + */ + public static void queueUsingRandomMirror(Context context, long repoId, String canonicalUrl) { + String mirrorUrl = FDroidApp.switchUrlToNewMirror(canonicalUrl, + RepoProvider.Helper.findById(context, repoId)); + queue(context, mirrorUrl, repoId, canonicalUrl); + } + + /** + * Tries to return a version of {@code urlString} from a mirror, if there + * is an error, it just returns {@code urlString}. + * + * @see FDroidApp#getNewMirrorOnError(String, org.fdroid.fdroid.data.Repo) + */ + public static void queueUsingDifferentMirror(Context context, long repoId, String canonicalUrl) { + try { + String mirrorUrl = FDroidApp.getNewMirrorOnError(canonicalUrl, + RepoProvider.Helper.findById(context, repoId)); + queue(context, mirrorUrl, repoId, canonicalUrl); + } catch (IOException e) { + queue(context, canonicalUrl, repoId, canonicalUrl); + } + } + /** * Remove a URL to the download queue, even if it is currently downloading. *

* All notifications are sent as an {@link Intent} via local broadcasts to be received by * - * @param context this app's {@link Context} - * @param urlString The URL to remove from the download queue + * @param context this app's {@link Context} + * @param canonicalUrl The URL to remove from the download queue * @see #queue(Context, String, long, String) */ - public static void cancel(Context context, String urlString) { - if (TextUtils.isEmpty(urlString)) { + public static void cancel(Context context, String canonicalUrl) { + if (TextUtils.isEmpty(canonicalUrl)) { return; } - Utils.debugLog(TAG, "Preparing cancellation of " + urlString + " download"); + Utils.debugLog(TAG, "Send cancel for " + canonicalUrl.hashCode() + "/" + canonicalUrl); Intent intent = new Intent(context, DownloaderService.class); intent.setAction(ACTION_CANCEL); - intent.setData(Uri.parse(urlString)); + intent.setData(Uri.parse(canonicalUrl)); + intent.putExtra(Downloader.EXTRA_CANONICAL_URL, canonicalUrl); context.startService(intent); } @@ -321,21 +368,21 @@ public class DownloaderService extends Service { * This is useful for checking whether to re-register {@link android.content.BroadcastReceiver}s * in {@link android.app.Activity#onResume()}. */ - public static boolean isQueuedOrActive(String urlString) { - if (TextUtils.isEmpty(urlString)) { //NOPMD - suggests unreadable format + public static boolean isQueuedOrActive(String canonicalUrl) { + if (TextUtils.isEmpty(canonicalUrl)) { //NOPMD - suggests unreadable format return false; } if (serviceHandler == null) { return false; // this service is not even running } - return serviceHandler.hasMessages(urlString.hashCode()) || isActive(urlString); + return serviceHandler.hasMessages(canonicalUrl.hashCode()) || isActive(canonicalUrl); } /** * Check if a URL is actively being downloaded. */ - private static boolean isActive(String urlString) { - return downloader != null && TextUtils.equals(urlString, downloader.urlString); + private static boolean isActive(String downloadUrl) { + return downloader != null && TextUtils.equals(downloadUrl, activeCanonicalUrl); } public static void setTimeout(int ms) { @@ -345,10 +392,10 @@ public class DownloaderService extends Service { /** * Get a prepared {@link IntentFilter} for use for matching this service's action events. * - * @param urlString The full file URL to match. + * @param canonicalUrl the URL used as the unique ID for the specific package */ - public static IntentFilter getIntentFilter(String urlString) { - Uri uri = Uri.parse(urlString); + public static IntentFilter getIntentFilter(String canonicalUrl) { + Uri uri = Uri.parse(canonicalUrl); IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Downloader.ACTION_STARTED); intentFilter.addAction(Downloader.ACTION_PROGRESS); diff --git a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java index 1d606c4fd..77160ceb8 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/AppDetailsActivity.java @@ -171,9 +171,9 @@ public class AppDetailsActivity extends AppCompatActivity AppUpdateStatusManager ausm = AppUpdateStatusManager.getInstance(this); for (AppUpdateStatusManager.AppUpdateStatus status : ausm.getByPackageName(app.packageName)) { if (status.status == AppUpdateStatusManager.Status.Installed) { - ausm.removeApk(status.getUniqueKey()); + ausm.removeApk(status.getCanonicalUrl()); } else { - ausm.refreshApk(status.getUniqueKey()); + ausm.refreshApk(status.getCanonicalUrl()); } } } @@ -449,7 +449,7 @@ public class AppDetailsActivity extends AppCompatActivity if (justReceived) { adapter.setIndeterminateProgress(R.string.installing); localBroadcastManager.registerReceiver(installReceiver, - Installer.getInstallIntentFilter(Uri.parse(newStatus.getUniqueKey()))); + Installer.getInstallIntentFilter(newStatus.getCanonicalUrl())); } break; @@ -459,7 +459,7 @@ public class AppDetailsActivity extends AppCompatActivity Toast.makeText(this, R.string.details_notinstalled, Toast.LENGTH_LONG).show(); } else { String msg = newStatus.errorText; - if (!newStatus.getUniqueKey().equals(msg)) msg += " " + newStatus.getUniqueKey(); + if (!newStatus.getCanonicalUrl().equals(msg)) msg += " " + newStatus.getCanonicalUrl(); Toast.makeText(this, R.string.download_error, Toast.LENGTH_SHORT).show(); Toast.makeText(this, msg, Toast.LENGTH_LONG).show(); } @@ -491,9 +491,9 @@ public class AppDetailsActivity extends AppCompatActivity AppUpdateStatusManager.BROADCAST_APPSTATUS_REMOVED); if (currentStatus != null && isRemoving - && !TextUtils.equals(status.getUniqueKey(), currentStatus.getUniqueKey())) { + && !TextUtils.equals(status.getCanonicalUrl(), currentStatus.getCanonicalUrl())) { Utils.debugLog(TAG, "Ignoring app status change because it belongs to " - + status.getUniqueKey() + " not " + currentStatus.getUniqueKey()); + + status.getCanonicalUrl() + " not " + currentStatus.getCanonicalUrl()); } else if (status != null && !TextUtils.equals(status.apk.packageName, app.packageName)) { Utils.debugLog(TAG, "Ignoring app status change because it belongs to " + status.apk.packageName + " not " + app.packageName); @@ -650,7 +650,7 @@ public class AppDetailsActivity extends AppCompatActivity AppUpdateStatusManager ausm = AppUpdateStatusManager.getInstance(this); for (AppUpdateStatusManager.AppUpdateStatus status : ausm.getByPackageName(packageName)) { if (status.status == AppUpdateStatusManager.Status.Installed) { - ausm.removeApk(status.getUniqueKey()); + ausm.removeApk(status.getCanonicalUrl()); } } if (app == null) { @@ -720,7 +720,7 @@ public class AppDetailsActivity extends AppCompatActivity @Override public void installCancel() { if (currentStatus != null) { - InstallManagerService.cancel(this, currentStatus.getUniqueKey()); + InstallManagerService.cancel(this, currentStatus.getCanonicalUrl()); } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java index 9427c5f45..7801ff82e 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/apps/AppListItemController.java @@ -477,15 +477,15 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { // Once it is explicitly launched by the user, then we can pretty much forget about // any sort of notification that the app was successfully installed. It should be // apparent to the user because they just launched it. - AppUpdateStatusManager.getInstance(activity).removeApk(currentStatus.getUniqueKey()); + AppUpdateStatusManager.getInstance(activity).removeApk(currentStatus.getCanonicalUrl()); } return; } if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) { - String urlString = currentStatus.apk.getUrl(); - File apkFilePath = ApkCache.getApkDownloadPath(activity, urlString); - Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getUrl() + + String canonicalUrl = currentStatus.apk.getCanonicalUrl(); + File apkFilePath = ApkCache.getApkDownloadPath(activity, canonicalUrl); + Utils.debugLog(TAG, "skip download, we have already downloaded " + currentStatus.apk.getCanonicalUrl() + " to " + apkFilePath); final LocalBroadcastManager broadcastManager = LocalBroadcastManager.getInstance(activity); @@ -505,10 +505,10 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { } }; - Uri apkDownloadUri = Uri.parse(urlString); - broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(apkDownloadUri)); + Uri canonicalUri = Uri.parse(canonicalUrl); + broadcastManager.registerReceiver(receiver, Installer.getInstallIntentFilter(canonicalUri)); Installer installer = InstallerFactory.create(activity, currentStatus.apk); - installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), apkDownloadUri); + installer.installPackage(Uri.parse(apkFilePath.toURI().toString()), canonicalUri); } else { final Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, app); InstallManagerService.queue(activity, app, suggestedApk); @@ -534,6 +534,6 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder { return; } - InstallManagerService.cancel(activity, currentStatus.getUniqueKey()); + InstallManagerService.cancel(activity, currentStatus.getCanonicalUrl()); } } diff --git a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java index d108c85e4..4022b3c79 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java +++ b/app/src/main/java/org/fdroid/fdroid/views/main/MainActivity.java @@ -479,7 +479,8 @@ public class MainActivity extends AppCompatActivity implements BottomNavigationB } // Check if we have moved into the ReadyToInstall or Installed state. - AppUpdateStatus status = manager.get(intent.getStringExtra(AppUpdateStatusManager.EXTRA_APK_URL)); + AppUpdateStatus status = manager.get( + intent.getStringExtra(org.fdroid.fdroid.net.Downloader.EXTRA_CANONICAL_URL)); boolean isStatusChange = intent.getBooleanExtra(AppUpdateStatusManager.EXTRA_IS_STATUS_UPDATE, false); if (isStatusChange && status != null diff --git a/app/src/main/java/org/fdroid/fdroid/views/updates/items/AppStatusListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/updates/items/AppStatusListItemController.java index 4bf93f9e0..2f612ca16 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/updates/items/AppStatusListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/updates/items/AppStatusListItemController.java @@ -59,7 +59,7 @@ public class AppStatusListItemController extends AppListItemController { CharSequence message = null; if (status != null) { AppUpdateStatusManager manager = AppUpdateStatusManager.getInstance(activity); - manager.removeApk(status.getUniqueKey()); + manager.removeApk(status.getCanonicalUrl()); switch (status.status) { case Downloading: cancelDownload(); diff --git a/app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java b/app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java index 6d3631940..0d1ac37dd 100644 --- a/app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java +++ b/app/src/main/java/org/fdroid/fdroid/views/updates/items/KnownVulnAppListItemController.java @@ -5,12 +5,10 @@ import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.net.Uri; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.v4.content.LocalBroadcastManager; import android.view.View; - import org.fdroid.fdroid.AppUpdateStatusManager; import org.fdroid.fdroid.R; import org.fdroid.fdroid.data.Apk; @@ -73,8 +71,8 @@ public class KnownVulnAppListItemController extends AppListItemController { Apk suggestedApk = ApkProvider.Helper.findSuggestedApk(activity, app); if (shouldUpgradeInsteadOfUninstall(app, suggestedApk)) { LocalBroadcastManager manager = LocalBroadcastManager.getInstance(activity); - Uri uri = Uri.parse(suggestedApk.getUrl()); - manager.registerReceiver(installReceiver, Installer.getInstallIntentFilter(uri)); + manager.registerReceiver(installReceiver, + Installer.getInstallIntentFilter(suggestedApk.getCanonicalUrl())); InstallManagerService.queue(activity, app, suggestedApk); } else { LocalBroadcastManager manager = LocalBroadcastManager.getInstance(activity); @@ -141,7 +139,8 @@ public class KnownVulnAppListItemController extends AppListItemController { try { uninstallPendingIntent.send(); - } catch (PendingIntent.CanceledException ignored) { } + } catch (PendingIntent.CanceledException ignored) { + } break; } }