Ammar Githam
4 years ago
2 changed files with 302 additions and 314 deletions
@ -1,399 +1,383 @@ |
|||||
package awais.instagrabber.workers; |
|
||||
|
package awais.instagrabber.workers |
||||
|
|
||||
import android.app.Notification; |
|
||||
import android.app.PendingIntent; |
|
||||
import android.content.ContentResolver; |
|
||||
import android.content.Context; |
|
||||
import android.content.Intent; |
|
||||
import android.graphics.Bitmap; |
|
||||
import android.media.MediaMetadataRetriever; |
|
||||
import android.media.MediaScannerConnection; |
|
||||
import android.net.Uri; |
|
||||
import android.os.Build; |
|
||||
import android.os.Handler; |
|
||||
import android.os.Looper; |
|
||||
import android.util.Log; |
|
||||
|
import android.app.Notification |
||||
|
import android.app.PendingIntent |
||||
|
import android.content.ContentResolver |
||||
|
import android.content.Context |
||||
|
import android.content.Intent |
||||
|
import android.graphics.Bitmap |
||||
|
import android.media.MediaMetadataRetriever |
||||
|
import android.media.MediaScannerConnection |
||||
|
import android.net.Uri |
||||
|
import android.os.Build |
||||
|
import android.os.Handler |
||||
|
import android.os.Looper |
||||
|
import android.util.Log |
||||
|
import androidx.core.app.NotificationCompat |
||||
|
import androidx.core.app.NotificationManagerCompat |
||||
|
import androidx.core.content.FileProvider |
||||
|
import androidx.work.CoroutineWorker |
||||
|
import androidx.work.Data |
||||
|
import androidx.work.ForegroundInfo |
||||
|
import androidx.work.WorkerParameters |
||||
|
import awais.instagrabber.BuildConfig |
||||
|
import awais.instagrabber.R |
||||
|
import awais.instagrabber.services.DeleteImageIntentService |
||||
|
import awais.instagrabber.utils.BitmapUtils |
||||
|
import awais.instagrabber.utils.Constants.DOWNLOAD_CHANNEL_ID |
||||
|
import awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME |
||||
|
import awais.instagrabber.utils.DownloadUtils |
||||
|
import awais.instagrabber.utils.TextUtils.isEmpty |
||||
|
import awais.instagrabber.utils.Utils |
||||
|
import awais.instagrabber.utils.extensions.TAG |
||||
|
import com.google.gson.Gson |
||||
|
import com.google.gson.JsonSyntaxException |
||||
|
import kotlinx.coroutines.Dispatchers |
||||
|
import kotlinx.coroutines.withContext |
||||
|
import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter |
||||
|
import java.io.BufferedInputStream |
||||
|
import java.io.File |
||||
|
import java.io.FileInputStream |
||||
|
import java.io.FileOutputStream |
||||
|
import java.net.URL |
||||
|
import java.util.* |
||||
|
import java.util.concurrent.ExecutionException |
||||
|
import kotlin.math.abs |
||||
|
|
||||
import androidx.annotation.NonNull; |
|
||||
import androidx.annotation.Nullable; |
|
||||
import androidx.core.app.NotificationCompat; |
|
||||
import androidx.core.app.NotificationManagerCompat; |
|
||||
import androidx.core.content.FileProvider; |
|
||||
import androidx.work.Data; |
|
||||
import androidx.work.ForegroundInfo; |
|
||||
import androidx.work.Worker; |
|
||||
import androidx.work.WorkerParameters; |
|
||||
|
class DownloadWorker(context: Context, workerParams: WorkerParameters) : CoroutineWorker(context, workerParams) { |
||||
|
private val notificationManager: NotificationManagerCompat = NotificationManagerCompat.from(context) |
||||
|
|
||||
import com.google.gson.Gson; |
|
||||
import com.google.gson.JsonSyntaxException; |
|
||||
|
|
||||
import org.apache.commons.imaging.formats.jpeg.iptc.JpegIptcRewriter; |
|
||||
|
|
||||
import java.io.BufferedInputStream; |
|
||||
import java.io.File; |
|
||||
import java.io.FileInputStream; |
|
||||
import java.io.FileOutputStream; |
|
||||
import java.net.URL; |
|
||||
import java.net.URLConnection; |
|
||||
import java.util.Collection; |
|
||||
import java.util.HashMap; |
|
||||
import java.util.LinkedList; |
|
||||
import java.util.List; |
|
||||
import java.util.Map; |
|
||||
import java.util.Scanner; |
|
||||
import java.util.Set; |
|
||||
import java.util.concurrent.ExecutionException; |
|
||||
|
|
||||
import awais.instagrabber.BuildConfig; |
|
||||
import awais.instagrabber.R; |
|
||||
import awais.instagrabber.services.DeleteImageIntentService; |
|
||||
import awais.instagrabber.utils.BitmapUtils; |
|
||||
import awais.instagrabber.utils.Constants; |
|
||||
import awais.instagrabber.utils.DownloadUtils; |
|
||||
import awais.instagrabber.utils.TextUtils; |
|
||||
import awais.instagrabber.utils.Utils; |
|
||||
|
|
||||
import static awais.instagrabber.utils.BitmapUtils.THUMBNAIL_SIZE; |
|
||||
import static awais.instagrabber.utils.Constants.DOWNLOAD_CHANNEL_ID; |
|
||||
import static awais.instagrabber.utils.Constants.NOTIF_GROUP_NAME; |
|
||||
|
|
||||
public class DownloadWorker extends Worker { |
|
||||
private static final String TAG = "DownloadWorker"; |
|
||||
private static final String DOWNLOAD_GROUP = "DOWNLOAD_GROUP"; |
|
||||
|
|
||||
public static final String PROGRESS = "PROGRESS"; |
|
||||
public static final String URL = "URL"; |
|
||||
public static final String KEY_DOWNLOAD_REQUEST_JSON = "download_request_json"; |
|
||||
public static final int DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE = 2020; |
|
||||
public static final int DELETE_IMAGE_REQUEST_CODE = 2030; |
|
||||
|
|
||||
private final NotificationManagerCompat notificationManager; |
|
||||
|
|
||||
public DownloadWorker(@NonNull final Context context, @NonNull final WorkerParameters workerParams) { |
|
||||
super(context, workerParams); |
|
||||
notificationManager = NotificationManagerCompat.from(context); |
|
||||
} |
|
||||
|
|
||||
@NonNull |
|
||||
@Override |
|
||||
public Result doWork() { |
|
||||
final String downloadRequestFilePath = getInputData().getString(KEY_DOWNLOAD_REQUEST_JSON); |
|
||||
if (TextUtils.isEmpty(downloadRequestFilePath)) { |
|
||||
return Result.failure(new Data.Builder() |
|
||||
.putString("error", "downloadRequest is empty or null") |
|
||||
.build()); |
|
||||
} |
|
||||
final String downloadRequestString; |
|
||||
final File requestFile = new File(downloadRequestFilePath); |
|
||||
try (Scanner scanner = new Scanner(requestFile)) { |
|
||||
downloadRequestString = scanner.useDelimiter("\\A").next(); |
|
||||
} catch (Exception e) { |
|
||||
Log.e(TAG, "doWork: ", e); |
|
||||
return Result.failure(new Data.Builder() |
|
||||
.putString("error", e.getLocalizedMessage()) |
|
||||
.build()); |
|
||||
} |
|
||||
if (TextUtils.isEmpty(downloadRequestString)) { |
|
||||
return Result.failure(new Data.Builder() |
|
||||
.putString("error", "downloadRequest is empty or null") |
|
||||
.build()); |
|
||||
|
override suspend fun doWork(): Result { |
||||
|
val downloadRequestFilePath = inputData.getString(KEY_DOWNLOAD_REQUEST_JSON) |
||||
|
if (downloadRequestFilePath.isNullOrBlank()) { |
||||
|
return Result.failure(Data.Builder() |
||||
|
.putString("error", "downloadRequest is empty or null") |
||||
|
.build()) |
||||
} |
} |
||||
final DownloadRequest downloadRequest; |
|
||||
|
val downloadRequestString: String |
||||
|
val requestFile = File(downloadRequestFilePath) |
||||
try { |
try { |
||||
downloadRequest = new Gson().fromJson(downloadRequestString, DownloadRequest.class); |
|
||||
} catch (JsonSyntaxException e) { |
|
||||
Log.e(TAG, "doWork", e); |
|
||||
return Result.failure(new Data.Builder() |
|
||||
.putString("error", e.getLocalizedMessage()) |
|
||||
.build()); |
|
||||
|
downloadRequestString = requestFile.bufferedReader().use { it.readText() } |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "doWork: ", e) |
||||
|
return Result.failure(Data.Builder() |
||||
|
.putString("error", e.localizedMessage) |
||||
|
.build()) |
||||
} |
} |
||||
if (downloadRequest == null) { |
|
||||
return Result.failure(new Data.Builder() |
|
||||
.putString("error", "downloadRequest is null") |
|
||||
.build()); |
|
||||
|
if (downloadRequestString.isBlank()) { |
||||
|
return Result.failure(Data.Builder() |
||||
|
.putString("error", "downloadRequest is empty") |
||||
|
.build()) |
||||
} |
} |
||||
final Map<String, String> urlToFilePathMap = downloadRequest.getUrlToFilePathMap(); |
|
||||
download(urlToFilePathMap); |
|
||||
new Handler(Looper.getMainLooper()).postDelayed(() -> showSummary(urlToFilePathMap), 500); |
|
||||
final boolean deleted = requestFile.delete(); |
|
||||
|
val downloadRequest: DownloadRequest = try { |
||||
|
Gson().fromJson(downloadRequestString, DownloadRequest::class.java) |
||||
|
} catch (e: JsonSyntaxException) { |
||||
|
Log.e(TAG, "doWork", e) |
||||
|
return Result.failure(Data.Builder() |
||||
|
.putString("error", e.localizedMessage) |
||||
|
.build()) |
||||
|
} ?: return Result.failure(Data.Builder() |
||||
|
.putString("error", "downloadRequest is null") |
||||
|
.build()) |
||||
|
val urlToFilePathMap = downloadRequest.urlToFilePathMap |
||||
|
download(urlToFilePathMap) |
||||
|
Handler(Looper.getMainLooper()).postDelayed({ showSummary(urlToFilePathMap) }, 500) |
||||
|
val deleted = requestFile.delete() |
||||
if (!deleted) { |
if (!deleted) { |
||||
Log.w(TAG, "doWork: requestFile not deleted!"); |
|
||||
|
Log.w(TAG, "doWork: requestFile not deleted!") |
||||
} |
} |
||||
return Result.success(); |
|
||||
|
return Result.success() |
||||
} |
} |
||||
|
|
||||
private void download(final Map<String, String> urlToFilePathMap) { |
|
||||
final int notificationId = getNotificationId(); |
|
||||
final Set<Map.Entry<String, String>> entries = urlToFilePathMap.entrySet(); |
|
||||
int count = 1; |
|
||||
final int total = urlToFilePathMap.size(); |
|
||||
for (final Map.Entry<String, String> urlAndFilePath : entries) { |
|
||||
final String url = urlAndFilePath.getKey(); |
|
||||
updateDownloadProgress(notificationId, count, total, 0); |
|
||||
download(notificationId, count, total, url, urlAndFilePath.getValue()); |
|
||||
count++; |
|
||||
|
private suspend fun download(urlToFilePathMap: Map<String, String>) { |
||||
|
val notificationId = notificationId |
||||
|
val entries = urlToFilePathMap.entries |
||||
|
var count = 1 |
||||
|
val total = urlToFilePathMap.size |
||||
|
for ((url, value) in entries) { |
||||
|
updateDownloadProgress(notificationId, count, total, 0f) |
||||
|
withContext(Dispatchers.IO) { |
||||
|
download(notificationId, count, total, url, value) |
||||
|
} |
||||
|
count++ |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
private int getNotificationId() { |
|
||||
return Math.abs(getId().hashCode()); |
|
||||
} |
|
||||
|
private val notificationId: Int |
||||
|
get() = abs(id.hashCode()) |
||||
|
|
||||
private void download(final int notificationId, |
|
||||
final int position, |
|
||||
final int total, |
|
||||
final String url, |
|
||||
final String filePath) { |
|
||||
final boolean isJpg = filePath.endsWith("jpg"); |
|
||||
|
private fun download( |
||||
|
notificationId: Int, |
||||
|
position: Int, |
||||
|
total: Int, |
||||
|
url: String, |
||||
|
filePath: String, |
||||
|
) { |
||||
|
val isJpg = filePath.endsWith("jpg") |
||||
// using temp file approach to remove IPTC so that download progress can be reported |
// using temp file approach to remove IPTC so that download progress can be reported |
||||
final File outFile = isJpg ? DownloadUtils.getTempFile() : new File(filePath); |
|
||||
|
val outFile = if (isJpg) DownloadUtils.getTempFile() else File(filePath) |
||||
try { |
try { |
||||
final URLConnection urlConnection = new URL(url).openConnection(); |
|
||||
final long fileSize = Build.VERSION.SDK_INT >= 24 ? urlConnection.getContentLengthLong() : |
|
||||
urlConnection.getContentLength(); |
|
||||
float totalRead = 0; |
|
||||
try (final BufferedInputStream bis = new BufferedInputStream(urlConnection.getInputStream()); |
|
||||
final FileOutputStream fos = new FileOutputStream(outFile)) { |
|
||||
final byte[] buffer = new byte[0x2000]; |
|
||||
int count; |
|
||||
while ((count = bis.read(buffer, 0, 0x2000)) != -1) { |
|
||||
totalRead = totalRead + count; |
|
||||
fos.write(buffer, 0, count); |
|
||||
setProgressAsync(new Data.Builder().putString(URL, url) |
|
||||
.putFloat(PROGRESS, totalRead * 100f / fileSize) |
|
||||
.build()); |
|
||||
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize); |
|
||||
|
val urlConnection = URL(url).openConnection() |
||||
|
val fileSize = if (Build.VERSION.SDK_INT >= 24) urlConnection.contentLengthLong else urlConnection.contentLength.toLong() |
||||
|
var totalRead = 0f |
||||
|
try { |
||||
|
BufferedInputStream(urlConnection.getInputStream()).use { bis -> |
||||
|
FileOutputStream(outFile).use { fos -> |
||||
|
val buffer = ByteArray(0x2000) |
||||
|
var count: Int |
||||
|
while (bis.read(buffer, 0, 0x2000).also { count = it } != -1) { |
||||
|
totalRead += count |
||||
|
fos.write(buffer, 0, count) |
||||
|
setProgressAsync(Data.Builder().putString(URL, url) |
||||
|
.putFloat(PROGRESS, totalRead * 100f / fileSize) |
||||
|
.build()) |
||||
|
updateDownloadProgress(notificationId, position, total, totalRead * 100f / fileSize) |
||||
|
} |
||||
|
fos.flush() |
||||
|
} |
||||
} |
} |
||||
fos.flush(); |
|
||||
} catch (final Exception e) { |
|
||||
Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.getAbsolutePath(), e); |
|
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "Error while writing data from url: " + url + " to file: " + outFile.absolutePath, e) |
||||
} |
} |
||||
if (isJpg) { |
if (isJpg) { |
||||
final File finalFile = new File(filePath); |
|
||||
try (FileInputStream fis = new FileInputStream(outFile); |
|
||||
FileOutputStream fos = new FileOutputStream(finalFile)) { |
|
||||
final JpegIptcRewriter jpegIptcRewriter = new JpegIptcRewriter(); |
|
||||
jpegIptcRewriter.removeIPTC(fis, fos); |
|
||||
} catch (Exception e) { |
|
||||
|
val finalFile = File(filePath) |
||||
|
try { |
||||
|
FileInputStream(outFile).use { fis -> |
||||
|
FileOutputStream(finalFile).use { fos -> |
||||
|
val jpegIptcRewriter = JpegIptcRewriter() |
||||
|
jpegIptcRewriter.removeIPTC(fis, fos) |
||||
|
} |
||||
|
} |
||||
|
} catch (e: Exception) { |
||||
Log.e(TAG, "Error while removing iptc: url: " + url |
Log.e(TAG, "Error while removing iptc: url: " + url |
||||
+ ", tempFile: " + outFile.getAbsolutePath() |
|
||||
+ ", finalFile: " + finalFile.getAbsolutePath(), e); |
|
||||
|
+ ", tempFile: " + outFile.absolutePath |
||||
|
+ ", finalFile: " + finalFile.absolutePath, e) |
||||
} |
} |
||||
final boolean deleted = outFile.delete(); |
|
||||
|
val deleted = outFile.delete() |
||||
if (!deleted) { |
if (!deleted) { |
||||
Log.w(TAG, "download: tempFile not deleted!"); |
|
||||
|
Log.w(TAG, "download: tempFile not deleted!") |
||||
} |
} |
||||
} |
} |
||||
} catch (final Exception e) { |
|
||||
Log.e(TAG, "Error while downloading: " + url, e); |
|
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "Error while downloading: $url", e) |
||||
} |
} |
||||
setProgressAsync(new Data.Builder().putString(URL, url) |
|
||||
.putFloat(PROGRESS, 100) |
|
||||
.build()); |
|
||||
updateDownloadProgress(notificationId, position, total, 100); |
|
||||
|
setProgressAsync(Data.Builder().putString(URL, url) |
||||
|
.putFloat(PROGRESS, 100f) |
||||
|
.build()) |
||||
|
updateDownloadProgress(notificationId, position, total, 100f) |
||||
} |
} |
||||
|
|
||||
private void updateDownloadProgress(final int notificationId, |
|
||||
final int position, |
|
||||
final int total, |
|
||||
final float percent) { |
|
||||
final Notification notification = createProgressNotification(position, total, percent); |
|
||||
|
private fun updateDownloadProgress( |
||||
|
notificationId: Int, |
||||
|
position: Int, |
||||
|
total: Int, |
||||
|
percent: Float, |
||||
|
) { |
||||
|
val notification = createProgressNotification(position, total, percent) |
||||
try { |
try { |
||||
if (notification == null) { |
if (notification == null) { |
||||
notificationManager.cancel(notificationId); |
|
||||
return; |
|
||||
|
notificationManager.cancel(notificationId) |
||||
|
return |
||||
} |
} |
||||
setForegroundAsync(new ForegroundInfo(notificationId, notification)).get(); |
|
||||
} catch (ExecutionException | InterruptedException e) { |
|
||||
Log.e(TAG, "updateDownloadProgress", e); |
|
||||
|
setForegroundAsync(ForegroundInfo(notificationId, notification)).get() |
||||
|
} catch (e: ExecutionException) { |
||||
|
Log.e(TAG, "updateDownloadProgress", e) |
||||
|
} catch (e: InterruptedException) { |
||||
|
Log.e(TAG, "updateDownloadProgress", e) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
private Notification createProgressNotification(final int position, final int total, final float percent) { |
|
||||
final Context context = getApplicationContext(); |
|
||||
boolean ongoing = true; |
|
||||
int totalPercent; |
|
||||
if (position == total && percent == 100) { |
|
||||
ongoing = false; |
|
||||
totalPercent = 100; |
|
||||
|
private fun createProgressNotification(position: Int, total: Int, percent: Float): Notification? { |
||||
|
val context = applicationContext |
||||
|
var ongoing = true |
||||
|
val totalPercent: Int |
||||
|
if (position == total && percent == 100f) { |
||||
|
ongoing = false |
||||
|
totalPercent = 100 |
||||
} else { |
} else { |
||||
totalPercent = (int) ((100f * (position - 1) / total) + (1f / total) * (percent)); |
|
||||
|
totalPercent = (100f * (position - 1) / total + 1f / total * percent).toInt() |
||||
} |
} |
||||
if (totalPercent == 100) { |
if (totalPercent == 100) { |
||||
return null; |
|
||||
|
return null |
||||
} |
} |
||||
// Log.d(TAG, "createProgressNotification: position: " + position |
// Log.d(TAG, "createProgressNotification: position: " + position |
||||
// + ", total: " + total |
// + ", total: " + total |
||||
// + ", percent: " + percent |
// + ", percent: " + percent |
||||
// + ", totalPercent: " + totalPercent); |
// + ", totalPercent: " + totalPercent); |
||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, Constants.DOWNLOAD_CHANNEL_ID) |
|
||||
.setCategory(NotificationCompat.CATEGORY_PROGRESS) |
|
||||
.setSmallIcon(R.drawable.ic_download) |
|
||||
.setOngoing(ongoing) |
|
||||
.setProgress(100, totalPercent, totalPercent < 0) |
|
||||
.setAutoCancel(false) |
|
||||
.setOnlyAlertOnce(true) |
|
||||
.setContentTitle(context.getString(R.string.downloader_downloading_post)); |
|
||||
|
val builder = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) |
||||
|
.setCategory(NotificationCompat.CATEGORY_PROGRESS) |
||||
|
.setSmallIcon(R.drawable.ic_download) |
||||
|
.setOngoing(ongoing) |
||||
|
.setProgress(100, totalPercent, totalPercent < 0) |
||||
|
.setAutoCancel(false) |
||||
|
.setOnlyAlertOnce(true) |
||||
|
.setContentTitle(context.getString(R.string.downloader_downloading_post)) |
||||
if (total > 1) { |
if (total > 1) { |
||||
builder.setContentText(context.getString(R.string.downloader_downloading_child, position, total)); |
|
||||
|
builder.setContentText(context.getString(R.string.downloader_downloading_child, position, total)) |
||||
} |
} |
||||
return builder.build(); |
|
||||
|
return builder.build() |
||||
} |
} |
||||
|
|
||||
private void showSummary(final Map<String, String> urlToFilePathMap) { |
|
||||
final Context context = getApplicationContext(); |
|
||||
final Collection<String> filePaths = urlToFilePathMap.values(); |
|
||||
final List<NotificationCompat.Builder> notifications = new LinkedList<>(); |
|
||||
final List<Integer> notificationIds = new LinkedList<>(); |
|
||||
int count = 1; |
|
||||
for (final String filePath : filePaths) { |
|
||||
final File file = new File(filePath); |
|
||||
context.sendBroadcast(new Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))); |
|
||||
MediaScannerConnection.scanFile(context, new String[]{file.getAbsolutePath()}, null, null); |
|
||||
final Uri uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file); |
|
||||
final ContentResolver contentResolver = context.getContentResolver(); |
|
||||
final Bitmap bitmap = getThumbnail(context, file, uri, contentResolver); |
|
||||
final String downloadComplete = context.getString(R.string.downloader_complete); |
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, uri) |
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
|
||||
| Intent.FLAG_FROM_BACKGROUND |
|
||||
| Intent.FLAG_GRANT_READ_URI_PERMISSION |
|
||||
| Intent.FLAG_GRANT_WRITE_URI_PERMISSION) |
|
||||
.putExtra(Intent.EXTRA_STREAM, uri); |
|
||||
final PendingIntent pendingIntent = PendingIntent.getActivity( |
|
||||
context, |
|
||||
DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, |
|
||||
intent, |
|
||||
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_ONE_SHOT |
|
||||
); |
|
||||
final int notificationId = getNotificationId() + count; |
|
||||
notificationIds.add(notificationId); |
|
||||
count++; |
|
||||
final NotificationCompat.Builder builder = new NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) |
|
||||
.setSmallIcon(R.drawable.ic_download) |
|
||||
.setContentText(null) |
|
||||
.setContentTitle(downloadComplete) |
|
||||
.setWhen(System.currentTimeMillis()) |
|
||||
.setOnlyAlertOnce(true) |
|
||||
.setAutoCancel(true) |
|
||||
.setGroup(NOTIF_GROUP_NAME + "_" + getId()) |
|
||||
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) |
|
||||
.setContentIntent(pendingIntent) |
|
||||
.addAction(R.drawable.ic_delete, |
|
||||
context.getString(R.string.delete), |
|
||||
DeleteImageIntentService.pendingIntent(context, filePath, notificationId)); |
|
||||
|
private fun showSummary(urlToFilePathMap: Map<String, String>?) { |
||||
|
val context = applicationContext |
||||
|
val filePaths = urlToFilePathMap!!.values |
||||
|
val notifications: MutableList<NotificationCompat.Builder> = LinkedList() |
||||
|
val notificationIds: MutableList<Int> = LinkedList() |
||||
|
var count = 1 |
||||
|
for (filePath in filePaths) { |
||||
|
val file = File(filePath) |
||||
|
context.sendBroadcast(Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE, Uri.fromFile(file))) |
||||
|
MediaScannerConnection.scanFile(context, arrayOf(file.absolutePath), null, null) |
||||
|
val uri = FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", file) |
||||
|
val contentResolver = context.contentResolver |
||||
|
val bitmap = getThumbnail(context, file, uri, contentResolver) |
||||
|
val downloadComplete = context.getString(R.string.downloader_complete) |
||||
|
val intent = Intent(Intent.ACTION_VIEW, uri) |
||||
|
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
||||
|
or Intent.FLAG_FROM_BACKGROUND |
||||
|
or Intent.FLAG_GRANT_READ_URI_PERMISSION |
||||
|
or Intent.FLAG_GRANT_WRITE_URI_PERMISSION) |
||||
|
.putExtra(Intent.EXTRA_STREAM, uri) |
||||
|
val pendingIntent = PendingIntent.getActivity( |
||||
|
context, |
||||
|
DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE, |
||||
|
intent, |
||||
|
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_ONE_SHOT |
||||
|
) |
||||
|
val notificationId = notificationId + count |
||||
|
notificationIds.add(notificationId) |
||||
|
count++ |
||||
|
val builder: NotificationCompat.Builder = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) |
||||
|
.setSmallIcon(R.drawable.ic_download) |
||||
|
.setContentText(null) |
||||
|
.setContentTitle(downloadComplete) |
||||
|
.setWhen(System.currentTimeMillis()) |
||||
|
.setOnlyAlertOnce(true) |
||||
|
.setAutoCancel(true) |
||||
|
.setGroup(NOTIF_GROUP_NAME + "_" + id) |
||||
|
.setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY) |
||||
|
.setContentIntent(pendingIntent) |
||||
|
.addAction(R.drawable.ic_delete, |
||||
|
context.getString(R.string.delete), |
||||
|
DeleteImageIntentService.pendingIntent(context, filePath, notificationId)) |
||||
if (bitmap != null) { |
if (bitmap != null) { |
||||
builder.setLargeIcon(bitmap) |
builder.setLargeIcon(bitmap) |
||||
.setStyle(new NotificationCompat.BigPictureStyle() |
|
||||
.bigPicture(bitmap) |
|
||||
.bigLargeIcon(null)) |
|
||||
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL); |
|
||||
|
.setStyle(NotificationCompat.BigPictureStyle() |
||||
|
.bigPicture(bitmap) |
||||
|
.bigLargeIcon(null)) |
||||
|
.setBadgeIconType(NotificationCompat.BADGE_ICON_SMALL) |
||||
} |
} |
||||
notifications.add(builder); |
|
||||
|
notifications.add(builder) |
||||
} |
} |
||||
Notification summaryNotification = null; |
|
||||
if (urlToFilePathMap.size() != 1) { |
|
||||
final String text = "Downloaded " + urlToFilePathMap.size() + " items"; |
|
||||
summaryNotification = new NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) |
|
||||
.setContentTitle("Downloaded") |
|
||||
.setContentText(text) |
|
||||
.setSmallIcon(R.drawable.ic_download) |
|
||||
.setStyle(new NotificationCompat.InboxStyle().setSummaryText(text)) |
|
||||
.setGroup(NOTIF_GROUP_NAME + "_" + getId()) |
|
||||
.setGroupSummary(true) |
|
||||
.build(); |
|
||||
|
var summaryNotification: Notification? = null |
||||
|
if (urlToFilePathMap.size != 1) { |
||||
|
val text = "Downloaded " + urlToFilePathMap.size + " items" |
||||
|
summaryNotification = NotificationCompat.Builder(context, DOWNLOAD_CHANNEL_ID) |
||||
|
.setContentTitle("Downloaded") |
||||
|
.setContentText(text) |
||||
|
.setSmallIcon(R.drawable.ic_download) |
||||
|
.setStyle(NotificationCompat.InboxStyle().setSummaryText(text)) |
||||
|
.setGroup(NOTIF_GROUP_NAME + "_" + id) |
||||
|
.setGroupSummary(true) |
||||
|
.build() |
||||
} |
} |
||||
for (int i = 0; i < notifications.size(); i++) { |
|
||||
final NotificationCompat.Builder builder = notifications.get(i); |
|
||||
|
for (i in notifications.indices) { |
||||
|
val builder = notifications[i] |
||||
// only make sound and vibrate for the last notification |
// only make sound and vibrate for the last notification |
||||
if (i != notifications.size() - 1) { |
|
||||
|
if (i != notifications.size - 1) { |
||||
builder.setSound(null) |
builder.setSound(null) |
||||
.setVibrate(null); |
|
||||
|
.setVibrate(null) |
||||
} |
} |
||||
notificationManager.notify(notificationIds.get(i), builder.build()); |
|
||||
|
notificationManager.notify(notificationIds[i], builder.build()) |
||||
} |
} |
||||
if (summaryNotification != null) { |
if (summaryNotification != null) { |
||||
notificationManager.notify(getNotificationId() + count, summaryNotification); |
|
||||
|
notificationManager.notify(notificationId + count, summaryNotification) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
@Nullable |
|
||||
private Bitmap getThumbnail(final Context context, |
|
||||
final File file, |
|
||||
final Uri uri, |
|
||||
final ContentResolver contentResolver) { |
|
||||
final String mimeType = Utils.getMimeType(uri, contentResolver); |
|
||||
if (TextUtils.isEmpty(mimeType)) return null; |
|
||||
Bitmap bitmap = null; |
|
||||
|
private fun getThumbnail( |
||||
|
context: Context, |
||||
|
file: File, |
||||
|
uri: Uri, |
||||
|
contentResolver: ContentResolver, |
||||
|
): Bitmap? { |
||||
|
val mimeType = Utils.getMimeType(uri, contentResolver) |
||||
|
if (isEmpty(mimeType)) return null |
||||
|
var bitmap: Bitmap? = null |
||||
if (mimeType.startsWith("image")) { |
if (mimeType.startsWith("image")) { |
||||
try { |
try { |
||||
final BitmapUtils.BitmapResult bitmapResult = BitmapUtils |
|
||||
.getBitmapResult(context.getContentResolver(), uri, THUMBNAIL_SIZE, THUMBNAIL_SIZE, -1, true); |
|
||||
if (bitmapResult == null) return null; |
|
||||
bitmap = bitmapResult.bitmap; |
|
||||
} catch (final Exception e) { |
|
||||
Log.e(TAG, "", e); |
|
||||
|
val bitmapResult = BitmapUtils.getBitmapResult( |
||||
|
context.contentResolver, |
||||
|
uri, |
||||
|
BitmapUtils.THUMBNAIL_SIZE, |
||||
|
BitmapUtils.THUMBNAIL_SIZE, |
||||
|
-1f, |
||||
|
true |
||||
|
) ?: return null |
||||
|
bitmap = bitmapResult.bitmap |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "", e) |
||||
} |
} |
||||
return bitmap; |
|
||||
|
return bitmap |
||||
} |
} |
||||
if (mimeType.startsWith("video")) { |
if (mimeType.startsWith("video")) { |
||||
try { |
try { |
||||
MediaMetadataRetriever retriever = new MediaMetadataRetriever(); |
|
||||
try { |
|
||||
|
val retriever = MediaMetadataRetriever() |
||||
|
bitmap = try { |
||||
try { |
try { |
||||
retriever.setDataSource(context, uri); |
|
||||
} catch (final Exception e) { |
|
||||
retriever.setDataSource(file.getAbsolutePath()); |
|
||||
|
retriever.setDataSource(context, uri) |
||||
|
} catch (e: Exception) { |
||||
|
retriever.setDataSource(file.absolutePath) |
||||
} |
} |
||||
bitmap = retriever.getFrameAtTime(); |
|
||||
|
retriever.frameAtTime |
||||
} finally { |
} finally { |
||||
try { |
try { |
||||
retriever.release(); |
|
||||
} catch (Exception e) { |
|
||||
Log.e(TAG, "getThumbnail: ", e); |
|
||||
|
retriever.release() |
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "getThumbnail: ", e) |
||||
} |
} |
||||
} |
} |
||||
} catch (final Exception e) { |
|
||||
Log.e(TAG, "", e); |
|
||||
|
} catch (e: Exception) { |
||||
|
Log.e(TAG, "", e) |
||||
} |
} |
||||
} |
} |
||||
return bitmap; |
|
||||
|
return bitmap |
||||
} |
} |
||||
|
|
||||
public static class DownloadRequest { |
|
||||
private final Map<String, String> urlToFilePathMap; |
|
||||
|
class DownloadRequest private constructor(val urlToFilePathMap: Map<String, String>) { |
||||
|
|
||||
public static class Builder { |
|
||||
private Map<String, String> urlToFilePathMap; |
|
||||
|
|
||||
public Builder setUrlToFilePathMap(final Map<String, String> urlToFilePathMap) { |
|
||||
this.urlToFilePathMap = urlToFilePathMap; |
|
||||
return this; |
|
||||
|
class Builder { |
||||
|
private var urlToFilePathMap: MutableMap<String, String> = mutableMapOf() |
||||
|
fun setUrlToFilePathMap(urlToFilePathMap: MutableMap<String, String>): Builder { |
||||
|
this.urlToFilePathMap = urlToFilePathMap |
||||
|
return this |
||||
} |
} |
||||
|
|
||||
public Builder addUrl(@NonNull final String url, @NonNull final String filePath) { |
|
||||
if (urlToFilePathMap == null) { |
|
||||
urlToFilePathMap = new HashMap<>(); |
|
||||
} |
|
||||
urlToFilePathMap.put(url, filePath); |
|
||||
return this; |
|
||||
|
fun addUrl(url: String, filePath: String): Builder { |
||||
|
urlToFilePathMap[url] = filePath |
||||
|
return this |
||||
} |
} |
||||
|
|
||||
public DownloadRequest build() { |
|
||||
return new DownloadRequest(urlToFilePathMap); |
|
||||
|
fun build(): DownloadRequest { |
||||
|
return DownloadRequest(urlToFilePathMap) |
||||
} |
} |
||||
} |
} |
||||
|
|
||||
public static Builder builder() { |
|
||||
return new Builder(); |
|
||||
} |
|
||||
|
|
||||
private DownloadRequest(final Map<String, String> urlToFilePathMap) { |
|
||||
this.urlToFilePathMap = urlToFilePathMap; |
|
||||
|
companion object { |
||||
|
@JvmStatic |
||||
|
fun builder(): Builder { |
||||
|
return Builder() |
||||
|
} |
||||
} |
} |
||||
|
} |
||||
|
|
||||
public Map<String, String> getUrlToFilePathMap() { |
|
||||
return urlToFilePathMap; |
|
||||
} |
|
||||
|
companion object { |
||||
|
const val PROGRESS = "PROGRESS" |
||||
|
const val URL = "URL" |
||||
|
const val KEY_DOWNLOAD_REQUEST_JSON = "download_request_json" |
||||
|
private const val DOWNLOAD_GROUP = "DOWNLOAD_GROUP" |
||||
|
private const val DOWNLOAD_NOTIFICATION_INTENT_REQUEST_CODE = 2020 |
||||
|
private const val DELETE_IMAGE_REQUEST_CODE = 2030 |
||||
} |
} |
||||
} |
|
||||
|
|
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue