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

@ -17,6 +17,7 @@
*/
package org.floens.chan.core.saver;
import android.Manifest;
import android.app.NotificationManager;
import android.content.Context;
import android.content.Intent;
@ -26,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.ui.activity.StartActivity;
import org.floens.chan.ui.helper.RuntimePermissionsHelper;
import org.floens.chan.ui.service.SavingNotification;
import java.io.File;
@ -51,6 +54,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
private ExecutorService executor = Executors.newSingleThreadExecutor();
private int doneTasks = 0;
private int totalTasks = 0;
private Toast toast;
public static ImageSaver getInstance() {
return instance;
@ -61,15 +65,54 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
notificationManager = (NotificationManager) getAppContext().getSystemService(Context.NOTIFICATION_SERVICE);
}
public void startBundledTask(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));
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(), 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);
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) {
@ -78,19 +121,6 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
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() {
return new File(ChanSettings.saveLocation.get());
}
@ -108,7 +138,7 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
showImageSaved(task);
}
if (task.isShowToast()) {
showToast(task);
showToast(task, success);
}
}
@ -123,6 +153,17 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
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() {
executor.shutdownNow();
executor = Executors.newSingleThreadExecutor();
@ -157,9 +198,16 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
notificationManager.notify(NOTIFICATION_ID, builder.build());
}
private void showToast(ImageSaveTask task) {
String savedAs = getAppContext().getString(R.string.image_save_as, task.getDestination().getName());
Toast.makeText(getAppContext(), savedAs, Toast.LENGTH_LONG).show();
private void showToast(ImageSaveTask task, boolean success) {
if (toast != null) {
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) {
@ -210,4 +258,12 @@ public class ImageSaver implements ImageSaveTask.ImageSaveTaskCallback {
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.helper.ImagePickDelegate;
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.theme.ThemeHelper;
import org.floens.chan.utils.AndroidUtils;
@ -72,6 +73,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
private BrowseController browseController;
private ImagePickDelegate imagePickDelegate;
private RuntimePermissionsHelper runtimePermissionsHelper;
public StartActivity() {
boardManager = Chan.getBoardManager();
@ -84,6 +86,7 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
ThemeHelper.getInstance().setupContext(this);
imagePickDelegate = new ImagePickDelegate(this);
runtimePermissionsHelper = new RuntimePermissionsHelper(this);
contentView = (ViewGroup) findViewById(android.R.id.content);
@ -277,6 +280,10 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
return imagePickDelegate;
}
public RuntimePermissionsHelper getRuntimePermissionsHelper() {
return runtimePermissionsHelper;
}
@Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
@ -337,6 +344,13 @@ public class StartActivity extends AppCompatActivity implements NfcAdapter.Creat
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() {
return stack.get(stack.size() - 1);
}

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

@ -201,7 +201,7 @@ public class ImageViewerController extends Controller implements ImageViewerPres
} else {
ImageSaveTask task = new ImageSaveTask(postImage);
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