From ef40b5f3db3b00200754fba1d5167a82297ea4d1 Mon Sep 17 00:00:00 2001
From: Toby Kurien <toby@tobykurien.com>
Date: Sat, 5 Sep 2015 11:54:17 +0200
Subject: [PATCH] Now displays a notification when download is complete, unless
 use is already on the app details screen, in which case it pops up the
 installer immediately

---
 F-Droid/res/values/strings.xml                |  1 +
 .../fdroid/fdroid/net/AsyncDownloader.java    | 81 +++++++++++++++++--
 .../receiver/DownloadManagerReceiver.java     | 52 ++++++++++--
 3 files changed, 118 insertions(+), 16 deletions(-)

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 @@
     <string name="perm_costs_money">this may cost you money</string>
     <string name="uninstall_update_confirm">Do you want to replace this app with the factory version?</string>
     <string name="uninstall_confirm">Do you want to uninstall this app?</string>
+	<string name="tap_to_install">Download completed, tap to install</string>
 
     <string name="perms_new_perm_prefix">NEW: </string>
     <string name="perms_description_app">Provided by %1$s.</string>
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);
+        }
     }
 }