Refactored AsyncDownloader to only ever be constructed by DownloadFactory.

This commit is contained in:
Peter Serwylo 2015-09-09 08:31:21 +10:00
parent 9b7c4c7b4a
commit 69ecaf023f
7 changed files with 428 additions and 436 deletions

View File

@ -22,7 +22,6 @@
package org.fdroid.fdroid;
import android.app.Activity;
import android.app.DownloadManager;
import android.bluetooth.BluetoothAdapter;
import android.content.ActivityNotFoundException;
import android.content.BroadcastReceiver;
@ -35,7 +34,6 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.database.ContentObserver;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
@ -94,7 +92,7 @@ import org.fdroid.fdroid.installer.Installer;
import org.fdroid.fdroid.installer.Installer.AndroidNotCompatibleException;
import org.fdroid.fdroid.installer.Installer.InstallerCallback;
import org.fdroid.fdroid.net.ApkDownloader;
import org.fdroid.fdroid.net.AsyncDownloader;
import org.fdroid.fdroid.net.AsyncDownloaderFromAndroid;
import org.fdroid.fdroid.net.Downloader;
import java.io.File;
@ -435,7 +433,7 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A
localBroadcastManager = LocalBroadcastManager.getInstance(this);
// Check if a download is running for this app
if (AsyncDownloader.isDownloading(this, app.id) >= 0) {
if (AsyncDownloaderFromAndroid.isDownloading(this, app.id) >= 0) {
// call install() to re-setup the listeners and downloaders
// the AsyncDownloader will not restart the download since the download is running,
// and thus the version we pass to install() is not important

View File

@ -47,7 +47,7 @@ import java.security.NoSuchAlgorithmException;
* If the file has previously been downloaded, it will make use of that
* instead, without going to the network to download a new one.
*/
public class ApkDownloader implements AsyncDownloadWrapper.Listener {
public class ApkDownloader implements AsyncDownloader.Listener {
private static final String TAG = "ApkDownloader";
@ -78,7 +78,7 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
@NonNull private final SanitizedFile potentiallyCachedFile;
private ProgressListener listener;
private AsyncDownloadWrapper dlWrapper = null;
private AsyncDownloader dlWrapper = null;
private boolean isComplete = false;
private final long id = ++downloadIdCounter;
@ -197,17 +197,7 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
Utils.DebugLog(TAG, "Downloading apk from " + remoteAddress + " to " + localFile);
try {
if (canUseDownloadManager(new URL(remoteAddress))) {
// If we can use Android's DownloadManager, let's use it, because
// of better OS integration, reliability, and async ability
dlWrapper = new AsyncDownloader(context, this,
app.name + " " + curApk.version, curApk.id,
remoteAddress, localFile);
} else {
Downloader downloader = DownloaderFactory.create(context, remoteAddress, localFile);
dlWrapper = new AsyncDownloadWrapper(downloader, this);
}
dlWrapper = DownloaderFactory.createAsync(context, remoteAddress, localFile, app.name + " " + curApk.version, curApk.id, this);
dlWrapper.download();
return true;
} catch (IOException e) {
@ -217,17 +207,6 @@ public class ApkDownloader implements AsyncDownloadWrapper.Listener {
return false;
}
/**
* Tests to see if we can use Android's DownloadManager to download the APK, instead of
* a downloader returned from DownloadFactory.
* @param url
* @return
*/
private boolean canUseDownloadManager(URL url) {
return Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO
&& !DownloaderFactory.isOnionAddress(url);
}
private void sendMessage(String type) {
sendProgressEvent(new ProgressListener.Event(type));
}

View File

@ -1,114 +0,0 @@
package org.fdroid.fdroid.net;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import org.fdroid.fdroid.ProgressListener;
import java.io.IOException;
/**
* Given a {@link org.fdroid.fdroid.net.Downloader}, this wrapper will conduct the download operation on a
* separate thread. All progress/status/error/etc events will be forwarded from that thread to the thread
* that {@link AsyncDownloadWrapper#download()} was invoked on. If you want to respond with UI feedback
* to these events, it is important that you execute the download method of this class from the UI thread.
* That way, all forwarded events will be handled on that thread.
*/
@SuppressWarnings("serial")
public class AsyncDownloadWrapper extends Handler {
private static final String TAG = "AsyncDownloadWrapper";
private static final int MSG_DOWNLOAD_COMPLETE = 2;
private static final int MSG_DOWNLOAD_CANCELLED = 3;
private static final int MSG_ERROR = 4;
private static final String MSG_DATA = "data";
private final Downloader downloader;
private final Listener listener;
private DownloadThread downloadThread = null;
/**
* Normally the listener would be provided using a setListener method.
* However for the purposes of this async downloader, it doesn't make
* sense to have an async task without any way to notify the outside
* world about completion. Therefore, we require the listener as a
* parameter to the constructor.
*/
public AsyncDownloadWrapper(Downloader downloader, Listener listener) {
this.downloader = downloader;
this.listener = listener;
}
public void download() {
downloadThread = new DownloadThread();
downloadThread.start();
}
public void attemptCancel(boolean userRequested) {
if (downloadThread != null) {
downloadThread.interrupt();
}
}
/**
* Receives "messages" from the download thread, and passes them onto the
* relevant {@link org.fdroid.fdroid.net.AsyncDownloadWrapper.Listener}
* @param message
*/
public void handleMessage(Message message) {
switch (message.arg1) {
case MSG_DOWNLOAD_COMPLETE:
listener.onDownloadComplete();
break;
case MSG_DOWNLOAD_CANCELLED:
listener.onDownloadCancelled();
break;
case MSG_ERROR:
listener.onErrorDownloading(message.getData().getString(MSG_DATA));
break;
}
}
public int getBytesRead() {
return downloader.getBytesRead();
}
public int getTotalBytes() {
return downloader.getTotalBytes();
}
public interface Listener extends ProgressListener {
void onErrorDownloading(String localisedExceptionDetails);
void onDownloadComplete();
void onDownloadCancelled();
}
private class DownloadThread extends Thread {
public void run() {
try {
downloader.download();
sendMessage(MSG_DOWNLOAD_COMPLETE);
} catch (InterruptedException e) {
sendMessage(MSG_DOWNLOAD_CANCELLED);
} catch (IOException e) {
Log.e(TAG, "I/O exception in download thread", e);
Bundle data = new Bundle(1);
data.putString(MSG_DATA, e.getLocalizedMessage());
Message message = new Message();
message.arg1 = MSG_ERROR;
message.setData(data);
AsyncDownloadWrapper.this.sendMessage(message);
}
}
private void sendMessage(int messageType) {
Message message = new Message();
message.arg1 = messageType;
AsyncDownloadWrapper.this.sendMessage(message);
}
}
}

View File

@ -1,47 +1,34 @@
package org.fdroid.fdroid.net;
import android.annotation.TargetApi;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.support.v4.content.LocalBroadcastManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import org.fdroid.fdroid.AppDetails;
import org.fdroid.fdroid.ProgressListener;
import org.fdroid.fdroid.data.Apk;
import org.fdroid.fdroid.data.App;
import org.fdroid.fdroid.data.SanitizedFile;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A downloader that uses Android's DownloadManager to perform a download.
* Given a {@link org.fdroid.fdroid.net.Downloader}, this wrapper will conduct the download operation on a
* separate thread. All progress/status/error/etc events will be forwarded from that thread to the thread
* that {@link AsyncDownloader#download()} was invoked on. If you want to respond with UI feedback
* to these events, it is important that you execute the download method of this class from the UI thread.
* That way, all forwarded events will be handled on that thread.
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public class AsyncDownloader extends AsyncDownloadWrapper {
private final Context context;
private final DownloadManager dm;
private SanitizedFile localFile;
private String remoteAddress;
private String appName;
private String appId;
private Listener listener;
@SuppressWarnings("serial")
public class AsyncDownloader extends Handler {
private long downloadId = -1;
private static final String TAG = "AsyncDownloadWrapper";
private static final int MSG_DOWNLOAD_COMPLETE = 2;
private static final int MSG_DOWNLOAD_CANCELLED = 3;
private static final int MSG_ERROR = 4;
private static final String MSG_DATA = "data";
private final Downloader downloader;
private final Listener listener;
private DownloadThread downloadThread = null;
/**
* Normally the listener would be provided using a setListener method.
@ -49,287 +36,79 @@ public class AsyncDownloader extends AsyncDownloadWrapper {
* sense to have an async task without any way to notify the outside
* world about completion. Therefore, we require the listener as a
* parameter to the constructor.
*
* @param listener
*/
public AsyncDownloader(Context context, Listener listener, String appName, String appId, String remoteAddress, SanitizedFile localFile) {
super(null, listener);
this.context = context;
this.appName = appName;
this.appId = appId;
this.remoteAddress = remoteAddress;
this.listener = listener;
this.localFile = localFile;
if (appName == null || appName.trim().length() == 0) {
this.appName = remoteAddress;
}
dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
public AsyncDownloader(Downloader downloader, Listener listener) {
this.downloader = downloader;
this.listener = listener;
}
@Override
public void download() {
// Check if the download is complete
if ((downloadId = isDownloadComplete(context, appId)) > 0) {
// clear the notification
dm.remove(downloadId);
try {
// write the downloaded file to the expected location
ParcelFileDescriptor fd = dm.openDownloadedFile(downloadId);
copyFile(fd.getFileDescriptor(), localFile);
listener.onDownloadComplete();
} catch (IOException e) {
listener.onErrorDownloading(e.getLocalizedMessage());
}
return;
}
// Check if the download is still in progress
if (downloadId < 0) {
downloadId = isDownloading(context, appId);
}
// Start a new download
if (downloadId < 0) {
// set up download request
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteAddress));
request.setTitle(appName);
request.setDescription(appId); // we will retrieve this later from the description field
this.downloadId = dm.enqueue(request);
}
context.registerReceiver(receiver,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
downloadThread = new DownloadThread();
downloadThread.start();
}
/**
* Copy input file to output file
* @param inputFile
* @param outputFile
* @throws IOException
*/
private void copyFile(FileDescriptor inputFile, SanitizedFile outputFile) throws IOException {
InputStream is = new FileInputStream(inputFile);
OutputStream os = new FileOutputStream(outputFile);
byte[] buffer = new byte[1024];
int count = 0;
try {
while ((count = is.read(buffer, 0, buffer.length)) > 0) {
os.write(buffer, 0, count);
}
} finally {
os.close();
is.close();
}
}
@Override
public int getBytesRead() {
if (downloadId < 0) return 0;
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
return c.getInt(columnIndex);
}
} finally {
c.close();
}
return 0;
}
@Override
public int getTotalBytes() {
if (downloadId < 0) return 0;
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
return c.getInt(columnIndex);
}
} finally {
c.close();
}
return 0;
}
@Override
public void attemptCancel(boolean userRequested) {
try {
context.unregisterReceiver(receiver);
} catch (Exception e) {
// ignore if receiver already unregistered
}
if (userRequested && downloadId >= 0) {
dm.remove(downloadId);
if (downloadThread != null) {
downloadThread.interrupt();
}
}
/**
* Extract the appId from a given download id.
* @param context
* @param downloadId
* @return - appId or null if not found
* Receives "messages" from the download thread, and passes them onto the
* relevant {@link AsyncDownloader.Listener}
* @param message
*/
public static String getAppId(Context context, long downloadId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
return c.getString(columnIndex);
}
} finally {
c.close();
public void handleMessage(Message message) {
switch (message.arg1) {
case MSG_DOWNLOAD_COMPLETE:
listener.onDownloadComplete();
break;
case MSG_DOWNLOAD_CANCELLED:
listener.onDownloadCancelled();
break;
case MSG_ERROR:
listener.onErrorDownloading(message.getData().getString(MSG_DATA));
break;
}
return null;
}
/**
* Extract the download title from a given download id.
* @param context
* @param downloadId
* @return - title or null if not found
*/
public static String getDownloadTitle(Context context, long downloadId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE);
return c.getString(columnIndex);
}
} finally {
c.close();
}
return null;
public int getBytesRead() {
return downloader.getBytesRead();
}
/**
* Get the downloadId from an Intent sent by the DownloadManagerReceiver
* @param intent
* @return
*/
public static long getDownloadId(Intent intent) {
if (intent != null) {
if (intent.hasExtra(DownloadManager.EXTRA_DOWNLOAD_ID)) {
// we have been passed a DownloadManager download id, so get the app id for it
return intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
}
if (intent.hasExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS)) {
// we have been passed multiple download id's - just return the first one
long[] downloadIds = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
if (downloadIds != null && downloadIds.length > 0) {
return downloadIds[0];
}
}
}
return -1;
public int getTotalBytes() {
return downloader.getTotalBytes();
}
/**
* Check if a download is running for the app
* @param context
* @param appId
* @return -1 if not downloading, else the downloadId
*/
public static long isDownloading(Context context, String appId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
Cursor c = dm.query(query);
int columnAppId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
try {
while (c.moveToNext()) {
if (appId.equals(c.getString(columnAppId))) {
return c.getLong(columnId);
}
}
} finally {
c.close();
}
return -1;
public interface Listener extends ProgressListener {
void onErrorDownloading(String localisedExceptionDetails);
void onDownloadComplete();
void onDownloadCancelled();
}
/**
* Check if a download for an app is complete.
* @param context
* @param appId
* @return -1 if download is not complete, otherwise the download id
*/
public static long isDownloadComplete(Context context, String appId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
Cursor c = dm.query(query);
int columnAppId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
private class DownloadThread extends Thread {
try {
while (c.moveToNext()) {
if (appId.equals(c.getString(columnAppId))) {
return c.getLong(columnId);
}
public void run() {
try {
downloader.download();
sendMessage(MSG_DOWNLOAD_COMPLETE);
} catch (InterruptedException e) {
sendMessage(MSG_DOWNLOAD_CANCELLED);
} catch (IOException e) {
Log.e(TAG, "I/O exception in download thread", e);
Bundle data = new Bundle(1);
data.putString(MSG_DATA, e.getLocalizedMessage());
Message message = new Message();
message.arg1 = MSG_ERROR;
message.setData(data);
AsyncDownloader.this.sendMessage(message);
}
} finally {
c.close();
}
return -1;
private void sendMessage(int messageType) {
Message message = new Message();
message.arg1 = messageType;
AsyncDownloader.this.sendMessage(message);
}
}
/**
* Broadcast receiver to listen for ACTION_DOWNLOAD_COMPLETE broadcasts
*/
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
long dId = getDownloadId(intent);
String appId = getAppId(context, dId);
if (listener != null && dId == downloadId && appId != null) {
// our current download has just completed, so let's throw up install dialog
// immediately
try {
context.unregisterReceiver(receiver);
} catch (Exception e) {
// ignore if receiver already unregistered
}
// call download() to copy the file and start the installer
download();
}
}
}
};
}

View File

@ -0,0 +1,326 @@
package org.fdroid.fdroid.net;
import android.annotation.TargetApi;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.ParcelFileDescriptor;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* A downloader that uses Android's DownloadManager to perform a download.
*/
@TargetApi(Build.VERSION_CODES.GINGERBREAD)
public class AsyncDownloaderFromAndroid extends AsyncDownloader {
private final Context context;
private final DownloadManager dm;
private File localFile;
private String remoteAddress;
private String appName;
private String appId;
private Listener listener;
private long downloadId = -1;
/**
* Normally the listener would be provided using a setListener method.
* However for the purposes of this async downloader, it doesn't make
* sense to have an async task without any way to notify the outside
* world about completion. Therefore, we require the listener as a
* parameter to the constructor.
*
* @param listener
*/
public AsyncDownloaderFromAndroid(Context context, Listener listener, String appName, String appId, String remoteAddress, File localFile) {
super(null, listener);
this.context = context;
this.appName = appName;
this.appId = appId;
this.remoteAddress = remoteAddress;
this.listener = listener;
this.localFile = localFile;
if (appName == null || appName.trim().length() == 0) {
this.appName = remoteAddress;
}
dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
}
@Override
public void download() {
// Check if the download is complete
if ((downloadId = isDownloadComplete(context, appId)) > 0) {
// clear the notification
dm.remove(downloadId);
try {
// write the downloaded file to the expected location
ParcelFileDescriptor fd = dm.openDownloadedFile(downloadId);
copyFile(fd.getFileDescriptor(), localFile);
listener.onDownloadComplete();
} catch (IOException e) {
listener.onErrorDownloading(e.getLocalizedMessage());
}
return;
}
// Check if the download is still in progress
if (downloadId < 0) {
downloadId = isDownloading(context, appId);
}
// Start a new download
if (downloadId < 0) {
// set up download request
DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteAddress));
request.setTitle(appName);
request.setDescription(appId); // we will retrieve this later from the description field
this.downloadId = dm.enqueue(request);
}
context.registerReceiver(receiver,
new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
}
/**
* Copy input file to output file
* @param inputFile
* @param outputFile
* @throws IOException
*/
private void copyFile(FileDescriptor inputFile, File outputFile) throws IOException {
InputStream is = new FileInputStream(inputFile);
OutputStream os = new FileOutputStream(outputFile);
byte[] buffer = new byte[1024];
int count = 0;
try {
while ((count = is.read(buffer, 0, buffer.length)) > 0) {
os.write(buffer, 0, count);
}
} finally {
os.close();
is.close();
}
}
@Override
public int getBytesRead() {
if (downloadId < 0) return 0;
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
return c.getInt(columnIndex);
}
} finally {
c.close();
}
return 0;
}
@Override
public int getTotalBytes() {
if (downloadId < 0) return 0;
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
return c.getInt(columnIndex);
}
} finally {
c.close();
}
return 0;
}
@Override
public void attemptCancel(boolean userRequested) {
try {
context.unregisterReceiver(receiver);
} catch (Exception e) {
// ignore if receiver already unregistered
}
if (userRequested && downloadId >= 0) {
dm.remove(downloadId);
}
}
/**
* Extract the appId from a given download id.
* @param context
* @param downloadId
* @return - appId or null if not found
*/
public static String getAppId(Context context, long downloadId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
return c.getString(columnIndex);
}
} finally {
c.close();
}
return null;
}
/**
* Extract the download title from a given download id.
* @param context
* @param downloadId
* @return - title or null if not found
*/
public static String getDownloadTitle(Context context, long downloadId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterById(downloadId);
Cursor c = dm.query(query);
try {
if (c.moveToFirst()) {
// we use the description column to store the app id
int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE);
return c.getString(columnIndex);
}
} finally {
c.close();
}
return null;
}
/**
* Get the downloadId from an Intent sent by the DownloadManagerReceiver
* @param intent
* @return
*/
public static long getDownloadId(Intent intent) {
if (intent != null) {
if (intent.hasExtra(DownloadManager.EXTRA_DOWNLOAD_ID)) {
// we have been passed a DownloadManager download id, so get the app id for it
return intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
}
if (intent.hasExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS)) {
// we have been passed multiple download id's - just return the first one
long[] downloadIds = intent.getLongArrayExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS);
if (downloadIds != null && downloadIds.length > 0) {
return downloadIds[0];
}
}
}
return -1;
}
/**
* Check if a download is running for the app
* @param context
* @param appId
* @return -1 if not downloading, else the downloadId
*/
public static long isDownloading(Context context, String appId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
Cursor c = dm.query(query);
int columnAppId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
try {
while (c.moveToNext()) {
if (appId.equals(c.getString(columnAppId))) {
return c.getLong(columnId);
}
}
} finally {
c.close();
}
return -1;
}
/**
* Check if a download for an app is complete.
* @param context
* @param appId
* @return -1 if download is not complete, otherwise the download id
*/
public static long isDownloadComplete(Context context, String appId) {
DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE);
DownloadManager.Query query = new DownloadManager.Query();
query.setFilterByStatus(DownloadManager.STATUS_SUCCESSFUL);
Cursor c = dm.query(query);
int columnAppId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION);
int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID);
try {
while (c.moveToNext()) {
if (appId.equals(c.getString(columnAppId))) {
return c.getLong(columnId);
}
}
} finally {
c.close();
}
return -1;
}
/**
* Broadcast receiver to listen for ACTION_DOWNLOAD_COMPLETE broadcasts
*/
BroadcastReceiver receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
long dId = getDownloadId(intent);
String appId = getAppId(context, dId);
if (listener != null && dId == downloadId && appId != null) {
// our current download has just completed, so let's throw up install dialog
// immediately
try {
context.unregisterReceiver(receiver);
} catch (Exception e) {
// ignore if receiver already unregistered
}
// call download() to copy the file and start the installer
download();
}
}
}
};
}

View File

@ -1,9 +1,11 @@
package org.fdroid.fdroid.net;
import android.content.Context;
import android.os.Build;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
public class DownloaderFactory {
@ -51,7 +53,29 @@ public class DownloaderFactory {
return "bluetooth".equalsIgnoreCase(url.getProtocol());
}
public static AsyncDownloader createAsync(Context context, String urlString, File destFile, String title, String id, AsyncDownloader.Listener listener) throws IOException {
return createAsync(context, new URL(urlString), destFile, title, id, listener);
}
public static AsyncDownloader createAsync(Context context, URL url, File destFile, String title, String id, AsyncDownloader.Listener listener)
throws IOException {
if (canUseDownloadManager(url)) {
return new AsyncDownloaderFromAndroid(context, listener, title, id, url.toString(), destFile);
} else {
return new AsyncDownloader(create(context, url, destFile), listener);
}
}
static boolean isOnionAddress(URL url) {
return url.getHost().endsWith(".onion");
}
/**
* Tests to see if we can use Android's DownloadManager to download the APK, instead of
* a downloader returned from DownloadFactory.
*/
private static boolean canUseDownloadManager(URL url) {
return Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO && !isOnionAddress(url);
}
}

View File

@ -11,7 +11,7 @@ import android.support.v4.app.NotificationCompat;
import org.fdroid.fdroid.AppDetails;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.net.AsyncDownloader;
import org.fdroid.fdroid.net.AsyncDownloaderFromAndroid;
/**
* Receive notifications from the Android DownloadManager and pass them onto the
@ -21,8 +21,8 @@ public class DownloadManagerReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
// work out the app Id to send to the AppDetails Screen
long downloadId = AsyncDownloader.getDownloadId(intent);
String appId = AsyncDownloader.getAppId(context, downloadId);
long downloadId = AsyncDownloaderFromAndroid.getDownloadId(intent);
String appId = AsyncDownloaderFromAndroid.getAppId(context, downloadId);
if (appId == null) {
// bogus broadcast (e.g. download cancelled, but system sent a DOWNLOAD_COMPLETE)
@ -41,7 +41,7 @@ public class DownloadManagerReceiver extends BroadcastReceiver {
context, 1, appDetails, PendingIntent.FLAG_ONE_SHOT);
// launch LocalRepoActivity if the user selects this notification
String downloadTitle = AsyncDownloader.getDownloadTitle(context, downloadId);
String downloadTitle = AsyncDownloaderFromAndroid.getDownloadTitle(context, downloadId);
Notification notif = new NotificationCompat.Builder(context)
.setContentTitle(downloadTitle)
.setContentText(context.getString(R.string.tap_to_install))