put up a notification for each completed download
This makes background installs and updates a lot easier
This commit is contained in:
parent
d114f428e7
commit
74d1c9521d
@ -34,7 +34,7 @@ public class DownloaderServiceTest extends ServiceTestCase<DownloaderService> {
|
||||
}
|
||||
}, new IntentFilter(Downloader.ACTION_PROGRESS));
|
||||
for (String url : urls) {
|
||||
DownloaderService.queue(getContext(), url);
|
||||
DownloaderService.queue(getContext(), null, url);
|
||||
}
|
||||
Thread.sleep(30000);
|
||||
}
|
||||
|
@ -442,6 +442,9 @@
|
||||
<service
|
||||
android:name=".net.DownloaderService"
|
||||
android:exported="false" />
|
||||
<service
|
||||
android:name=".net.DownloadCompleteService"
|
||||
android:exported="false" />
|
||||
<service android:name=".net.WifiStateChangeService" />
|
||||
<service android:name=".localrepo.SwapService" />
|
||||
</application>
|
||||
|
@ -511,7 +511,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
File localFile = new File(intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH));
|
||||
try {
|
||||
installer.installPackage(localFile, app.packageName);
|
||||
installer.installPackage(localFile, app.packageName, intent.getDataString());
|
||||
} catch (InstallFailedException e) {
|
||||
Log.e(TAG, "Android not compatible with this Installer!", e);
|
||||
}
|
||||
@ -869,7 +869,7 @@ public class AppDetails extends AppCompatActivity {
|
||||
activeDownloadUrlString = urlString;
|
||||
registerDownloaderReceivers();
|
||||
headerFragment.startProgress();
|
||||
DownloaderService.queue(this, activeDownloadUrlString);
|
||||
DownloaderService.queue(this, apk.packageName, activeDownloadUrlString);
|
||||
}
|
||||
|
||||
public void removeApk(String packageName) {
|
||||
|
@ -496,7 +496,7 @@ public class UpdateService extends IntentService implements ProgressListener {
|
||||
ApkProvider.DataColumns.NAME,
|
||||
});
|
||||
String urlString = Utils.getApkUrl(repoAddress, apk);
|
||||
DownloaderService.queue(this, urlString);
|
||||
DownloaderService.queue(this, app.packageName, urlString);
|
||||
cursor.moveToNext();
|
||||
}
|
||||
}
|
||||
|
@ -66,6 +66,7 @@ import java.util.Formatter;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.zip.Adler32;
|
||||
|
||||
public final class Utils {
|
||||
|
||||
@ -321,7 +322,7 @@ public final class Utils {
|
||||
* This location is only for caching, do not install directly from this location
|
||||
* because if the file is on the External Storage, any other app could swap out
|
||||
* the APK while the install was in process, allowing malware to install things.
|
||||
* Using {@link org.fdroid.fdroid.installer.Installer#installPackage(File, String)}
|
||||
* Using {@link org.fdroid.fdroid.installer.Installer#installPackage(File, String, String)}
|
||||
* is fine since that does the right thing.
|
||||
*/
|
||||
public static SanitizedFile getApkCacheDir(Context context) {
|
||||
@ -412,6 +413,15 @@ public final class Utils {
|
||||
return repoAddress + "/" + apk.apkName.replace(" ", "%20");
|
||||
}
|
||||
|
||||
/**
|
||||
* This generates a unique, reproducible ID for notifications related to {@code urlString}
|
||||
*/
|
||||
public static int getApkUrlNotificationId(String urlString) {
|
||||
Adler32 checksum = new Adler32();
|
||||
checksum.update(urlString.getBytes());
|
||||
return (int) checksum.getValue();
|
||||
}
|
||||
|
||||
public static final class CommaSeparatedList implements Iterable<String> {
|
||||
private final String value;
|
||||
|
||||
|
@ -20,6 +20,7 @@
|
||||
package org.fdroid.fdroid.installer;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.app.NotificationManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.pm.PackageManager;
|
||||
@ -170,7 +171,8 @@ public abstract class Installer {
|
||||
/**
|
||||
* This is the safe, single point of entry for submitting an APK file to be installed.
|
||||
*/
|
||||
public void installPackage(File apkFile, String packageName) throws InstallFailedException {
|
||||
public void installPackage(File apkFile, String packageName, String urlString)
|
||||
throws InstallFailedException {
|
||||
SanitizedFile apkToInstall = null;
|
||||
try {
|
||||
Map<String, Object> attributes = AndroidXMLDecompress.getManifestHeaderAttributes(apkFile.getAbsolutePath());
|
||||
@ -228,6 +230,9 @@ public abstract class Installer {
|
||||
FileCompat.setReadable(apkToInstall, true, false);
|
||||
installPackageInternal(apkToInstall);
|
||||
|
||||
NotificationManager nm = (NotificationManager)
|
||||
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
|
||||
nm.cancel(Utils.getApkUrlNotificationId(urlString));
|
||||
} catch (NumberFormatException | NoSuchAlgorithmException | IOException e) {
|
||||
throw new InstallFailedException(e);
|
||||
} catch (ClassCastException e) {
|
||||
|
@ -0,0 +1,88 @@
|
||||
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;
|
||||
import org.fdroid.fdroid.data.AppProvider;
|
||||
|
||||
public class DownloadCompleteService extends IntentService {
|
||||
private static final String TAG = "DownloadCompleteService";
|
||||
|
||||
private static final String ACTION_NOTIFY = "org.fdroid.fdroid.net.action.NOTIFY";
|
||||
private static final String EXTRA_PACKAGE_NAME = "org.fdroid.fdroid.net.extra.PACKAGE_NAME";
|
||||
|
||||
public DownloadCompleteService() {
|
||||
super("DownloadCompleteService");
|
||||
}
|
||||
|
||||
public static void notify(Context context, String packageName, String urlString) {
|
||||
Intent intent = new Intent(context, DownloadCompleteService.class);
|
||||
intent.setAction(ACTION_NOTIFY);
|
||||
intent.setData(Uri.parse(urlString));
|
||||
intent.putExtra(EXTRA_PACKAGE_NAME, packageName);
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onHandleIntent(Intent intent) {
|
||||
Process.setThreadPriority(Process.THREAD_PRIORITY_LOWEST);
|
||||
if (intent != null) {
|
||||
final String action = intent.getAction();
|
||||
if (!ACTION_NOTIFY.equals(action)) {
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
.setContentText(getString(R.string.tap_to_install));
|
||||
NotificationManager nm = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
|
||||
nm.notify(Utils.getApkUrlNotificationId(intent.getDataString()), builder.build());
|
||||
}
|
||||
}
|
||||
}
|
@ -72,6 +72,8 @@ import java.util.HashMap;
|
||||
public class DownloaderService extends Service {
|
||||
public static final String TAG = "DownloaderService";
|
||||
|
||||
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";
|
||||
|
||||
@ -216,6 +218,8 @@ public class DownloaderService extends Service {
|
||||
});
|
||||
downloader.download();
|
||||
sendBroadcast(uri, Downloader.ACTION_COMPLETE, localFile);
|
||||
DownloadCompleteService.notify(this, intent.getStringExtra(EXTRA_PACKAGE_NAME),
|
||||
intent.getDataString());
|
||||
} catch (InterruptedException e) {
|
||||
sendBroadcast(uri, Downloader.ACTION_INTERRUPTED, localFile);
|
||||
} catch (IOException e) {
|
||||
@ -250,14 +254,18 @@ public class DownloaderService extends Service {
|
||||
* All notifications are sent as an {@link Intent} via local broadcasts to be received by
|
||||
*
|
||||
* @param 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 urlString) {
|
||||
public static void queue(Context context, String packageName, String urlString) {
|
||||
Log.i(TAG, "queue " + urlString);
|
||||
Intent intent = new Intent(context, DownloaderService.class);
|
||||
intent.setAction(ACTION_QUEUE);
|
||||
intent.setData(Uri.parse(urlString));
|
||||
if (!TextUtils.isEmpty(EXTRA_PACKAGE_NAME)) {
|
||||
intent.putExtra(EXTRA_PACKAGE_NAME, packageName);
|
||||
}
|
||||
context.startService(intent);
|
||||
}
|
||||
|
||||
@ -268,7 +276,7 @@ public class DownloaderService extends Service {
|
||||
*
|
||||
* @param context
|
||||
* @param urlString The URL to remove from the download queue
|
||||
* @see #queue(Context, String)
|
||||
* @see #queue(Context, String, String)
|
||||
*/
|
||||
public static void cancel(Context context, String urlString) {
|
||||
Log.i(TAG, "cancel " + urlString);
|
||||
|
@ -788,15 +788,15 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
String path = intent.getStringExtra(Downloader.EXTRA_DOWNLOAD_PATH);
|
||||
handleDownloadComplete(new File(path), app.packageName);
|
||||
handleDownloadComplete(new File(path), app.packageName, intent.getDataString());
|
||||
}
|
||||
};
|
||||
localBroadcastManager.registerReceiver(downloadCompleteReceiver,
|
||||
DownloaderService.getIntentFilter(urlString, Downloader.ACTION_COMPLETE));
|
||||
DownloaderService.queue(this, urlString);
|
||||
DownloaderService.queue(this, app.packageName, urlString);
|
||||
}
|
||||
|
||||
private void handleDownloadComplete(File apkFile, String packageName) {
|
||||
private void handleDownloadComplete(File apkFile, String packageName, String urlString) {
|
||||
|
||||
try {
|
||||
Installer.getActivityInstaller(this, new Installer.InstallerCallback() {
|
||||
@ -811,7 +811,7 @@ public class SwapWorkflowActivity extends AppCompatActivity {
|
||||
public void onError(int operation, int errorCode) {
|
||||
// TODO: Boo!
|
||||
}
|
||||
}).installPackage(apkFile, packageName);
|
||||
}).installPackage(apkFile, packageName, urlString);
|
||||
localBroadcastManager.unregisterReceiver(downloadCompleteReceiver);
|
||||
} catch (Installer.InstallFailedException e) {
|
||||
// TODO: Handle exception properly
|
||||
|
@ -341,6 +341,8 @@
|
||||
<string name="swap_not_enabled">Swapping not enabled</string>
|
||||
<string name="swap_not_enabled_description">Before swapping, your device must be made visible.</string>
|
||||
|
||||
<string name="tap_to_install_format">Tap to install %s</string>
|
||||
<string name="tap_to_update_format">Tap to update %s</string>
|
||||
<string name="install_confirm">Do you want to install this application?
|
||||
It will get access to:</string>
|
||||
<string name="install_confirm_no_perms">Do you want to install this application?
|
||||
|
Loading…
x
Reference in New Issue
Block a user