Add more notifications for downloading Reddit videos. Open the downloaded Reddit video when clicking the notification after downloading finishes. Put all work in an AsyncTask.

This commit is contained in:
Alex Ning 2020-06-07 17:14:05 +08:00
parent aa36d0c267
commit 580f9d2459
2 changed files with 324 additions and 259 deletions

View File

@ -13,7 +13,6 @@ import android.media.MediaCodec;
import android.media.MediaExtractor; import android.media.MediaExtractor;
import android.media.MediaFormat; import android.media.MediaFormat;
import android.media.MediaMuxer; import android.media.MediaMuxer;
import android.media.MediaScannerConnection;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Build; import android.os.Build;
@ -22,10 +21,10 @@ import android.os.IBinder;
import android.provider.MediaStore; import android.provider.MediaStore;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.core.content.FileProvider;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
@ -60,6 +59,14 @@ public class DownloadRedditVideoService extends Service {
public static final String EXTRA_SUBREDDIT = "ES"; public static final String EXTRA_SUBREDDIT = "ES";
public static final String EXTRA_POST_ID = "EPI"; public static final String EXTRA_POST_ID = "EPI";
private static final int NO_ERROR = -1;
private static final int ERROR_CANNOT_GET_CACHE_DIRECTORY = 0;
private static final int ERROR_VIDEO_FILE_CANNOT_DOWNLOAD = 1;
private static final int ERROR_VIDEO_FILE_CANNOT_SAVE = 2;
private static final int ERROR_AUDIO_FILE_CANNOT_SAVE = 3;
private static final int ERROR_MUX_FAILED = 4;
private static final int ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE = 5;
@Inject @Inject
@Named("download_reddit_video") @Named("download_reddit_video")
Retrofit retrofit; Retrofit retrofit;
@ -97,142 +104,224 @@ public class DownloadRedditVideoService extends Service {
startForeground( startForeground(
NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID,
createNotification(R.string.downloading_reddit_video, fileName + ".mp4", false, null) createNotification(R.string.downloading_reddit_video, fileName + ".mp4", null)
); );
DownloadRedditVideo downloadRedditVideo = retrofit.create(DownloadRedditVideo.class); DownloadRedditVideo downloadRedditVideo = retrofit.create(DownloadRedditVideo.class);
File directory = getExternalCacheDir(); File directory = getExternalCacheDir();
String destinationFileName = fileName + ".mp4";
if (directory != null) { if (directory != null) {
String directoryPath = directory.getAbsolutePath() + "/"; String directoryPath = directory.getAbsolutePath() + "/";
downloadRedditVideo.downloadFile(videoUrl).enqueue(new Callback<ResponseBody>() { downloadRedditVideo.downloadFile(videoUrl).enqueue(new Callback<ResponseBody>() {
@Override @Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> videoResponse) { public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> videoResponse) {
if (videoResponse.isSuccessful() && videoResponse.body() != null) { if (videoResponse.isSuccessful() && videoResponse.body() != null) {
String videoFilePath = writeResponseBodyToDisk(videoResponse.body(), directoryPath + fileName + "-cache.mp4"); updateNotification(R.string.downloading_reddit_video_audio_track, destinationFileName, null);
if (videoFilePath != null) {
resultFile = videoFilePath;
updateNotification(R.string.downloading_reddit_video_audio_track, fileName + ".mp3", false);
downloadRedditVideo.downloadFile(audioUrl).enqueue(new Callback<ResponseBody>() { downloadRedditVideo.downloadFile(audioUrl).enqueue(new Callback<ResponseBody>() {
@Override @Override
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> audioResponse) { public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> audioResponse) {
if (audioResponse.isSuccessful() && audioResponse.body() != null) { if (audioResponse.isSuccessful() && audioResponse.body() != null) {
String audioFilePath = writeResponseBodyToDisk(audioResponse.body(), directoryPath + fileName + "-cache.mp3"); String videoFilePath = directoryPath + fileName + "-cache.mp4";
if (audioFilePath != null) { String audioFilePath = directoryPath + fileName + "-cache.mp3";
updateNotification(R.string.downloading_reddit_video_muxing, null, false);
String outputFilePath = directoryPath + fileName + ".mp4"; String outputFilePath = directoryPath + fileName + ".mp4";
if (muxVideoAndAudio(videoFilePath, audioFilePath, outputFilePath)) { new SaveTempMuxAndCopyAsyncTask(videoResponse.body(),
resultFile = outputFilePath; audioResponse.body(), videoFilePath, audioFilePath, outputFilePath,
} destinationFileName, getContentResolver(),
new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() {
@Override
public void finished(Uri destinationFileUri, int errorCode) {
new File(videoFilePath).delete();
new File(audioFilePath).delete(); new File(audioFilePath).delete();
} new File(outputFilePath).delete();
} downloadFinished(destinationFileUri, destinationFileName, errorCode);
copyVideoToPublicDir(resultFile, fileName, new String[]{videoFilePath, resultFile});
} }
@Override @Override
public void onFailure(Call<ResponseBody> call, Throwable t) { public void updateProgressNotification(int stringResId) {
copyVideoToPublicDir(resultFile, fileName, new String[]{videoFilePath, resultFile}); updateNotification(stringResId, destinationFileName, null);
}
}).execute();
} else {
String videoFilePath = directoryPath + fileName + "-cache.mp4";
String destinationFileName = fileName + ".mp4";
new SaveTempMuxAndCopyAsyncTask(videoResponse.body(),
null, videoFilePath, null, null,
destinationFileName, getContentResolver(),
new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() {
@Override
public void finished(Uri destinationFileUri, int errorCode) {
new File(videoFilePath).delete();
downloadFinished(destinationFileUri, destinationFileName, errorCode);
} }
});
} else { @Override
downloadFinished(false); public void updateProgressNotification(int stringResId) {
updateNotification(stringResId, destinationFileName, null);
} }
} else { }).execute();
downloadFinished(false);
} }
} }
@Override @Override
public void onFailure(Call<ResponseBody> call, Throwable t) { public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
downloadFinished(false); String videoFilePath = directoryPath + fileName + "-cache.mp4";
String destinationFileName = fileName + ".mp4";
new SaveTempMuxAndCopyAsyncTask(videoResponse.body(),
null, videoFilePath, null, null,
destinationFileName, getContentResolver(),
new SaveTempMuxAndCopyAsyncTask.SaveTempMuxAndCopyAsyncTaskListener() {
@Override
public void finished(Uri destinationFileUri, int errorCode) {
new File(videoFilePath).delete();
downloadFinished(destinationFileUri, destinationFileName, errorCode);
}
@Override
public void updateProgressNotification(int stringResId) {
updateNotification(stringResId, destinationFileName, null);
}
}).execute();
} }
}); });
} else { } else {
downloadFinished(false); downloadFinished(null, destinationFileName, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD);
}
}
@Override
public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
downloadFinished(null, destinationFileName, ERROR_VIDEO_FILE_CANNOT_DOWNLOAD);
}
});
} else {
downloadFinished(null, destinationFileName, ERROR_CANNOT_GET_CACHE_DIRECTORY);
} }
return START_NOT_STICKY; return START_NOT_STICKY;
} }
private void addMediaFileAndShareNotification(File f, int stringResId, String fileName) { private void downloadFinished(Uri destinationFileUri, String fileName, int errorCode) {
MediaScannerConnection.scanFile( if (errorCode != NO_ERROR) {
this, new String[]{f.getAbsolutePath()}, null, switch (errorCode) {
(path, uri) -> openVideoNotification(f, stringResId, fileName) case ERROR_CANNOT_GET_CACHE_DIRECTORY:
); updateNotification(R.string.downloading_reddit_video_failed_cannot_get_cache_directory, fileName, null);
break;
case ERROR_VIDEO_FILE_CANNOT_DOWNLOAD:
updateNotification(R.string.downloading_reddit_video_failed_cannot_download_video, fileName, null);
break;
case ERROR_VIDEO_FILE_CANNOT_SAVE:
updateNotification(R.string.downloading_reddit_video_failed_cannot_save_video, fileName, null);
break;
case ERROR_AUDIO_FILE_CANNOT_SAVE:
updateNotification(R.string.downloading_reddit_video_failed_cannot_save_audio, fileName, null);
break;
case ERROR_MUX_FAILED:
updateNotification(R.string.downloading_reddit_video_failed_cannot_mux, fileName, null);
break;
case ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE:
updateNotification(R.string.downloading_reddit_video_failed_cannot_save_mux_video, fileName, null);
break;
} }
EventBus.getDefault().post(new DownloadRedditVideoEvent(false));
private void openVideoNotification(File f, int stringResId, String fileName) {
final Intent viewIntent = new Intent(Intent.ACTION_VIEW);
Uri selectedUri = FileProvider.getUriForFile(this, getApplicationContext().getPackageName() + ".provider", f);
viewIntent.setDataAndType(selectedUri, getContentResolver().getType(selectedUri));
viewIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
PendingIntent contentIntent = PendingIntent.getActivity(this, 0, viewIntent, PendingIntent.FLAG_CANCEL_CURRENT);
updateNotification(stringResId, fileName, true, contentIntent);
}
private void downloadFinished(boolean isSuccessful) {
EventBus.getDefault().post(new DownloadRedditVideoEvent(isSuccessful));
if (!isSuccessful)
updateNotification(R.string.downloading_reddit_video_failed, null, true);
stopService();
}
private Notification createNotification(int stringResId, String fileName, boolean finished, PendingIntent contentIntent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO);
if (fileName != null) {
builder.setContentTitle(getString(stringResId, fileName));
} else { } else {
builder.setContentTitle(getString(stringResId)); Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(destinationFileUri, "video/*");
PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
updateNotification(R.string.downloading_reddit_video_finished, fileName, pendingIntent);
EventBus.getDefault().post(new DownloadRedditVideoEvent(true));
}
stopForeground(false);
}
private Notification createNotification(int stringResId, String fileName, PendingIntent pendingIntent) {
NotificationCompat.Builder builder = new NotificationCompat.Builder(this, NotificationUtils.CHANNEL_ID_DOWNLOAD_REDDIT_VIDEO);
builder.setContentTitle(fileName).setContentText(getString(stringResId));
if (pendingIntent != null) {
builder.setContentIntent(pendingIntent);
} }
if (!finished)
builder.setContentText(getString(R.string.please_wait));
if (contentIntent != null)
builder.setContentIntent(contentIntent);
return builder.setSmallIcon(R.drawable.ic_notification) return builder.setSmallIcon(R.drawable.ic_notification)
.setColor(mCustomThemeWrapper.getColorPrimaryLightTheme()) .setColor(mCustomThemeWrapper.getColorPrimaryLightTheme())
.build(); .build();
} }
private void updateNotification(int stringResId, String fileName, boolean finished) { private void updateNotification(int stringResId, String fileName, PendingIntent pendingIntent) {
updateNotification(stringResId, fileName, finished, null);
}
private void updateNotification(int stringResId, String fileName, boolean finished, PendingIntent contentIntent) {
NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
if (notificationManager != null) { if (notificationManager != null) {
notificationManager.notify(NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID, notificationManager.notify(NotificationUtils.DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID,
createNotification(stringResId, fileName, finished, contentIntent)); createNotification(stringResId, fileName, pendingIntent));
} }
} }
private void copyVideoToPublicDir(String resFilePath, String newFileName, String[] tempFiles) { private static class SaveTempMuxAndCopyAsyncTask extends AsyncTask<Void, Integer, Void> {
new CopyFileAsyncTask(new File(resFilePath), newFileName + ".mp4",
getContentResolver(), new CopyFileAsyncTask.CopyFileAsyncTaskListener() { private ResponseBody videoResponse;
private ResponseBody audioResponse;
private String videoFilePath;
private String audioFilePath;
private String outputFilePath;
private String destinationFileName;
private ContentResolver contentResolver;
private SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener;
private Uri destinationFileUri;
private int errorCode = NO_ERROR;
public SaveTempMuxAndCopyAsyncTask(ResponseBody videoResponse, ResponseBody audioResponse,
String videoFilePath, String audioFilePath, String outputFilePath,
String destinationFileName, ContentResolver contentResolver,
SaveTempMuxAndCopyAsyncTaskListener saveTempMuxAndCopyAsyncTaskListener) {
this.videoResponse = videoResponse;
this.audioResponse = audioResponse;
this.videoFilePath = videoFilePath;
this.audioFilePath = audioFilePath;
this.outputFilePath = outputFilePath;
this.destinationFileName = destinationFileName;
this.contentResolver = contentResolver;
this.saveTempMuxAndCopyAsyncTaskListener = saveTempMuxAndCopyAsyncTaskListener;
}
@Override @Override
public void successful(File dstFile) { protected void onProgressUpdate(Integer... values) {
if (dstFile != null) { super.onProgressUpdate(values);
addMediaFileAndShareNotification(dstFile, R.string.downloading_reddit_video_finished, newFileName + ".mp4"); saveTempMuxAndCopyAsyncTaskListener.updateProgressNotification(values[0]);
}
@Override
protected Void doInBackground(Void... voids) {
publishProgress(R.string.downloading_reddit_video_save_video);
String savedVideoFilePath = writeResponseBodyToDisk(videoResponse, videoFilePath);
if (savedVideoFilePath == null) {
errorCode = ERROR_VIDEO_FILE_CANNOT_SAVE;
return null;
}
if (audioResponse != null) {
publishProgress(R.string.downloading_reddit_video_save_audio);
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);
copyToDestination(outputFilePath);
} else { } else {
updateNotification(R.string.downloading_reddit_video_finished, newFileName + ".mp4", true); publishProgress(R.string.downloading_reddit_video_save_file_to_public_dir);
copyToDestination(videoFilePath);
} }
return null;
for (int i = 0; i < tempFiles.length; i++) {
new File(tempFiles[i]).delete();
}
downloadFinished(true);
} }
@Override @Override
public void failed() { protected void onPostExecute(Void aVoid) {
for (int i = 0; i < tempFiles.length; i++) { super.onPostExecute(aVoid);
new File(tempFiles[i]).delete(); saveTempMuxAndCopyAsyncTaskListener.finished(destinationFileUri, errorCode);
}
downloadFinished(false);
}
}).execute();
} }
private String writeResponseBodyToDisk(ResponseBody body, String filePath) { private String writeResponseBodyToDisk(ResponseBody body, String filePath) {
@ -319,7 +408,6 @@ public class DownloadRedditVideoService extends Service {
if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) { if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) {
// Log.d(TAG, "saw input EOS.");
sawEOS = true; sawEOS = true;
videoBufferInfo.size = 0; videoBufferInfo.size = 0;
} else { } else {
@ -357,50 +445,19 @@ public class DownloadRedditVideoService extends Service {
return true; return true;
} }
private void stopService() { private void copyToDestination(String srcPath) {
stopForeground(false);
}
private static class CopyFileAsyncTask extends AsyncTask<Void, Void, Void> {
private File src;
private String destinationFileName;
private File destinationFile;
private ContentResolver contentResolver;
private CopyFileAsyncTaskListener copyFileAsyncTaskListener;
private boolean successful;
CopyFileAsyncTask(File src, String destinationFileName, ContentResolver contentResolver, CopyFileAsyncTaskListener copyFileAsyncTaskListener) {
this.src = src;
this.destinationFileName = destinationFileName;
this.contentResolver = contentResolver;
this.copyFileAsyncTaskListener = copyFileAsyncTaskListener;
}
@Override
protected Void doInBackground(Void... voids) {
if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { if (android.os.Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
successful = copy(src, destinationFileName); if (!copy(new File(srcPath), destinationFileName)) {
errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE;
}
} else { } else {
try { try {
copyFileQ(src, destinationFileName); copyFileQ(new File(srcPath), destinationFileName);
successful = true;
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
successful = false; errorCode = ERROR_MUXED_VIDEO_FILE_CANNOT_SAVE;
} }
} }
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (successful) {
copyFileAsyncTaskListener.successful(destinationFile);
} else {
copyFileAsyncTaskListener.failed();
}
} }
@RequiresApi(api = Build.VERSION_CODES.Q) @RequiresApi(api = Build.VERSION_CODES.Q)
@ -440,6 +497,7 @@ public class DownloadRedditVideoService extends Service {
contentValues.clear(); contentValues.clear();
contentValues.put(MediaStore.Images.Media.IS_PENDING, 0); contentValues.put(MediaStore.Images.Media.IS_PENDING, 0);
contentResolver.update(uri, contentValues, null, null); contentResolver.update(uri, contentValues, null, null);
destinationFileUri = uri;
} catch (IOException e) { } catch (IOException e) {
if (uri != null) { if (uri != null) {
// Don't leave an orphan entry in the MediaStore // Don't leave an orphan entry in the MediaStore
@ -458,10 +516,11 @@ public class DownloadRedditVideoService extends Service {
File directory = getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES); File directory = getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES);
if (directory != null) { if (directory != null) {
String directoryPath = directory.getAbsolutePath() + "/Infinity/"; String directoryPath = directory.getAbsolutePath() + "/Infinity/";
destinationFile = new File(directoryPath, outputFileName); String destinationFilePath = directoryPath + outputFileName;
destinationFileUri = Uri.parse(destinationFilePath);
try (InputStream in = new FileInputStream(src)) { try (InputStream in = new FileInputStream(src)) {
try (OutputStream out = new FileOutputStream(destinationFile)) { try (OutputStream out = new FileOutputStream(destinationFilePath)) {
byte[] buf = new byte[1024]; byte[] buf = new byte[1024];
int len; int len;
while ((len = in.read(buf)) > 0) { while ((len = in.read(buf)) > 0) {
@ -482,11 +541,9 @@ public class DownloadRedditVideoService extends Service {
} }
} }
interface CopyFileAsyncTaskListener { interface SaveTempMuxAndCopyAsyncTaskListener {
void finished(Uri destinationFileUri, int errorCode);
void successful(File dstFile); void updateProgressNotification(int stringResId);
void failed();
} }
} }
} }

View File

@ -740,10 +740,18 @@
<string name="error_fetching_imgur_media">Cannot load images</string> <string name="error_fetching_imgur_media">Cannot load images</string>
<string name="downloading_reddit_video">Downloading Video %1$s</string> <string name="downloading_reddit_video">Downloading Video Track</string>
<string name="downloading_reddit_video_audio_track">Downloading Audio Track For %1$s</string> <string name="downloading_reddit_video_audio_track">Downloading Audio Track</string>
<string name="downloading_reddit_video_muxing">Muxing Video</string> <string name="downloading_reddit_video_save_video">Saving Video Track</string>
<string name="downloading_reddit_video_finished">%1$s Downloaded</string> <string name="downloading_reddit_video_save_audio">Saving Audio Track</string>
<string name="downloading_reddit_video_failed">Failed To Download Video</string> <string name="downloading_reddit_video_muxing">Muxing Video and Audio</string>
<string name="downloading_reddit_video_save_file_to_public_dir">Saving Video</string>
<string name="downloading_reddit_video_finished">Downloaded</string>
<string name="downloading_reddit_video_failed_cannot_get_cache_directory">Download failed: cannot access cache directory </string>
<string name="downloading_reddit_video_failed_cannot_download_video">Download failed: cannot download video</string>
<string name="downloading_reddit_video_failed_cannot_save_video">Download failed: cannot save video to cache directory</string>
<string name="downloading_reddit_video_failed_cannot_save_audio">Download failed: cannot save audio to cache directory</string>
<string name="downloading_reddit_video_failed_cannot_mux">Download failed: cannot mux video and audio</string>
<string name="downloading_reddit_video_failed_cannot_save_mux_video">Download failed: cannot save the video to public directory</string>
</resources> </resources>