Correctly notify user of downloaded but not installed apks
Previously this only worked for apps which were being upgraded to the latest available version. Now it works for apks being upgraded from an old version to a newer-but-not-newest version. Perhaps more importantly, it also now works for newly installed apps. This has a problem where if a user installs, then uninstalls an app, they are still notified about installing it. This is because the apk is in the cache and the code doesn't know whether it is there from the initial install, or because it has been redownloaded by the user to install some time after uninstalling.
This commit is contained in:
parent
c4fd369feb
commit
79d526ead1
@ -3,17 +3,17 @@ package org.fdroid.fdroid;
|
||||
import android.app.IntentService;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.ApkProvider;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.data.Schema;
|
||||
import org.fdroid.fdroid.installer.ApkCache;
|
||||
import org.fdroid.fdroid.installer.InstallManagerService;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@ -23,12 +23,11 @@ import java.util.List;
|
||||
* {@link AppUpdateStatusManager.Status#ReadyToInstall}. This is an {@link IntentService} so as to
|
||||
* run on a background thread, as it hits the disk a bit to figure out the hash of each downloaded
|
||||
* file.
|
||||
*
|
||||
* TODO: Deal with more than just the suggested version. It should also work for people downloading earlier versions (but still newer than their current)
|
||||
* TODO: Identify new apps which have not been installed before, but which have been downloading. Currently only works for updates.
|
||||
*/
|
||||
public class AppUpdateStatusService extends IntentService {
|
||||
|
||||
private static final String TAG = "AppUpdateStatusService";
|
||||
|
||||
/**
|
||||
* Queue up a background scan of all downloaded apk files to see if we should notify the user
|
||||
* that they are ready to install.
|
||||
@ -43,17 +42,69 @@ public class AppUpdateStatusService extends IntentService {
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(@Nullable Intent intent) {
|
||||
List<App> apps = AppProvider.Helper.findCanUpdate(this, Schema.AppMetadataTable.Cols.ALL);
|
||||
List<Apk> apksReadyToInstall = new ArrayList<>();
|
||||
for (App app : apps) {
|
||||
Apk apk = ApkProvider.Helper.findApkFromAnyRepo(this, app.packageName, app.suggestedVersionCode);
|
||||
Uri downloadUri = Uri.parse(apk.getUrl());
|
||||
if (ApkCache.apkIsCached(ApkCache.getApkDownloadPath(this, downloadUri), apk)) {
|
||||
File cacheDir = ApkCache.getApkCacheDir(this);
|
||||
for (String repoDirName : cacheDir.list()) {
|
||||
File repoDir = new File(cacheDir, repoDirName);
|
||||
for (String apkFileName : repoDir.list()) {
|
||||
Apk apk = processDownloadedApk(new File(repoDir, apkFileName));
|
||||
if (apk != null) {
|
||||
Log.i(TAG, "Found downloaded apk " + apk.packageName + ". Notifying user that it should be installed.");
|
||||
apksReadyToInstall.add(apk);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppUpdateStatusManager.getInstance(this).addApks(apksReadyToInstall, AppUpdateStatusManager.Status.ReadyToInstall);
|
||||
InstallManagerService.managePreviouslyDownloadedApks(this);
|
||||
}
|
||||
|
||||
@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.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// NOTE: This presumes SHA256 is the only supported hash. It seems like that is an assumption
|
||||
// in more than one place in the F-Droid client. If this becomes a problem in the future, we
|
||||
// can query the Apk table for `SELECT DISTINCT hashType FROM fdroid_apk` and then we can just
|
||||
// try each of the hash types that have been specified in the metadata. Seems a bit overkill
|
||||
// at the time of writing though.
|
||||
Utils.debugLog(TAG, "Found package for " + downloadedInfo.packageName + ", checking its hash to see if it downloaded correctly.");
|
||||
String hash = Utils.getBinaryHash(apkPath, "sha256");
|
||||
|
||||
List<Apk> apksMatchingHash = ApkProvider.Helper.findApksByHash(this, hash);
|
||||
Utils.debugLog(TAG, "Found " + apksMatchingHash.size() + " apk(s) matching the hash " + hash);
|
||||
|
||||
if (apksMatchingHash.size() == 0) {
|
||||
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.");
|
||||
return null;
|
||||
}
|
||||
|
||||
// It makes zero difference whether we return the apk from one repo or another. The hash
|
||||
// calculation shows that they are exactly the same binary.
|
||||
Apk downloadedApk = apksMatchingHash.get(0);
|
||||
|
||||
PackageInfo installedInfo = null;
|
||||
try {
|
||||
installedInfo = getPackageManager().getPackageInfo(downloadedApk.packageName, PackageManager.GET_META_DATA);
|
||||
} catch (PackageManager.NameNotFoundException ignored) {}
|
||||
|
||||
if (installedInfo == null) {
|
||||
// This will return some false positives, because it is possible that
|
||||
// the user downloaded + installed, tried the apk, and then uninstalled it.
|
||||
Utils.debugLog(TAG, downloadedApk.packageName + " is not installed, so presuming we need to notify the user about installing it.");
|
||||
return downloadedApk;
|
||||
}
|
||||
|
||||
if (installedInfo.versionCode >= downloadedInfo.versionCode) {
|
||||
Utils.debugLog(TAG, downloadedApk.packageName + " already installed with versionCode " + installedInfo.versionCode + " and downloaded apk is only " + downloadedInfo.versionCode + ", so ignoring downloaded apk.");
|
||||
return null;
|
||||
}
|
||||
|
||||
return downloadedApk;
|
||||
}
|
||||
}
|
@ -6,6 +6,7 @@ import android.content.Context;
|
||||
import android.content.UriMatcher;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.util.Log;
|
||||
|
||||
import org.fdroid.fdroid.data.Schema.ApkTable;
|
||||
@ -173,6 +174,16 @@ public class ApkProvider extends FDroidProvider {
|
||||
}
|
||||
return apk;
|
||||
}
|
||||
|
||||
@NonNull
|
||||
public static List<Apk> findApksByHash(Context context, String apkHash) {
|
||||
ContentResolver resolver = context.getContentResolver();
|
||||
final Uri uri = getContentUri();
|
||||
String selection = " apk." + Cols.HASH + " = ? ";
|
||||
String[] selectionArgs = new String[]{apkHash};
|
||||
Cursor cursor = resolver.query(uri, Cols.ALL, selection, selectionArgs, null);
|
||||
return cursorToList(cursor);
|
||||
}
|
||||
}
|
||||
|
||||
private static final int CODE_PACKAGE = CODE_SINGLE + 1;
|
||||
|
Loading…
x
Reference in New Issue
Block a user