move all downloading notifications to InstallManagerService
This keeps DownloaderService tightly focused on downloading, and makes it a lot easier to manage Notifications since InstallManagerService's lifecycle lasts as long as the Notifications, unlike DownloaderService.
This commit is contained in:
parent
67e66a7b0c
commit
08988f2369
@ -1,15 +1,27 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Notification;
|
||||
import android.app.NotificationManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.Service;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.IBinder;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.v4.app.NotificationCompat;
|
||||
import android.support.v4.app.TaskStackBuilder;
|
||||
import android.support.v4.content.LocalBroadcastManager;
|
||||
|
||||
import org.fdroid.fdroid.AppDetails;
|
||||
import org.fdroid.fdroid.FDroid;
|
||||
import org.fdroid.fdroid.R;
|
||||
import org.fdroid.fdroid.Utils;
|
||||
import org.fdroid.fdroid.data.Apk;
|
||||
import org.fdroid.fdroid.data.App;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
import org.fdroid.fdroid.net.Downloader;
|
||||
import org.fdroid.fdroid.net.DownloaderService;
|
||||
|
||||
@ -45,12 +57,19 @@ public class InstallManagerService extends Service {
|
||||
public static final String TAG = "InstallManagerService";
|
||||
|
||||
private static final String ACTION_INSTALL = "org.fdroid.fdroid.InstallManagerService.action.INSTALL";
|
||||
private static final int NOTIFY_DOWNLOADING = 0x2344;
|
||||
|
||||
/**
|
||||
* The collection of APKs that are actively going through this whole process.
|
||||
*/
|
||||
private static final HashMap<String, Apk> ACTIVE_APKS = new HashMap<String, Apk>(3);
|
||||
|
||||
/**
|
||||
* The array of active {@link BroadcastReceiver}s for each active APK. The key is the
|
||||
* download URL, as in {@link Apk#getUrl()} or {@code urlString}.
|
||||
*/
|
||||
private final HashMap<String, BroadcastReceiver[]> receivers = new HashMap<String, BroadcastReceiver[]>(3);
|
||||
|
||||
private LocalBroadcastManager localBroadcastManager;
|
||||
|
||||
/**
|
||||
@ -73,11 +92,17 @@ public class InstallManagerService extends Service {
|
||||
Utils.debugLog(TAG, "onStartCommand " + intent);
|
||||
String urlString = intent.getDataString();
|
||||
Apk apk = ACTIVE_APKS.get(urlString);
|
||||
|
||||
Notification notification = createNotification(intent.getDataString(), apk.packageName).build();
|
||||
startForeground(NOTIFY_DOWNLOADING, notification);
|
||||
|
||||
registerDownloaderReceivers(urlString);
|
||||
|
||||
File apkFilePath = Utils.getApkDownloadPath(this, intent.getData());
|
||||
long apkFileSize = apkFilePath.length();
|
||||
if (!apkFilePath.exists() || apkFileSize < apk.size) {
|
||||
Utils.debugLog(TAG, "download " + urlString + " " + apkFilePath);
|
||||
DownloaderService.queue(this, apk.packageName, urlString);
|
||||
DownloaderService.queue(this, urlString);
|
||||
} else if (apkFileSize == apk.size) {
|
||||
Utils.debugLog(TAG, "skip download, we have it, straight to install " + urlString + " " + apkFilePath);
|
||||
sendBroadcast(intent.getData(), Downloader.ACTION_STARTED, apkFilePath);
|
||||
@ -85,7 +110,7 @@ public class InstallManagerService extends Service {
|
||||
} else {
|
||||
Utils.debugLog(TAG, " delete and download again " + urlString + " " + apkFilePath);
|
||||
apkFilePath.delete();
|
||||
DownloaderService.queue(this, apk.packageName, urlString);
|
||||
DownloaderService.queue(this, urlString);
|
||||
}
|
||||
return START_REDELIVER_INTENT; // if killed before completion, retry Intent
|
||||
}
|
||||
@ -97,6 +122,142 @@ public class InstallManagerService extends Service {
|
||||
localBroadcastManager.sendBroadcast(intent);
|
||||
}
|
||||
|
||||
private void unregisterDownloaderReceivers(String urlString) {
|
||||
for (BroadcastReceiver receiver : receivers.get(urlString)) {
|
||||
localBroadcastManager.unregisterReceiver(receiver);
|
||||
}
|
||||
}
|
||||
|
||||
private void registerDownloaderReceivers(String urlString) {
|
||||
BroadcastReceiver startedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
}
|
||||
};
|
||||
BroadcastReceiver progressReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String urlString = intent.getDataString();
|
||||
Apk apk = ACTIVE_APKS.get(urlString);
|
||||
int bytesRead = intent.getIntExtra(Downloader.EXTRA_BYTES_READ, 0);
|
||||
int totalBytes = intent.getIntExtra(Downloader.EXTRA_TOTAL_BYTES, 0);
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
Notification notification = createNotification(urlString, apk.packageName)
|
||||
.setProgress(totalBytes, bytesRead, false)
|
||||
.build();
|
||||
nm.notify(NOTIFY_DOWNLOADING, notification);
|
||||
}
|
||||
};
|
||||
BroadcastReceiver completeReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String urlString = intent.getDataString();
|
||||
Apk apk = ACTIVE_APKS.remove(urlString);
|
||||
notifyDownloadComplete(apk.packageName, intent.getDataString());
|
||||
unregisterDownloaderReceivers(urlString);
|
||||
}
|
||||
};
|
||||
BroadcastReceiver interruptedReceiver = new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String urlString = intent.getDataString();
|
||||
ACTIVE_APKS.remove(urlString);
|
||||
unregisterDownloaderReceivers(urlString);
|
||||
}
|
||||
};
|
||||
localBroadcastManager.registerReceiver(startedReceiver,
|
||||
DownloaderService.getIntentFilter(urlString, Downloader.ACTION_STARTED));
|
||||
localBroadcastManager.registerReceiver(progressReceiver,
|
||||
DownloaderService.getIntentFilter(urlString, Downloader.ACTION_PROGRESS));
|
||||
localBroadcastManager.registerReceiver(completeReceiver,
|
||||
DownloaderService.getIntentFilter(urlString, Downloader.ACTION_COMPLETE));
|
||||
localBroadcastManager.registerReceiver(interruptedReceiver,
|
||||
DownloaderService.getIntentFilter(urlString, Downloader.ACTION_INTERRUPTED));
|
||||
receivers.put(urlString, new BroadcastReceiver[]{
|
||||
startedReceiver, progressReceiver, completeReceiver, interruptedReceiver,
|
||||
});
|
||||
}
|
||||
|
||||
private NotificationCompat.Builder createNotification(String urlString, @Nullable String packageName) {
|
||||
return new NotificationCompat.Builder(this)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(createAppDetailsIntent(0, packageName))
|
||||
.setContentTitle(getNotificationTitle(packageName))
|
||||
.addAction(R.drawable.ic_cancel_black_24dp, getString(R.string.cancel),
|
||||
DownloaderService.createCancelDownloadIntent(this, 0, urlString))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentText(urlString)
|
||||
.setProgress(100, 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* If downloading an apk (i.e. <code>packageName != null</code>) 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;
|
||||
}
|
||||
|
||||
private PendingIntent createAppDetailsIntent(int requestCode, String packageName) {
|
||||
TaskStackBuilder stackBuilder;
|
||||
if (packageName != null) {
|
||||
Intent notifyIntent = new Intent(getApplicationContext(), AppDetails.class)
|
||||
.putExtra(AppDetails.EXTRA_APPID, packageName);
|
||||
|
||||
stackBuilder = TaskStackBuilder
|
||||
.create(getApplicationContext())
|
||||
.addParentStack(AppDetails.class)
|
||||
.addNextIntent(notifyIntent);
|
||||
} else {
|
||||
Intent notifyIntent = new Intent(getApplicationContext(), FDroid.class);
|
||||
stackBuilder = TaskStackBuilder
|
||||
.create(getApplicationContext())
|
||||
.addParentStack(FDroid.class)
|
||||
.addNextIntent(notifyIntent);
|
||||
}
|
||||
|
||||
return stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a notification about a completed download. {@code packageName} must be a valid
|
||||
* and currently in the app index database.
|
||||
*/
|
||||
private void notifyDownloadComplete(String packageName, String urlString) {
|
||||
String title;
|
||||
try {
|
||||
PackageManager pm = getPackageManager();
|
||||
title = String.format(getString(R.string.tap_to_update_format),
|
||||
pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
App app = AppProvider.Helper.findByPackageName(getContentResolver(), packageName,
|
||||
new String[]{
|
||||
AppProvider.DataColumns.NAME,
|
||||
});
|
||||
title = String.format(getString(R.string.tap_to_install_format), app.name);
|
||||
}
|
||||
|
||||
int downloadUrlId = urlString.hashCode();
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(this)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(title)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentIntent(createAppDetailsIntent(downloadUrlId, packageName))
|
||||
.setContentText(getString(R.string.tap_to_install));
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.notify(downloadUrlId, builder.build());
|
||||
}
|
||||
|
||||
/**
|
||||
* Install an APK, checking the cache and downloading if necessary before starting the process.
|
||||
* All notifications are sent as an {@link Intent} via local broadcasts to be received by
|
||||
|
@ -17,14 +17,11 @@
|
||||
|
||||
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;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
@ -34,19 +31,11 @@ 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.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;
|
||||
@ -85,13 +74,9 @@ import java.net.URL;
|
||||
public class DownloaderService extends Service {
|
||||
private static final String TAG = "DownloaderService";
|
||||
|
||||
private static final String EXTRA_PACKAGE_NAME = "org.fdroid.fdroid.net.DownloaderService.extra.PACKAGE_NAME";
|
||||
|
||||
private static final String ACTION_QUEUE = "org.fdroid.fdroid.net.DownloaderService.action.QUEUE";
|
||||
private static final String ACTION_CANCEL = "org.fdroid.fdroid.net.DownloaderService.action.CANCEL";
|
||||
|
||||
private static final int NOTIFY_DOWNLOADING = 0x2344;
|
||||
|
||||
private volatile Looper serviceLooper;
|
||||
private static volatile ServiceHandler serviceHandler;
|
||||
private static volatile Downloader downloader;
|
||||
@ -153,55 +138,6 @@ public class DownloaderService extends Service {
|
||||
}
|
||||
}
|
||||
|
||||
private NotificationCompat.Builder createNotification(String urlString, @Nullable String packageName) {
|
||||
return new NotificationCompat.Builder(this)
|
||||
.setAutoCancel(true)
|
||||
.setContentIntent(createAppDetailsIntent(0, packageName))
|
||||
.setContentTitle(getNotificationTitle(packageName))
|
||||
.addAction(R.drawable.ic_cancel_black_24dp, getString(R.string.cancel),
|
||||
createCancelDownloadIntent(this, 0, urlString))
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
.setContentText(urlString)
|
||||
.setProgress(100, 0, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* If downloading an apk (i.e. <code>packageName != null</code>) 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) {
|
||||
if (packageName != null) {
|
||||
final App app = AppProvider.Helper.findByPackageName(
|
||||
getContentResolver(), packageName, new String[]{AppProvider.DataColumns.NAME});
|
||||
if (app != null) {
|
||||
return getString(R.string.downloading_apk, app.name);
|
||||
}
|
||||
}
|
||||
return getString(R.string.downloading);
|
||||
}
|
||||
|
||||
private PendingIntent createAppDetailsIntent(int requestCode, String packageName) {
|
||||
TaskStackBuilder stackBuilder;
|
||||
if (packageName != null) {
|
||||
Intent notifyIntent = new Intent(getApplicationContext(), AppDetails.class)
|
||||
.putExtra(AppDetails.EXTRA_APPID, packageName);
|
||||
|
||||
stackBuilder = TaskStackBuilder
|
||||
.create(getApplicationContext())
|
||||
.addParentStack(AppDetails.class)
|
||||
.addNextIntent(notifyIntent);
|
||||
} else {
|
||||
Intent notifyIntent = new Intent(getApplicationContext(), FDroid.class);
|
||||
stackBuilder = TaskStackBuilder
|
||||
.create(getApplicationContext())
|
||||
.addParentStack(FDroid.class)
|
||||
.addNextIntent(notifyIntent);
|
||||
}
|
||||
|
||||
return stackBuilder.getPendingIntent(requestCode, PendingIntent.FLAG_UPDATE_CURRENT);
|
||||
}
|
||||
|
||||
public static PendingIntent createCancelDownloadIntent(@NonNull Context context, int
|
||||
requestCode, @NonNull String urlString) {
|
||||
Intent cancelIntent = new Intent(context.getApplicationContext(), DownloaderService.class)
|
||||
@ -254,12 +190,8 @@ public class DownloaderService extends Service {
|
||||
protected void handleIntent(Intent intent) {
|
||||
final Uri uri = intent.getData();
|
||||
final SanitizedFile localFile = Utils.getApkDownloadPath(this, uri);
|
||||
final String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||
sendBroadcast(uri, Downloader.ACTION_STARTED, localFile);
|
||||
|
||||
Notification notification = createNotification(intent.getDataString(), packageName).build();
|
||||
startForeground(NOTIFY_DOWNLOADING, notification);
|
||||
|
||||
try {
|
||||
downloader = DownloaderFactory.create(this, uri, localFile);
|
||||
downloader.setListener(new Downloader.DownloaderProgressListener() {
|
||||
@ -270,17 +202,10 @@ public class DownloaderService extends Service {
|
||||
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(), packageName)
|
||||
.setProgress(totalBytes, bytesRead, false)
|
||||
.build();
|
||||
nm.notify(NOTIFY_DOWNLOADING, notification);
|
||||
}
|
||||
});
|
||||
downloader.download();
|
||||
sendBroadcast(uri, Downloader.ACTION_COMPLETE, localFile);
|
||||
notifyDownloadComplete(packageName, intent.getDataString());
|
||||
} catch (InterruptedException e) {
|
||||
sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile);
|
||||
} catch (IOException e) {
|
||||
@ -296,36 +221,6 @@ public class DownloaderService extends Service {
|
||||
downloader = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Post a notification about a completed download. {@code packageName} must be a valid
|
||||
* and currently in the app index database.
|
||||
*/
|
||||
private void notifyDownloadComplete(String packageName, String urlString) {
|
||||
String title;
|
||||
try {
|
||||
PackageManager pm = getPackageManager();
|
||||
title = String.format(getString(R.string.tap_to_update_format),
|
||||
pm.getApplicationLabel(pm.getApplicationInfo(packageName, 0)));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
App app = AppProvider.Helper.findByPackageName(getContentResolver(), packageName,
|
||||
new String[]{
|
||||
AppProvider.DataColumns.NAME,
|
||||
});
|
||||
title = String.format(getString(R.string.tap_to_install_format), app.name);
|
||||
}
|
||||
|
||||
int downloadUrlId = urlString.hashCode();
|
||||
NotificationCompat.Builder builder =
|
||||
new NotificationCompat.Builder(this)
|
||||
.setAutoCancel(true)
|
||||
.setContentTitle(title)
|
||||
.setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
.setContentIntent(createAppDetailsIntent(downloadUrlId, packageName))
|
||||
.setContentText(getString(R.string.tap_to_install));
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.notify(downloadUrlId, builder.build());
|
||||
}
|
||||
|
||||
private void sendBroadcast(Uri uri, String action, File file) {
|
||||
sendBroadcast(uri, action, file, null);
|
||||
}
|
||||
@ -346,18 +241,14 @@ public class DownloaderService extends Service {
|
||||
* All notifications are sent as an {@link Intent} via local broadcasts to be received by
|
||||
*
|
||||
* @param context this app's {@link Context}
|
||||
* @param packageName The packageName of the app being downloaded
|
||||
* @param urlString The URL to add to the download queue
|
||||
* @see #cancel(Context, String)
|
||||
*/
|
||||
public static void queue(Context context, String packageName, String urlString) {
|
||||
public static void queue(Context context, String 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(packageName)) {
|
||||
intent.putExtra(EXTRA_PACKAGE_NAME, packageName);
|
||||
}
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
@ -368,7 +259,7 @@ public class DownloaderService extends Service {
|
||||
*
|
||||
* @param context this app's {@link Context}
|
||||
* @param urlString The URL to remove from the download queue
|
||||
* @see #queue(Context, String, String)
|
||||
* @see #queue(Context, String)
|
||||
*/
|
||||
public static void cancel(Context context, String urlString) {
|
||||
Utils.debugLog(TAG, "Preparing cancellation of " + urlString + " download");
|
||||
|
Loading…
x
Reference in New Issue
Block a user