diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index df55f5589..61ed38012 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -437,14 +437,6 @@ - - - - - - - - diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java index 9617cdf2c..6785e52ea 100644 --- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java +++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java @@ -92,7 +92,6 @@ 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.AsyncDownloaderFromAndroid; import org.fdroid.fdroid.net.Downloader; import java.io.File; @@ -434,18 +433,6 @@ public class AppDetails extends AppCompatActivity implements ProgressListener, A } localBroadcastManager = LocalBroadcastManager.getInstance(this); - - // Check if a download is running for this app - if (AsyncDownloaderFromAndroid.isDownloading(this, app.packageName) >= 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 - refreshHeader(); - refreshApkList(); - final Apk apkToInstall = ApkProvider.Helper.find(this, app.packageName, app.suggestedVercode); - install(apkToInstall); - } - } @Override diff --git a/app/src/main/java/org/fdroid/fdroid/net/AsyncDownloaderFromAndroid.java b/app/src/main/java/org/fdroid/fdroid/net/AsyncDownloaderFromAndroid.java deleted file mode 100644 index 409e56e6f..000000000 --- a/app/src/main/java/org/fdroid/fdroid/net/AsyncDownloaderFromAndroid.java +++ /dev/null @@ -1,396 +0,0 @@ -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.text.TextUtils; - -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.Utils; - -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 implements AsyncDownloader { - private final Context context; - private final DownloadManager dm; - private final LocalBroadcastManager localBroadcastManager; - private final File localFile; - private final String remoteAddress; - private final String downloadTitle; - private final String uniqueDownloadId; - private final Listener listener; - private boolean isCancelled; - - private long downloadManagerId = -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. - */ - public AsyncDownloaderFromAndroid(Context context, Listener listener, String downloadTitle, String downloadId, String remoteAddress, File localFile) { - this.context = context; - this.uniqueDownloadId = downloadId; - this.remoteAddress = remoteAddress; - this.listener = listener; - this.localFile = localFile; - - if (TextUtils.isEmpty(downloadTitle)) { - this.downloadTitle = remoteAddress; - } else { - this.downloadTitle = downloadTitle; - } - - dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - localBroadcastManager = LocalBroadcastManager.getInstance(context); - } - - @Override - public void download() { - isCancelled = false; - - // check if download failed - if (downloadManagerId >= 0) { - int status = validDownload(context, downloadManagerId); - if (status > 0) { - // error downloading - dm.remove(downloadManagerId); - if (listener != null) { - listener.onErrorDownloading(context.getString(R.string.download_error)); - } - return; - } - } - - // Check if the download is complete - downloadManagerId = isDownloadComplete(context, uniqueDownloadId); - if (downloadManagerId > 0) { - // clear the download - dm.remove(downloadManagerId); - - try { - // write the downloaded file to the expected location - ParcelFileDescriptor fd = dm.openDownloadedFile(downloadManagerId); - copyFile(fd.getFileDescriptor(), localFile); - listener.onDownloadComplete(); - } catch (IOException e) { - listener.onErrorDownloading(e.getLocalizedMessage()); - } - return; - } - - // Check if the download is still in progress - if (downloadManagerId < 0) { - downloadManagerId = isDownloading(context, uniqueDownloadId); - } - - // Start a new download - if (downloadManagerId < 0) { - // set up download request - DownloadManager.Request request = new DownloadManager.Request(Uri.parse(remoteAddress)); - request.setTitle(downloadTitle); - request.setDescription(uniqueDownloadId); // we will retrieve this later from the description field - this.downloadManagerId = dm.enqueue(request); - } - - context.registerReceiver(receiver, - new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); - - Thread progressThread = new Thread() { - @Override - public void run() { - while (!isCancelled && isDownloading(context, uniqueDownloadId) >= 0) { - try { - Thread.sleep(1000); - } catch (Exception e) { - // ignore - } - sendProgress(getBytesRead(), getTotalBytes()); - } - } - }; - progressThread.start(); - } - - /** - * Copy input file to output file - * @throws IOException - */ - private void copyFile(FileDescriptor inputFile, File outputFile) throws IOException { - InputStream input = null; - OutputStream output = null; - try { - input = new FileInputStream(inputFile); - output = new FileOutputStream(outputFile); - Utils.copy(input, output); - } finally { - Utils.closeQuietly(output); - Utils.closeQuietly(input); - } - } - - @Override - public int getBytesRead() { - if (downloadManagerId < 0) return 0; - - DownloadManager.Query query = new DownloadManager.Query(); - query.setFilterById(downloadManagerId); - Cursor c = dm.query(query); - - try { - if (c.moveToFirst()) { - // we use the description column to store the unique id of this download - int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR); - return c.getInt(columnIndex); - } - } finally { - c.close(); - } - - return 0; - } - - @Override - public int getTotalBytes() { - if (downloadManagerId < 0) return 0; - - DownloadManager.Query query = new DownloadManager.Query(); - query.setFilterById(downloadManagerId); - Cursor c = dm.query(query); - - try { - if (c.moveToFirst()) { - // we use the description column to store the unique id for this download - int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES); - return c.getInt(columnIndex); - } - } finally { - c.close(); - } - - return 0; - } - - private void sendProgress(int bytesRead, int totalBytes) { - Intent intent = new Intent(Downloader.LOCAL_ACTION_PROGRESS); - intent.putExtra(Downloader.EXTRA_ADDRESS, remoteAddress); - intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead); - intent.putExtra(Downloader.EXTRA_TOTAL_BYTES, totalBytes); - localBroadcastManager.sendBroadcast(intent); - } - - @Override - public void attemptCancel(boolean userRequested) { - isCancelled = true; - try { - context.unregisterReceiver(receiver); - } catch (Exception e) { - // ignore if receiver already unregistered - } - - if (userRequested && downloadManagerId >= 0) { - dm.remove(downloadManagerId); - } - } - - /** - * Extract the uniqueDownloadId from a given download id. - * @return - uniqueDownloadId or null if not found - */ - public static String getDownloadId(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 unique id for this download - 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. - * @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()) { - int columnIndex = c.getColumnIndex(DownloadManager.COLUMN_TITLE); - return c.getString(columnIndex); - } - } finally { - c.close(); - } - - return null; - } - - /** - * Get the downloadManagerId from an Intent sent by the DownloadManagerReceiver - */ - @TargetApi(Build.VERSION_CODES.HONEYCOMB) - 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 unique id for that download - 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 specified id - * @return -1 if not downloading, else the id from the Android download manager - */ - public static long isDownloading(Context context, String uniqueDownloadId) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { - // TODO: remove. This is necessary because AppDetails calls this - // static method directly, without using the whole pipe through - // DownloaderFactory. This shouldn't be called at all on android-8 - // devices, since AppDetails is really using the old downloader, - // not this one. - return -1; - } - DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Query query = new DownloadManager.Query(); - Cursor c = dm.query(query); - if (c == null) { - // TODO: same as above. - return -1; - } - int columnUniqueDownloadId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION); - int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID); - - try { - while (c.moveToNext()) { - if (uniqueDownloadId.equals(c.getString(columnUniqueDownloadId))) { - return c.getLong(columnId); - } - } - } finally { - c.close(); - } - - return -1; - } - - /** - * Check if a specific download is complete. - * @return -1 if download is not complete, otherwise the download id - */ - private static long isDownloadComplete(Context context, String uniqueDownloadId) { - 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 columnUniqueDownloadId = c.getColumnIndex(DownloadManager.COLUMN_DESCRIPTION); - int columnId = c.getColumnIndex(DownloadManager.COLUMN_ID); - - try { - while (c.moveToNext()) { - if (uniqueDownloadId.equals(c.getString(columnUniqueDownloadId))) { - return c.getLong(columnId); - } - } - } finally { - c.close(); - } - - return -1; - } - - /** - * Check if download was valid, see issue - * http://code.google.com/p/android/issues/detail?id=18462 - * From http://stackoverflow.com/questions/8937817/downloadmanager-action-download-complete-broadcast-receiver-receiving-same-downl - * @return 0 if successful, -1 if download doesn't exist, else the DownloadManager.ERROR_... code - */ - public static int validDownload(Context context, long downloadId) { - //Verify if download is a success - DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - Cursor c = dm.query(new DownloadManager.Query().setFilterById(downloadId)); - - try { - if (c.moveToFirst()) { - int status = c.getInt(c.getColumnIndex(DownloadManager.COLUMN_STATUS)); - - if (status == DownloadManager.STATUS_SUCCESSFUL) { - return 0; // Download is valid, celebrate - } - return c.getInt(c.getColumnIndex(DownloadManager.COLUMN_REASON)); - } - } finally { - c.close(); - } - - return -1; // download doesn't exist - } - - /** - * Broadcast receiver to listen for ACTION_DOWNLOAD_COMPLETE broadcasts - */ - private final 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 downloadId = getDownloadId(context, dId); - if (listener != null && dId == AsyncDownloaderFromAndroid.this.downloadManagerId && downloadId != 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(); - } - } - } - }; -} diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java index 2ac93be4d..b65001cb8 100644 --- a/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java +++ b/app/src/main/java/org/fdroid/fdroid/net/DownloaderFactory.java @@ -1,14 +1,9 @@ package org.fdroid.fdroid.net; -import android.annotation.TargetApi; -import android.app.DownloadManager; import android.content.Context; import android.content.Intent; -import android.database.Cursor; -import android.os.Build; import android.support.v4.content.LocalBroadcastManager; -import org.fdroid.fdroid.Utils; import org.fdroid.fdroid.data.Credentials; import java.io.File; @@ -90,62 +85,11 @@ public class DownloaderFactory { } public static AsyncDownloader createAsync(Context context, String urlString, File destFile, String title, String id, Credentials credentials, AsyncDownloader.Listener listener) throws IOException { - return createAsync(context, new URL(urlString), destFile, title, id, credentials, listener); - } - - public static AsyncDownloader createAsync(Context context, URL url, File destFile, String title, String id, Credentials credentials, AsyncDownloader.Listener listener) - throws IOException { - // To re-enable, fix the following: - // * https://gitlab.com/fdroid/fdroidclient/issues/445 - // * https://gitlab.com/fdroid/fdroidclient/issues/459 - if (false && canUseDownloadManager(context, url)) { - Utils.debugLog(TAG, "Using AsyncDownloaderFromAndroid"); - return new AsyncDownloaderFromAndroid(context, listener, title, id, url.toString(), destFile); - } - Utils.debugLog(TAG, "Using AsyncDownloadWrapper"); + URL url = new URL(urlString); return new AsyncDownloadWrapper(create(context, url, destFile, credentials), listener); } private static boolean isOnionAddress(URL url) { return url.getHost().endsWith(".onion"); } - - @TargetApi(Build.VERSION_CODES.GINGERBREAD) - private static boolean hasDownloadManager(Context context) { - DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - if (dm == null) { - // Service was not found - return false; - } - DownloadManager.Query query = new DownloadManager.Query(); - Cursor c = dm.query(query); - if (c == null) { - // Download Manager was disabled - return false; - } - c.close(); - return true; - } - - /** - * 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(Context context, URL url) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - // No HTTPS support on 2.3, no DownloadManager on 2.2. Don't have - // 3.0 devices to test on, so require 4.0. - return false; - } - if (isOnionAddress(url)) { - // We support onion addresses through our own downloader. - return false; - } - if (isBluetoothAddress(url)) { - // Completely differnet protocol not understood by the download manager. - return false; - } - return hasDownloadManager(context); - } - } diff --git a/app/src/main/java/org/fdroid/fdroid/receiver/DownloadManagerReceiver.java b/app/src/main/java/org/fdroid/fdroid/receiver/DownloadManagerReceiver.java deleted file mode 100644 index 1da9af76a..000000000 --- a/app/src/main/java/org/fdroid/fdroid/receiver/DownloadManagerReceiver.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.fdroid.fdroid.receiver; - -import android.annotation.TargetApi; -import android.app.DownloadManager; -import android.app.Notification; -import android.app.NotificationManager; -import android.app.PendingIntent; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.support.annotation.StringRes; -import android.support.v4.app.NotificationCompat; - -import org.fdroid.fdroid.AppDetails; -import org.fdroid.fdroid.R; -import org.fdroid.fdroid.net.AsyncDownloaderFromAndroid; - -/** - * Receive notifications from the Android DownloadManager and pass them onto the - * AppDetails activity - */ -@TargetApi(9) -public class DownloadManagerReceiver extends BroadcastReceiver { - @Override - public void onReceive(Context context, Intent intent) { - // work out the package name to send to the AppDetails Screen - long downloadId = AsyncDownloaderFromAndroid.getDownloadId(intent); - String packageName = AsyncDownloaderFromAndroid.getDownloadId(context, downloadId); - - if (packageName == null) { - // bogus broadcast (e.g. download cancelled, but system sent a DOWNLOAD_COMPLETE) - return; - } - - if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { - int status = AsyncDownloaderFromAndroid.validDownload(context, downloadId); - if (status == 0) { - // successful download - showNotification(context, packageName, intent, downloadId, R.string.tap_to_install); - } else { - // download failed! - showNotification(context, packageName, intent, downloadId, R.string.download_error); - - // clear the download to allow user to download again - DownloadManager dm = (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - dm.remove(downloadId); - } - } else if (DownloadManager.ACTION_NOTIFICATION_CLICKED.equals(intent.getAction())) { - // pass the notification click onto the AppDetails screen and let it handle it - Intent appDetails = new Intent(context, AppDetails.class); - appDetails.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - appDetails.setAction(intent.getAction()); - appDetails.putExtras(intent.getExtras()); - appDetails.putExtra(AppDetails.EXTRA_APPID, packageName); - context.startActivity(appDetails); - } - } - - private void showNotification(Context context, String packageName, Intent intent, long downloadId, - @StringRes int messageResId) { - // show a notification the user can click to install the app - Intent appDetails = new Intent(context, AppDetails.class); - appDetails.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - appDetails.setAction(intent.getAction()); - appDetails.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId); - appDetails.putExtra(AppDetails.EXTRA_APPID, packageName); - - // set separate pending intents per download id - PendingIntent pi = PendingIntent.getActivity( - context, (int) downloadId, appDetails, PendingIntent.FLAG_ONE_SHOT); - - // build & show notification - String downloadTitle = AsyncDownloaderFromAndroid.getDownloadTitle(context, downloadId); - Notification notif = new NotificationCompat.Builder(context) - .setContentTitle(downloadTitle) - .setContentText(context.getString(messageResId)) - .setSmallIcon(R.drawable.ic_stat_notify) - .setContentIntent(pi) - .setAutoCancel(true) - .build(); - - NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); - nm.notify((int) downloadId, notif); - } -}