rename Canonical vs. Download URL in InstallManagerService process

AppUpdateStatusManager and InstallManagerService should be using only the
Canonical URL of the package since that is the global unique ID.  The actual
URL used to download it needs to be isolated in DownloaderService, which can
entirely manage the mirror selection process.  This is just a bunch of
renaming to make this all clearer.
This commit is contained in:
Hans-Christoph Steiner 2019-03-27 13:13:53 +01:00
parent 3a5f96e0b3
commit 3378c0a333
16 changed files with 170 additions and 147 deletions

View File

@ -314,10 +314,8 @@ public class SwapAppsView extends ListView implements
}
if (apk != null) {
String urlString = apk.getCanonicalUrl();
// 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);
}

View File

@ -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.getCanonicalUrl());
localBroadcastManager.registerReceiver(installReceiver,
Installer.getInstallIntentFilter(downloadUri));
Installer.getInstallIntentFilter(apk.getCanonicalUrl()));
InstallManagerService.queue(this, app, apk);
}

View File

@ -129,7 +129,7 @@ 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 getCanonicalUrl() {
@ -225,9 +225,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);
}
}
@ -420,16 +420,15 @@ public final class AppUpdateStatusManager {
/**
* @param errorText If null, then it is likely because the user cancelled the download.
*/
// TODO should url actually be canonicalUrl?
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);
}
}
}

View File

@ -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(NotificationHelper.EXTRA_NOTIFICATION_KEY);
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;
}
}

View File

@ -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(Installer.EXTRA_DOWNLOAD_URI, 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);
}

View File

@ -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(Installer.EXTRA_DOWNLOAD_URI);
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;

View File

@ -44,15 +44,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(Installer.EXTRA_DOWNLOAD_URI, canonicalUri);
installIntent.putExtra(Installer.EXTRA_APK, apk);
installIntent.setData(localApkUri);
@ -62,7 +62,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);
}

View File

@ -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(Installer.EXTRA_DOWNLOAD_URI);
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();
}

View File

@ -65,7 +65,7 @@ import java.io.IOException;
* APK on different servers, signed by different keys, or even different builds.
* <p><ul>
* <li>for a {@link Uri} ID, use {@code Uri}, {@link Intent#getData()}
* <li>for a {@code String} ID, use {@code urlString}, {@link Uri#toString()}, or
* <li>for a {@code String} ID, use {@code canonicalUrl}, {@link Uri#toString()}, or
* {@link Intent#getDataString()}
* <li>for an {@code int} ID, use {@link String#hashCode()} or {@link Uri#hashCode()}
* </ul></p>
@ -146,24 +146,24 @@ 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) {
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;
}
@ -173,14 +173,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;
}
@ -204,23 +204,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);
getObb(canonicalUrl, apk.getMainObbUrl(), apk.getMainObbFile(), apk.obbMainFileSha256);
getObb(canonicalUrl, apk.getPatchObbUrl(), apk.getPatchObbFile(), apk.obbPatchFileSha256);
File apkFilePath = ApkCache.getApkDownloadPath(this, apk.getCanonicalUrl());
long apkFileSize = apkFilePath.length();
if (!apkFilePath.exists() || apkFileSize < apk.size) {
Utils.debugLog(TAG, "download " + urlString + " " + apkFilePath);
DownloaderService.queueUsingRandomMirror(this, 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.queueUsingRandomMirror(this, apk.repoId, urlString);
DownloaderService.queueUsingRandomMirror(this, apk.repoId, canonicalUrl);
}
return START_REDELIVER_INTENT; // if killed before completion, retry Intent
@ -240,7 +240,7 @@ public class InstallManagerService extends Service {
*
* @see <a href="https://developer.android.com/google/play/expansion-files.html">APK Expansion Files</a>
*/
private void getObb(final String urlString, String obbUrlString,
private void getObb(final String canonicalUrl, String obbUrlString,
final File obbDestFile, final String hash) {
if (obbDestFile == null || obbDestFile.exists() || TextUtils.isEmpty(obbUrlString)) {
return;
@ -259,7 +259,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));
@ -291,7 +291,7 @@ public class InstallManagerService extends Service {
} else if (Downloader.ACTION_INTERRUPTED.equals(action)) {
localBroadcastManager.unregisterReceiver(this);
} else if (Downloader.ACTION_CONNECTION_FAILED.equals(action)) {
DownloaderService.queueUsingDifferentMirror(context, 0, urlString);
DownloaderService.queueUsingDifferentMirror(context, 0, canonicalUrl);
} else {
throw new RuntimeException("intent action not handled!");
}
@ -304,9 +304,9 @@ public class InstallManagerService extends Service {
/**
* 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
@ -315,8 +315,8 @@ 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);
switch (intent.getAction()) {
@ -324,14 +324,15 @@ public class InstallManagerService extends Service {
// 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));
@ -339,29 +340,33 @@ public class InstallManagerService extends Service {
Utils.debugLog(TAG, "download completed of "
+ intent.getStringExtra(Downloader.EXTRA_MIRROR_URL) + " to " + localApkUri);
appUpdateStatusManager.updateApk(urlString, AppUpdateStatusManager.Status.ReadyToInstall, null);
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(
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;
@ -372,14 +377,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
@ -388,15 +393,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 {
@ -414,7 +421,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;
@ -430,7 +437,7 @@ public class InstallManagerService extends Service {
};
localBroadcastManager.registerReceiver(installReceiver,
Installer.getInstallIntentFilter(downloadUri));
Installer.getInstallIntentFilter(canonicalUrl));
}
/**

View File

@ -165,23 +165,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 +226,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);
@ -263,19 +277,19 @@ public abstract class Installer {
* 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
* @param canonicalUri serves as the unique ID for all actions related to the
* installation of that specific APK
* @see InstallManagerService
* @see <a href="https://issuetracker.google.com/issues/37091886">ACTION_INSTALL_PACKAGE Fails For Any Possible Uri</a>
*/
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 +299,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 +309,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

View File

@ -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(Installer.EXTRA_DOWNLOAD_URI);
installer.installPackage(uri, canonicalUri);
} else if (ACTION_UNINSTALL.equals(intent.getAction())) {
installer.uninstallPackage();
new Thread() {
@ -113,17 +113,18 @@ public class InstallerService extends JobIntentService {
*
* @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 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(Installer.EXTRA_DOWNLOAD_URI, canonicalUri);
intent.putExtra(Installer.EXTRA_APK, apk);
enqueueWork(context, intent);
}

View File

@ -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");
}
}

View File

@ -75,17 +75,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.
* <p>
* 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";
@ -333,17 +334,17 @@ 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 urlString The URL to remove from the download queue
* @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, "Preparing cancellation of " + canonicalUrl + " download");
Intent intent = new Intent(context, DownloaderService.class);
intent.setAction(ACTION_CANCEL);
intent.setData(Uri.parse(urlString));
intent.setData(Uri.parse(canonicalUrl));
context.startService(intent);
}
@ -352,21 +353,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, downloader.urlString);
}
public static void setTimeout(int ms) {

View File

@ -449,7 +449,7 @@ public class AppDetailsActivity extends AppCompatActivity
if (justReceived) {
adapter.setIndeterminateProgress(R.string.installing);
localBroadcastManager.registerReceiver(installReceiver,
Installer.getInstallIntentFilter(Uri.parse(newStatus.getCanonicalUrl())));
Installer.getInstallIntentFilter(newStatus.getCanonicalUrl()));
}
break;

View File

@ -483,8 +483,8 @@ public abstract class AppListItemController extends RecyclerView.ViewHolder {
}
if (currentStatus != null && currentStatus.status == AppUpdateStatusManager.Status.ReadyToInstall) {
String urlString = currentStatus.apk.getCanonicalUrl();
File apkFilePath = ApkCache.getApkDownloadPath(activity, urlString);
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);
@ -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);

View File

@ -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.getCanonicalUrl());
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;
}
}