diff --git a/app/src/main/java/org/fdroid/fdroid/AppDetails.java b/app/src/main/java/org/fdroid/fdroid/AppDetails.java
index a295724d0..149065263 100644
--- a/app/src/main/java/org/fdroid/fdroid/AppDetails.java
+++ b/app/src/main/java/org/fdroid/fdroid/AppDetails.java
@@ -426,8 +426,8 @@ public class AppDetails extends AppCompatActivity {
@Override
protected void onResumeFragments() {
super.onResumeFragments();
+ headerFragment = (AppDetailsHeaderFragment) getSupportFragmentManager().findFragmentById(R.id.header);
refreshApkList();
- refreshHeader();
supportInvalidateOptionsMenu();
if (DownloaderService.isQueuedOrActive(activeDownloadUrlString)) {
registerDownloaderReceivers();
@@ -564,7 +564,7 @@ public class AppDetails extends AppCompatActivity {
@Override
protected void onDestroy() {
- cleanUpFinishedDownload();
+ unregisterDownloaderReceivers();
super.onDestroy();
}
@@ -615,8 +615,6 @@ public class AppDetails extends AppCompatActivity {
}
private void refreshHeader() {
- headerFragment = (AppDetailsHeaderFragment)
- getSupportFragmentManager().findFragmentById(R.id.header);
if (headerFragment != null) {
headerFragment.updateViews();
}
@@ -1416,17 +1414,44 @@ public class AppDetails extends AppCompatActivity {
public void onResume() {
super.onResume();
updateViews();
+ restoreProgressBarOnResume();
+ }
+
+ /**
+ * After resuming the fragment, decide whether or not we need to show the progress bar.
+ * Also, put an appropriate message depending on whether or not the download is active or
+ * just queued.
+ *
+ * NOTE: this can't be done in the `updateViews` method as it currently stands. The reason
+ * is because that method gets called all the time, for all sorts of reasons. The progress
+ * bar is updated with actual progress values in response to async broadcasts. If we always
+ * tried to force the progress bar in `updateViews`, it would override the values that were
+ * set by the async progress broadcasts.
+ */
+ private void restoreProgressBarOnResume() {
+ if (appDetails.activeDownloadUrlString != null) {
+ // We don't actually know what the current progress is, so this will show an indeterminate
+ // progress bar until the first progress/complete event we receive.
+ final String message = DownloaderService.isQueued(appDetails.activeDownloadUrlString)
+ ? getString(R.string.download_pending)
+ : "";
+ showIndeterminateProgress(message);
+ }
}
/**
* Displays empty, indeterminate progress bar and related views.
*/
public void startProgress() {
+ showIndeterminateProgress(getString(R.string.download_pending));
+ updateViews();
+ }
+
+ private void showIndeterminateProgress(String message) {
setProgressVisible(true);
progressBar.setIndeterminate(true);
- progressSize.setText("");
+ progressSize.setText(message);
progressPercent.setText("");
- updateViews();
}
/**
diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloadCompleteService.java b/app/src/main/java/org/fdroid/fdroid/net/DownloadCompleteService.java
index 6574a76fd..fa5029d94 100644
--- a/app/src/main/java/org/fdroid/fdroid/net/DownloadCompleteService.java
+++ b/app/src/main/java/org/fdroid/fdroid/net/DownloadCompleteService.java
@@ -2,17 +2,14 @@ package org.fdroid.fdroid.net;
import android.app.IntentService;
import android.app.NotificationManager;
-import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Process;
import android.support.v4.app.NotificationCompat;
-import android.support.v4.app.TaskStackBuilder;
import android.text.TextUtils;
-import org.fdroid.fdroid.AppDetails;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
import org.fdroid.fdroid.data.App;
@@ -42,12 +39,12 @@ public class DownloadCompleteService extends IntentService {
if (intent != null) {
final String action = intent.getAction();
if (!ACTION_NOTIFY.equals(action)) {
- Utils.debugLog(TAG, "intent action is not ACTION_NOTIFY");
+ Utils.debugLog(TAG, "Intent action is not ACTION_NOTIFY");
return;
}
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
if (TextUtils.isEmpty(packageName)) {
- Utils.debugLog(TAG, "intent is missing EXTRA_PACKAGE_NAME");
+ Utils.debugLog(TAG, "Intent is missing EXTRA_PACKAGE_NAME");
return;
}
@@ -64,22 +61,13 @@ public class DownloadCompleteService extends IntentService {
title = String.format(getString(R.string.tap_to_install_format), app.name);
}
- Intent notifyIntent = new Intent(this, AppDetails.class);
- notifyIntent.putExtra(AppDetails.EXTRA_APPID, packageName);
- TaskStackBuilder stackBuilder = TaskStackBuilder
- .create(this)
- .addParentStack(AppDetails.class)
- .addNextIntent(notifyIntent);
int requestCode = Utils.getApkUrlNotificationId(intent.getDataString());
- PendingIntent pendingIntent = stackBuilder.getPendingIntent(requestCode,
- PendingIntent.FLAG_UPDATE_CURRENT);
-
NotificationCompat.Builder builder =
new NotificationCompat.Builder(this)
.setAutoCancel(true)
.setContentTitle(title)
.setSmallIcon(android.R.drawable.stat_sys_download_done)
- .setContentIntent(pendingIntent)
+ .setContentIntent(DownloaderService.createAppDetailsIntent(this, requestCode, packageName))
.setContentText(getString(R.string.tap_to_install));
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
nm.notify(Utils.getApkUrlNotificationId(intent.getDataString()), builder.build());
diff --git a/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java b/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java
index 3d4bd7679..2d31a6574 100644
--- a/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java
+++ b/app/src/main/java/org/fdroid/fdroid/net/DownloaderService.java
@@ -19,6 +19,7 @@ package org.fdroid.fdroid.net;
import android.app.Notification;
import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
@@ -31,14 +32,21 @@ import android.os.Looper;
import android.os.Message;
import android.os.PatternMatcher;
import android.os.Process;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
+import android.support.v4.app.TaskStackBuilder;
import android.support.v4.content.LocalBroadcastManager;
import android.text.TextUtils;
import android.util.Log;
+import org.fdroid.fdroid.AppDetails;
+import org.fdroid.fdroid.FDroid;
import org.fdroid.fdroid.Preferences;
import org.fdroid.fdroid.R;
import org.fdroid.fdroid.Utils;
+import org.fdroid.fdroid.data.App;
+import org.fdroid.fdroid.data.AppProvider;
import org.fdroid.fdroid.data.SanitizedFile;
import java.io.File;
@@ -104,7 +112,7 @@ public class DownloaderService extends Service {
@Override
public void onCreate() {
super.onCreate();
- Log.i(TAG, "onCreate");
+ Utils.debugLog(TAG, "Creating downloader service.");
HandlerThread thread = new HandlerThread(TAG, Process.THREAD_PRIORITY_BACKGROUND);
thread.start();
@@ -117,60 +125,102 @@ public class DownloaderService extends Service {
@Override
public void onStart(Intent intent, int startId) {
super.onStart(intent, startId);
- Log.i(TAG, "onStart " + startId + " " + intent);
+ Utils.debugLog(TAG, "Received Intent for downloading: " + intent + " (with a startId of " + startId + ")");
String uriString = intent.getDataString();
if (uriString == null) {
Log.e(TAG, "Received Intent with no URI: " + intent);
return;
}
if (ACTION_CANCEL.equals(intent.getAction())) {
- Log.i(TAG, "Removed " + intent);
- Integer what = QUEUE_WHATS.remove(uriString);
- if (what != null && serviceHandler.hasMessages(what)) {
- // the URL is in the queue, remove it
+ Utils.debugLog(TAG, "Cancelling download of " + uriString);
+ if (isQueued(uriString)) {
serviceHandler.removeMessages(what);
- } else if (downloader != null && TextUtils.equals(uriString, downloader.sourceUrl.toString())) {
- // the URL is being downloaded, cancel it
+ } else if (isActive(uriString)) {
downloader.cancelDownload();
} else {
- Log.e(TAG, "CANCEL called on something not queued or running: " + startId + " " + intent);
+ Log.e(TAG, "ACTION_CANCEL called on something not queued or running");
+ }
+
+ QUEUE_WHATS.remove(uriString);
+ if (isQueueEmpty()) {
+ stopForeground(true);
}
} else if (ACTION_QUEUE.equals(intent.getAction())) {
- if (Preferences.get().isUpdateNotificationEnabled()) {
- startForeground(NOTIFY_DOWNLOADING, createNotification(intent.getDataString()).build());
- }
- Log.i(TAG, "Queued " + intent);
Message msg = serviceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
msg.what = what++;
serviceHandler.sendMessage(msg);
- Log.i(TAG, "QUEUE_WHATS.put(" + uriString + ", " + msg.what);
QUEUE_WHATS.put(uriString, msg.what);
+ Utils.debugLog(TAG, "Queued download of " + uriString + ". Now " + QUEUE_WHATS.size() + " downloads in the queue");
} else {
Log.e(TAG, "Received Intent with unknown action: " + intent);
}
}
- private NotificationCompat.Builder createNotification(String urlString) {
+ @Nullable
+ private static String getPackageNameFromIntent(@NonNull Intent intent) {
+ return intent.hasExtra(EXTRA_PACKAGE_NAME) ? intent.getStringExtra(EXTRA_PACKAGE_NAME) : null;
+ }
+
+ private NotificationCompat.Builder createNotification(String urlString, @Nullable String packageName) {
return new NotificationCompat.Builder(this)
.setAutoCancel(true)
- .setContentTitle(getString(R.string.downloading))
+ .setContentIntent(createAppDetailsIntent(this, 0, packageName))
+ .setContentTitle(getNotificationTitle(packageName))
.setSmallIcon(android.R.drawable.stat_sys_download)
.setContentText(urlString)
.setProgress(100, 0, true);
}
+ /**
+ * If downloading an apk (i.e. packageName != null
) then the title will indicate
+ * the name of the app which the apk belongs to. Otherwise, it will be a generic "Downloading..."
+ * message.
+ */
+ private String getNotificationTitle(@Nullable String packageName) {
+ String title;
+ if (packageName != null) {
+ App app = AppProvider.Helper.findByPackageName(
+ getContentResolver(), packageName, new String[] {AppProvider.DataColumns.NAME});
+ title = getString(R.string.downloading_apk, app.name);
+ } else {
+ title = getString(R.string.downloading);
+ }
+ return title;
+ }
+
+ public static PendingIntent createAppDetailsIntent(@NonNull Context context, int requestCode, @Nullable String packageName) {
+ TaskStackBuilder stackBuilder;
+ if (packageName != null) {
+ Intent notifyIntent = new Intent(context.getApplicationContext(), AppDetails.class)
+ .putExtra(AppDetails.EXTRA_APPID, packageName);
+
+ stackBuilder = TaskStackBuilder
+ .create(context.getApplicationContext())
+ .addParentStack(AppDetails.class)
+ .addNextIntent(notifyIntent);
+ } else {
+ Intent notifyIntent = new Intent(context.getApplicationContext(), FDroid.class);
+ stackBuilder = TaskStackBuilder
+ .create(context.getApplicationContext())
+ .addParentStack(FDroid.class)
+ .addNextIntent(notifyIntent);
+ }
+
+ return stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT);
+ }
+
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
onStart(intent, startId);
- Log.i(TAG, "onStartCommand " + intent);
+ Utils.debugLog(TAG, "onStartCommand " + intent);
return START_REDELIVER_INTENT; // if killed before completion, retry Intent
}
@Override
public void onDestroy() {
- Log.i(TAG, "onDestroy");
+ Utils.debugLog(TAG, "Destroying downloader service. Will move to background and stop our Looper.");
stopForeground(true);
serviceLooper.quit(); //NOPMD - this is copied from IntentService, no super call needed
}
@@ -204,29 +254,39 @@ public class DownloaderService extends Service {
File downloadDir = new File(Utils.getApkCacheDir(this), uri.getHost() + "-" + uri.getPort());
downloadDir.mkdirs();
final SanitizedFile localFile = new SanitizedFile(downloadDir, uri.getLastPathSegment());
+ final String packageName = getPackageNameFromIntent(intent);
sendBroadcast(uri, Downloader.ACTION_STARTED, localFile);
+
+ if (Preferences.get().isUpdateNotificationEnabled()) {
+ Notification notification = createNotification(intent.getDataString(), getPackageNameFromIntent(intent)).build();
+ startForeground(NOTIFY_DOWNLOADING, notification);
+ }
+
try {
downloader = DownloaderFactory.create(this, uri, localFile);
downloader.setListener(new Downloader.DownloaderProgressListener() {
@Override
public void sendProgress(URL sourceUrl, int bytesRead, int totalBytes) {
- Intent intent = new Intent(Downloader.ACTION_PROGRESS);
- intent.setData(uri);
- intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead);
- intent.putExtra(Downloader.EXTRA_TOTAL_BYTES, totalBytes);
- localBroadcastManager.sendBroadcast(intent);
+ if (isActive(uri.toString())) {
+ Intent intent = new Intent(Downloader.ACTION_PROGRESS);
+ intent.setData(uri);
+ intent.putExtra(Downloader.EXTRA_BYTES_READ, bytesRead);
+ intent.putExtra(Downloader.EXTRA_TOTAL_BYTES, totalBytes);
+ localBroadcastManager.sendBroadcast(intent);
- NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
- Notification notification = createNotification(uri.toString())
- .setProgress(totalBytes, bytesRead, false)
- .build();
- nm.notify(NOTIFY_DOWNLOADING, notification);
+ if (Preferences.get().isUpdateNotificationEnabled()) {
+ NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+ Notification notification = createNotification(uri.toString(), packageName)
+ .setProgress(totalBytes, bytesRead, false)
+ .build();
+ nm.notify(NOTIFY_DOWNLOADING, notification);
+ }
+ }
}
});
downloader.download();
sendBroadcast(uri, Downloader.ACTION_COMPLETE, localFile);
- DownloadCompleteService.notify(this, intent.getStringExtra(EXTRA_PACKAGE_NAME),
- intent.getDataString());
+ DownloadCompleteService.notify(this, packageName, intent.getDataString());
} catch (InterruptedException e) {
sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile);
} catch (IOException e) {
@@ -237,6 +297,10 @@ public class DownloaderService extends Service {
if (downloader != null) {
downloader.close();
}
+ // May have already been removed in response to a cancel intent, but that wont cause
+ // problems if we ask to remove it again.
+ QUEUE_WHATS.remove(uri.toString());
+ stopForeground(true);
}
downloader = null;
}
@@ -266,11 +330,11 @@ public class DownloaderService extends Service {
* @see #cancel(Context, String)
*/
public static void queue(Context context, String packageName, String urlString) {
- Log.i(TAG, "queue " + urlString);
+ Utils.debugLog(TAG, "Preparing " + urlString + " to go into the download queue");
Intent intent = new Intent(context, DownloaderService.class);
intent.setAction(ACTION_QUEUE);
intent.setData(Uri.parse(urlString));
- if (!TextUtils.isEmpty(EXTRA_PACKAGE_NAME)) {
+ if (!TextUtils.isEmpty(packageName)) {
intent.putExtra(EXTRA_PACKAGE_NAME, packageName);
}
context.startService(intent);
@@ -286,7 +350,7 @@ public class DownloaderService extends Service {
* @see #queue(Context, String, String)
*/
public static void cancel(Context context, String urlString) {
- Log.i(TAG, "cancel " + urlString);
+ Utils.debugLog(TAG, "Preparing cancellation of " + urlString + " download");
Intent intent = new Intent(context, DownloaderService.class);
intent.setAction(ACTION_CANCEL);
intent.setData(Uri.parse(urlString));
@@ -294,18 +358,36 @@ public class DownloaderService extends Service {
}
/**
- * Check if a URL is waiting in the queue for downloading or if actively
- * being downloaded. This is useful for checking whether to re-register
- * {@link android.content.BroadcastReceiver}s in
- * {@link android.app.Activity#onResume()}
+ * Check if a URL is waiting in the queue for downloading or if actively being downloaded.
+ * This is useful for checking whether to re-register {@link android.content.BroadcastReceiver}s
+ * in {@link android.app.Activity#onResume()}.
+ * @see DownloaderService#isQueued(String)
+ * @see DownloaderService#isActive(String)
*/
public static boolean isQueuedOrActive(String urlString) {
+ return isQueued(urlString) || isActive(urlString);
+ }
+
+ public static boolean isQueueEmpty() {
+ return QUEUE_WHATS.isEmpty();
+ }
+
+ /**
+ * Check if a URL is waiting in the queue for downloading.
+ */
+ public static boolean isQueued(String urlString) {
if (TextUtils.isEmpty(urlString)) {
return false;
}
Integer what = QUEUE_WHATS.get(urlString);
- return (what != null && serviceHandler.hasMessages(what))
- || (downloader != null && TextUtils.equals(urlString, downloader.sourceUrl.toString()));
+ return what != null && serviceHandler.hasMessages(what);
+ }
+
+ /**
+ * Check if a URL is actively being downloaded.
+ */
+ public static boolean isActive(String urlString) {
+ return downloader != null && QUEUE_WHATS.containsKey(urlString) && TextUtils.equals(urlString, downloader.sourceUrl.toString());
}
/**
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index e3969b4c6..20d0988a3 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -367,10 +367,12 @@
Do you want to uninstall this app?
Download completed, tap to install
Download unsuccessful
+ Waiting to start download…
New:
Provided by %1$s.
Downloading…
+ Downloading %1$s
Never
Hourly