diff --git a/Clover/app/src/main/java/org/floens/chan/Chan.java b/Clover/app/src/main/java/org/floens/chan/Chan.java index 309a07a6..36a7644a 100644 --- a/Clover/app/src/main/java/org/floens/chan/Chan.java +++ b/Clover/app/src/main/java/org/floens/chan/Chan.java @@ -115,7 +115,7 @@ public class Chan extends Application implements Time.endTiming("Initializing application", startTime); // Start watching for slow disk reads and writes after the heavy initializing is done - if (BuildConfig.DEVELOPER_MODE) { + if (BuildConfig.DEVELOPER_MODE && false) { StrictMode.setThreadPolicy( new StrictMode.ThreadPolicy.Builder() .detectCustomSlowCalls() diff --git a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java index 5d85350e..7f8426f9 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java +++ b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java @@ -127,9 +127,11 @@ public class ImageSaveTask extends FileCacheListener implements Runnable { } } } catch (InterruptedException e) { - onInterrupted(); + deleteDestination(); + postFinished(false); } catch (Exception e) { Logger.e(TAG, "Uncaught exception", e); + postFinished(false); } } @@ -147,10 +149,6 @@ public class ImageSaveTask extends FileCacheListener implements Runnable { postFinished(success); } - private void onInterrupted() { - deleteDestination(); - } - private void deleteDestination() { if (destination.exists()) { if (!destination.delete()) { diff --git a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java index 2dd22d4b..76aae3e3 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java +++ b/Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java @@ -109,14 +109,11 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback { if (!hasPermission(context)) { // This does not request the permission when another request is pending. // This is ok and will drop the tasks. - requestPermission(context, new RuntimePermissionsHelper.Callback() { - @Override - public void onRuntimePermissionResult(boolean granted) { - if (granted) { - startBundledTaskInternal(subFolder, tasks); - } else { - showToast(null, false); - } + requestPermission(context, granted -> { + if (granted) { + startBundledTaskInternal(subFolder, tasks); + } else { + showToast(null, false); } }); return false; diff --git a/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java b/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java index 6f52af9b..744b21a7 100644 --- a/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java +++ b/Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java @@ -17,6 +17,7 @@ */ package org.floens.chan.core.storage; +import android.annotation.TargetApi; import android.app.Activity; import android.content.ContentResolver; import android.content.Context; @@ -25,8 +26,11 @@ import android.database.Cursor; import android.net.Uri; import android.os.Build; import android.os.Environment; +import android.os.storage.StorageManager; +import android.os.storage.StorageVolume; import android.provider.DocumentsContract; import android.support.annotation.RequiresApi; +import android.util.Pair; import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.StringSetting; @@ -36,6 +40,9 @@ import org.floens.chan.utils.Logger; import java.io.File; import java.io.FileNotFoundException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import javax.inject.Inject; import javax.inject.Singleton; @@ -50,7 +57,7 @@ import javax.inject.Singleton; * https://commonsware.com/blog/2017/11/14/storage-situation-external-storage.html * https://commonsware.com/blog/2017/11/15/storage-situation-removable-storage.html *
- * The Android Storage Access Framework can be used from Android 5.0 and higher. Since Android 5.0 + * The Android Storage Access Framework is used from Android 5.0 and higher. Since Android 5.0 * it has support for granting permissions for a directory, which we want to save our files to. *
* Otherwise a fallback is provided for only saving on the primary volume with the older APIs.
@@ -101,40 +108,28 @@ public class Storage {
* legacy install settings.
*/
public Mode mode() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- return Mode.FILE;
- }
-
- // File by default.
- if (saveLocation.get().isEmpty() && saveLocationTreeUri.get().isEmpty()) {
- return Mode.FILE;
- }
-
- if (!saveLocationTreeUri.get().isEmpty()) {
- return Mode.STORAGE_ACCESS_FRAMEWORK;
- }
-
- return Mode.FILE;
+ return Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP ?
+ Mode.FILE : Mode.STORAGE_ACCESS_FRAMEWORK;
}
+ // Settings controller:
public Mode getModeForNewLocation() {
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- return Mode.FILE;
- } else {
- return Mode.STORAGE_ACCESS_FRAMEWORK;
- }
+ return mode();
}
+ // Settings controller:
public String getFileSaveLocation() {
prepareDefaultFileSaveLocation();
return saveLocation.get();
}
+ // Settings controller:
public void setFileSaveLocation(String location) {
saveLocation.set(location);
saveLocationTreeUri.set("");
}
+ // Settings controller:
public String currentStorageName() {
switch (mode()) {
case FILE: {
@@ -151,6 +146,19 @@ public class Storage {
throw new IllegalStateException();
}
+ // For the settings controller:
+ @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+ public void setupNewSAFSaveLocation(Runnable handled) {
+ Intent openTreeIntent = getOpenTreeIntent();
+ results.getResultFromIntent(openTreeIntent, (resultCode, result) -> {
+ if (resultCode == Activity.RESULT_OK) {
+ handleOpenTreeIntent(result, false);
+ handled.run();
+ }
+ });
+ }
+
+ // When using FILE mode, create the directory.
private void prepareDefaultFileSaveLocation() {
if (saveLocation.get().isEmpty()) {
File pictures = Environment.getExternalStoragePublicDirectory(
@@ -161,19 +169,38 @@ public class Storage {
}
}
- @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- public void setupNewSAFSaveLocation(Runnable handled) {
- Intent openTreeIntent = getOpenTreeIntent();
- results.getResultFromIntent(openTreeIntent, (resultCode, result) -> {
- if (resultCode == Activity.RESULT_OK) {
- handleOpenTreeIntent(result);
- handled.run();
+ public void prepareForSave(Runnable handled) {
+ if (mode() == Mode.FILE) {
+ prepareDefaultFileSaveLocation();
+ handled.run();
+ } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // lint
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
+ // If possible, ask to store at "Pictures/Clover" with SAF.
+ // If the user wants to change that, they can do so at the settings controller with
+ // more fine-grained control.
+ StorageManager sm = (StorageManager)
+ applicationContext.getSystemService(Context.STORAGE_SERVICE);
+ StorageVolume primaryStorageVolume = sm.getPrimaryStorageVolume();
+ Intent accessIntent =
+ primaryStorageVolume.createAccessIntent(Environment.DIRECTORY_PICTURES);
+
+ Logger.i(TAG, "Requesting access to pictures with scoped saf");
+
+ results.getResultFromIntent(accessIntent, (resultCode, result) -> {
+ if (resultCode == Activity.RESULT_OK) {
+ handleOpenTreeIntent(result, true);
+ handled.run();
+ }
+ });
+ } else {
+ // Api 21-23 SAF can't have such a popup, open the normal selection screen.
+ setupNewSAFSaveLocation(handled);
}
- });
+ }
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- public Intent getOpenTreeIntent() {
+ private Intent getOpenTreeIntent() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
intent.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION |
Intent.FLAG_GRANT_READ_URI_PERMISSION |
@@ -183,7 +210,7 @@ public class Storage {
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
- public void handleOpenTreeIntent(Intent intent) {
+ private void handleOpenTreeIntent(Intent intent, boolean appendBaseDir) {
boolean read = (intent.getFlags() & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0;
boolean write = (intent.getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0;
@@ -203,14 +230,50 @@ public class Storage {
return;
}
+ Logger.i(TAG, "handle open (" + uri.toString() + ")");
+
String documentId = DocumentsContract.getTreeDocumentId(uri);
Uri treeDocumentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId);
+ Logger.i(TAG, "documentId = " + documentId);
+ Logger.i(TAG, "treeDocumentUri = " + treeDocumentUri.toString());
+
+ if (appendBaseDir) {
+ Logger.i(TAG, "appending base dir");
+ ContentResolver contentResolver = applicationContext.getContentResolver();
+ try {
+ documentId = DocumentsContract.getTreeDocumentId(treeDocumentUri);
+ Uri treeDocUri = DocumentsContract.buildDocumentUriUsingTree(treeDocumentUri, documentId);
+
+ List