diff options
author | Alex Ning <chineseperson5@gmail.com> | 2020-09-06 03:54:50 +0000 |
---|---|---|
committer | Alex Ning <chineseperson5@gmail.com> | 2020-09-06 03:54:50 +0000 |
commit | cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1 (patch) | |
tree | c6f3de227af994c97c32be94dc2bc13ec2121263 /app | |
parent | 60eb71fe11b166a30dd0b879ea935e1769120c63 (diff) | |
download | infinity-for-reddit-cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1.tar infinity-for-reddit-cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1.tar.gz infinity-for-reddit-cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1.tar.bz2 infinity-for-reddit-cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1.tar.lz infinity-for-reddit-cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1.tar.xz infinity-for-reddit-cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1.tar.zst infinity-for-reddit-cefc21fd1ba9023e89d9d70f27e36ebb29ed53b1.zip |
Select download location is now available. Download images and gifs to locations set by users in ViewImageOrGifActivity is available.
Diffstat (limited to 'app')
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" |