package io.lbry.browser.reactmodules; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Build; import android.support.v4.app.NotificationCompat; import android.support.v4.app.NotificationManagerCompat; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import io.lbry.browser.MainActivity; import io.lbry.browser.R; import io.lbry.browser.receivers.NotificationDeletedReceiver; import java.text.DecimalFormat; import java.util.HashMap; import java.util.Random; public class DownloadManagerModule extends ReactContextBaseJavaModule { private Context context; private HashMap<Integer, NotificationCompat.Builder> builders = new HashMap<Integer, NotificationCompat.Builder>(); private HashMap<String, Integer> downloadIdNotificationIdMap = new HashMap<String, Integer>(); private static final DecimalFormat DECIMAL_FORMAT = new DecimalFormat("#"); private static final int MAX_PROGRESS = 100; private static final String GROUP_DOWNLOADS = "io.lbry.browser.GROUP_DOWNLOADS"; private static final String NOTIFICATION_CHANNEL_ID = "io.lbry.browser.DOWNLOADS_NOTIFICATION_CHANNEL"; private static boolean channelCreated = false; public static final String NOTIFICATION_ID_KEY = "io.lbry.browser.notificationId"; public static final int GROUP_ID = 0; public static boolean groupCreated = false; public DownloadManagerModule(ReactApplicationContext reactContext) { super(reactContext); this.context = reactContext; } private int generateNotificationId() { return new Random().nextInt(); } @Override public String getName() { return "LbryDownloadManager"; } private void createNotificationChannel() { // Only applies to Android 8.0 Oreo (API Level 26) or higher if (!channelCreated && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE); NotificationChannel channel = new NotificationChannel( NOTIFICATION_CHANNEL_ID, "LBRY Downloads", NotificationManager.IMPORTANCE_LOW); channel.setDescription("LBRY file downloads"); notificationManager.createNotificationChannel(channel); } } private void createNotificationGroup() { if (!groupCreated) { Intent intent = new Intent(context, NotificationDeletedReceiver.class); intent.putExtra(NOTIFICATION_ID_KEY, GROUP_ID); PendingIntent pendingIntent = PendingIntent.getBroadcast(context, GROUP_ID, intent, 0); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); builder.setContentTitle("Active LBRY downloads") .setContentText("Active LBRY downloads") .setSmallIcon(android.R.drawable.stat_sys_download) .setPriority(NotificationCompat.PRIORITY_LOW) .setGroup(GROUP_DOWNLOADS) .setGroupSummary(true) .setDeleteIntent(pendingIntent); notificationManager.notify(GROUP_ID, builder.build()); groupCreated = true; } } private PendingIntent getLaunchPendingIntent(String uri) { Intent launchIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(uri)); launchIntent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP); PendingIntent intent = PendingIntent.getActivity(context, 0, launchIntent, 0); return intent; } @ReactMethod public void startDownload(String id, String fileName) { createNotificationChannel(); createNotificationGroup(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); NotificationCompat.Builder builder = new NotificationCompat.Builder(context, NOTIFICATION_CHANNEL_ID); // The file URI is used as the unique ID builder.setContentIntent(getLaunchPendingIntent(id)) .setContentTitle(String.format("Downloading %s...", fileName)) .setGroup(GROUP_DOWNLOADS) .setPriority(NotificationCompat.PRIORITY_LOW) .setProgress(MAX_PROGRESS, 0, false) .setSmallIcon(android.R.drawable.stat_sys_download); int notificationId = generateNotificationId(); downloadIdNotificationIdMap.put(id, notificationId); builders.put(notificationId, builder); notificationManager.notify(notificationId, builder.build()); } @ReactMethod public void updateDownload(String id, String fileName, double progress, double writtenBytes, double totalBytes) { if (!downloadIdNotificationIdMap.containsKey(id)) { return; } int notificationId = downloadIdNotificationIdMap.get(id); if (!builders.containsKey(notificationId)) { return; } createNotificationChannel(); createNotificationGroup(); NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); NotificationCompat.Builder builder = builders.get(notificationId); builder.setContentIntent(getLaunchPendingIntent(id)) .setContentText(String.format("%.0f%% (%s / %s)", progress, formatBytes(writtenBytes), formatBytes(totalBytes))) .setGroup(GROUP_DOWNLOADS) .setProgress(MAX_PROGRESS, new Double(progress).intValue(), false); notificationManager.notify(notificationId, builder.build()); if (progress == MAX_PROGRESS) { builder.setContentTitle(String.format("Downloaded %s", fileName)) .setContentText(String.format("%s", formatBytes(totalBytes))) .setProgress(0, 0, false); notificationManager.notify(notificationId, builder.build()); downloadIdNotificationIdMap.remove(id); builders.remove(notificationId); } } @ReactMethod public void stopDownload(String id, String filename) { if (!downloadIdNotificationIdMap.containsKey(id)) { return; } int notificationId = downloadIdNotificationIdMap.get(id); if (!builders.containsKey(notificationId)) { return; } NotificationManagerCompat notificationManager = NotificationManagerCompat.from(context); NotificationCompat.Builder builder = builders.get(notificationId); notificationManager.cancel(notificationId); downloadIdNotificationIdMap.remove(id); builders.remove(notificationId); if (builders.values().size() == 0) { notificationManager.cancel(GROUP_ID); groupCreated = false; } } private String formatBytes(double bytes) { if (bytes < 1048576) { // < 1MB return String.format("%s KB", DECIMAL_FORMAT.format(bytes / 1024.0)); } if (bytes < 1073741824) { // < 1GB return String.format("%s MB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0))); } return String.format("%s GB", DECIMAL_FORMAT.format(bytes / (1024.0 * 1024.0 * 1024.0))); } }