diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Infinity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Infinity.java index a654eab4..274d1ad7 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/Infinity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Infinity.java @@ -11,7 +11,6 @@ import androidx.annotation.Nullable; import com.evernote.android.state.StateSaver; import com.livefront.bridge.Bridge; import com.livefront.bridge.SavedStateHandler; -import com.melegy.redscreenofdeath.RedScreenOfDeath; import org.greenrobot.eventbus.EventBus; @@ -27,7 +26,7 @@ public class Infinity extends Application { public void onCreate() { super.onCreate(); - RedScreenOfDeath.init(this); + //RedScreenOfDeath.init(this); mAppComponent = DaggerAppComponent.builder() .appModule(new AppModule(this)) diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java index f7cd0e5b..aaa1450d 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/RedditDataRoomDatabase.java @@ -45,7 +45,8 @@ public abstract class RedditDataRoomDatabase extends RoomDatabase { RedditDataRoomDatabase.class, "reddit_data") .addMigrations(MIGRATION_1_2, MIGRATION_2_3, MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, MIGRATION_8_9, - MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12, MIGRATION_12_13) + MIGRATION_9_10, MIGRATION_10_11, MIGRATION_11_12, MIGRATION_12_13, + MIGRATION_13_14) .build(); } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadMediaService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadMediaService.java index 2d5f641b..1246634a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadMediaService.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadMediaService.java @@ -99,7 +99,7 @@ public class DownloadMediaService extends Service { Bundle intent = msg.getData(); downloadFinished = false; String fileUrl = intent.getString(EXTRA_URL); - final String[] fileName = {intent.getString(EXTRA_FILE_NAME)}; + String fileName = intent.getString(EXTRA_FILE_NAME); String subredditName = intent.getString(EXTRA_SUBREDDIT_NAME); int mediaType = intent.getInt(EXTRA_MEDIA_TYPE, EXTRA_MEDIA_TYPE_IMAGE); String mimeType = mediaType == EXTRA_MEDIA_TYPE_VIDEO ? "video/*" : "image/*"; @@ -152,7 +152,7 @@ public class DownloadMediaService extends Service { null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); return; } - destinationFileUriString = directoryPath + fileName[0]; + destinationFileUriString = directoryPath + fileName; } else { downloadFinished(mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); @@ -191,16 +191,16 @@ public class DownloadMediaService extends Service { return; } } - DocumentFile checkForDuplicates = dir.findFile(fileName[0]); - int extensionPosition = fileName[0].lastIndexOf('.'); - String extension = fileName[0].substring(extensionPosition); + DocumentFile checkForDuplicates = dir.findFile(fileName); + int extensionPosition = fileName.lastIndexOf('.'); + String extension = fileName.substring(extensionPosition); int num = 1; while (checkForDuplicates != null) { - fileName[0] = fileName[0].substring(0, extensionPosition) + " (" + num + ")" + extension; - checkForDuplicates = dir.findFile(fileName[0]); + fileName = fileName.substring(0, extensionPosition) + " (" + num + ")" + extension; + checkForDuplicates = dir.findFile(fileName); num++; } - picFile = dir.createFile(mimeType, fileName[0]); + picFile = dir.createFile(mimeType, fileName); if (picFile == null) { downloadFinished(mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); @@ -219,7 +219,7 @@ public class DownloadMediaService extends Service { try { if (response != null && response.body() != null) { Uri destinationFileUri = writeResponseBodyToDisk(response.body(), isDefaultDestination, destinationFileUriString, - fileName[0], mediaType); + fileName, mediaType); downloadFinished(mediaType, randomNotificationIdOffset, mimeType, destinationFileUri, NO_ERROR); } @@ -348,7 +348,6 @@ public class DownloadMediaService extends Service { @Override public void onCreate() { - super.onCreate(); ((Infinity) getApplication()).getAppComponent().inject(this); notificationManager = NotificationManagerCompat.from(this); // Start up the thread running the service. Note that we create a diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadRedditVideoService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadRedditVideoService.java index 696a1d34..c4f37aca 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadRedditVideoService.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadRedditVideoService.java @@ -15,13 +15,17 @@ import android.media.MediaFormat; import android.media.MediaMuxer; import android.media.MediaScannerConnection; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; +import android.os.Bundle; import android.os.Environment; +import android.os.Handler; +import android.os.HandlerThread; import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.Process; import android.provider.MediaStore; -import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.documentfile.provider.DocumentFile; @@ -35,6 +39,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.ByteBuffer; +import java.util.Random; import javax.inject.Inject; import javax.inject.Named; @@ -49,8 +54,6 @@ import ml.docilealligator.infinityforreddit.utils.NotificationUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; -import retrofit2.Call; -import retrofit2.Callback; import retrofit2.Response; import retrofit2.Retrofit; @@ -79,101 +82,82 @@ public class DownloadRedditVideoService extends Service { SharedPreferences sharedPreferences; @Inject CustomThemeWrapper customThemeWrapper; - String resultFile; + private ServiceHandler serviceHandler; private NotificationManagerCompat notificationManager; private NotificationCompat.Builder builder; public DownloadRedditVideoService() { } - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - ((Infinity) getApplication()).getAppComponent().inject(this); - - String videoUrl = intent.getStringExtra(EXTRA_VIDEO_URL); - String audioUrl = videoUrl.substring(0, videoUrl.lastIndexOf('/')) + "/DASH_audio.mp4"; - String subredditName = intent.getStringExtra(EXTRA_SUBREDDIT); - final String[] fileNameWithoutExtension = {subredditName + "-" + intent.getStringExtra(EXTRA_POST_ID)}; - - notificationManager = NotificationManagerCompat.from(this); - builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO); - - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel serviceChannel; - serviceChannel = new NotificationChannel( - NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO, - NotificationUtils.CHANNEL_DOWNLOAD_REDDIT_VIDEO, - NotificationManager.IMPORTANCE_LOW - ); - - notificationManager.createNotificationChannel(serviceChannel); + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); } + @Override + public void handleMessage(Message msg) { + Bundle intent = msg.getData(); + String videoUrl = intent.getString(EXTRA_VIDEO_URL); + String audioUrl = videoUrl.substring(0, videoUrl.lastIndexOf('/')) + "/DASH_audio.mp4"; + String subredditName = intent.getString(EXTRA_SUBREDDIT); + String fileNameWithoutExtension = subredditName + "-" + intent.getString(EXTRA_POST_ID); + int randomNotificationIdOffset = msg.arg1; - startForeground( - NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, - createNotification(fileNameWithoutExtension[0] + ".mp4") - ); + final DownloadProgressResponseBody.ProgressListener progressListener = new DownloadProgressResponseBody.ProgressListener() { + long time = 0; - final DownloadProgressResponseBody.ProgressListener progressListener = new DownloadProgressResponseBody.ProgressListener() { - long time = 0; - - @Override public void update(long bytesRead, long contentLength, boolean done) { - if (!done) { - if (contentLength != -1) { - long currentTime = System.currentTimeMillis(); - if (currentTime - time > 1000) { - time = currentTime; - updateNotification(0, (int) ((100 * bytesRead) / contentLength), null); + @Override public void update(long bytesRead, long contentLength, boolean done) { + if (!done) { + if (contentLength != -1) { + long currentTime = System.currentTimeMillis(); + if (currentTime - time > 1000) { + time = currentTime; + updateNotification(0, (int) ((100 * bytesRead) / contentLength), + randomNotificationIdOffset, null); + } } } } - } - }; + }; - OkHttpClient client = new OkHttpClient.Builder() - .addNetworkInterceptor(chain -> { - okhttp3.Response originalResponse = chain.proceed(chain.request()); - return originalResponse.newBuilder() - .body(new DownloadProgressResponseBody(originalResponse.body(), progressListener)) - .build(); - }) - .build(); + OkHttpClient client = new OkHttpClient.Builder() + .addNetworkInterceptor(chain -> { + okhttp3.Response originalResponse = chain.proceed(chain.request()); + return originalResponse.newBuilder() + .body(new DownloadProgressResponseBody(originalResponse.body(), progressListener)) + .build(); + }) + .build(); - retrofit = retrofit.newBuilder().client(client).build(); + retrofit = retrofit.newBuilder().client(client).build(); - DownloadFile downloadFile = retrofit.create(DownloadFile.class); + DownloadFile downloadFile = retrofit.create(DownloadFile.class); - boolean separateDownloadFolder = sharedPreferences.getBoolean(SharedPreferencesUtils.SEPARATE_FOLDER_FOR_EACH_SUBREDDIT, false); + boolean separateDownloadFolder = sharedPreferences.getBoolean(SharedPreferencesUtils.SEPARATE_FOLDER_FOR_EACH_SUBREDDIT, false); - File directory = getExternalCacheDir(); - String destinationFileName = fileNameWithoutExtension[0] + ".mp4"; - if (directory != null) { - String directoryPath = directory.getAbsolutePath() + "/"; - downloadFile.downloadFile(videoUrl).enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response videoResponse) { + File externalCacheDirectory = getExternalCacheDir(); + if (externalCacheDirectory != null) { + String destinationFileName = fileNameWithoutExtension + ".mp4"; + + try { + Response videoResponse = downloadFile.downloadFile(videoUrl).execute(); if (videoResponse.isSuccessful() && videoResponse.body() != null) { + String externalCacheDirectoryPath = externalCacheDirectory.getAbsolutePath() + "/"; String destinationFileDirectory = sharedPreferences.getString(SharedPreferencesUtils.VIDEO_DOWNLOAD_LOCATION, ""); String destinationFileUriString; boolean isDefaultDestination; if (destinationFileDirectory.equals("")) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - File directory = getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); - if (directory != null) { - String directoryPath = separateDownloadFolder ? directory.getAbsolutePath() + "/Infinity/" + subredditName + "/" : directory.getAbsolutePath() + "/Infinity/"; - File infinityDir = new File(directoryPath); + File destinationDirectory = getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); + if (destinationDirectory != null) { + String destinationDirectoryPath = separateDownloadFolder ? destinationDirectory.getAbsolutePath() + "/Infinity/" + subredditName + "/" : destinationDirectory.getAbsolutePath() + "/Infinity/"; + File infinityDir = new File(destinationDirectoryPath); if (!infinityDir.exists() && !infinityDir.mkdir()) { - downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); + downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY, randomNotificationIdOffset); return; } - destinationFileUriString = directoryPath + destinationFileName; + destinationFileUriString = destinationDirectoryPath + destinationFileName; } else { - downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); + downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY, randomNotificationIdOffset); return; } } else { @@ -187,272 +171,104 @@ public class DownloadRedditVideoService extends Service { if (separateDownloadFolder) { dir = DocumentFile.fromTreeUri(DownloadRedditVideoService.this, Uri.parse(destinationFileDirectory)); if (dir == null) { - downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); + downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY, randomNotificationIdOffset); return; } dir = dir.findFile(subredditName); if (dir == null) { dir = DocumentFile.fromTreeUri(DownloadRedditVideoService.this, Uri.parse(destinationFileDirectory)).createDirectory(subredditName); if (dir == null) { - downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); + downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY, randomNotificationIdOffset); return; } } } else { dir = DocumentFile.fromTreeUri(DownloadRedditVideoService.this, Uri.parse(destinationFileDirectory)); if (dir == null) { - downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); + downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY, randomNotificationIdOffset); return; } } DocumentFile checkForDuplicates = dir.findFile(destinationFileName); int num = 1; while (checkForDuplicates != null) { - fileNameWithoutExtension[0] = fileNameWithoutExtension[0] + " (" + num + ")"; - checkForDuplicates = dir.findFile(fileNameWithoutExtension[0] + ".mp4"); + fileNameWithoutExtension = fileNameWithoutExtension + " (" + num + ")"; + checkForDuplicates = dir.findFile(fileNameWithoutExtension + ".mp4"); num++; } - picFile = dir.createFile("video/*", fileNameWithoutExtension[0] + ".mp4"); + picFile = dir.createFile("video/*", fileNameWithoutExtension + ".mp4"); if (picFile == null) { - downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); + downloadFinished(null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY, randomNotificationIdOffset); return; } destinationFileUriString = picFile.getUri().toString(); } - updateNotification(R.string.downloading_reddit_video_audio_track, 0, null); - downloadFile.downloadFile(audioUrl).enqueue(new Callback() { - @Override - public void onResponse(@NonNull Call call, @NonNull Response audioResponse) { - if (audioResponse.isSuccessful() && audioResponse.body() != null) { - String videoFilePath = directoryPath + fileNameWithoutExtension[0] + "-cache.mp4"; - String audioFilePath = directoryPath + fileNameWithoutExtension[0] + "-cache.mp3"; - String outputFilePath = directoryPath + fileNameWithoutExtension[0] + ".mp4"; - new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), - audioResponse.body(), videoFilePath, audioFilePath, outputFilePath, - destinationFileName, destinationFileUriString, isDefaultDestination, - getContentResolver(), - new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { - @Override - public void finished(Uri destinationFileUri, int errorCode) { - new File(videoFilePath).delete(); - new File(audioFilePath).delete(); - new File(outputFilePath).delete(); - downloadFinished(destinationFileUri, errorCode); - } + updateNotification(R.string.downloading_reddit_video_audio_track, 0, + randomNotificationIdOffset, null); - @Override - public void updateProgressNotification(int stringResId) { - updateNotification(stringResId, -1, null); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } else { - String videoFilePath = directoryPath + fileNameWithoutExtension[0] + "-cache.mp4"; - new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), - null, videoFilePath, null, null, - destinationFileName, destinationFileUriString, isDefaultDestination, - getContentResolver(), - new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { - @Override - public void finished(Uri destinationFileUri, int errorCode) { - new File(videoFilePath).delete(); - downloadFinished(destinationFileUri, errorCode); - } + Response audioResponse = downloadFile.downloadFile(audioUrl).execute(); + if (audioResponse.isSuccessful() && audioResponse.body() != null) { + String videoFilePath = externalCacheDirectoryPath + fileNameWithoutExtension + "-cache.mp4"; + String audioFilePath = externalCacheDirectoryPath + fileNameWithoutExtension + "-cache.mp3"; + String outputFilePath = externalCacheDirectoryPath + fileNameWithoutExtension + ".mp4"; - @Override - public void updateProgressNotification(int stringResId) { - updateNotification(stringResId, -1, null); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } + String savedVideoFilePath = writeResponseBodyToDisk(videoResponse.body(), videoFilePath); + if (savedVideoFilePath == null) { + downloadFinished(null, ERROR_VIDEO_FILE_CANNOT_SAVE, randomNotificationIdOffset); + return; + } + String savedAudioFilePath = writeResponseBodyToDisk(audioResponse.body(), audioFilePath); + if (savedAudioFilePath == null) { + downloadFinished(null, ERROR_AUDIO_FILE_CANNOT_SAVE, randomNotificationIdOffset); + return; } - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - String videoFilePath = directoryPath + fileNameWithoutExtension[0] + "-cache.mp4"; - new SaveTempMuxAndCopyAsyncTask(videoResponse.body(), - null, videoFilePath, null, null, - destinationFileName, destinationFileUriString, isDefaultDestination, - getContentResolver(), - new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() { - @Override - public void finished(Uri destinationFileUri, int errorCode) { - new File(videoFilePath).delete(); - downloadFinished(destinationFileUri, errorCode); - } - - @Override - public void updateProgressNotification(int stringResId) { - updateNotification(stringResId, -1, null); - } - }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + updateNotification(R.string.downloading_reddit_video_muxing, -1, + randomNotificationIdOffset, null); + if (!muxVideoAndAudio(videoFilePath, audioFilePath, outputFilePath)) { + downloadFinished(null, ERROR_MUX_FAILED, randomNotificationIdOffset); + return; } - }); + + updateNotification(R.string.downloading_reddit_video_save_file_to_public_dir, -1, + randomNotificationIdOffset, null); + try { + Uri destinationFileUri = copyToDestination(outputFilePath, destinationFileUriString, destinationFileName, isDefaultDestination); + + new File(videoFilePath).delete(); + new File(audioFilePath).delete(); + new File(outputFilePath).delete(); + + downloadFinished(destinationFileUri, NO_ERROR, randomNotificationIdOffset); + } catch (IOException e) { + e.printStackTrace(); + downloadFinished(null, ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE, randomNotificationIdOffset); + } + } else { + String videoFilePath = externalCacheDirectoryPath + fileNameWithoutExtension + "-cache.mp4"; + + updateNotification(R.string.downloading_reddit_video_save_file_to_public_dir, -1, + randomNotificationIdOffset, null); + try { + Uri destinationFileUri = copyToDestination(videoFilePath, destinationFileUriString, destinationFileName, isDefaultDestination); + new File(videoFilePath).delete(); + downloadFinished(destinationFileUri, NO_ERROR, randomNotificationIdOffset); + } catch (IOException e) { + e.printStackTrace(); + downloadFinished(null, ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE, randomNotificationIdOffset); + } + } } else { - downloadFinished(null, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD); + downloadFinished(null, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD, randomNotificationIdOffset); } - } - - @Override - public void onFailure(@NonNull Call call, @NonNull Throwable t) { - downloadFinished(null, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD); - } - }); - } else { - downloadFinished(null, ERROR_CANNOT_GET_CACHE_DIRECTORY); - } - - return START_NOT_STICKY; - } - - private void downloadFinished(Uri destinationFileUri, int errorCode) { - if (errorCode != NO_ERROR) { - switch (errorCode) { - case ERROR_CANNOT_GET_CACHE_DIRECTORY: - updateNotification(R.string.downloading_reddit_video_failed_cannot_get_cache_directory, -1, null); - break; - case ERROR_VIDEO_FILE_CANNOT_DOWNLOAD: - updateNotification(R.string.downloading_reddit_video_failed_cannot_download_video, -1, null); - break; - case ERROR_VIDEO_FILE_CANNOT_SAVE: - updateNotification(R.string.downloading_reddit_video_failed_cannot_save_video, -1, null); - break; - case ERROR_AUDIO_FILE_CANNOT_SAVE: - updateNotification(R.string.downloading_reddit_video_failed_cannot_save_audio, -1, null); - break; - case ERROR_MUX_FAILED: - updateNotification(R.string.downloading_reddit_video_failed_cannot_mux, -1, null); - break; - case ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE: - updateNotification(R.string.downloading_reddit_video_failed_cannot_save_mux_video, -1, null); - break; - case ERROR_CANNOT_GET_DESTINATION_DIRECTORY: - updateNotification(R.string.downloading_media_failed_cannot_save_to_destination_directory, -1, null); - break; - } - EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); - } else { - MediaScannerConnection.scanFile( - this, new String[]{destinationFileUri.toString()}, null, - (path, uri) -> { - Intent intent = new Intent(); - intent.setAction(android.content.Intent.ACTION_VIEW); - intent.setDataAndType(destinationFileUri, "video/*"); - intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); - updateNotification(R.string.downloading_reddit_video_finished, -1, pendingIntent); - EventBus.getDefault().post(new DownloadRedditVideoEvent(true)); - } - ); - } - stopForeground(false); - } - - private Notification createNotification(String fileName) { - builder.setContentTitle(fileName).setContentText(getString(R.string.downloading_reddit_video)).setProgress(100, 0, false); - return builder.setSmallIcon(R.drawable.ic_notification) - .setColor(customThemeWrapper.getColorPrimaryLightTheme()) - .build(); - } - - private void updateNotification(int contentStringResId, int progress, PendingIntent pendingIntent) { - if (notificationManager != null) { - if (progress < 0) { - builder.setProgress(0, 0, false); - } else { - builder.setProgress(100, progress, false); - } - if (contentStringResId != 0) { - builder.setContentText(getString(contentStringResId)); - } - if (pendingIntent != null) { - builder.setContentIntent(pendingIntent); - } - notificationManager.notify(NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, builder.build()); - } - } - - private static class SaveTempMuxAndCopyAsyncTask extends AsyncTask { - - private ResponseBody videoResponse; - private ResponseBody audioResponse; - private String videoFilePath; - private String audioFilePath; - private String outputFilePath; - private String destinationFileName; - @NonNull - private String destinationFileUriString; - private boolean isDefaultDestination; - private ContentResolver contentResolver; - private SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener; - private int errorCode = NO_ERROR; - - public SaveTempMuxAndCopyAsyncTask(ResponseBody videoResponse, ResponseBody audioResponse, - String videoFilePath, String audioFilePath, String outputFilePath, - String destinationFileName, @NonNull String destinationFileUriString, - boolean isDefaultDestination, ContentResolver contentResolver, - SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener) { - this.videoResponse = videoResponse; - this.audioResponse = audioResponse; - this.videoFilePath = videoFilePath; - this.audioFilePath = audioFilePath; - this.outputFilePath = outputFilePath; - this.destinationFileName = destinationFileName; - this.destinationFileUriString = destinationFileUriString; - this.isDefaultDestination = isDefaultDestination; - this.contentResolver = contentResolver; - this.saveTempMuxAndCopyAsyncTaskListener = saveTempMuxAndCopyAsyncTaskListener; - } - - @Override - protected void onProgressUpdate(Integer... values) { - super.onProgressUpdate(values); - saveTempMuxAndCopyAsyncTaskListener.updateProgressNotification(values[0]); - } - - @Override - protected Void doInBackground(Void... voids) { - String savedVideoFilePath = writeResponseBodyToDisk(videoResponse, videoFilePath); - if (savedVideoFilePath == null) { - errorCode = ERROR_VIDEO_FILE_CANNOT_SAVE; - return null; - } - if (audioResponse != null) { - String savedAudioFilePath = writeResponseBodyToDisk(audioResponse, audioFilePath); - if (savedAudioFilePath == null) { - errorCode = ERROR_AUDIO_FILE_CANNOT_SAVE; - return null; - } - - publishProgress(R.string.downloading_reddit_video_muxing); - if (!muxVideoAndAudio(videoFilePath, audioFilePath, outputFilePath)) { - errorCode = ERROR_MUX_FAILED; - return null; - } - - publishProgress(R.string.downloading_reddit_video_save_file_to_public_dir); - try { - copyToDestination(outputFilePath); } catch (IOException e) { e.printStackTrace(); - errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE; + downloadFinished(null, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD, randomNotificationIdOffset); } } else { - publishProgress(R.string.downloading_reddit_video_save_file_to_public_dir); - try { - copyToDestination(videoFilePath); - } catch (IOException e) { - e.printStackTrace(); - errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE; - } + downloadFinished(null, ERROR_CANNOT_GET_CACHE_DIRECTORY, randomNotificationIdOffset); } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - super.onPostExecute(aVoid); - saveTempMuxAndCopyAsyncTaskListener.finished(Uri.parse(destinationFileUriString), errorCode); } private String writeResponseBodyToDisk(ResponseBody body, String filePath) { @@ -576,7 +392,9 @@ public class DownloadRedditVideoService extends Service { return true; } - private void copyToDestination(String srcPath) throws IOException { + private Uri copyToDestination(String srcPath, String destinationFileUriString, String destinationFileName, + boolean isDefaultDestination) throws IOException { + ContentResolver contentResolver = getContentResolver(); if (isDefaultDestination) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { InputStream in = new FileInputStream(srcPath); @@ -623,7 +441,7 @@ public class DownloadRedditVideoService extends Service { contentValues.clear(); contentValues.put(MediaStore.Video.Media.IS_PENDING, 0); contentResolver.update(uri, contentValues, null, null); - destinationFileUriString = uri.toString(); + return uri; } catch (IOException e) { if (uri != null) { // Don't leave an orphan entry in the MediaStore @@ -651,11 +469,136 @@ public class DownloadRedditVideoService extends Service { stream.write(buf, 0, len); } } + + return Uri.parse(destinationFileUriString); + } + } + + @Override + public void onCreate() { + ((Infinity) getApplication()).getAppComponent().inject(this); + notificationManager = NotificationManagerCompat.from(this); + // Start up the thread running the service. Note that we create a + // separate thread because the service normally runs in the process's + // main thread, which we don't want to block. We also make it + // background priority so CPU-intensive work doesn't disrupt our UI. + HandlerThread thread = new HandlerThread("ServiceStartArguments", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + + // Get the HandlerThread's Looper and use it for our Handler + serviceHandler = new ServiceHandler(thread.getLooper()); + } + + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + builder = new NotificationCompat.Builder(DownloadRedditVideoService.this, NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO); + + String subredditName = intent.getStringExtra(EXTRA_SUBREDDIT); + String fileNameWithoutExtension = subredditName + "-" + intent.getStringExtra(EXTRA_POST_ID); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel serviceChannel; + serviceChannel = new NotificationChannel( + NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO, + NotificationUtils.CHANNEL_DOWNLOAD_REDDIT_VIDEO, + NotificationManager.IMPORTANCE_LOW + ); + + notificationManager.createNotificationChannel(serviceChannel); } - interface SaveTempMuxAndCopyAsyncTaskListener { - void finished(Uri destinationFileUri, int errorCode); - void updateProgressNotification(int stringResId); + int randomNotificationIdOffset = new Random().nextInt(10000); + startForeground( + NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(fileNameWithoutExtension + ".mp4") + ); + + Message msg = serviceHandler.obtainMessage(); + Bundle bundle = intent.getExtras(); + msg.setData(bundle); + msg.arg1 = randomNotificationIdOffset; + serviceHandler.sendMessage(msg); + + return START_NOT_STICKY; + } + + private void downloadFinished(Uri destinationFileUri, int errorCode, int randomNotificationIdOffset) { + if (errorCode != NO_ERROR) { + switch (errorCode) { + case ERROR_CANNOT_GET_CACHE_DIRECTORY: + updateNotification(R.string.downloading_reddit_video_failed_cannot_get_cache_directory, -1, + randomNotificationIdOffset, null); + break; + case ERROR_VIDEO_FILE_CANNOT_DOWNLOAD: + updateNotification(R.string.downloading_reddit_video_failed_cannot_download_video, -1, + randomNotificationIdOffset, null); + break; + case ERROR_VIDEO_FILE_CANNOT_SAVE: + updateNotification(R.string.downloading_reddit_video_failed_cannot_save_video, -1, + randomNotificationIdOffset, null); + break; + case ERROR_AUDIO_FILE_CANNOT_SAVE: + updateNotification(R.string.downloading_reddit_video_failed_cannot_save_audio, -1, + randomNotificationIdOffset, null); + break; + case ERROR_MUX_FAILED: + updateNotification(R.string.downloading_reddit_video_failed_cannot_mux, -1, + randomNotificationIdOffset, null); + break; + case ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE: + updateNotification(R.string.downloading_reddit_video_failed_cannot_save_mux_video, -1, + randomNotificationIdOffset, null); + break; + case ERROR_CANNOT_GET_DESTINATION_DIRECTORY: + updateNotification(R.string.downloading_media_failed_cannot_save_to_destination_directory, -1, + randomNotificationIdOffset, null); + break; + } + EventBus.getDefault().post(new DownloadRedditVideoEvent(false)); + } else { + MediaScannerConnection.scanFile( + this, new String[]{destinationFileUri.toString()}, null, + (path, uri) -> { + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(destinationFileUri, "video/*"); + intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); + PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT); + updateNotification(R.string.downloading_reddit_video_finished, -1, randomNotificationIdOffset, pendingIntent); + EventBus.getDefault().post(new DownloadRedditVideoEvent(true)); + } + ); + } + stopForeground(false); + } + + private Notification createNotification(String fileName) { + builder.setContentTitle(fileName).setContentText(getString(R.string.downloading_reddit_video)).setProgress(100, 0, false); + return builder.setSmallIcon(R.drawable.ic_notification) + .setColor(customThemeWrapper.getColorPrimaryLightTheme()) + .build(); + } + + private void updateNotification(int contentStringResId, int progress, int randomNotificationIdOffset, PendingIntent pendingIntent) { + if (notificationManager != null) { + if (progress < 0) { + builder.setProgress(0, 0, false); + } else { + builder.setProgress(100, progress, false); + } + if (contentStringResId != 0) { + builder.setContentText(getString(contentStringResId)); + } + if (pendingIntent != null) { + builder.setContentIntent(pendingIntent); + } + notificationManager.notify(NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID + randomNotificationIdOffset, builder.build()); } } } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/NotificationUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/NotificationUtils.java index 940838af..75cc004a 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/utils/NotificationUtils.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/utils/NotificationUtils.java @@ -26,7 +26,6 @@ public class NotificationUtils { public static final int DOWNLOAD_VIDEO_NOTIFICATION_ID = 30000; public static final int DOWNLOAD_IMAGE_NOTIFICATION_ID = 40000; public static final int DOWNLOAD_GIF_NOTIFICATION_ID = 50000; - public static final int PULL_NOTIFICATION_ALARM_RECEIVER_REQUEST_CODE = 12; private static final int SUMMARY_BASE_ID_UNREAD_MESSAGE = 0; private static final int NOTIFICATION_BASE_ID_UNREAD_MESSAGE = 1;