Android 6: Ask for storage permission when saving images

Fix success toast always showing, even when the task was not successful
multisite
Floens 10 years ago
parent a18b41e572
commit ee98b5c3cf
  1. 7
      Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaveTask.java
  2. 102
      Clover/app/src/main/java/org/floens/chan/core/saver/ImageSaver.java
  3. 14
      Clover/app/src/main/java/org/floens/chan/ui/activity/StartActivity.java
  4. 5
      Clover/app/src/main/java/org/floens/chan/ui/controller/AlbumDownloadController.java
  5. 2
      Clover/app/src/main/java/org/floens/chan/ui/controller/ImageViewerController.java
  6. 82
      Clover/app/src/main/java/org/floens/chan/ui/helper/RuntimePermissionsHelper.java

@ -100,8 +100,6 @@ public class ImageSaveTask implements Runnable, FileCache.DownloadedCallback {
@Override @Override
public void run() { public void run() {
try { try {
Logger.test("Start! " + toString());
if (destination.exists()) { if (destination.exists()) {
onDestination(); onDestination();
} else { } else {
@ -113,8 +111,6 @@ public class ImageSaveTask implements Runnable, FileCache.DownloadedCallback {
fileCacheDownloader.getFuture().get(); fileCacheDownloader.getFuture().get();
} }
} }
Logger.test("End! " + toString());
} catch (InterruptedException e) { } catch (InterruptedException e) {
onInterrupted(); onInterrupted();
} catch (Exception e) { } catch (Exception e) {
@ -169,13 +165,14 @@ public class ImageSaveTask implements Runnable, FileCache.DownloadedCallback {
is = new FileInputStream(source); is = new FileInputStream(source);
os = new FileOutputStream(destination); os = new FileOutputStream(destination);
IOUtils.copy(is, os); IOUtils.copy(is, os);
success = true;
} catch (IOException e) { } catch (IOException e) {
Logger.e(TAG, "Error writing to file", e); Logger.e(TAG, "Error writing to file", e);
} finally { } finally {
IOUtils.closeQuietly(is); IOUtils.closeQuietly(is);
IOUtils.closeQuietly(os); IOUtils.closeQuietly(os);
} }
success = true;
} }
private void scanDestination() { private void scanDestination() {

@ -17,6 +17,7 @@
*/ */
package org.floens.chan.core.saver; package org.floens.chan.core.saver;
import android.Manifest;
import android.app.NotificationManager; import android.app.NotificationManager;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
@ -26,6 +27,8 @@ import android.widget.Toast;
import org.floens.chan.R; import org.floens.chan.R;
import org.floens.chan.core.model.PostImage; import org.floens.chan.core.model.PostImage;
import org.floens.chan.core.settings.ChanSettings; import org.floens.chan.core.settings.ChanSettings;
import org.floens.chan.ui.activity.StartActivity;
import org.floens.chan.ui.helper.RuntimePermissionsHelper;
import org.floens.chan.ui.service.SavingNotification; import org.floens.chan.ui.service.SavingNotification;
import java.io.File; import java.io.File;
@ -51,6 +54,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
private ExecutorService executor = Executors.newSingleThreadExecutor(); private ExecutorService executor = Executors.newSingleThreadExecutor();
private int doneTasks = 0; private int doneTasks = 0;
private int totalTasks = 0; private int totalTasks = 0;
private Toast toast;
public static ImageSaver getInstance() { public static ImageSaver getInstance() {
return instance; return instance;
@ -61,15 +65,54 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
notificationManager = (NotificationManager) getAppContext().getSystemService(Context.NOTIFICATION_SERVICE); notificationManager = (NotificationManager) getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
} }
public void startBundledTask(String subFolder, List<ImageSaveTask> tasks) { public void startDownloadTask(Context context, final ImageSaveTask task) {
for (ImageSaveTask task : tasks) { PostImage postImage = task.getPostImage();
PostImage postImage = task.getPostImage(); String name = ChanSettings.saveOriginalFilename.get() ? postImage.originalName : postImage.filename;
String fileName = filterName(postImage.originalName + "." + postImage.extension); String fileName = filterName(name + "." + postImage.extension);
task.setDestination(new File(getSaveLocation() + File.separator + subFolder + File.separator + fileName)); task.setDestination(findUnusedFileName(new File(getSaveLocation(), fileName), false));
// task.setMakeBitmap(true);
task.setShowToast(true);
if (!hasPermission(context)) {
// This does not request the permission when another request is pending.
// This is ok and will drop the task.
requestPermission(context, new RuntimePermissionsHelper.Callback() {
@Override
public void onRuntimePermissionResult(boolean granted) {
if (granted) {
startTask(task);
updateNotification();
} else {
showToast(null, false);
}
}
});
} else {
startTask(task); startTask(task);
updateNotification();
}
}
public boolean startBundledTask(Context context, final String subFolder, final List<ImageSaveTask> tasks) {
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);
}
}
});
return false;
} else {
startBundledTaskInternal(subFolder, tasks);
return true;
} }
updateNotification();
} }
public String getSubFolder(String name) { public String getSubFolder(String name) {
@ -78,19 +121,6 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
return filtered; return filtered;
} }
public void startDownloadTask(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(), fileName), false));
// task.setMakeBitmap(true);
task.setShowToast(true);
startTask(task);
updateNotification();
}
public File getSaveLocation() { public File getSaveLocation() {
return new File(ChanSettings.saveLocation.get()); return new File(ChanSettings.saveLocation.get());
} }
@ -108,7 +138,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
showImageSaved(task); showImageSaved(task);
} }
if (task.isShowToast()) { if (task.isShowToast()) {
showToast(task); showToast(task, success);
} }
} }
@ -123,6 +153,17 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
executor.execute(task); executor.execute(task);
} }
private void startBundledTaskInternal(String subFolder, List<ImageSaveTask> tasks) {
for (ImageSaveTask task : tasks) {
PostImage postImage = task.getPostImage();
String fileName = filterName(postImage.originalName + "." + postImage.extension);
task.setDestination(new File(getSaveLocation() + File.separator + subFolder + File.separator + fileName));
startTask(task);
}
updateNotification();
}
private void cancelAll() { private void cancelAll() {
executor.shutdownNow(); executor.shutdownNow();
executor = Executors.newSingleThreadExecutor(); executor = Executors.newSingleThreadExecutor();
@ -157,9 +198,16 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
notificationManager.notify(NOTIFICATION_ID, builder.build()); notificationManager.notify(NOTIFICATION_ID, builder.build());
} }
private void showToast(ImageSaveTask task) { private void showToast(ImageSaveTask task, boolean success) {
String savedAs = getAppContext().getString(R.string.image_save_as, task.getDestination().getName()); if (toast != null) {
Toast.makeText(getAppContext(), savedAs, Toast.LENGTH_LONG).show(); toast.cancel();
}
String text = success ?
getAppContext().getString(R.string.image_save_as, task.getDestination().getName()) :
getString(R.string.image_save_failed);
toast = Toast.makeText(getAppContext(), text, Toast.LENGTH_LONG);
toast.show();
} }
private String filterName(String name) { private String filterName(String name) {
@ -210,4 +258,12 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
return test; return test;
} }
private boolean hasPermission(Context context) {
return ((StartActivity) context).getRuntimePermissionsHelper().hasPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE);
}
private void requestPermission(Context context, RuntimePermissionsHelper.Callback callback) {
((StartActivity) context).getRuntimePermissionsHelper().requestPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE, callback);
}
} }

@ -50,6 +50,7 @@ import org.floens.chan.ui.controller.StyledToolbarNavigationController;
import org.floens.chan.ui.controller.ViewThreadController; import org.floens.chan.ui.controller.ViewThreadController;
import org.floens.chan.ui.helper.ImagePickDelegate; import org.floens.chan.ui.helper.ImagePickDelegate;
import org.floens.chan.ui.helper.PreviousVersionHandler; import org.floens.chan.ui.helper.PreviousVersionHandler;
import org.floens.chan.ui.helper.RuntimePermissionsHelper;
import org.floens.chan.ui.state.ChanState; import org.floens.chan.ui.state.ChanState;
import org.floens.chan.ui.theme.ThemeHelper; import org.floens.chan.ui.theme.ThemeHelper;
import org.floens.chan.utils.AndroidUtils; import org.floens.chan.utils.AndroidUtils;
@ -72,6 +73,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
private BrowseController browseController; private BrowseController browseController;
private ImagePickDelegate imagePickDelegate; private ImagePickDelegate imagePickDelegate;
private RuntimePermissionsHelper runtimePermissionsHelper;
public StartActivity() { public StartActivity() {
boardManager = Chan.getBoardManager(); boardManager = Chan.getBoardManager();
@ -84,6 +86,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
ThemeHelper.getInstance().setupContext(this); ThemeHelper.getInstance().setupContext(this);
imagePickDelegate = new ImagePickDelegate(this); imagePickDelegate = new ImagePickDelegate(this);
runtimePermissionsHelper = new RuntimePermissionsHelper(this);
contentView = (ViewGroup) findViewById(android.R.id.content); contentView = (ViewGroup) findViewById(android.R.id.content);
@ -277,6 +280,10 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
return imagePickDelegate; return imagePickDelegate;
} }
public RuntimePermissionsHelper getRuntimePermissionsHelper() {
return runtimePermissionsHelper;
}
@Override @Override
public void onConfigurationChanged(Configuration newConfig) { public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig); super.onConfigurationChanged(newConfig);
@ -337,6 +344,13 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
imagePickDelegate.onActivityResult(requestCode, resultCode, data); imagePickDelegate.onActivityResult(requestCode, resultCode, data);
} }
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
runtimePermissionsHelper.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private Controller stackTop() { private Controller stackTop() {
return stack.get(stack.size() - 1); return stack.get(stack.size() - 1);
} }

@ -122,8 +122,9 @@ public class AlbumDownloadController extends Controller implements ToolbarMenuIt
} }
} }
imageSaver.startBundledTask(folderForAlbum, tasks); if (imageSaver.startBundledTask(context, folderForAlbum, tasks)) {
navigationController.popController(); navigationController.popController();
}
} }
}) })
.show(); .show();

@ -201,7 +201,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres
} else { } else {
ImageSaveTask task = new ImageSaveTask(postImage); ImageSaveTask task = new ImageSaveTask(postImage);
task.setShare(share); task.setShare(share);
ImageSaver.getInstance().startDownloadTask(task); ImageSaver.getInstance().startDownloadTask(context, task);
} }
} }

@ -0,0 +1,82 @@
/*
* Clover - 4chan browser https://github.com/Floens/Clover/
* Copyright (C) 2014 Floens
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.floens.chan.ui.helper;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.support.annotation.NonNull;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import static org.floens.chan.utils.AndroidUtils.getAppContext;
public class RuntimePermissionsHelper {
private static final int RUNTIME_PERMISSION_RESULT_ID = 3;
private ActivityCompat.OnRequestPermissionsResultCallback callbackActvity;
private CallbackHolder pendingCallback;
public RuntimePermissionsHelper(ActivityCompat.OnRequestPermissionsResultCallback callbackActvity) {
this.callbackActvity = callbackActvity;
}
public boolean hasPermission(String permission) {
return ContextCompat.checkSelfPermission(getAppContext(), permission) == PackageManager.PERMISSION_GRANTED;
}
public boolean requestPermission(String permission, Callback callback) {
if (pendingCallback == null) {
pendingCallback = new CallbackHolder();
pendingCallback.callback = callback;
pendingCallback.permission = permission;
ActivityCompat.requestPermissions((Activity) callbackActvity, new String[]{permission}, RUNTIME_PERMISSION_RESULT_ID);
return true;
} else {
return false;
}
}
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
if (requestCode == RUNTIME_PERMISSION_RESULT_ID && pendingCallback != null) {
boolean granted = false;
for (int i = 0; i < permissions.length; i++) {
String permission = permissions[i];
if (permission.equals(pendingCallback.permission) && grantResults[i] == PackageManager.PERMISSION_GRANTED) {
granted = true;
break;
}
}
pendingCallback.callback.onRuntimePermissionResult(granted);
pendingCallback = null;
}
}
private class CallbackHolder {
private Callback callback;
private String permission;
}
public interface Callback {
void onRuntimePermissionResult(boolean granted);
}
}
Loading…
Cancel
Save