diff --git a/F-Droid/res/values/strings.xml b/F-Droid/res/values/strings.xml index 737cbdc04..5326ce92c 100644 --- a/F-Droid/res/values/strings.xml +++ b/F-Droid/res/values/strings.xml @@ -389,6 +389,7 @@ this may cost you money Do you want to replace this app with the factory version? Do you want to uninstall this app? + Download completed, tap to install NEW: Provided by %1$s. diff --git a/F-Droid/src/org/fdroid/fdroid/net/AsyncDownloader.java b/F-Droid/src/org/fdroid/fdroid/net/AsyncDownloader.java index 57046e0c1..0f49c3e1d 100644 --- a/F-Droid/src/org/fdroid/fdroid/net/AsyncDownloader.java +++ b/F-Droid/src/org/fdroid/fdroid/net/AsyncDownloader.java @@ -10,6 +10,8 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.ParcelFileDescriptor; +import android.support.v4.content.LocalBroadcastManager; +import android.util.Log; import org.fdroid.fdroid.AppDetails; import org.fdroid.fdroid.ProgressListener; @@ -68,6 +70,7 @@ public class AsyncDownloader extends AsyncDownloadWrapper { @Override public void download() { + // Check if the download is complete if ((downloadId = isDownloadComplete(context, appId)) > 0) { // clear the notification dm.remove(downloadId); @@ -80,19 +83,25 @@ public class AsyncDownloader extends AsyncDownloadWrapper { } catch (IOException e) { listener.onErrorDownloading(e.getLocalizedMessage()); } - return; } - downloadId = isDownloading(context, appId); - if (downloadId >= 0) return; + // Check if the download is still in progress + if (downloadId < 0) { + downloadId = isDownloading(context, appId); + } - // 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 + // 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); + } - this.downloadId = dm.enqueue(request); + context.registerReceiver(receiver, + new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE)); } /** @@ -161,6 +170,12 @@ public class AsyncDownloader extends AsyncDownloadWrapper { @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); } @@ -191,6 +206,31 @@ public class AsyncDownloader extends AsyncDownloadWrapper { 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 @@ -267,4 +307,29 @@ public class AsyncDownloader extends AsyncDownloadWrapper { 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(); + } + } + } + }; } diff --git a/F-Droid/src/org/fdroid/fdroid/receiver/DownloadManagerReceiver.java b/F-Droid/src/org/fdroid/fdroid/receiver/DownloadManagerReceiver.java index 0a689b40d..0194a0aaf 100644 --- a/F-Droid/src/org/fdroid/fdroid/receiver/DownloadManagerReceiver.java +++ b/F-Droid/src/org/fdroid/fdroid/receiver/DownloadManagerReceiver.java @@ -1,15 +1,21 @@ package org.fdroid.fdroid.receiver; 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.v4.app.NotificationCompat; import org.fdroid.fdroid.AppDetails; +import org.fdroid.fdroid.R; import org.fdroid.fdroid.net.AsyncDownloader; /** - * Receive notifications from the Android DownloadManager + * Receive notifications from the Android DownloadManager and pass them onto the + * AppDetails activity */ public class DownloadManagerReceiver extends BroadcastReceiver { @Override @@ -18,12 +24,42 @@ public class DownloadManagerReceiver extends BroadcastReceiver { long downloadId = AsyncDownloader.getDownloadId(intent); String appId = AsyncDownloader.getAppId(context, downloadId); - // pass the download manager broadcast 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, appId); - context.startActivity(appDetails); + if (appId == null) { + // bogus broadcast (e.g. download cancelled, but system sent a DOWNLOAD_COMPLETE) + return; + } + + if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) { + // 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.putExtras(intent.getExtras()); + appDetails.putExtra(AppDetails.EXTRA_APPID, appId); + + PendingIntent pi = PendingIntent.getActivity( + context, 1, appDetails, PendingIntent.FLAG_ONE_SHOT); + + // launch LocalRepoActivity if the user selects this notification + String downloadTitle = AsyncDownloader.getDownloadTitle(context, downloadId); + Notification notif = new NotificationCompat.Builder(context) + .setContentTitle(downloadTitle) + .setContentText(context.getString(R.string.tap_to_install)) + .setSmallIcon(R.drawable.ic_stat_notify) + .setContentIntent(pi) + .setAutoCancel(true) + .build(); + + NotificationManager nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); + nm.notify((int)downloadId, notif); + } 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, appId); + context.startActivity(appDetails); + } } }