implement file and saf abstractions.

external-storage-support
Floens 7 years ago
parent 3db2ac07de
commit e22cfcf8ee
  1. 29
      Clover/app/src/main/java/org/floens/chan/core/presenter/StorageSetupPresenter.java
  2. 6
      Clover/app/src/main/java/org/floens/chan/core/settings/ChanSettings.java
  3. 62
      Clover/app/src/main/java/org/floens/chan/core/storage/Storage.java
  4. 23
      Clover/app/src/main/java/org/floens/chan/core/storage/StorageFile.java
  5. 38
      Clover/app/src/main/java/org/floens/chan/ui/controller/MediaSettingsController.java

@ -17,11 +17,9 @@
*/
package org.floens.chan.core.presenter;
import android.app.Activity;
import android.content.Intent;
import android.os.Build;
import org.floens.chan.core.storage.Storage;
import org.floens.chan.ui.activity.ActivityResultHelper;
import javax.inject.Inject;
@ -29,12 +27,10 @@ public class StorageSetupPresenter {
private Callback callback;
private Storage storage;
private ActivityResultHelper results;
@Inject
public StorageSetupPresenter(Storage storage, ActivityResultHelper results) {
public StorageSetupPresenter(Storage storage) {
this.storage = storage;
this.results = results;
}
public void create(Callback callback) {
@ -43,13 +39,20 @@ public class StorageSetupPresenter {
updateDescription();
}
public void saveLocationClicked() {
Intent openTreeIntent = storage.getOpenTreeIntent();
results.getResultFromIntent(openTreeIntent, (resultCode, result) -> {
if (resultCode == Activity.RESULT_OK) {
storage.handleOpenTreeIntent(result);
public void saveLocationClicked(boolean forceFile) {
if (!forceFile &&
storage.getModeForNewLocation() == Storage.Mode.STORAGE_ACCESS_FRAMEWORK) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // lint
storage.setupNewSAFSaveLocation(this::updateDescription);
}
});
} else {
String fileSaveLocation = storage.getFileSaveLocation();
callback.showPathDialog(fileSaveLocation);
}
}
public void saveLocationEntered(String input) {
storage.setFileSaveLocation(input);
}
private void updateDescription() {
@ -59,5 +62,7 @@ public class StorageSetupPresenter {
public interface Callback {
void setSaveLocationDescription(String description);
void showPathDialog(String path);
}
}

@ -17,7 +17,6 @@
*/
package org.floens.chan.core.settings;
import android.os.Environment;
import android.text.TextUtils;
import org.floens.chan.BuildConfig;
@ -27,7 +26,6 @@ import org.floens.chan.core.update.UpdateManager;
import org.floens.chan.ui.adapter.PostsFilter;
import org.floens.chan.utils.AndroidUtils;
import java.io.File;
import java.net.InetSocketAddress;
import java.net.Proxy;
@ -195,9 +193,7 @@ public class ChanSettings {
developer = new BooleanSetting(p, "preference_developer", false);
saveLocation = new StringSetting(p, "preference_image_save_location", Environment.getExternalStorageDirectory() + File.separator + "Clover");
saveLocation.addCallback((setting, value) ->
EventBus.getDefault().post(new SettingChanged<>(saveLocation)));
saveLocation = new StringSetting(p, "preference_image_save_location", "");
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);

@ -17,20 +17,24 @@
*/
package org.floens.chan.core.storage;
import android.app.Activity;
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.os.Environment;
import android.provider.DocumentsContract;
import android.support.annotation.RequiresApi;
import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.core.settings.StringSetting;
import org.floens.chan.ui.activity.ActivityResultHelper;
import org.floens.chan.utils.IOUtils;
import org.floens.chan.utils.Logger;
import java.io.File;
import java.io.FileNotFoundException;
import javax.inject.Inject;
@ -55,6 +59,8 @@ import javax.inject.Singleton;
public class Storage {
private static final String TAG = "Storage";
private static final String DEFAULT_DIRECTORY_NAME = "Clover";
/**
* The current mode of the Storage.
*/
@ -76,13 +82,15 @@ public class Storage {
}
private Context applicationContext;
private ActivityResultHelper results;
private final StringSetting saveLocation;
private final StringSetting saveLocationTreeUri;
@Inject
public Storage(Context applicationContext) {
public Storage(Context applicationContext, ActivityResultHelper results) {
this.applicationContext = applicationContext;
this.results = results;
saveLocation = ChanSettings.saveLocation;
saveLocationTreeUri = ChanSettings.saveLocationTreeUri;
@ -97,11 +105,34 @@ public class Storage {
return Mode.FILE;
}
if (!saveLocation.get().isEmpty()) {
// File by default.
if (saveLocation.get().isEmpty() && saveLocationTreeUri.get().isEmpty()) {
return Mode.FILE;
}
return Mode.STORAGE_ACCESS_FRAMEWORK;
if (!saveLocationTreeUri.get().isEmpty()) {
return Mode.STORAGE_ACCESS_FRAMEWORK;
}
return Mode.FILE;
}
public Mode getModeForNewLocation() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return Mode.FILE;
} else {
return Mode.STORAGE_ACCESS_FRAMEWORK;
}
}
public String getFileSaveLocation() {
prepareDefaultFileSaveLocation();
return saveLocation.get();
}
public void setFileSaveLocation(String location) {
saveLocation.set(location);
saveLocationTreeUri.set("");
}
public String currentStorageName() {
@ -112,7 +143,7 @@ public class Storage {
case STORAGE_ACCESS_FRAMEWORK: {
String uriString = saveLocationTreeUri.get();
Uri treeUri = Uri.parse(uriString);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { // lint
return queryTreeName(treeUri);
}
}
@ -120,14 +151,27 @@ public class Storage {
throw new IllegalStateException();
}
public Mode getModeForNewLocation() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return Mode.FILE;
} else {
return Mode.STORAGE_ACCESS_FRAMEWORK;
private void prepareDefaultFileSaveLocation() {
if (saveLocation.get().isEmpty()) {
File pictures = Environment.getExternalStoragePublicDirectory(
Environment.DIRECTORY_PICTURES);
File directory = new File(pictures, DEFAULT_DIRECTORY_NAME);
String absolutePath = directory.getAbsolutePath();
saveLocation.set(absolutePath);
}
}
@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();
}
});
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public Intent getOpenTreeIntent() {
Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT_TREE);

@ -40,7 +40,7 @@ public class StorageFile {
}
public InputStream inputStream() throws IOException {
if (file != null) {
if (isFile()) {
return new FileInputStream(file);
} else {
return contentResolver.openInputStream(uriOpenableByContentResolvers);
@ -48,7 +48,7 @@ public class StorageFile {
}
public OutputStream outputStream() throws IOException {
if (file != null) {
if (isFile()) {
File parent = file.getParentFile();
if (!parent.mkdirs() && !parent.isDirectory()) {
throw new IOException("Could not create parent directory");
@ -69,13 +69,20 @@ public class StorageFile {
}
public boolean exists() {
// TODO
return false;
if (isFile()) {
return file.exists() && file.isFile();
} else {
return false; // we don't know?
}
}
public boolean delete() {
// TODO
return true;
if (isFile()) {
return file.delete();
} else {
// TODO
return true;
}
}
public void copyFrom(File source) throws IOException {
@ -90,4 +97,8 @@ public class StorageFile {
IOUtils.closeQuietly(os);
}
}
private boolean isFile() {
return file != null;
}
}

@ -18,8 +18,12 @@
package org.floens.chan.ui.controller;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.widget.EditText;
import android.widget.FrameLayout;
import org.floens.chan.R;
import org.floens.chan.core.presenter.StorageSetupPresenter;
@ -37,6 +41,7 @@ import java.util.List;
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.getString;
public class MediaSettingsController extends SettingsController implements
@ -102,6 +107,26 @@ public class MediaSettingsController extends SettingsController implements
saveLocation.setDescription(description);
}
@Override
public void showPathDialog(String path) {
FrameLayout container = new FrameLayout(context);
EditText dialogView = new EditText(context);
dialogView.setText(path);
FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(
FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT
);
lp.leftMargin = lp.rightMargin = dp(20);
container.addView(dialogView, lp);
new AlertDialog.Builder(context)
.setTitle(R.string.save_location_screen)
.setView(container)
.setNegativeButton(R.string.cancel, null)
.setPositiveButton(R.string.ok, (dialog, which) -> {
presenter.saveLocationEntered(dialogView.getText().toString());
})
.show();
}
private void populatePreferences() {
// Media group
{
@ -215,8 +240,19 @@ public class MediaSettingsController extends SettingsController implements
}
private void setupSaveLocationSetting(SettingsGroup media) {
// Register a normal click listener and a long click listener that sets the
// force file option to true.
saveLocation = (LinkSettingView) media.add(new LinkSettingView(this,
R.string.save_location_screen, 0,
v -> presenter.saveLocationClicked()));
v -> presenter.saveLocationClicked(false)) {
@Override
public void setView(View view) {
super.setView(view);
view.setOnLongClickListener(v -> {
presenter.saveLocationClicked(true);
return true;
});
}
});
}
}

Loading…
Cancel
Save