diff options
8 files changed, 334 insertions, 233 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index d6d5aa9c..b80426e9 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -161,6 +161,7 @@ <service android:name=".services.DownloadMediaService" android:enabled="true" + android:permission="android.permission.BIND_JOB_SERVICE" android:exported="false" /> <activity diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewImageOrGifActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewImageOrGifActivity.java index 88668712..fe4778c2 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewImageOrGifActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewImageOrGifActivity.java @@ -1,6 +1,9 @@ package ml.docilealligator.infinityforreddit.activities; import android.Manifest; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; @@ -12,6 +15,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.PersistableBundle; import android.text.Html; import android.text.Spanned; import android.view.Menu; @@ -355,13 +359,25 @@ public class ViewImageOrGifActivity extends AppCompatActivity implements SetAsWa private void download() { isDownloading = false; - Intent intent = new Intent(this, DownloadMediaService.class); + /*Intent intent = new Intent(this, DownloadMediaService.class); intent.putExtra(DownloadMediaService.EXTRA_URL, mImageUrl); intent.putExtra(DownloadMediaService.EXTRA_MEDIA_TYPE, isGif ? DownloadMediaService.EXTRA_MEDIA_TYPE_GIF : DownloadMediaService.EXTRA_MEDIA_TYPE_IMAGE); intent.putExtra(DownloadMediaService.EXTRA_FILE_NAME, mImageFileName); intent.putExtra(DownloadMediaService.EXTRA_SUBREDDIT_NAME, mSubredditName); intent.putExtra(DownloadMediaService.EXTRA_IS_NSFW, isNsfw); - ContextCompat.startForegroundService(this, intent); + ContextCompat.startForegroundService(this, intent);*/ + + PersistableBundle extras = new PersistableBundle(); + extras.putString(DownloadMediaService.EXTRA_URL, mImageUrl); + extras.putInt(DownloadMediaService.EXTRA_MEDIA_TYPE, isGif ? DownloadMediaService.EXTRA_MEDIA_TYPE_GIF : DownloadMediaService.EXTRA_MEDIA_TYPE_IMAGE); + extras.putString(DownloadMediaService.EXTRA_FILE_NAME, mImageFileName); + extras.putString(DownloadMediaService.EXTRA_SUBREDDIT_NAME, mSubredditName); + extras.putInt(DownloadMediaService.EXTRA_IS_NSFW, isNsfw ? 1 : 0); + + //TODO: contentEstimatedBytes + JobInfo jobInfo = DownloadMediaService.constructJobInfo(this, 5000000, extras); + ((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo); + Toast.makeText(this, R.string.download_started, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java index 7f3cd774..14c544b4 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/activities/ViewVideoActivity.java @@ -7,6 +7,9 @@ import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES; import android.Manifest; import android.app.Dialog; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; @@ -22,6 +25,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.PersistableBundle; import android.provider.Settings; import android.view.Menu; import android.view.MenuItem; @@ -107,7 +111,6 @@ import ml.docilealligator.infinityforreddit.font.TitleFontStyle; import ml.docilealligator.infinityforreddit.post.FetchPost; import ml.docilealligator.infinityforreddit.post.Post; import ml.docilealligator.infinityforreddit.services.DownloadMediaService; -import ml.docilealligator.infinityforreddit.services.DownloadRedditVideoService; import ml.docilealligator.infinityforreddit.utils.APIUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import ml.docilealligator.infinityforreddit.utils.Utils; @@ -978,7 +981,7 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe private void download() { isDownloading = false; - Intent intent; + /*Intent intent; if (videoType != VIDEO_TYPE_NORMAL) { intent = new Intent(this, DownloadMediaService.class); if (post.getPostType() == Post.GIF_TYPE) { @@ -1001,7 +1004,35 @@ public class ViewVideoActivity extends AppCompatActivity implements CustomFontRe intent.putExtra(DownloadRedditVideoService.EXTRA_SUBREDDIT, subredditName); intent.putExtra(DownloadRedditVideoService.EXTRA_IS_NSFW, isNSFW); } - ContextCompat.startForegroundService(this, intent); + ContextCompat.startForegroundService(this, intent);*/ + + if (videoType != VIDEO_TYPE_NORMAL) { + PersistableBundle extras = new PersistableBundle(); + if (post.getPostType() == Post.GIF_TYPE) { + extras.putString(DownloadMediaService.EXTRA_URL, post.getVideoUrl()); + extras.putInt(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_GIF); + extras.putString(DownloadMediaService.EXTRA_FILE_NAME, post.getSubredditName() + + "-" + post.getId() + ".gif"); + } else { + extras.putString(DownloadMediaService.EXTRA_URL, videoDownloadUrl); + extras.putInt(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_VIDEO); + extras.putString(DownloadMediaService.EXTRA_FILE_NAME, videoFileName); + } + + extras.putString(DownloadMediaService.EXTRA_SUBREDDIT_NAME, subredditName); + extras.putInt(DownloadMediaService.EXTRA_IS_NSFW, isNSFW ? 1 : 0); + + //TODO: contentEstimatedBytes + JobInfo jobInfo = DownloadMediaService.constructJobInfo(this, 5000000, extras); + ((JobScheduler) getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo); + } else { + /* intent = new Intent(this, DownloadRedditVideoService.class); + intent.putExtra(DownloadRedditVideoService.EXTRA_VIDEO_URL, videoDownloadUrl); + intent.putExtra(DownloadRedditVideoService.EXTRA_POST_ID, id); + intent.putExtra(DownloadRedditVideoService.EXTRA_SUBREDDIT, subredditName); + intent.putExtra(DownloadRedditVideoService.EXTRA_IS_NSFW, isNSFW);*/ + } + Toast.makeText(this, R.string.download_started, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurImageFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurImageFragment.java index 4e13c051..5b67f459 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurImageFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurImageFragment.java @@ -1,6 +1,8 @@ package ml.docilealligator.infinityforreddit.fragments; import android.Manifest; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -10,6 +12,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.PersistableBundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -221,11 +224,21 @@ public class ViewImgurImageFragment extends Fragment { private void download() { isDownloading = false; - Intent intent = new Intent(activity, DownloadMediaService.class); + /*Intent intent = new Intent(activity, DownloadMediaService.class); intent.putExtra(DownloadMediaService.EXTRA_URL, imgurMedia.getLink()); intent.putExtra(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_IMAGE); intent.putExtra(DownloadMediaService.EXTRA_FILE_NAME, imgurMedia.getFileName()); - ContextCompat.startForegroundService(activity, intent); + ContextCompat.startForegroundService(activity, intent);*/ + + PersistableBundle extras = new PersistableBundle(); + extras.putString(DownloadMediaService.EXTRA_URL, imgurMedia.getLink()); + extras.putInt(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_IMAGE); + extras.putString(DownloadMediaService.EXTRA_FILE_NAME, imgurMedia.getFileName()); + + //TODO: contentEstimatedBytes + JobInfo jobInfo = DownloadMediaService.constructJobInfo(activity, 5000000, extras); + ((JobScheduler) activity.getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo); + Toast.makeText(activity, R.string.download_started, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java index eb6adac8..47d04de7 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewImgurVideoFragment.java @@ -1,8 +1,9 @@ package ml.docilealligator.infinityforreddit.fragments; import android.Manifest; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -10,6 +11,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; +import android.os.PersistableBundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -258,11 +260,21 @@ public class ViewImgurVideoFragment extends Fragment { private void download() { isDownloading = false; - Intent intent = new Intent(activity, DownloadMediaService.class); + /*Intent intent = new Intent(activity, DownloadMediaService.class); intent.putExtra(DownloadMediaService.EXTRA_URL, imgurMedia.getLink()); intent.putExtra(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_VIDEO); intent.putExtra(DownloadMediaService.EXTRA_FILE_NAME, imgurMedia.getFileName()); - ContextCompat.startForegroundService(activity, intent); + ContextCompat.startForegroundService(activity, intent);*/ + + PersistableBundle extras = new PersistableBundle(); + extras.putString(DownloadMediaService.EXTRA_URL, imgurMedia.getLink()); + extras.putInt(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_VIDEO); + extras.putString(DownloadMediaService.EXTRA_FILE_NAME, imgurMedia.getFileName()); + + //TODO: contentEstimatedBytes + JobInfo jobInfo = DownloadMediaService.constructJobInfo(activity, 5000000, extras); + ((JobScheduler) activity.getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo); + Toast.makeText(activity, R.string.download_started, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryImageOrGifFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryImageOrGifFragment.java index cb60df38..7a462ad4 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryImageOrGifFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryImageOrGifFragment.java @@ -1,6 +1,8 @@ package ml.docilealligator.infinityforreddit.fragments; import android.Manifest; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; @@ -11,6 +13,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; +import android.os.PersistableBundle; import android.text.TextUtils; import android.text.util.Linkify; import android.view.LayoutInflater; @@ -373,13 +376,25 @@ public class ViewRedditGalleryImageOrGifFragment extends Fragment { private void download() { isDownloading = false; - Intent intent = new Intent(activity, DownloadMediaService.class); + /*Intent intent = new Intent(activity, DownloadMediaService.class); intent.putExtra(DownloadMediaService.EXTRA_URL, media.hasFallback() ? media.fallbackUrl : media.url); // Retrieve original instead of the one additionally compressed by reddit intent.putExtra(DownloadMediaService.EXTRA_MEDIA_TYPE, media.mediaType == Post.Gallery.TYPE_GIF ? DownloadMediaService.EXTRA_MEDIA_TYPE_GIF: DownloadMediaService.EXTRA_MEDIA_TYPE_IMAGE); intent.putExtra(DownloadMediaService.EXTRA_FILE_NAME, media.fileName); intent.putExtra(DownloadMediaService.EXTRA_SUBREDDIT_NAME, subredditName); intent.putExtra(DownloadMediaService.EXTRA_IS_NSFW, isNsfw); - ContextCompat.startForegroundService(activity, intent); + ContextCompat.startForegroundService(activity, intent);*/ + + PersistableBundle extras = new PersistableBundle(); + extras.putString(DownloadMediaService.EXTRA_URL, media.hasFallback() ? media.fallbackUrl : media.url); // Retrieve original instead of the one additionally compressed by reddit + extras.putInt(DownloadMediaService.EXTRA_MEDIA_TYPE, media.mediaType == Post.Gallery.TYPE_GIF ? DownloadMediaService.EXTRA_MEDIA_TYPE_GIF: DownloadMediaService.EXTRA_MEDIA_TYPE_IMAGE); + extras.putString(DownloadMediaService.EXTRA_FILE_NAME, media.fileName); + extras.putString(DownloadMediaService.EXTRA_SUBREDDIT_NAME, subredditName); + extras.putInt(DownloadMediaService.EXTRA_IS_NSFW, isNsfw ? 1 : 0); + + //TODO: contentEstimatedBytes + JobInfo jobInfo = DownloadMediaService.constructJobInfo(activity, 5000000, extras); + ((JobScheduler) activity.getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo); + Toast.makeText(activity, R.string.download_started, Toast.LENGTH_SHORT).show(); } diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java index 8a64999e..c716c03b 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/fragments/ViewRedditGalleryVideoFragment.java @@ -1,8 +1,9 @@ package ml.docilealligator.infinityforreddit.fragments; import android.Manifest; +import android.app.job.JobInfo; +import android.app.job.JobScheduler; import android.content.Context; -import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; @@ -10,6 +11,7 @@ import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.os.Build; import android.os.Bundle; +import android.os.PersistableBundle; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -257,13 +259,25 @@ public class ViewRedditGalleryVideoFragment extends Fragment { private void download() { isDownloading = false; - Intent intent = new Intent(activity, DownloadMediaService.class); + /*Intent intent = new Intent(activity, DownloadMediaService.class); intent.putExtra(DownloadMediaService.EXTRA_URL, galleryVideo.url); intent.putExtra(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_VIDEO); intent.putExtra(DownloadMediaService.EXTRA_FILE_NAME, galleryVideo.fileName); intent.putExtra(DownloadMediaService.EXTRA_SUBREDDIT_NAME, subredditName); intent.putExtra(DownloadMediaService.EXTRA_IS_NSFW, isNsfw); - ContextCompat.startForegroundService(activity, intent); + ContextCompat.startForegroundService(activity, intent);*/ + + PersistableBundle extras = new PersistableBundle(); + extras.putString(DownloadMediaService.EXTRA_URL, galleryVideo.url); + extras.putInt(DownloadMediaService.EXTRA_MEDIA_TYPE, DownloadMediaService.EXTRA_MEDIA_TYPE_VIDEO); + extras.putString(DownloadMediaService.EXTRA_FILE_NAME, galleryVideo.fileName); + extras.putString(DownloadMediaService.EXTRA_SUBREDDIT_NAME, subredditName); + extras.putInt(DownloadMediaService.EXTRA_IS_NSFW, isNsfw ? 1 : 0); + + //TODO: contentEstimatedBytes + JobInfo jobInfo = DownloadMediaService.constructJobInfo(activity, 5000000, extras); + ((JobScheduler) activity.getSystemService(Context.JOB_SCHEDULER_SERVICE)).schedule(jobInfo); + Toast.makeText(activity, R.string.download_started, Toast.LENGTH_SHORT).show(); } 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 872ff654..09506159 100644 --- a/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadMediaService.java +++ b/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadMediaService.java @@ -4,22 +4,21 @@ import static android.os.Environment.getExternalStoragePublicDirectory; import android.app.Notification; import android.app.PendingIntent; -import android.app.Service; +import android.app.job.JobInfo; +import android.app.job.JobParameters; +import android.app.job.JobService; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.media.MediaScannerConnection; +import android.net.NetworkRequest; import android.net.Uri; 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.os.PersistableBundle; import android.provider.MediaStore; import androidx.core.app.NotificationChannelCompat; @@ -33,6 +32,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.Random; +import java.util.concurrent.Executor; import javax.inject.Inject; import javax.inject.Named; @@ -48,11 +48,10 @@ import ml.docilealligator.infinityforreddit.utils.NotificationUtils; import ml.docilealligator.infinityforreddit.utils.SharedPreferencesUtils; import okhttp3.OkHttpClient; import okhttp3.ResponseBody; -import retrofit2.Call; import retrofit2.Response; import retrofit2.Retrofit; -public class DownloadMediaService extends Service { +public class DownloadMediaService extends JobService { public static final String EXTRA_URL = "EU"; public static final String EXTRA_FILE_NAME = "EFN"; public static final String EXTRA_SUBREDDIT_NAME = "ESN"; @@ -66,6 +65,9 @@ public class DownloadMediaService extends Service { private static final int ERROR_CANNOT_GET_DESTINATION_DIRECTORY = 0; private static final int ERROR_FILE_CANNOT_DOWNLOAD = 1; private static final int ERROR_FILE_CANNOT_SAVE = 2; + + private static int JOB_ID = 20000; + @Inject @Named("download_media") Retrofit retrofit; @@ -74,35 +76,89 @@ public class DownloadMediaService extends Service { SharedPreferences mSharedPreferences; @Inject CustomThemeWrapper mCustomThemeWrapper; + @Inject + Executor mExecutor; private NotificationManagerCompat notificationManager; private NotificationCompat.Builder builder; - private ServiceHandler serviceHandler; public DownloadMediaService() { } + public static JobInfo constructJobInfo(Context context, long contentEstimatedBytes, PersistableBundle extras) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + return new JobInfo.Builder(JOB_ID++, new ComponentName(context, DownloadMediaService.class)) + .setUserInitiated(true) + .setRequiredNetwork(new NetworkRequest.Builder().build()) + .setEstimatedNetworkBytes(0, contentEstimatedBytes + 500) + .setExtras(extras) + .build(); + } else { + return new JobInfo.Builder(JOB_ID++, new ComponentName(context, DownloadMediaService.class)) + .build(); + } + } + @Override - public IBinder onBind(Intent intent) { - return null; + public void onCreate() { + ((Infinity) getApplication()).getAppComponent().inject(this); + notificationManager = NotificationManagerCompat.from(this); } - // Handler that receives messages from the thread - private final class ServiceHandler extends Handler { - private boolean downloadFinished; + @Override + public boolean onStartJob(JobParameters params) { + PersistableBundle intent = params.getExtras(); + int mediaType = intent.getInt(EXTRA_MEDIA_TYPE, EXTRA_MEDIA_TYPE_IMAGE); + builder = new NotificationCompat.Builder(this, getNotificationChannelId(mediaType)); + + NotificationChannelCompat serviceChannel = + new NotificationChannelCompat.Builder( + getNotificationChannelId(mediaType), + NotificationManagerCompat.IMPORTANCE_LOW) + .setName(getNotificationChannel(mediaType)) + .build(); + notificationManager.createNotificationChannel(serviceChannel); - public ServiceHandler(Looper looper) { - super(looper); + int randomNotificationIdOffset = new Random().nextInt(10000); + switch (intent.getInt(EXTRA_MEDIA_TYPE, EXTRA_MEDIA_TYPE_IMAGE)) { + case EXTRA_MEDIA_TYPE_GIF: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + setNotification(params, + NotificationUtils.DOWNLOAD_GIF_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(intent.getString(EXTRA_FILE_NAME)), + JobService.JOB_END_NOTIFICATION_POLICY_DETACH); + } else { + notificationManager.notify(NotificationUtils.DOWNLOAD_GIF_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(intent.getString(EXTRA_FILE_NAME))); + } + break; + case EXTRA_MEDIA_TYPE_VIDEO: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + setNotification(params, + NotificationUtils.DOWNLOAD_VIDEO_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(intent.getString(EXTRA_FILE_NAME)), + JobService.JOB_END_NOTIFICATION_POLICY_DETACH); + } else { + notificationManager.notify(NotificationUtils.DOWNLOAD_VIDEO_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(intent.getString(EXTRA_FILE_NAME))); + } + break; + default: + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { + setNotification(params, + NotificationUtils.DOWNLOAD_IMAGE_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(intent.getString(EXTRA_FILE_NAME)), + JobService.JOB_END_NOTIFICATION_POLICY_DETACH); + } else { + notificationManager.notify(NotificationUtils.DOWNLOAD_IMAGE_NOTIFICATION_ID + randomNotificationIdOffset, + createNotification(intent.getString(EXTRA_FILE_NAME))); + } } - @Override - public void handleMessage(Message msg) { - int randomNotificationIdOffset = msg.arg1; - Bundle intent = msg.getData(); - downloadFinished = false; + + mExecutor.execute(() -> { String fileUrl = intent.getString(EXTRA_URL); 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); - boolean isNsfw = intent.getBoolean(EXTRA_IS_NSFW, false); + boolean isNsfw = intent.getInt(EXTRA_IS_NSFW, 0) == 1; String mimeType = mediaType == EXTRA_MEDIA_TYPE_VIDEO ? "video/*" : "image/*"; final DownloadProgressResponseBody.ProgressListener progressListener = new DownloadProgressResponseBody.ProgressListener() { @@ -141,12 +197,11 @@ public class DownloadMediaService extends Service { boolean separateDownloadFolder = mSharedPreferences.getBoolean(SharedPreferencesUtils.SEPARATE_FOLDER_FOR_EACH_SUBREDDIT, false); - Response<ResponseBody> response = null; + Response<ResponseBody> response; String destinationFileUriString = null; boolean isDefaultDestination = true; try { - Call<ResponseBody> call = retrofit.create(DownloadFile.class).downloadFile(fileUrl); - response = call.execute(); + response = retrofit.create(DownloadFile.class).downloadFile(fileUrl).execute(); if (response.isSuccessful() && response.body() != null) { String destinationFileDirectory = getDownloadLocation(mediaType, isNsfw); if (destinationFileDirectory.equals("")) { @@ -156,13 +211,13 @@ public class DownloadMediaService extends Service { String directoryPath = separateDownloadFolder && subredditName != null && !subredditName.equals("") ? directory.getAbsolutePath() + "/Infinity/" + subredditName + "/" : directory.getAbsolutePath() + "/Infinity/"; File infinityDir = new File(directoryPath); if (!infinityDir.exists() && !infinityDir.mkdirs()) { - downloadFinished(mediaType, randomNotificationIdOffset, mimeType, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); return; } destinationFileUriString = directoryPath + fileName; } else { - downloadFinished(mediaType, randomNotificationIdOffset, mimeType, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); return; } @@ -170,7 +225,6 @@ public class DownloadMediaService extends Service { String dir = mediaType == EXTRA_MEDIA_TYPE_VIDEO ? Environment.DIRECTORY_MOVIES : Environment.DIRECTORY_PICTURES; destinationFileUriString = separateDownloadFolder && subredditName != null && !subredditName.equals("") ? dir + "/Infinity/" + subredditName + "/" : dir + "/Infinity/"; } - isDefaultDestination = true; } else { isDefaultDestination = false; DocumentFile picFile; @@ -178,7 +232,7 @@ public class DownloadMediaService extends Service { if (separateDownloadFolder && subredditName != null && !subredditName.equals("")) { dir = DocumentFile.fromTreeUri(DownloadMediaService.this, Uri.parse(destinationFileDirectory)); if (dir == null) { - downloadFinished(mediaType, randomNotificationIdOffset, mimeType, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); return; } @@ -186,7 +240,7 @@ public class DownloadMediaService extends Service { if (dir == null) { dir = DocumentFile.fromTreeUri(DownloadMediaService.this, Uri.parse(destinationFileDirectory)).createDirectory(subredditName); if (dir == null) { - downloadFinished(mediaType, randomNotificationIdOffset, mimeType, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); return; } @@ -194,7 +248,7 @@ public class DownloadMediaService extends Service { } else { dir = DocumentFile.fromTreeUri(DownloadMediaService.this, Uri.parse(destinationFileDirectory)); if (dir == null) { - downloadFinished(mediaType, randomNotificationIdOffset, mimeType, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); return; } @@ -210,211 +264,42 @@ public class DownloadMediaService extends Service { } picFile = dir.createFile(mimeType, fileName); if (picFile == null) { - downloadFinished(mediaType, randomNotificationIdOffset, mimeType, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_CANNOT_GET_DESTINATION_DIRECTORY); return; } destinationFileUriString = picFile.getUri().toString(); } } else { - downloadFinished(mediaType, randomNotificationIdOffset, mimeType, null, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_FILE_CANNOT_DOWNLOAD); + return; } } catch (IOException e) { e.printStackTrace(); + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, + ERROR_FILE_CANNOT_DOWNLOAD); + return; } try { - if (response != null && response.body() != null) { - Uri destinationFileUri = writeResponseBodyToDisk(response.body(), isDefaultDestination, destinationFileUriString, - fileName, mediaType); - downloadFinished(mediaType, randomNotificationIdOffset, - mimeType, destinationFileUri, NO_ERROR); - } + Uri destinationFileUri = writeResponseBodyToDisk(response.body(), isDefaultDestination, destinationFileUriString, + fileName, mediaType); + downloadFinished(params, mediaType, randomNotificationIdOffset, + mimeType, destinationFileUri, NO_ERROR); } catch (IOException e) { e.printStackTrace(); - downloadFinished(mediaType, randomNotificationIdOffset, + downloadFinished(params, mediaType, randomNotificationIdOffset, mimeType, null, ERROR_FILE_CANNOT_SAVE); } - } - - private Uri writeResponseBodyToDisk(ResponseBody body, boolean isDefaultDestination, - String destinationFileUriString, String destinationFileName, - int mediaType) throws IOException { - ContentResolver contentResolver = getContentResolver(); - if (isDefaultDestination) { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { - InputStream inputStream = body.byteStream(); - OutputStream outputStream = new FileOutputStream(destinationFileUriString); - byte[] fileReader = new byte[4096]; - - long fileSize = body.contentLength(); - long fileSizeDownloaded = 0; - - while (true) { - int read = inputStream.read(fileReader); - - if (read == -1) { - break; - } - - outputStream.write(fileReader, 0, read); - - fileSizeDownloaded += read; - } - - outputStream.flush(); - } else { - ContentValues contentValues = new ContentValues(); - contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, destinationFileName); - String mimeType; - switch (mediaType) { - case EXTRA_MEDIA_TYPE_VIDEO: - mimeType = "video/mpeg"; - break; - case EXTRA_MEDIA_TYPE_GIF: - mimeType = "image/gif"; - break; - default: - mimeType = "image/jpeg"; - } - contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); - contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, destinationFileUriString); - contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1); - - final Uri contentUri = mediaType == EXTRA_MEDIA_TYPE_VIDEO ? MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) : MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); - Uri uri = contentResolver.insert(contentUri, contentValues); - - if (uri == null) { - throw new IOException("Failed to create new MediaStore record."); - } - - OutputStream stream = contentResolver.openOutputStream(uri); - - if (stream == null) { - throw new IOException("Failed to get output stream."); - } - - InputStream in = body.byteStream(); - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - stream.write(buf, 0, len); - } - contentValues.clear(); - contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0); - contentResolver.update(uri, contentValues, null, null); - destinationFileUriString = uri.toString(); - } - } else { - try (OutputStream stream = contentResolver.openOutputStream(Uri.parse(destinationFileUriString))) { - if (stream == null) { - throw new IOException("Failed to get output stream."); - } - - InputStream in = body.byteStream(); - - byte[] buf = new byte[1024]; - int len; - while ((len = in.read(buf)) > 0) { - stream.write(buf, 0, len); - } - } - } - return Uri.parse(destinationFileUriString); - } - - private void downloadFinished(int mediaType, int randomNotificationIdOffset, String mimeType, Uri destinationFileUri, int errorCode) { - if (downloadFinished) { - return; - } + }); - downloadFinished = true; - if (errorCode != NO_ERROR) { - switch (errorCode) { - case ERROR_CANNOT_GET_DESTINATION_DIRECTORY: - updateNotification(mediaType, R.string.downloading_image_or_gif_failed_cannot_get_destination_directory, - -1, randomNotificationIdOffset, null, null); - break; - case ERROR_FILE_CANNOT_DOWNLOAD: - updateNotification(mediaType, R.string.downloading_media_failed_cannot_download_media, - -1, randomNotificationIdOffset, null, null); - break; - case ERROR_FILE_CANNOT_SAVE: - updateNotification(mediaType, R.string.downloading_media_failed_cannot_save_to_destination_directory, - -1, randomNotificationIdOffset, null, null); - break; - } - } else { - MediaScannerConnection.scanFile( - DownloadMediaService.this, new String[]{destinationFileUri.toString()}, null, - (path, uri) -> { - updateNotification(mediaType, R.string.downloading_media_finished, -1, - randomNotificationIdOffset, destinationFileUri, mimeType); - } - ); - } - stopForeground(false); - } + return true; } @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 int onStartCommand(Intent intent, int flags, int startId) { - int mediaType = intent.getIntExtra(EXTRA_MEDIA_TYPE, EXTRA_MEDIA_TYPE_IMAGE); - builder = new NotificationCompat.Builder(this, getNotificationChannelId(mediaType)); - - NotificationChannelCompat serviceChannel = - new NotificationChannelCompat.Builder( - getNotificationChannelId(mediaType), - NotificationManagerCompat.IMPORTANCE_LOW) - .setName(getNotificationChannel(mediaType)) - .build(); - notificationManager.createNotificationChannel(serviceChannel); - - int randomNotificationIdOffset = new Random().nextInt(10000); - switch (intent.getIntExtra(EXTRA_MEDIA_TYPE, EXTRA_MEDIA_TYPE_IMAGE)) { - case EXTRA_MEDIA_TYPE_GIF: - startForeground( - NotificationUtils.DOWNLOAD_GIF_NOTIFICATION_ID + randomNotificationIdOffset, - createNotification(intent.getStringExtra(EXTRA_FILE_NAME)) - ); - break; - case EXTRA_MEDIA_TYPE_VIDEO: - startForeground( - NotificationUtils.DOWNLOAD_VIDEO_NOTIFICATION_ID + randomNotificationIdOffset, - createNotification(intent.getStringExtra(EXTRA_FILE_NAME)) - ); - break; - default: - startForeground( - NotificationUtils.DOWNLOAD_IMAGE_NOTIFICATION_ID + randomNotificationIdOffset, - createNotification(intent.getStringExtra(EXTRA_FILE_NAME)) - ); - } - - Message msg = serviceHandler.obtainMessage(); - Bundle bundle = intent.getExtras(); - msg.setData(bundle); - msg.arg1 = randomNotificationIdOffset; - serviceHandler.sendMessage(msg); - - return START_NOT_STICKY; + public boolean onStopJob(JobParameters params) { + return false; } private Notification createNotification(String fileName) { @@ -511,4 +396,118 @@ public class DownloadMediaService extends Service { return mSharedPreferences.getString(SharedPreferencesUtils.IMAGE_DOWNLOAD_LOCATION, ""); } } + + private Uri writeResponseBodyToDisk(ResponseBody body, boolean isDefaultDestination, + String destinationFileUriString, String destinationFileName, + int mediaType) throws IOException { + ContentResolver contentResolver = getContentResolver(); + if (isDefaultDestination) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + InputStream inputStream = body.byteStream(); + OutputStream outputStream = new FileOutputStream(destinationFileUriString); + byte[] fileReader = new byte[4096]; + + long fileSize = body.contentLength(); + long fileSizeDownloaded = 0; + + while (true) { + int read = inputStream.read(fileReader); + + if (read == -1) { + break; + } + + outputStream.write(fileReader, 0, read); + + fileSizeDownloaded += read; + } + + outputStream.flush(); + } else { + ContentValues contentValues = new ContentValues(); + contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, destinationFileName); + String mimeType; + switch (mediaType) { + case EXTRA_MEDIA_TYPE_VIDEO: + mimeType = "video/mpeg"; + break; + case EXTRA_MEDIA_TYPE_GIF: + mimeType = "image/gif"; + break; + default: + mimeType = "image/jpeg"; + } + contentValues.put(MediaStore.MediaColumns.MIME_TYPE, mimeType); + contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, destinationFileUriString); + contentValues.put(MediaStore.MediaColumns.IS_PENDING, 1); + + final Uri contentUri = mediaType == EXTRA_MEDIA_TYPE_VIDEO ? MediaStore.Video.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY) : MediaStore.Images.Media.getContentUri(MediaStore.VOLUME_EXTERNAL_PRIMARY); + Uri uri = contentResolver.insert(contentUri, contentValues); + + if (uri == null) { + throw new IOException("Failed to create new MediaStore record."); + } + + OutputStream stream = contentResolver.openOutputStream(uri); + + if (stream == null) { + throw new IOException("Failed to get output stream."); + } + + InputStream in = body.byteStream(); + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + stream.write(buf, 0, len); + } + contentValues.clear(); + contentValues.put(MediaStore.MediaColumns.IS_PENDING, 0); + contentResolver.update(uri, contentValues, null, null); + destinationFileUriString = uri.toString(); + } + } else { + try (OutputStream stream = contentResolver.openOutputStream(Uri.parse(destinationFileUriString))) { + if (stream == null) { + throw new IOException("Failed to get output stream."); + } + + InputStream in = body.byteStream(); + + byte[] buf = new byte[1024]; + int len; + while ((len = in.read(buf)) > 0) { + stream.write(buf, 0, len); + } + } + } + return Uri.parse(destinationFileUriString); + } + + private void downloadFinished(JobParameters parameters, int mediaType, int randomNotificationIdOffset, String mimeType, Uri destinationFileUri, int errorCode) { + if (errorCode != NO_ERROR) { + switch (errorCode) { + case ERROR_CANNOT_GET_DESTINATION_DIRECTORY: + updateNotification(mediaType, R.string.downloading_image_or_gif_failed_cannot_get_destination_directory, + -1, randomNotificationIdOffset, null, null); + break; + case ERROR_FILE_CANNOT_DOWNLOAD: + updateNotification(mediaType, R.string.downloading_media_failed_cannot_download_media, + -1, randomNotificationIdOffset, null, null); + break; + case ERROR_FILE_CANNOT_SAVE: + updateNotification(mediaType, R.string.downloading_media_failed_cannot_save_to_destination_directory, + -1, randomNotificationIdOffset, null, null); + break; + } + } else { + MediaScannerConnection.scanFile( + DownloadMediaService.this, new String[]{destinationFileUri.toString()}, null, + (path, uri) -> { + updateNotification(mediaType, R.string.downloading_media_finished, -1, + randomNotificationIdOffset, destinationFileUri, mimeType); + } + ); + } + jobFinished(parameters, false); + } } |