add storage abstraction, work on moving to StorageFile

external-storage-support
Floens 7 years ago
parent 97a15fdb85
commit 5e09cd398b
  1. 10
      Clover/app/src/main/java/org/floens/chan/controller/Controller.java
  2. 56
      Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java
  3. 35
      Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java
  4. 2
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  5. 38
      Clover/app/src/main/java/org/floens/chan/core/storage/BaseStorageImpl.java
  6. 106
      Clover/app/src/main/java/org/floens/chan/core/storage/LollipopStorageImpl.java
  7. 80
      Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java
  8. 93
      Clover/app/src/main/java/org/floens/chan/core/storage/StorageFile.java
  9. 16
      Clover/app/src/main/java/org/floens/chan/core/storage/StorageImpl.java
  10. 5
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  11. 7
      Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumDownloadController.java
  12. 5
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  13. 15
      Clover/app/src/main/java/org/floens/chan/ui/controller/SaveLocationController.java
  14. 79
      Clover/app/src/main/java/org/floens/chan/ui/controller/StorageSetupController.java
  15. 12
      Clover/app/src/main/java/org/floens/chan/utils/ImageDecoder.java
  16. 25
      Clover/app/src/main/res/layout/controller_storage_setup.xml
  17. 4
      Clover/app/src/main/res/values/strings.xml

@ -18,6 +18,7 @@
package org.floens.chan.controller;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@ -135,6 +136,9 @@ public abstract class Controller {
controller.navigationController = navigationController;
}
controller.onCreate();
if (controller.view == null) {
throw new IllegalArgumentException("Controller has no view");
}
}
public boolean removeChildController(Controller controller) {
@ -186,6 +190,12 @@ public abstract class Controller {
return false;
}
public void onActivityResult(int requestCode, int resultCode, Intent data) {
for (Controller childController : childControllers) {
childController.onActivityResult(requestCode, resultCode, data);
}
}
public void presentController(Controller controller) {
presentController(controller, true);
}

@ -19,15 +19,14 @@ package org.floens.chan.core.saver;
import android.content.Intent;
import android.graphics.Bitmap;
import android.media.MediaScannerConnection;
import android.net.Uri;
import org.floens.chan.core.cache.FileCacheListener;
import org.floens.chan.core.cache.FileCache;
import org.floens.chan.core.cache.FileCacheDownloader;
import org.floens.chan.core.cache.FileCacheListener;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.storage.StorageFile;
import org.floens.chan.utils.AndroidUtils;
import org.floens.chan.utils.IOUtils;
import org.floens.chan.utils.ImageDecoder;
import org.floens.chan.utils.Logger;
@ -38,7 +37,6 @@ import javax.inject.Inject;
import static org.floens.chan.Chan.inject;
import static org.floens.chan.utils.AndroidUtils.dp;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
public class ImageSaveTask extends FileCacheListener implements Runnable {
private static final String TAG = "ImageSaveTask";
@ -48,7 +46,7 @@ public class ImageSaveTask extends FileCacheListener implements Runnable {
private PostImage postImage;
private ImageSaveTaskCallback callback;
private File destination;
private StorageFile destination;
private boolean share;
private boolean makeBitmap;
private Bitmap bitmap;
@ -78,11 +76,11 @@ public class ImageSaveTask extends FileCacheListener implements Runnable {
return postImage;
}
public void setDestination(File destination) {
public void setDestination(StorageFile destination) {
this.destination = destination;
}
public File getDestination() {
public StorageFile getDestination() {
return destination;
}
@ -165,46 +163,32 @@ public class ImageSaveTask extends FileCacheListener implements Runnable {
success = true;
scanDestination();
if (makeBitmap) {
bitmap = ImageDecoder.decodeFile(destination, dp(512), dp(256));
try {
bitmap = ImageDecoder.decodeFile(destination.inputStream(), dp(512), dp(256));
} catch (IOException e) {
Logger.e(TAG, "onDestination decodeFile", e);
bitmap = null;
}
}
}
private boolean copyToDestination(File source) {
boolean result = false;
try {
File parent = destination.getParentFile();
if (!parent.mkdirs() && !parent.isDirectory()) {
throw new IOException("Could not create parent directory");
}
if (destination.isDirectory()) {
throw new IOException("Destination file is already a directory");
}
IOUtils.copyFile(source, destination);
result = true;
destination.copyFrom(source);
return true;
} catch (IOException e) {
Logger.e(TAG, "Error writing to file", e);
Logger.e(TAG, "copyToDestination copyFrom", e);
}
return result;
return false;
}
private void scanDestination() {
MediaScannerConnection.scanFile(getAppContext(), new String[]{destination.getAbsolutePath()}, null, new MediaScannerConnection.OnScanCompletedListener() {
@Override
public void onScanCompleted(String path, final Uri uri) {
// Runs on a binder thread
AndroidUtils.runOnUiThread(new Runnable() {
@Override
public void run() {
afterScan(uri);
}
});
}
});
// TODO
// MediaScannerConnection.scanFile(getAppContext(), new String[]{destination.getAbsolutePath()}, null, (path, uri) -> {
// Runs on a binder thread
// AndroidUtils.runOnUiThread(() -> afterScan(uri));
// });
}
private void afterScan(final Uri uri) {

@ -27,6 +27,8 @@ import android.widget.Toast;
import org.floens.chan.R;
import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.storage.Storage;
import org.floens.chan.core.storage.StorageFile;
import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.helper.RuntimePermissionsHelper;
import org.floens.chan.ui.service.SavingNotification;
@ -37,11 +39,15 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.regex.Pattern;
import javax.inject.Inject;
import javax.inject.Singleton;
import de.greenrobot.event.EventBus;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
import static org.floens.chan.utils.AndroidUtils.getString;
@Singleton
public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
private static final String TAG = "ImageSaver";
private static final int MAX_RENAME_TRIES = 500;
@ -49,18 +55,18 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
private static final int MAX_NAME_LENGTH = 50;
private static final Pattern REPEATED_UNDERSCORES_PATTERN = Pattern.compile("_+");
private static final Pattern SAFE_CHARACTERS_PATTERN = Pattern.compile("[^a-zA-Z0-9._]");
private static final ImageSaver instance = new ImageSaver();
private NotificationManager notificationManager;
private ExecutorService executor = Executors.newSingleThreadExecutor();
private int doneTasks = 0;
private int totalTasks = 0;
private Toast toast;
public static ImageSaver getInstance() {
return instance;
}
private Storage storage;
@Inject
public ImageSaver(Storage storage) {
this.storage = storage;
private ImageSaver() {
EventBus.getDefault().register(this);
notificationManager = (NotificationManager) getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
}
@ -68,8 +74,13 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
public void startDownloadTask(Context context, final ImageSaveTask task) {
PostImage postImage = task.getPostImage();
String name = ChanSettings.saveOriginalFilename.get() ? postImage.originalName : postImage.filename;
String fileName = filterName(name + "." + postImage.extension);
task.setDestination(findUnusedFileName(new File(getSaveLocation(task), fileName), false));
// String fileName = filterName(name + "." + postImage.extension);
// TODO
StorageFile file = storage.obtainStorageFileForName(name + "." + postImage.extension);
task.setDestination(file);
// task.setDestination(findUnusedFileName(new File(getSaveLocation(task), fileName), false));
// task.setMakeBitmap(true);
task.setShowToast(true);
@ -163,9 +174,9 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
for (ImageSaveTask task : tasks) {
PostImage postImage = task.getPostImage();
String fileName = filterName(postImage.originalName + "." + postImage.extension);
task.setDestination(new File(getSaveLocation(task) + File.separator + subFolder + File.separator + fileName));
startTask(task);
// TODO
// task.setDestination(new File(getSaveLocation(task) + File.separator + subFolder + File.separator + fileName));
// startTask(task);
}
updateNotification();
}
@ -194,7 +205,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
NotificationCompat.Builder builder = new NotificationCompat.Builder(getAppContext());
builder.setSmallIcon(R.drawable.ic_stat_notify);
builder.setContentTitle(getString(R.string.image_save_saved));
String savedAs = getAppContext().getString(R.string.image_save_as, task.getDestination().getName());
String savedAs = getAppContext().getString(R.string.image_save_as, task.getDestination().name());
builder.setContentText(savedAs);
builder.setPriority(NotificationCompat.PRIORITY_HIGH);
builder.setStyle(new NotificationCompat.BigPictureStyle()
@ -210,7 +221,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
}
String text = success ?
getAppContext().getString(R.string.image_save_as, task.getDestination().getName()) :
getAppContext().getString(R.string.image_save_as, task.getDestination().name()) :
getString(R.string.image_save_failed);
toast = Toast.makeText(getAppContext(), text, Toast.LENGTH_LONG);
toast.show();

@ -115,6 +115,7 @@ public class ChanSettings {
public static final BooleanSetting developer;
public static final StringSetting saveLocation;
public static final StringSetting saveLocationTreeUri;
public static final BooleanSetting saveOriginalFilename;
public static final BooleanSetting shareUrl;
public static final BooleanSetting enableReplyFab;
@ -197,6 +198,7 @@ public class ChanSettings {
saveLocation = new StringSetting(p, "preference_image_save_location", Environment.getExternalStorageDirectory() + File.separator + "Clover");
saveLocation.addCallback((setting, value) ->
EventBus.getDefault().post(new SettingChanged<>(saveLocation)));
saveLocationTreeUri = new StringSetting(p, "preference_image_save_tree_uri", "");
saveOriginalFilename = new BooleanSetting(p, "preference_image_save_original", false);
shareUrl = new BooleanSetting(p, "preference_image_share_url", false);
enableReplyFab = new BooleanSetting(p, "preference_enable_reply_fab", true);

@ -0,0 +1,38 @@
package org.floens.chan.core.storage;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
public class BaseStorageImpl implements StorageImpl {
protected Context applicationContext;
public BaseStorageImpl(Context applicationContext) {
this.applicationContext = applicationContext;
}
@Override
public boolean supportsExternalStorage() {
return false;
}
@Override
public Intent getOpenTreeIntent() {
throw new UnsupportedOperationException();
}
@Override
public void handleOpenTreeIntent(Uri uri) {
throw new UnsupportedOperationException();
}
@Override
public StorageFile obtainStorageFileForName(String name) {
throw new UnsupportedOperationException();
}
@Override
public String currentStorageName() {
throw new UnsupportedOperationException();
}
}

@ -0,0 +1,106 @@
package org.floens.chan.core.storage;
import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.support.annotation.RequiresApi;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.utils.IOUtils;
import org.floens.chan.utils.Logger;
import java.io.FileNotFoundException;
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public class LollipopStorageImpl extends BaseStorageImpl {
private static final String TAG = "LollipopStorageImpl";
public LollipopStorageImpl(Context applicationContext) {
super(applicationContext);
}
@Override
public boolean supportsExternalStorage() {
return true;
}
@Override
public Intent getOpenTreeIntent() {
return new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);
}
@Override
public void handleOpenTreeIntent(Uri uri) {
String documentId = DocumentsContract.getTreeDocumentId(uri);
Uri treeDocumentUri = DocumentsContract.buildDocumentUriUsingTree(uri, documentId);
ChanSettings.saveLocationTreeUri.set(treeDocumentUri.toString());
}
@Override
public StorageFile obtainStorageFileForName(String name) {
String uriString = ChanSettings.saveLocationTreeUri.get();
if (uriString.isEmpty()) {
return null;
}
Uri treeUri = Uri.parse(uriString);
ContentResolver contentResolver = applicationContext.getContentResolver();
String documentId = DocumentsContract.getTreeDocumentId(treeUri);
Uri treeDocumentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId);
Uri docUri;
try {
docUri = DocumentsContract.createDocument(contentResolver, treeDocumentUri,
"text", name);
} catch (FileNotFoundException e) {
Logger.e(TAG, "obtainStorageFileForName createDocument", e);
return null;
}
return StorageFile.fromUri(contentResolver, docUri);
}
@Override
public String currentStorageName() {
String uriString = ChanSettings.saveLocationTreeUri.get();
if (uriString.isEmpty()) {
return null;
}
Uri treeUri = Uri.parse(uriString);
return queryTreeName(treeUri);
}
private String queryTreeName(Uri uri) {
ContentResolver contentResolver = applicationContext.getContentResolver();
Cursor c = null;
String name = null;
try {
c = contentResolver.query(uri, new String[]{
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_MIME_TYPE
}, null, null, null);
if (c != null && c.moveToNext()) {
name = c.getString(0);
// mime = c.getString(1);
}
return name;
} catch (Exception e) {
Logger.e(TAG, "queryTreeName", e);
} finally {
IOUtils.closeQuietly(c);
}
return null;
}
}

@ -1,66 +1,58 @@
package org.floens.chan.core.storage;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.storage.StorageManager;
import android.os.storage.StorageVolume;
import java.util.Objects;
import javax.inject.Inject;
import javax.inject.Singleton;
/**
* Abstraction of the storage APIs available in Android.
* <p>
* This is used primarily for saving images, especially on removable storage.
* <p>
* First, a good read:
* https://commonsware.com/blog/2017/11/13/storage-situation-internal-storage.html
* https://commonsware.com/blog/2017/11/14/storage-situation-external-storage.html
* https://commonsware.com/blog/2017/11/15/storage-situation-removable-storage.html
* <p>
* The Android Storage Access Framework can be 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.
* <p>
* Otherwise a fallback is provided for only saving on the primary volume with the older APIs.
*/
@Singleton
public class Storage {
private static final Storage instance;
private StorageImpl impl;
static {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
instance = new Storage(new BaseStorageImpl());
@Inject
public Storage(Context applicationContext) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
impl = new BaseStorageImpl(applicationContext);
} else {
instance = new Storage(new NougatStorageImpl());
}
}
public static Storage getInstance() {
return instance;
impl = new LollipopStorageImpl(applicationContext);
}
private StorageImpl impl;
public Storage(StorageImpl impl) {
this.impl = impl;
}
public Intent requestExternalPermission(Context applicationContext) {
return impl.requestExternalPermission(applicationContext);
public boolean supportsExternalStorage() {
return impl.supportsExternalStorage();
}
public interface StorageImpl {
Intent requestExternalPermission(Context applicationContext);
public Intent getOpenTreeIntent() {
return impl.getOpenTreeIntent();
}
public static class BaseStorageImpl implements StorageImpl {
@Override
public Intent requestExternalPermission(Context applicationContext) {
throw new UnsupportedOperationException();
}
public void handleOpenTreeIntent(Uri uri) {
impl.handleOpenTreeIntent(uri);
}
@TargetApi(Build.VERSION_CODES.N)
public static class NougatStorageImpl extends BaseStorageImpl {
@Override
public Intent requestExternalPermission(Context applicationContext) {
StorageManager sm = (StorageManager)
applicationContext.getSystemService(Context.STORAGE_SERVICE);
Objects.requireNonNull(sm);
for (StorageVolume storageVolume : sm.getStorageVolumes()) {
if (!storageVolume.isPrimary()) {
Intent accessIntent = storageVolume.createAccessIntent(null);
return accessIntent;
}
public StorageFile obtainStorageFileForName(String name) {
return impl.obtainStorageFileForName(name);
}
return null;
}
public String currentStorageName() {
return impl.currentStorageName();
}
}

@ -0,0 +1,93 @@
package org.floens.chan.core.storage;
import android.content.ContentResolver;
import android.net.Uri;
import org.floens.chan.utils.IOUtils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
public class StorageFile {
private final ContentResolver contentResolver;
private final Uri uriOpenableByContentResolvers;
private final File file;
public static StorageFile fromFile(File file) {
return new StorageFile(file);
}
public static StorageFile fromUri(ContentResolver contentResolver, Uri uriOpenableByContentResolvers) {
return new StorageFile(contentResolver, uriOpenableByContentResolvers);
}
private StorageFile(ContentResolver contentResolver, Uri uriOpenableByContentResolvers) {
this.contentResolver = contentResolver;
this.uriOpenableByContentResolvers = uriOpenableByContentResolvers;
this.file = null;
}
private StorageFile(File file) {
this.contentResolver = null;
this.uriOpenableByContentResolvers = null;
this.file = file;
}
public InputStream inputStream() throws IOException {
if (file != null) {
return new FileInputStream(file);
} else {
return contentResolver.openInputStream(uriOpenableByContentResolvers);
}
}
public OutputStream outputStream() throws IOException {
if (file != null) {
File parent = file.getParentFile();
if (!parent.mkdirs() && !parent.isDirectory()) {
throw new IOException("Could not create parent directory");
}
if (file.isDirectory()) {
throw new IOException("Destination not a file");
}
return new FileOutputStream(file);
} else {
return contentResolver.openOutputStream(uriOpenableByContentResolvers);
}
}
public String name() {
return "dummy name";
}
public boolean exists() {
// TODO
return false;
}
public boolean delete() {
// TODO
return true;
}
public void copyFrom(File source) throws IOException {
InputStream is = null;
OutputStream os = null;
try {
is = new BufferedInputStream(new FileInputStream(source));
os = new BufferedOutputStream(outputStream());
IOUtils.copy(is, os);
} finally {
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os);
}
}
}

@ -0,0 +1,16 @@
package org.floens.chan.core.storage;
import android.content.Intent;
import android.net.Uri;
public interface StorageImpl {
boolean supportsExternalStorage();
Intent getOpenTreeIntent();
void handleOpenTreeIntent(Uri uri);
StorageFile obtainStorageFileForName(String name);
String currentStorageName();
}

@ -51,6 +51,7 @@ import org.floens.chan.ui.controller.BrowseController;
import org.floens.chan.ui.controller.DoubleNavigationController;
import org.floens.chan.ui.controller.DrawerController;
import org.floens.chan.ui.controller.SplitNavigationController;
import org.floens.chan.ui.controller.StorageSetupController;
import org.floens.chan.ui.controller.StyledToolbarNavigationController;
import org.floens.chan.ui.controller.ThreadSlideController;
import org.floens.chan.ui.controller.ViewThreadController;
@ -164,6 +165,8 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
} else {
browseController.loadWithDefaultBoard();
}
mainNavigationController.pushController(new StorageSetupController(this), false);
}
private boolean restoreFromUrl() {
@ -487,6 +490,8 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
super.onActivityResult(requestCode, resultCode, data);
imagePickDelegate.onActivityResult(requestCode, resultCode, data);
drawerController.onActivityResult(requestCode, resultCode, data);
}
private Controller stackTop() {

@ -46,6 +46,9 @@ import org.floens.chan.utils.RecyclerUtils;
import java.util.ArrayList;
import java.util.List;
import javax.inject.Inject;
import static org.floens.chan.Chan.inject;
import static org.floens.chan.ui.theme.ThemeHelper.theme;
import static org.floens.chan.utils.AndroidUtils.dp;
@ -59,6 +62,8 @@ public class AlbumDownloadController extends Controller implements View.OnClickL
private boolean allChecked = true;
private AlbumAdapter adapter;
@Inject
private ImageSaver imageSaver;
public AlbumDownloadController(Context context) {
@ -69,7 +74,7 @@ public class AlbumDownloadController extends Controller implements View.OnClickL
public void onCreate() {
super.onCreate();
imageSaver = ImageSaver.getInstance();
inject(this);
view = inflateRes(R.layout.controller_album_download);

@ -92,6 +92,9 @@ public class ImageViewerController extends Controller implements ImageViewerPres
@Inject
ImageLoader imageLoader;
@Inject
ImageSaver imageSaver;
private int statusBarColorPrevious;
private AnimatorSet startAnimation;
private AnimatorSet endAnimation;
@ -226,7 +229,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres
File.separator +
presenter.getLoadable().boardCode);
}
ImageSaver.getInstance().startDownloadTask(context, task);
imageSaver.startDownloadTask(context, task);
}
}

@ -18,9 +18,7 @@
package org.floens.chan.ui.controller;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.support.design.widget.FloatingActionButton;
import android.view.View;
@ -28,7 +26,6 @@ import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.saver.FileWatcher;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.storage.Storage;
import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.adapter.FilesAdapter;
import org.floens.chan.ui.helper.RuntimePermissionsHelper;
@ -77,18 +74,6 @@ public class SaveLocationController extends Controller implements FileWatcher.Fi
} else {
requestPermission();
}
test();
}
private void test() {
Storage storage = Storage.getInstance();
Intent intent = storage.requestExternalPermission(context.getApplicationContext());
if (intent != null) {
if (intent.resolveActivity(context.getPackageManager()) != null) {
((Activity) context).startActivityForResult(intent, 10024);
}
}
}
@Override

@ -0,0 +1,79 @@
package org.floens.chan.ui.controller;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
import org.floens.chan.R;
import org.floens.chan.controller.Controller;
import org.floens.chan.core.storage.Storage;
import javax.inject.Inject;
import static org.floens.chan.Chan.inject;
public class StorageSetupController extends Controller implements View.OnClickListener {
private static final int OPEN_TREE_INTENT_RESULT_ID = 101;
private static final String TAG = "StorageSetupController";
@Inject
private Storage storage;
private TextView text;
private Button button;
public StorageSetupController(Context context) {
super(context);
}
@Override
public void onCreate() {
super.onCreate();
inject(this);
// Navigation
navigation.setTitle(R.string.storage_setup_screen);
// View inflation
view = inflateRes(R.layout.controller_storage_setup);
// View binding
text = view.findViewById(R.id.text);
button = view.findViewById(R.id.button);
// View setup
button.setOnClickListener(this);
updateName();
}
@Override
public void onClick(View v) {
if (v == button) {
requestTree();
}
}
private void requestTree() {
Intent i = storage.getOpenTreeIntent();
((Activity) context).startActivityForResult(i, OPEN_TREE_INTENT_RESULT_ID);
updateName();
}
private void updateName() {
text.setText(storage.currentStorageName());
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == OPEN_TREE_INTENT_RESULT_ID && resultCode == Activity.RESULT_OK) {
storage.handleOpenTreeIntent(data.getData());
updateName();
}
}
}

@ -25,6 +25,7 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/**
* Simple ImageDecoder. Taken from Volley ImageRequest.
@ -55,26 +56,27 @@ public class ImageDecoder {
if (!file.exists())
return null;
FileInputStream fis;
try {
fis = new FileInputStream(file);
InputStream fis = new FileInputStream(file);
return decodeFile(fis, maxWidth, maxHeight);
} catch (FileNotFoundException e) {
e.printStackTrace();
return null;
}
}
public static Bitmap decodeFile(InputStream is, int maxWidth, int maxHeight) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
Bitmap bitmap = null;
try {
IOUtils.copy(fis, baos);
IOUtils.copy(is, baos);
bitmap = decode(baos.toByteArray(), maxWidth, maxHeight);
} catch (IOException | OutOfMemoryError e) {
e.printStackTrace();
} finally {
IOUtils.closeQuietly(fis);
IOUtils.closeQuietly(is);
IOUtils.closeQuietly(baos);
}

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/text"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="open" />
</LinearLayout>
</ScrollView>

@ -546,6 +546,10 @@ Re-enable this permission in the app settings if you permanently disabled it."
<string name="setting_folder_navigate_up">Up</string>
<!-- Storage setup settings -->
<string name="storage_setup_screen">Setup storage</string>
<!-- Theme settings -->
<string name="setting_theme_explanation">
"Swipe to change the theme.

Loading…
Cancel
Save