aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--app/src/main/AndroidManifest.xml10
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadFile.java (renamed from app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadVideo.java)2
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewImageOrGifActivity.java39
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java10
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java2
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/AsyncTask/SaveBitmapImageToFileAsyncTask.java (renamed from app/src/main/java/ml/docilealligator/infinityforreddit/AsyncTask/SaveImageToFileAsyncTask.java)16
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Event/DownloadImageOrGifEvent.java9
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewImgurImageFragment.java6
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewRedditGalleryImageOrGifFragment.java6
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java7
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java6
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadImageService.java345
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java12
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Settings/DownloadLocationPreferenceFragment.java115
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Utils/GetPathUtils.java238
-rw-r--r--app/src/main/java/ml/docilealligator/infinityforreddit/Utils/SharedPreferencesUtils.java3
-rw-r--r--app/src/main/res/drawable-night/ic_download_24dp.xml9
-rw-r--r--app/src/main/res/drawable/ic_download_24dp.xml9
-rw-r--r--app/src/main/res/values/arrays.xml14
-rw-r--r--app/src/main/res/values/strings.xml30
-rw-r--r--app/src/main/res/xml/download_location_preferences.xml13
-rw-r--r--app/src/main/res/xml/main_preferences.xml5
22 files changed, 851 insertions, 55 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 13339b1b..690f1026 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,7 @@
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.SET_WALLPAPER" />
<uses-permission android:name="android.permission.SET_WALLPAPER_HINTS" />
- <uses-permission android:name="android.permission.VIBRATE"/>
+ <uses-permission android:name="android.permission.VIBRATE" />
<uses-permission
android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="22" />
@@ -24,7 +24,13 @@
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true"
tools:replace="android:label">
- <activity android:name=".Activity.ViewRedditGalleryActivity"
+ <service
+ android:name=".Service.DownloadImageService"
+ android:enabled="true"
+ android:exported="false"></service>
+
+ <activity
+ android:name=".Activity.ViewRedditGalleryActivity"
android:configChanges="orientation|screenSize|layoutDirection"
android:parentActivityName=".Activity.MainActivity"
android:theme="@style/AppTheme.Draggable" />
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadVideo.java b/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadFile.java
index e3d9d238..10a8dda4 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadVideo.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/API/DownloadFile.java
@@ -6,7 +6,7 @@ import retrofit2.http.GET;
import retrofit2.http.Streaming;
import retrofit2.http.Url;
-public interface DownloadVideo {
+public interface DownloadFile {
@Streaming
@GET()
Call<ResponseBody> downloadFile(@Url String fileUrl);
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewImageOrGifActivity.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewImageOrGifActivity.java
index fe1a0e9c..471a4ea1 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewImageOrGifActivity.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Activity/ViewImageOrGifActivity.java
@@ -1,6 +1,5 @@
package ml.docilealligator.infinityforreddit.Activity;
-import android.Manifest;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
@@ -22,8 +21,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.app.ActivityCompat;
-import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import com.bumptech.glide.Glide;
@@ -52,8 +49,8 @@ import javax.inject.Named;
import butterknife.BindView;
import butterknife.ButterKnife;
+import ml.docilealligator.infinityforreddit.AsyncTask.SaveBitmapImageToFileAsyncTask;
import ml.docilealligator.infinityforreddit.AsyncTask.SaveGIFToFileAsyncTask;
-import ml.docilealligator.infinityforreddit.AsyncTask.SaveImageToFileAsyncTask;
import ml.docilealligator.infinityforreddit.BottomSheetFragment.SetAsWallpaperBottomSheetFragment;
import ml.docilealligator.infinityforreddit.BuildConfig;
import ml.docilealligator.infinityforreddit.Font.ContentFontFamily;
@@ -66,6 +63,7 @@ import ml.docilealligator.infinityforreddit.Infinity;
import ml.docilealligator.infinityforreddit.MediaDownloader;
import ml.docilealligator.infinityforreddit.MediaDownloaderImpl;
import ml.docilealligator.infinityforreddit.R;
+import ml.docilealligator.infinityforreddit.Service.DownloadImageService;
import ml.docilealligator.infinityforreddit.SetAsWallpaperCallback;
import ml.docilealligator.infinityforreddit.Utils.SharedPreferencesUtils;
import ml.docilealligator.infinityforreddit.WallpaperSetter;
@@ -279,29 +277,16 @@ public class ViewImageOrGifActivity extends AppCompatActivity implements SetAsWa
finish();
return true;
case R.id.action_download_view_image_or_gif_activity:
- if (isDownloading) {
- return false;
- }
-
- isDownloading = true;
-
- if (Build.VERSION.SDK_INT >= 23) {
- if (ContextCompat.checkSelfPermission(this,
- Manifest.permission.WRITE_EXTERNAL_STORAGE)
- != PackageManager.PERMISSION_GRANTED) {
-
- // Permission is not granted
- // No explanation needed; request the permission
- ActivityCompat.requestPermissions(this,
- new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
- PERMISSION_REQUEST_WRITE_EXTERNAL_STORAGE);
- } else {
- // Permission has already been granted
- mediaDownloader.download(mImageUrl, mImageFileName, this);
- }
+ Intent intent = new Intent(this, DownloadImageService.class);
+ intent.putExtra(DownloadImageService.EXTRA_URL, mImageUrl);
+ intent.putExtra(DownloadImageService.EXTRA_IS_GIF, isGif);
+ intent.putExtra(DownloadImageService.EXTRA_FILE_NAME, mImageFileName);
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ startForegroundService(intent);
} else {
- mediaDownloader.download(mImageUrl, mImageFileName, this);
+ startService(intent);
}
+ Toast.makeText(this, R.string.download_started, Toast.LENGTH_SHORT).show();
return true;
case R.id.action_share_view_image_or_gif_activity:
@@ -343,8 +328,8 @@ public class ViewImageOrGifActivity extends AppCompatActivity implements SetAsWa
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
if (getExternalCacheDir() != null) {
Toast.makeText(ViewImageOrGifActivity.this, R.string.save_image_first, Toast.LENGTH_SHORT).show();
- new SaveImageToFileAsyncTask(resource, getExternalCacheDir().getPath(), mImageFileName,
- new SaveImageToFileAsyncTask.SaveImageToFileAsyncTaskListener() {
+ new SaveBitmapImageToFileAsyncTask(resource, getExternalCacheDir().getPath(), mImageFileName,
+ new SaveBitmapImageToFileAsyncTask.SaveBitmapImageToFileAsyncTaskListener() {
@Override
public void saveSuccess(File imageFile) {
Uri uri = FileProvider.getUriForFile(ViewImageOrGifActivity.this,
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java
index 35705736..0d3e6aec 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppComponent.java
@@ -13,6 +13,7 @@ import ml.docilealligator.infinityforreddit.Activity.EditCommentActivity;
import ml.docilealligator.infinityforreddit.Activity.EditMultiRedditActivity;
import ml.docilealligator.infinityforreddit.Activity.EditPostActivity;
import ml.docilealligator.infinityforreddit.Activity.FilteredThingActivity;
+import ml.docilealligator.infinityforreddit.Activity.InboxActivity;
import ml.docilealligator.infinityforreddit.Activity.LinkResolverActivity;
import ml.docilealligator.infinityforreddit.Activity.LoginActivity;
import ml.docilealligator.infinityforreddit.Activity.MainActivity;
@@ -34,7 +35,6 @@ import ml.docilealligator.infinityforreddit.Activity.SubscribedThingListingActiv
import ml.docilealligator.infinityforreddit.Activity.ThemePreviewActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewImageOrGifActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewImgurMediaActivity;
-import ml.docilealligator.infinityforreddit.Activity.InboxActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewMultiRedditDetailActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewPostDetailActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewPrivateMessagesActivity;
@@ -42,8 +42,8 @@ import ml.docilealligator.infinityforreddit.Activity.ViewRedditGalleryActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewSubredditDetailActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewUserDetailActivity;
import ml.docilealligator.infinityforreddit.Activity.ViewVideoActivity;
-import ml.docilealligator.infinityforreddit.Fragment.CommentsListingFragment;
import ml.docilealligator.infinityforreddit.BottomSheetFragment.FlairBottomSheetFragment;
+import ml.docilealligator.infinityforreddit.Fragment.CommentsListingFragment;
import ml.docilealligator.infinityforreddit.Fragment.FollowedUsersListingFragment;
import ml.docilealligator.infinityforreddit.Fragment.InboxFragment;
import ml.docilealligator.infinityforreddit.Fragment.MultiRedditListingFragment;
@@ -54,10 +54,12 @@ import ml.docilealligator.infinityforreddit.Fragment.SubscribedSubredditsListing
import ml.docilealligator.infinityforreddit.Fragment.UserListingFragment;
import ml.docilealligator.infinityforreddit.Fragment.ViewImgurVideoFragment;
import ml.docilealligator.infinityforreddit.Fragment.ViewRedditGalleryVideoFragment;
+import ml.docilealligator.infinityforreddit.Service.DownloadImageService;
import ml.docilealligator.infinityforreddit.Service.DownloadVideoService;
import ml.docilealligator.infinityforreddit.Service.SubmitPostService;
import ml.docilealligator.infinityforreddit.Settings.AdvancedPreferenceFragment;
import ml.docilealligator.infinityforreddit.Settings.CustomizeMainPageTabsFragment;
+import ml.docilealligator.infinityforreddit.Settings.DownloadLocationPreferenceFragment;
import ml.docilealligator.infinityforreddit.Settings.GesturesAndButtonsPreferenceFragment;
import ml.docilealligator.infinityforreddit.Settings.MainPreferenceFragment;
import ml.docilealligator.infinityforreddit.Settings.NotificationPreferenceFragment;
@@ -188,4 +190,8 @@ public interface AppComponent {
void inject(ViewRedditGalleryVideoFragment viewRedditGalleryVideoFragment);
void inject(CustomizeMainPageTabsFragment customizeMainPageTabsFragment);
+
+ void inject(DownloadImageService downloadImageService);
+
+ void inject(DownloadLocationPreferenceFragment downloadLocationPreferenceFragment);
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java
index 44c86977..a1dd3517 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AppModule.java
@@ -91,7 +91,7 @@ class AppModule {
}
@Provides
- @Named("download_reddit_video")
+ @Named("download_media")
@Singleton
Retrofit provideDownloadRedditVideoRetrofit() {
return new Retrofit.Builder()
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/AsyncTask/SaveImageToFileAsyncTask.java b/app/src/main/java/ml/docilealligator/infinityforreddit/AsyncTask/SaveBitmapImageToFileAsyncTask.java
index 65c1c199..f70a87be 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/AsyncTask/SaveImageToFileAsyncTask.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/AsyncTask/SaveBitmapImageToFileAsyncTask.java
@@ -8,23 +8,23 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
-public class SaveImageToFileAsyncTask extends AsyncTask<Void, Void, Void> {
+public class SaveBitmapImageToFileAsyncTask extends AsyncTask<Void, Void, Void> {
private Bitmap resource;
private String cacheDirPath;
private String fileName;
- private SaveImageToFileAsyncTaskListener saveImageToFileAsyncTaskListener;
+ private SaveBitmapImageToFileAsyncTaskListener saveBitmapImageToFileAsyncTaskListener;
private boolean saveSuccess = true;
private File imageFile;
- public SaveImageToFileAsyncTask(Bitmap resource, String cacheDirPath, String fileName,
- SaveImageToFileAsyncTaskListener saveImageToFileAsyncTaskListener) {
+ public SaveBitmapImageToFileAsyncTask(Bitmap resource, String cacheDirPath, String fileName,
+ SaveBitmapImageToFileAsyncTaskListener saveBitmapImageToFileAsyncTaskListener) {
this.resource = resource;
this.cacheDirPath = cacheDirPath;
this.fileName = fileName;
- this.saveImageToFileAsyncTaskListener = saveImageToFileAsyncTaskListener;
+ this.saveBitmapImageToFileAsyncTaskListener = saveBitmapImageToFileAsyncTaskListener;
}
- public interface SaveImageToFileAsyncTaskListener {
+ public interface SaveBitmapImageToFileAsyncTaskListener {
void saveSuccess(File imageFile);
void saveFailed();
}
@@ -47,9 +47,9 @@ public class SaveImageToFileAsyncTask extends AsyncTask<Void, Void, Void> {
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (saveSuccess) {
- saveImageToFileAsyncTaskListener.saveSuccess(imageFile);
+ saveBitmapImageToFileAsyncTaskListener.saveSuccess(imageFile);
} else {
- saveImageToFileAsyncTaskListener.saveFailed();
+ saveBitmapImageToFileAsyncTaskListener.saveFailed();
}
}
}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Event/DownloadImageOrGifEvent.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Event/DownloadImageOrGifEvent.java
new file mode 100644
index 00000000..61e87c1f
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Event/DownloadImageOrGifEvent.java
@@ -0,0 +1,9 @@
+package ml.docilealligator.infinityforreddit.Event;
+
+public class DownloadImageOrGifEvent {
+ public boolean isSuccessful;
+
+ public DownloadImageOrGifEvent(boolean isSuccessful) {
+ this.isSuccessful = isSuccessful;
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewImgurImageFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewImgurImageFragment.java
index b29ccf2d..91d25aed 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewImgurImageFragment.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewImgurImageFragment.java
@@ -41,7 +41,7 @@ import java.io.File;
import butterknife.BindView;
import butterknife.ButterKnife;
import ml.docilealligator.infinityforreddit.Activity.ViewImgurMediaActivity;
-import ml.docilealligator.infinityforreddit.AsyncTask.SaveImageToFileAsyncTask;
+import ml.docilealligator.infinityforreddit.AsyncTask.SaveBitmapImageToFileAsyncTask;
import ml.docilealligator.infinityforreddit.BottomSheetFragment.SetAsWallpaperBottomSheetFragment;
import ml.docilealligator.infinityforreddit.BuildConfig;
import ml.docilealligator.infinityforreddit.ImgurMedia;
@@ -183,9 +183,9 @@ public class ViewImgurImageFragment extends Fragment {
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
if (activity.getExternalCacheDir() != null) {
Toast.makeText(activity, R.string.save_image_first, Toast.LENGTH_SHORT).show();
- new SaveImageToFileAsyncTask(resource, activity.getExternalCacheDir().getPath(),
+ new SaveBitmapImageToFileAsyncTask(resource, activity.getExternalCacheDir().getPath(),
imgurMedia.getFileName(),
- new SaveImageToFileAsyncTask.SaveImageToFileAsyncTaskListener() {
+ new SaveBitmapImageToFileAsyncTask.SaveBitmapImageToFileAsyncTaskListener() {
@Override
public void saveSuccess(File imageFile) {
Uri uri = FileProvider.getUriForFile(activity,
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewRedditGalleryImageOrGifFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewRedditGalleryImageOrGifFragment.java
index a3674051..e8954f41 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewRedditGalleryImageOrGifFragment.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Fragment/ViewRedditGalleryImageOrGifFragment.java
@@ -47,7 +47,7 @@ import butterknife.BindView;
import butterknife.ButterKnife;
import ml.docilealligator.infinityforreddit.Activity.ViewRedditGalleryActivity;
import ml.docilealligator.infinityforreddit.AsyncTask.SaveGIFToFileAsyncTask;
-import ml.docilealligator.infinityforreddit.AsyncTask.SaveImageToFileAsyncTask;
+import ml.docilealligator.infinityforreddit.AsyncTask.SaveBitmapImageToFileAsyncTask;
import ml.docilealligator.infinityforreddit.BottomSheetFragment.SetAsWallpaperBottomSheetFragment;
import ml.docilealligator.infinityforreddit.BuildConfig;
import ml.docilealligator.infinityforreddit.MediaDownloader;
@@ -277,9 +277,9 @@ public class ViewRedditGalleryImageOrGifFragment extends Fragment {
public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
if (activity.getExternalCacheDir() != null) {
Toast.makeText(activity, R.string.save_image_first, Toast.LENGTH_SHORT).show();
- new SaveImageToFileAsyncTask(resource, activity.getExternalCacheDir().getPath(),
+ new SaveBitmapImageToFileAsyncTask(resource, activity.getExternalCacheDir().getPath(),
media.fileName,
- new SaveImageToFileAsyncTask.SaveImageToFileAsyncTaskListener() {
+ new SaveBitmapImageToFileAsyncTask.SaveBitmapImageToFileAsyncTaskListener() {
@Override
public void saveSuccess(File imageFile) {
Uri uri = FileProvider.getUriForFile(activity,
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java b/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java
index fd802284..892f370f 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/MediaDownloaderImpl.java
@@ -7,6 +7,8 @@ import android.os.Build;
import android.os.Environment;
import android.widget.Toast;
+import androidx.documentfile.provider.DocumentFile;
+
import java.io.File;
public class MediaDownloaderImpl implements MediaDownloader {
@@ -21,7 +23,10 @@ public class MediaDownloaderImpl implements MediaDownloader {
//Android Q support
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
- request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, fileName);
+ //ctx.getContentResolver().takePersistableUriPermission(Uri.parse("content://com.android.providers.downloads.documents/tree/downloads"), Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ request.setDestinationInExternalPublicDir(DocumentFile.fromTreeUri(ctx, Uri.parse("content://com.android.providers.downloads.documents/tree/downloads")).toString(), fileName);
+ //request.setDestinationInExternalPublicDir(Environment.DIRECTORY_PICTURES, fileName);
+ //request.setDestinationUri(Uri.parse(Paths.get("content://com.android.providers.downloads.documents/tree/downloads", fileName).toString()));
} else {
String path = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).toString();
File directory = new File(path + "/Infinity/");
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java
index 8fe4b33e..c0935772 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/NotificationUtils.java
@@ -15,9 +15,15 @@ public class NotificationUtils {
public static final String CHANNEL_DOWNLOAD_REDDIT_VIDEO = "Download Reddit Video";
public static final String CHANNEL_ID_DOWNLOAD_VIDEO = "download_video";
public static final String CHANNEL_DOWNLOAD_VIDEO = "Download Video";
+ public static final String CHANNEL_ID_DOWNLOAD_IMAGE = "download_image";
+ public static final String CHANNEL_DOWNLOAD_IMAGE = "Download Image";
+ public static final String CHANNEL_ID_DOWNLOAD_GIF = "download_gif";
+ public static final String CHANNEL_DOWNLOAD_GIF = "Download Gif";
public static final int SUBMIT_POST_SERVICE_NOTIFICATION_ID = 10000;
public static final int DOWNLOAD_REDDIT_VIDEO_NOTIFICATION_ID = 20000;
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;
private static final int SUMMARY_BASE_ID_UNREAD_MESSAGE = 0;
private static final int NOTIFICATION_BASE_ID_UNREAD_MESSAGE = 1;
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadImageService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadImageService.java
new file mode 100644
index 00000000..06d7449e
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadImageService.java
@@ -0,0 +1,345 @@
+package ml.docilealligator.infinityforreddit.Service;
+
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+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.Uri;
+import android.os.AsyncTask;
+import android.os.Build;
+import android.os.Environment;
+import android.os.IBinder;
+import android.provider.MediaStore;
+
+import androidx.annotation.NonNull;
+import androidx.core.app.NotificationCompat;
+import androidx.core.app.NotificationManagerCompat;
+import androidx.documentfile.provider.DocumentFile;
+
+import org.greenrobot.eventbus.EventBus;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import ml.docilealligator.infinityforreddit.API.DownloadFile;
+import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper;
+import ml.docilealligator.infinityforreddit.Event.DownloadImageOrGifEvent;
+import ml.docilealligator.infinityforreddit.Infinity;
+import ml.docilealligator.infinityforreddit.NotificationUtils;
+import ml.docilealligator.infinityforreddit.R;
+import ml.docilealligator.infinityforreddit.Utils.SharedPreferencesUtils;
+import okhttp3.ResponseBody;
+import retrofit2.Call;
+import retrofit2.Callback;
+import retrofit2.Response;
+import retrofit2.Retrofit;
+
+import static android.os.Environment.getExternalStoragePublicDirectory;
+
+public class DownloadImageService extends Service {
+ public static final String EXTRA_URL = "EU";
+ public static final String EXTRA_FILE_NAME = "EFN";
+ public static final String EXTRA_IS_GIF = "EIG";
+
+ private static final int NO_ERROR = -1;
+ 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;
+ @Inject
+ @Named("download_media")
+ Retrofit retrofit;
+ @Inject
+ @Named("default")
+ SharedPreferences mSharedPreferences;
+ @Inject
+ CustomThemeWrapper mCustomThemeWrapper;
+ private boolean isGif;
+
+ public DownloadImageService() {
+ }
+
+ @Override
+ public IBinder onBind(Intent intent) {
+ return null;
+ }
+
+ @Override
+ public int onStartCommand(Intent intent, int flags, int startId) {
+ ((Infinity) getApplication()).getAppComponent().inject(this);
+
+ String fileUrl = intent.getStringExtra(EXTRA_URL);
+ String fileName;
+ fileName = intent.getStringExtra(EXTRA_FILE_NAME);
+ isGif = intent.getBooleanExtra(EXTRA_IS_GIF, false);
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
+ NotificationChannel serviceChannel;
+ serviceChannel = new NotificationChannel(
+ isGif ? NotificationUtils.CHANNEL_ID_DOWNLOAD_GIF : NotificationUtils.CHANNEL_ID_DOWNLOAD_IMAGE,
+ isGif ? NotificationUtils.CHANNEL_DOWNLOAD_GIF : NotificationUtils.CHANNEL_DOWNLOAD_IMAGE,
+ NotificationManager.IMPORTANCE_LOW
+ );
+
+ NotificationManagerCompat manager = NotificationManagerCompat.from(this);
+ manager.createNotificationChannel(serviceChannel);
+ }
+
+ startForeground(
+ isGif ? NotificationUtils.DOWNLOAD_GIF_NOTIFICATION_ID : NotificationUtils.DOWNLOAD_IMAGE_NOTIFICATION_ID,
+ createNotification(isGif ? R.string.downloading_gif : R.string.downloading_image, fileName, null)
+ );
+
+ retrofit.create(DownloadFile.class).downloadFile(fileUrl).enqueue(new Callback<ResponseBody>() {
+ @Override
+ public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> response) {
+ if (response.isSuccessful() && response.body() != null) {
+ String destinationFileDirectory = isGif ? mSharedPreferences.getString(SharedPreferencesUtils.GIF_DOWNLOAD_LOCATION, "") :
+ mSharedPreferences.getString(SharedPreferencesUtils.IMAGE_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 = directory.getAbsolutePath() + "/Infinity/";
+ File infinityDir = new File(directoryPath);
+ if (!infinityDir.exists() && !infinityDir.mkdir()) {
+ downloadFinished(null, fileName, ERROR_CANNOT_GET_DESTINATION_DIRECTORY);
+ return;
+ }
+ destinationFileUriString = directoryPath + fileName;
+ } else {
+ downloadFinished(null, fileName, ERROR_CANNOT_GET_DESTINATION_DIRECTORY);
+ return;
+ }
+ } else {
+ destinationFileUriString = Environment.DIRECTORY_PICTURES + "/Infinity/";
+ }
+ isDefaultDestination = true;
+ } else {
+ isDefaultDestination = false;
+ DocumentFile picFile = DocumentFile.fromTreeUri(DownloadImageService.this, Uri.parse(destinationFileDirectory)).createFile("image/*", fileName);
+ if (picFile == null) {
+ downloadFinished(null, fileName, ERROR_CANNOT_GET_DESTINATION_DIRECTORY);
+ return;
+ }
+ destinationFileUriString = picFile.getUri().toString();
+ }
+
+ new SaveImageOrGifAndCopyToExternalStorageAsyncTask(response.body(), isGif,
+ isDefaultDestination, fileName, destinationFileUriString, getContentResolver(),
+ new SaveImageOrGifAndCopyToExternalStorageAsyncTask.SaveImageOrGifAndCopyToExternalStorageAsyncTaskListener() {
+ @Override
+ public void finished(Uri destinationFileUri, int errorCode) {
+ downloadFinished(destinationFileUri, fileName, errorCode);
+ }
+
+ @Override
+ public void updateProgressNotification(int stringResId) {
+ updateNotification(stringResId, fileName, null);
+ }
+ }).execute();
+ }
+ }
+
+ @Override
+ public void onFailure(@NonNull Call<ResponseBody> call, @NonNull Throwable t) {
+ downloadFinished(null, fileName, ERROR_FILE_CANNOT_DOWNLOAD);
+ }
+ });
+ return super.onStartCommand(intent, flags, startId);
+ }
+
+ private Notification createNotification(int stringResId, String fileName, PendingIntent pendingIntent) {
+ NotificationCompat.Builder builder;
+ builder = new NotificationCompat.Builder(this, isGif ? NotificationUtils.CHANNEL_ID_DOWNLOAD_GIF : NotificationUtils.CHANNEL_ID_DOWNLOAD_IMAGE);
+ builder.setContentTitle(fileName).setContentText(getString(stringResId));
+ if (pendingIntent != null) {
+ builder.setContentIntent(pendingIntent);
+ }
+ return builder.setSmallIcon(R.drawable.ic_notification)
+ .setColor(mCustomThemeWrapper.getColorPrimaryLightTheme())
+ .build();
+ }
+
+ private void updateNotification(int stringResId, String fileName, PendingIntent pendingIntent) {
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ if (notificationManager != null) {
+ notificationManager.notify(isGif ? NotificationUtils.DOWNLOAD_GIF_NOTIFICATION_ID : NotificationUtils.DOWNLOAD_IMAGE_NOTIFICATION_ID,
+ createNotification(stringResId, fileName, pendingIntent));
+ }
+ }
+
+ private void downloadFinished(Uri destinationFileUri, String fileName, int errorCode) {
+ if (errorCode != NO_ERROR) {
+ switch (errorCode) {
+ case ERROR_CANNOT_GET_DESTINATION_DIRECTORY:
+ updateNotification(R.string.downloading_image_or_gif_failed_cannot_get_destination_directory, fileName, null);
+ break;
+ case ERROR_FILE_CANNOT_DOWNLOAD:
+ updateNotification(isGif ? R.string.downloading_gif_failed_cannot_download_gif : R.string.downloading_image_failed_cannot_download_image, fileName, null);
+ break;
+ case ERROR_FILE_CANNOT_SAVE:
+ updateNotification(isGif ? R.string.downloading_gif_failed_cannot_save_gif : R.string.downloading_image_failed_cannot_save_image, fileName, null);
+ break;
+ }
+ EventBus.getDefault().post(new DownloadImageOrGifEvent(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, "image/*");
+ PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+ updateNotification(isGif ? R.string.downloading_gif_finished : R.string.downloading_image_finished, fileName, pendingIntent);
+ EventBus.getDefault().post(new DownloadImageOrGifEvent(true));
+ }
+ );
+ }
+ stopForeground(false);
+ }
+
+ private static class SaveImageOrGifAndCopyToExternalStorageAsyncTask extends AsyncTask<Void, Integer, Void> {
+
+ private ResponseBody response;
+ private boolean isGif;
+ private boolean isDefaultDestination;
+ private String destinationFileName;
+ @NonNull
+ private String destinationFileUriString;
+ private ContentResolver contentResolver;
+ private SaveImageOrGifAndCopyToExternalStorageAsyncTaskListener saveImageOrGifAndCopyToExternalStorageAsyncTaskListener;
+ private int errorCode = NO_ERROR;
+
+ interface SaveImageOrGifAndCopyToExternalStorageAsyncTaskListener {
+ void finished(Uri destinationFileUri, int errorCode);
+ void updateProgressNotification(int stringResId);
+ }
+
+ public SaveImageOrGifAndCopyToExternalStorageAsyncTask(ResponseBody response, boolean isGif,
+ boolean isDefaultDestination,
+ String destinationFileName,
+ @NonNull String destinationFileUriString,
+ ContentResolver contentResolver,
+ SaveImageOrGifAndCopyToExternalStorageAsyncTaskListener saveImageOrGifAndCopyToExternalStorageAsyncTaskListener) {
+ this.response = response;
+ this.isGif = isGif;
+ this.isDefaultDestination = isDefaultDestination;
+ this.destinationFileName = destinationFileName;
+ this.destinationFileUriString = destinationFileUriString;
+ this.contentResolver = contentResolver;
+ this.saveImageOrGifAndCopyToExternalStorageAsyncTaskListener = saveImageOrGifAndCopyToExternalStorageAsyncTaskListener;
+ }
+
+ @Override
+ protected void onProgressUpdate(Integer... values) {
+ super.onProgressUpdate(values);
+ saveImageOrGifAndCopyToExternalStorageAsyncTaskListener.updateProgressNotification(values[0]);
+ }
+
+ @Override
+ protected Void doInBackground(Void... voids) {
+ publishProgress(isGif ? R.string.downloading_gif_save_gif : R.string.downloading_image_save_image);
+ try {
+ writeResponseBodyToDisk(response);
+ } catch (IOException e) {
+ errorCode = ERROR_FILE_CANNOT_SAVE;
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void aVoid) {
+ super.onPostExecute(aVoid);
+ saveImageOrGifAndCopyToExternalStorageAsyncTaskListener.finished(Uri.parse(destinationFileUriString), errorCode);
+ }
+
+ private void writeResponseBodyToDisk(ResponseBody body) throws IOException {
+ if (isDefaultDestination) {
+ if (android.os.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 {
+ String relativeLocation = Environment.DIRECTORY_PICTURES + "/Infinity/";
+
+ ContentValues contentValues = new ContentValues();
+ contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, destinationFileName);
+ contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "image/*");
+ contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
+ contentValues.put(MediaStore.Images.Media.IS_PENDING, 1);
+
+ final Uri contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ 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.Images.Media.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);
+ }
+ destinationFileUriString = destinationFileUriString.replaceAll("%3A", ":").replaceAll("%2F", "/");
+ }
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java
index 1f39c604..4926785f 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Service/DownloadVideoService.java
@@ -39,7 +39,7 @@ import java.nio.ByteBuffer;
import javax.inject.Inject;
import javax.inject.Named;
-import ml.docilealligator.infinityforreddit.API.DownloadVideo;
+import ml.docilealligator.infinityforreddit.API.DownloadFile;
import ml.docilealligator.infinityforreddit.CustomTheme.CustomThemeWrapper;
import ml.docilealligator.infinityforreddit.Event.DownloadRedditVideoEvent;
import ml.docilealligator.infinityforreddit.Infinity;
@@ -72,7 +72,7 @@ public class DownloadVideoService extends Service {
private boolean isRedditVideo;
@Inject
- @Named("download_reddit_video")
+ @Named("download_media")
Retrofit retrofit;
@Inject
CustomThemeWrapper mCustomThemeWrapper;
@@ -132,19 +132,19 @@ public class DownloadVideoService extends Service {
);
}
- DownloadVideo downloadVideo = retrofit.create(DownloadVideo.class);
+ DownloadFile downloadFile = retrofit.create(DownloadFile.class);
File directory = getExternalCacheDir();
String destinationFileName = fileName + ".mp4";
if (directory != null) {
String directoryPath = directory.getAbsolutePath() + "/";
- downloadVideo.downloadFile(videoUrl).enqueue(new Callback<ResponseBody>() {
+ downloadFile.downloadFile(videoUrl).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> videoResponse) {
if (videoResponse.isSuccessful() && videoResponse.body() != null) {
if (isRedditVideo) {
updateNotification(R.string.downloading_reddit_video_audio_track, destinationFileName, null);
- downloadVideo.downloadFile(audioUrl).enqueue(new Callback<ResponseBody>() {
+ downloadFile.downloadFile(audioUrl).enqueue(new Callback<ResponseBody>() {
@Override
public void onResponse(@NonNull Call<ResponseBody> call, @NonNull Response<ResponseBody> audioResponse) {
if (audioResponse.isSuccessful() && audioResponse.body() != null) {
@@ -521,10 +521,12 @@ public class DownloadVideoService extends Service {
@RequiresApi(api = Build.VERSION_CODES.Q)
private void copyFileQ(File src, String outputFileName) throws IOException {
+
String relativeLocation = Environment.DIRECTORY_MOVIES + "/Infinity/";
ContentValues contentValues = new ContentValues();
contentValues.put(MediaStore.MediaColumns.DISPLAY_NAME, outputFileName);
+ contentValues.put(MediaStore.MediaColumns.MIME_TYPE, "video/*");
contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, relativeLocation);
contentValues.put(MediaStore.Video.Media.IS_PENDING, 1);
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Settings/DownloadLocationPreferenceFragment.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Settings/DownloadLocationPreferenceFragment.java
new file mode 100644
index 00000000..50c436eb
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Settings/DownloadLocationPreferenceFragment.java
@@ -0,0 +1,115 @@
+package ml.docilealligator.infinityforreddit.Settings;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceFragmentCompat;
+
+import javax.inject.Inject;
+import javax.inject.Named;
+
+import ml.docilealligator.infinityforreddit.Infinity;
+import ml.docilealligator.infinityforreddit.R;
+import ml.docilealligator.infinityforreddit.Utils.SharedPreferencesUtils;
+
+import static android.content.Intent.ACTION_OPEN_DOCUMENT_TREE;
+
+public class DownloadLocationPreferenceFragment extends PreferenceFragmentCompat {
+ private static final int IMAGE_DOWNLOAD_LOCATION_REQUEST_CODE = 10;
+ private static final int GIF_DOWNLOAD_LOCATION_REQUEST_CODE = 11;
+ private static final int VIDEO_DOWNLOAD_LOCATION_REQUEST_CODE = 12;
+
+ Preference imageDownloadLocationPreference;
+ Preference gifDownloadLocationPreference;
+ Preference videoDownloadLocationPreference;
+ private Activity activity;
+ @Inject
+ @Named("default")
+ SharedPreferences sharedPreferences;
+
+ @Override
+ public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
+ ((Infinity) activity.getApplication()).getAppComponent().inject(this);
+ setPreferencesFromResource(R.xml.download_location_preferences, rootKey);
+ imageDownloadLocationPreference = findPreference(SharedPreferencesUtils.IMAGE_DOWNLOAD_LOCATION);
+ gifDownloadLocationPreference = findPreference(SharedPreferencesUtils.GIF_DOWNLOAD_LOCATION);
+ videoDownloadLocationPreference = findPreference(SharedPreferencesUtils.VIDEO_DOWNLOAD_LOCATION);
+
+ if (imageDownloadLocationPreference != null) {
+ String downloadLocation = sharedPreferences.getString(SharedPreferencesUtils.IMAGE_DOWNLOAD_LOCATION, "");
+ if (!downloadLocation.equals("")) {
+ imageDownloadLocationPreference.setSummary(downloadLocation);
+ }
+
+ imageDownloadLocationPreference.setOnPreferenceClickListener(preference -> {
+ Intent intent = new Intent(ACTION_OPEN_DOCUMENT_TREE);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivityForResult(intent, IMAGE_DOWNLOAD_LOCATION_REQUEST_CODE);
+ return true;
+ });
+ }
+
+ if (gifDownloadLocationPreference != null) {
+ String downloadLocation = sharedPreferences.getString(SharedPreferencesUtils.GIF_DOWNLOAD_LOCATION, "");
+ if (!downloadLocation.equals("")) {
+ gifDownloadLocationPreference.setSummary(downloadLocation);
+ }
+
+ gifDownloadLocationPreference.setOnPreferenceClickListener(preference -> {
+ Intent intent = new Intent(ACTION_OPEN_DOCUMENT_TREE);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivityForResult(intent, GIF_DOWNLOAD_LOCATION_REQUEST_CODE);
+ return true;
+ });
+ }
+
+ if (videoDownloadLocationPreference != null) {
+ String downloadLocation = sharedPreferences.getString(SharedPreferencesUtils.VIDEO_DOWNLOAD_LOCATION, "");
+ if (!downloadLocation.equals("")) {
+ videoDownloadLocationPreference.setSummary(downloadLocation);
+ }
+
+ videoDownloadLocationPreference.setOnPreferenceClickListener(preference -> {
+ Intent intent = new Intent(ACTION_OPEN_DOCUMENT_TREE);
+ intent.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
+ startActivityForResult(intent, VIDEO_DOWNLOAD_LOCATION_REQUEST_CODE);
+ return true;
+ });
+ }
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ if (resultCode == Activity.RESULT_OK && data != null) {
+ if (requestCode == IMAGE_DOWNLOAD_LOCATION_REQUEST_CODE) {
+ sharedPreferences.edit().putString(SharedPreferencesUtils.IMAGE_DOWNLOAD_LOCATION, data.getDataString()).apply();
+ if (imageDownloadLocationPreference != null) {
+ imageDownloadLocationPreference.setSummary(data.getDataString());
+ }
+ } else if (requestCode == GIF_DOWNLOAD_LOCATION_REQUEST_CODE) {
+ sharedPreferences.edit().putString(SharedPreferencesUtils.GIF_DOWNLOAD_LOCATION, data.getDataString()).apply();
+ if (gifDownloadLocationPreference != null) {
+ gifDownloadLocationPreference.setSummary(data.getDataString());
+ }
+ } else if (requestCode == VIDEO_DOWNLOAD_LOCATION_REQUEST_CODE) {
+ sharedPreferences.edit().putString(SharedPreferencesUtils.VIDEO_DOWNLOAD_LOCATION, data.getDataString()).apply();
+ if (videoDownloadLocationPreference != null) {
+ videoDownloadLocationPreference.setSummary(data.getDataString());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ activity = (Activity) context;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Utils/GetPathUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Utils/GetPathUtils.java
new file mode 100644
index 00000000..1ccb6b47
--- /dev/null
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Utils/GetPathUtils.java
@@ -0,0 +1,238 @@
+package ml.docilealligator.infinityforreddit.Utils;
+
+import android.annotation.TargetApi;
+import android.content.ContentUris;
+import android.content.Context;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Environment;
+import android.provider.DocumentsContract;
+import android.provider.MediaStore;
+
+import androidx.core.content.ContextCompat;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+
+public class GetPathUtils {
+
+ private static final String PATH_TREE = "tree";
+ private static final String PRIMARY_TYPE = "primary";
+ private static final String RAW_TYPE = "raw";
+
+ @TargetApi(Build.VERSION_CODES.KITKAT)
+ public static String getFilePathFromUri(final Context context, final Uri uri) {
+
+ // DocumentProvider
+ if (DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ //Timber.d("docId -> %s", docId);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if (PRIMARY_TYPE.equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ } else {
+ // TODO handle non-primary volumes
+ StringBuilder path = new StringBuilder();
+ String pathSegment[] = docId.split(":");
+ return path.append(getRemovableStorageRootPath(context, pathSegment[0])).append(File.separator).append(pathSegment[1]).toString();
+ }
+ } else if (isDownloadsDocument(uri)) { // DownloadsProvider
+
+ final String id = DocumentsContract.getDocumentId(uri);
+
+ if (id.contains("raw:")) {
+ return id.substring(id.indexOf(File.separator));
+ } else {
+ Uri contentUri = ContentUris.withAppendedId(
+ Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
+
+ return getDataColumn(context, contentUri, null, null);
+ }
+
+ } else if (isMediaDocument(uri)) { // MediaProvider
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[]{
+ split[1]
+ };
+
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ }
+ } else if ("content".equalsIgnoreCase(uri.getScheme())) { // MediaStore (and general)
+ return getDataColumn(context, uri, null, null);
+ } else if ("file".equalsIgnoreCase(uri.getScheme())) { // File
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ /**
+ * Get the value of the data column for this Uri. This is useful for
+ * MediaStore Uris, and other file-based ContentProviders.
+ *
+ * @param context The context.
+ * @param uri The Uri to query.
+ * @param selection (Optional) Filter used in the query.
+ * @param selectionArgs (Optional) Selection arguments used in the query.
+ * @return The value of the _data column, which is typically a file path.
+ */
+ public static String getDataColumn(Context context, Uri uri, String selection, String[] selectionArgs) {
+
+ Cursor cursor = null;
+ final String column = MediaStore.MediaColumns.DATA;
+ final String[] projection = {column};
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int column_index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(column_index);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+
+ return null;
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is ExternalStorageProvider.
+ */
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is DownloadsProvider.
+ */
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri The Uri to check.
+ * @return Whether the Uri authority is MediaProvider.
+ */
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ /**
+ * @param uri
+ * @return file path of Uri
+ */
+ public static String getDirectoryPathFromUri(Context context, Uri uri) {
+
+ if ("file".equals(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT
+ && isTreeUri(uri)) {
+
+ String treeId = getTreeDocumentId(uri);
+
+ if (treeId != null) {
+ String[] paths = treeId.split(":");
+ String type = paths[0];
+ String subPath = paths.length == 2 ? paths[1] : "";
+
+ if (RAW_TYPE.equalsIgnoreCase(type)) {
+ return treeId.substring(treeId.indexOf(File.separator));
+ } else if (PRIMARY_TYPE.equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + File.separator + subPath;
+ } else {
+ StringBuilder path = new StringBuilder();
+ String[] pathSegment = treeId.split(":");
+ if (pathSegment.length == 1) {
+ path.append(getRemovableStorageRootPath(context, paths[0]));
+ } else {
+ String rootPath = getRemovableStorageRootPath(context, paths[0]);
+ path.append(rootPath).append(File.separator).append(pathSegment[1]);
+ }
+
+ return path.toString();
+ }
+ }
+ }
+ return null;
+ }
+
+ private static String getRemovableStorageRootPath(Context context, String storageId) {
+ StringBuilder rootPath = new StringBuilder();
+ File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
+ for (File fileDir : externalFilesDirs) {
+ if (fileDir.getPath().contains(storageId)) {
+ String[] pathSegment = fileDir.getPath().split(File.separator);
+ for (String segment : pathSegment) {
+ if (segment.equals(storageId)) {
+ rootPath.append(storageId);
+ break;
+ }
+ rootPath.append(segment).append(File.separator);
+ }
+ //rootPath.append(fileDir.getPath().split("/Android")[0]); // faster
+ break;
+ }
+ }
+ return rootPath.toString();
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public static List<String> getListRemovableStorage(Context context) {
+
+ List<String> paths = new ArrayList<>();
+
+ File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
+ for (File fileDir : externalFilesDirs) {
+ if (Environment.isExternalStorageRemovable(fileDir)) {
+ String path = fileDir.getPath();
+ if (path.contains("/Android")) {
+ paths.add(path.substring(0, path.indexOf("/Android")));
+ }
+ }
+ }
+
+ return paths;
+ }
+
+ //https://github.com/rcketscientist/DocumentActivity/blob/master/library/src/main/java/com/anthonymandra/framework/DocumentUtil.java#L56
+ /**
+ * Extract the via {@link DocumentsContract.Document#COLUMN_DOCUMENT_ID} from the given URI.
+ * From {@link DocumentsContract} but return null instead of throw
+ */
+ public static String getTreeDocumentId(Uri uri) {
+ final List<String> paths = uri.getPathSegments();
+ if (paths.size() >= 2 && PATH_TREE.equals(paths.get(0))) {
+ return paths.get(1);
+ }
+ return null;
+ }
+
+ public static boolean isTreeUri(Uri uri) {
+ final List<String> paths = uri.getPathSegments();
+ return (paths.size() == 2 && PATH_TREE.equals(paths.get(0)));
+ }
+}
diff --git a/app/src/main/java/ml/docilealligator/infinityforreddit/Utils/SharedPreferencesUtils.java b/app/src/main/java/ml/docilealligator/infinityforreddit/Utils/SharedPreferencesUtils.java
index fab9bdd4..16df65e4 100644
--- a/app/src/main/java/ml/docilealligator/infinityforreddit/Utils/SharedPreferencesUtils.java
+++ b/app/src/main/java/ml/docilealligator/infinityforreddit/Utils/SharedPreferencesUtils.java
@@ -117,6 +117,9 @@ public class SharedPreferencesUtils {
public static final String DELETE_FRONT_PAGE_SCROLLED_POSITIONS_IN_DATABASE = "delete_front_page_scrolled_positions_in_database";
public static final String DELETE_ALL_LEGACY_SETTINGS = "delete_all_legacy_settings";
public static final String RESET_ALL_SETTINGS = "reset_all_settings";
+ public static final String IMAGE_DOWNLOAD_LOCATION = "image_download_location";
+ public static final String GIF_DOWNLOAD_LOCATION = "gif_download_location";
+ public static final String VIDEO_DOWNLOAD_LOCATION = "video_download_location";
public static final String MAIN_PAGE_TABS_SHARED_PREFERENCES_FILE = "ml.docilealligator.infinityforreddit.main_page_tabs";
public static final String MAIN_PAGE_TAB_1_TITLE = "_main_page_tab_1_title";
diff --git a/app/src/main/res/drawable-night/ic_download_24dp.xml b/app/src/main/res/drawable-night/ic_download_24dp.xml
new file mode 100644
index 00000000..eb39e3f3
--- /dev/null
+++ b/app/src/main/res/drawable-night/ic_download_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13,5v6h1.17L12,13.17 9.83,11L11,11L11,5h2m2,-2L9,3v6L5,9l7,7 7,-7h-4L15,3zM19,18L5,18v2h14v-2z"
+ android:fillColor="#FFFFFF"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_download_24dp.xml b/app/src/main/res/drawable/ic_download_24dp.xml
new file mode 100644
index 00000000..ad852174
--- /dev/null
+++ b/app/src/main/res/drawable/ic_download_24dp.xml
@@ -0,0 +1,9 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportWidth="24"
+ android:viewportHeight="24">
+ <path
+ android:pathData="M13,5v6h1.17L12,13.17 9.83,11L11,11L11,5h2m2,-2L9,3v6L5,9l7,7 7,-7h-4L15,3zM19,18L5,18v2h14v-2z"
+ android:fillColor="#000000"/>
+</vector>
diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml
index 38edbb2a..94df20c9 100644
--- a/app/src/main/res/values/arrays.xml
+++ b/app/src/main/res/values/arrays.xml
@@ -160,7 +160,7 @@
<item>ManropeBold</item>
<item>Sriracha</item>
</string-array>
-
+
<string-array name="settings_tab_post_type">
<item>Home</item>
<item>Popular</item>
@@ -228,9 +228,19 @@
<item>yyyy.M.d HH:mm</item>
<item>yyyy.M.d hh:mm a</item>
</string-array>
-
+
<string-array name="settings_number_of_columns_in_post_feed">
<item>1</item>
<item>2</item>
</string-array>
+ <!-- Reply Preference -->
+ <string-array name="reply_entries">
+ <item>Reply</item>
+ <item>Reply to all</item>
+ </string-array>
+
+ <string-array name="reply_values">
+ <item>reply</item>
+ <item>reply_all</item>
+ </string-array>
</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f50a431d..5d31c088 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -471,6 +471,10 @@
<string name="settings_tab_multi_reddit_name">MultiReddit Name (/user/yourusername/m/yourmultiredditname)</string>
<string name="settings_tab_username">Username (Without u/ prefix)</string>
<string name="no_developer_easter_egg">There\'s no developer options here</string>
+ <string name="settings_download_location_title">Download Location</string>
+ <string name="settings_image_download_location_title">Image Download Location</string>
+ <string name="settings_gif_download_location_title">Gif Download Location</string>
+ <string name="settings_video_download_location_title">Video Download Location</string>
<string name="no_link_available">Cannot get the link</string>
@@ -840,6 +844,18 @@
<string name="downloading_reddit_video_failed_cannot_save_mux_video">Download failed: cannot save the video to public directory</string>
<string name="downloading_video">Downloading Video</string>
+ <string name="downloading_image">Downloading Image</string>
+ <string name="downloading_gif">Downloading Gif</string>
+ <string name="downloading_image_save_image">Saving Image</string>
+ <string name="downloading_gif_save_gif">Saving Gif</string>
+ <string name="downloading_image_finished">Downloaded</string>
+ <string name="downloading_gif_finished">Downloaded</string>
+ <string name="downloading_image_or_gif_failed_cannot_get_destination_directory">Download failed: cannot access destination directory</string>
+ <string name="downloading_image_failed_cannot_download_image">Download image failed</string>
+ <string name="downloading_image_failed_cannot_save_image">Download failed: cannot save image to destination directory</string>
+ <string name="downloading_gif_failed_cannot_download_gif">Download gif failed</string>
+ <string name="downloading_gif_failed_cannot_save_gif">Download failed: cannot save gif to destination directory</string>
+
<string name="wallpaper_set">Wallpaper set</string>
<string name="error_set_wallpaper">Cannot set wallpaper</string>
@@ -858,5 +874,19 @@
<string name="block_user">Block User</string>
<string name="block_user_success">Blocked</string>
<string name="block_user_failed">Failed to block user</string>
+ <!-- Preference Titles -->
+ <string name="messages_header">Messages</string>
+ <string name="sync_header">Sync</string>
+
+ <!-- Messages Preferences -->
+ <string name="signature_title">Your signature</string>
+ <string name="reply_title">Default reply action</string>
+
+ <!-- Sync Preferences -->
+ <string name="sync_title">Sync email periodically</string>
+ <string name="attachment_title">Download incoming attachments</string>
+ <string name="attachment_summary_on">Automatically download attachments for incoming emails
+ </string>
+ <string name="attachment_summary_off">Only download attachments when manually requested</string>
</resources>
diff --git a/app/src/main/res/xml/download_location_preferences.xml b/app/src/main/res/xml/download_location_preferences.xml
new file mode 100644
index 00000000..7954cb87
--- /dev/null
+++ b/app/src/main/res/xml/download_location_preferences.xml
@@ -0,0 +1,13 @@
+<PreferenceScreen xmlns:app="http://schemas.android.com/apk/res-auto">
+ <Preference
+ app:key="image_download_location"
+ app:title="@string/settings_image_download_location_title" />
+
+ <Preference
+ app:key="gif_download_location"
+ app:title="@string/settings_gif_download_location_title" />
+
+ <Preference
+ app:key="video_download_location"
+ app:title="@string/settings_video_download_location_title" />
+</PreferenceScreen> \ No newline at end of file
diff --git a/app/src/main/res/xml/main_preferences.xml b/app/src/main/res/xml/main_preferences.xml
index 9af72116..ce6ff184 100644
--- a/app/src/main/res/xml/main_preferences.xml
+++ b/app/src/main/res/xml/main_preferences.xml
@@ -36,6 +36,11 @@
app:title="@string/settings_lazy_mode_interval_title"
app:useSimpleSummaryProvider="true" />
+ <Preference
+ app:title="@string/settings_download_location_title"
+ android:icon="@drawable/ic_download_24dp"
+ app:fragment="ml.docilealligator.infinityforreddit.Settings.DownloadLocationPreferenceFragment" />
+
<SwitchPreference
app:defaultValue="false"
app:key="save_front_page_scrolled_position"