diff --git a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java index 7a5b70085..fcdf0ef3d 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java +++ b/app/src/main/java/org/fdroid/fdroid/AppUpdateStatusService.java @@ -7,10 +7,12 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.support.annotation.Nullable; +import android.text.TextUtils; import android.util.Log; import org.fdroid.fdroid.data.Apk; import org.fdroid.fdroid.data.ApkProvider; +import org.fdroid.fdroid.data.InstalledAppProviderService; import org.fdroid.fdroid.installer.ApkCache; import org.fdroid.fdroid.installer.InstallManagerService; @@ -75,29 +77,46 @@ public class AppUpdateStatusService extends IntentService { } } + /** + * Verifies that {@param apkPath} is a valid apk which the user intends to install. + * If it is corrupted to the point where {@link PackageManager} can't read it, doesn't match the hash of any apk + * we know about in our database, is not pending install, or is already installed, then it will return null. + */ @Nullable private Apk processDownloadedApk(File apkPath) { Utils.debugLog(TAG, "Checking " + apkPath); PackageInfo downloadedInfo = getPackageManager().getPackageArchiveInfo(apkPath.getAbsolutePath(), PackageManager.GET_GIDS); if (downloadedInfo == null) { - Utils.debugLog(TAG, "Skipping " + apkPath + " because PackageManager was unable to read it."); + 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."); Apk downloadedApk = findApkMatchingHash(apkPath); if (downloadedApk == null) { - Utils.debugLog(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)) { - Utils.debugLog(TAG, downloadedApk.packageName + " is pending install, so we need to notify the user about installing it."); - return downloadedApk; - } else { - Utils.debugLog(TAG, downloadedApk.packageName + " is NOT pending install, probably just left over from a previous install."); + if (!AppUpdateStatusManager.getInstance(this).isPendingInstall(downloadedApk.hash)) { + Log.i(TAG, downloadedApk.packageName + " is NOT pending install, probably just left over from a previous install."); return null; } + + try { + PackageInfo info = getPackageManager().getPackageInfo(downloadedApk.packageName, 0); + File pathToInstalled = InstalledAppProviderService.getPathToInstalledApk(info); + 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."); + 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."); + return downloadedApk; } /** 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 d319b4033..6dcc83e33 100644 --- a/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java +++ b/app/src/main/java/org/fdroid/fdroid/data/InstalledAppProviderService.java @@ -188,6 +188,29 @@ public class InstalledAppProviderService extends IntentService { } } + @Nullable + public static File getPathToInstalledApk(PackageInfo packageInfo) { + File apk = new File(packageInfo.applicationInfo.publicSourceDir); + if (apk.isDirectory()) { + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + return name.endsWith(".apk"); + } + }; + File[] files = apk.listFiles(filter); + if (files == null) { + String msg = packageInfo.packageName + " sourceDir has no APKs: " + apk.getAbsolutePath(); + Utils.debugLog(TAG, msg); + ACRA.getErrorReporter().handleException(new IllegalArgumentException(msg), false); + return null; + } + apk = files[0]; + } + + return apk; + } + @Override protected void onHandleIntent(Intent intent) { Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST); @@ -201,23 +224,9 @@ public class InstalledAppProviderService extends IntentService { PackageInfo packageInfo = getPackageInfo(intent, packageName); if (packageInfo != null) { Log.i(TAG, "Marking " + packageName + " as installed"); - File apk = new File(packageInfo.applicationInfo.publicSourceDir); - if (apk.isDirectory()) { - FilenameFilter filter = new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith(".apk"); - } - }; - File[] files = apk.listFiles(filter); - if (files == null) { - String msg = packageName + " sourceDir has no APKs: " - + apk.getAbsolutePath(); - Utils.debugLog(TAG, msg); - ACRA.getErrorReporter().handleException(new IllegalArgumentException(msg), false); - return; - } - apk = files[0]; + File apk = getPathToInstalledApk(packageInfo); + if (apk == null) { + return; } if (apk.exists() && apk.canRead()) { try {