diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java index 78f79c55..61ca9202 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/AndroFotoFinderApp.java @@ -49,9 +49,11 @@ import de.k3b.android.androFotoFinder.queries.MediaDBRepository; import de.k3b.android.androFotoFinder.queries.MergedMediaRepository; import de.k3b.android.osmdroid.forge.MapsForgeSupport; +import de.k3b.android.util.DocumentFileTranslator; import de.k3b.android.util.LogCat; import de.k3b.android.util.PhotoChangeNotifyer; import de.k3b.android.widget.ActivityWithCallContext; +import de.k3b.android.widget.FilePermissionActivity; import de.k3b.android.widget.LocalizedActivity; import de.k3b.database.QueryParameter; import de.k3b.io.FileApi; @@ -149,6 +151,7 @@ public static RefWatcher getRefWatcher(Context context) { FotoSqlBase.init(); super.onCreate(); + FilePermissionActivity.init(this); LibGlobal.appName = getString(R.string.app_name); LibGlobal.appVersion = GuiUtil.getAppVersionName(this); @@ -171,7 +174,8 @@ public static RefWatcher getRefWatcher(Context context) { ExifInterface.LOG_TAG, PhotoPropertiesImageReader.LOG_TAG, FotoSql.LOG_TAG, MediaDBRepository.LOG_TAG, FileApi.TAG, - MediaContentproviderRepositoryImpl.LOG_TAG) { + MediaContentproviderRepositoryImpl.LOG_TAG, + DocumentFileTranslator.TAG, DocumentFileTranslator.TAG_DOCFILE) { @Override public void uncaughtException(Thread thread, Throwable ex) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java b/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java index 67fdfe21..00f7d479 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/GalleryFilterActivity.java @@ -964,7 +964,7 @@ public void invalidateDirectories(String why) { public void onDirectoryCancel(int queryTypeId) {closeDialogIfNeeded();} @Override - protected void closeDialogIfNeeded() { + public void closeDialogIfNeeded() { super.closeDialogIfNeeded(); mDirPicker = null; } diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java index 04020944..b70c4788 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/gallery/cursor/GalleryCursorFragment.java @@ -89,6 +89,7 @@ import de.k3b.android.util.ResourceUtils; import de.k3b.android.widget.AboutDialogPreference; import de.k3b.android.widget.Dialogs; +import de.k3b.android.widget.FilePermissionActivity; import de.k3b.android.widget.UpdateTask; import de.k3b.database.QueryParameter; import de.k3b.geo.api.GeoPointDto; @@ -859,7 +860,9 @@ public boolean onOptionsItemSelected(MenuItem menuItem) { AndroidFileCommands fileCommands = mFileCommands; final SelectedFiles selectedFiles = this.mAdapter.createSelectedFiles(getActivity(), this.mSelectedItems); - if ((mSelectedItems != null) && (fileCommands.onOptionsItemSelected(getActivity(), menuItem, selectedFiles, this))) { + if ((mSelectedItems != null) + && (fileCommands.onOptionsItemSelected( + (FilePermissionActivity) getActivity(), menuItem, selectedFiles, this))) { return true; } switch (menuItem.getItemId()) { diff --git a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java index 1b5c7281..0d41ac74 100644 --- a/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java +++ b/app/src/main/java/de/k3b/android/androFotoFinder/imagedetail/ImageDetailActivityViewPager.java @@ -708,9 +708,8 @@ private boolean osRenameTo(final CharSequence title, final File dest, final Sele // close rename dialog to allow messagebox that prepares to ask closeDialogIfNeeded(); File missingRoot = getMissingRootDirFileOrNull(currentFoto.getFiles()); - if (missingRoot == null) { - return mFileCommands.setContext(this).rename(currentFoto, dest, null); - } else { + if (missingRoot != null) { + // ask for needed permissions requestRootUriDialog(missingRoot, title, new IOnDirectoryPermissionGrantedHandler() { @Override @@ -720,6 +719,9 @@ public void afterGrant(FilePermissionActivity activity) { }); return false; } + + // needed permissions available: Go on + return mFileCommands.setContext(this).rename(currentFoto, dest, null); } @Override diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileApi.java b/app/src/main/java/de/k3b/android/util/AndroidFileApi.java index 60059a36..63de884d 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileApi.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileApi.java @@ -58,25 +58,17 @@ private DocumentFileTranslator getDocumentFileTranslator() { } protected boolean osRenameTo(File dest, File source) { - final String context = this.getClass().getSimpleName() + + String context = this.getClass().getSimpleName() + ".osRenameTo(" + dest + " <== " + source + ")"; if ((source != null) && (dest != null)) { if (dest.getParentFile().equals(source.getParentFile())) { Boolean result = null; try { - DocumentFile documentFile = DocumentFile.fromFile(source); - if (!documentFile.canWrite()) { - if (source.isDirectory()) { - documentFile = getOrCreateDirectory(source); - } else { - DocumentFile dir = getOrCreateDirectory(source.getParentFile()); - DocumentFile found = dir.findFile(source.getName()); - if (found != null) { - documentFile = found; - } - } + DocumentFile documentFile = geWritabletDocumentFile(source); + context += FastDocumentFileTranslator.toUriDebugString(documentFile); + if (documentFile != null) { + result = documentFile.renameTo(dest.getName()); } - result = documentFile.renameTo(dest.getName()); return result; } finally { if (Global.debugEnabled) { @@ -89,4 +81,43 @@ protected boolean osRenameTo(File dest, File source) { Log.w(TAG, context + " move between different directories is not implemented yet"); return super.osRenameTo(dest, source); } + + protected boolean osDeleteFile(File file) { + String context = this.getClass().getSimpleName() + + ".osDeleteFile(" + file + ")"; + if (file != null) { + Boolean result = null; + try { + DocumentFile documentFile = geWritabletDocumentFile(file); + context += FastDocumentFileTranslator.toUriDebugString(documentFile); + if (documentFile != null) { + result = documentFile.delete(); + } + return result; + } finally { + if (Global.debugEnabled) { + Log.d(TAG, context + " ==> " + result); + } + } + } + + return super.osDeleteFile(file); + } + + private DocumentFile geWritabletDocumentFile(File file) { + DocumentFile documentFile = DocumentFile.fromFile(file); + if (!documentFile.canWrite()) { + if (file.isDirectory()) { + documentFile = getOrCreateDirectory(file); + } else { + DocumentFile dir = getOrCreateDirectory(file.getParentFile()); + DocumentFile found = dir.findFile(file.getName()); + if (found != null) { + documentFile = found; + } + } + } + return documentFile; + } + } diff --git a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java index 72831274..d309fee5 100644 --- a/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java +++ b/app/src/main/java/de/k3b/android/util/AndroidFileCommands.java @@ -47,6 +47,7 @@ import de.k3b.android.androFotoFinder.queries.FotoSql; import de.k3b.android.androFotoFinder.tagDB.TagSql; import de.k3b.android.androFotoFinder.transactionlog.TransactionLogSql; +import de.k3b.android.widget.FilePermissionActivity; import de.k3b.database.QueryParameter; import de.k3b.io.DirectoryFormatter; import de.k3b.io.FileCommands; @@ -174,12 +175,12 @@ private static int getResourceId(int opCode) { } - public boolean onOptionsItemSelected(Activity activity, final MenuItem item, final SelectedFiles selectedFileNames, PhotoChangeNotifyer.PhotoChangedListener photoChangedListener) { + public boolean onOptionsItemSelected(FilePermissionActivity activity, final MenuItem item, final SelectedFiles selectedFileNames, PhotoChangeNotifyer.PhotoChangedListener photoChangedListener) { if ((selectedFileNames != null) && (selectedFileNames.size() > 0)) { // Handle item selection switch (item.getItemId()) { case R.id.cmd_delete: - return cmdDeleteFileWithQuestion(activity, selectedFileNames, photoChangedListener); + return cmdDeleteFileWithQuestion(item.getTitle(), activity, selectedFileNames, photoChangedListener); default:break; } } @@ -277,10 +278,25 @@ private void setLastCopyToPath(String copyToPath) { edit.apply(); } - public boolean cmdDeleteFileWithQuestion(Activity activity, final SelectedFiles fotos, - final PhotoChangeNotifyer.PhotoChangedListener photoChangedListener) { + private boolean cmdDeleteFileWithQuestion(final CharSequence title, FilePermissionActivity activity, final SelectedFiles fotos, + final PhotoChangeNotifyer.PhotoChangedListener photoChangedListener) { String[] pathNames = fotos.getFileNames(); - String errorMessage = checkWriteProtected(R.string.delete_menu_title, SelectedFiles.getFiles(pathNames)); + activity.closeDialogIfNeeded(); + File missingRoot = activity.getMissingRootDirFileOrNull(fotos.getFiles()); + if (missingRoot != null) { + activity.requestRootUriDialog(missingRoot, title, + new FilePermissionActivity.IOnDirectoryPermissionGrantedHandler() { + @Override + public void afterGrant(FilePermissionActivity activity) { + cmdDeleteFileWithQuestion(title, activity, fotos, photoChangedListener); + } + }); + return false; + } + + //!!! how to distinguish between sd writeprotected and file writeprotected + // String errorMessage = checkWriteProtected(R.string.delete_menu_title, SelectedFiles.getFiles(pathNames)); + String errorMessage = null; if (errorMessage != null) { if (!isInBackground) { @@ -295,10 +311,10 @@ public boolean cmdDeleteFileWithQuestion(Activity activity, final SelectedFiles .getString(R.string.delete_question_message_format, names.toString()); final AlertDialog.Builder builder = new AlertDialog.Builder(activity); - final String title = mContext.getText(R.string.delete_question_title) + final String titleQuestion = mContext.getText(R.string.delete_question_title) .toString(); - builder.setTitle(title + pathNames.length); + builder.setTitle(titleQuestion + pathNames.length); builder.setMessage(message) .setCancelable(false) .setPositiveButton(android.R.string.yes, diff --git a/app/src/main/java/de/k3b/android/util/DocumentFileTranslator.java b/app/src/main/java/de/k3b/android/util/DocumentFileTranslator.java index 4650348e..df07e122 100644 --- a/app/src/main/java/de/k3b/android/util/DocumentFileTranslator.java +++ b/app/src/main/java/de/k3b/android/util/DocumentFileTranslator.java @@ -37,6 +37,10 @@ */ public class DocumentFileTranslator { public static final String TAG = "k3b.DocFileUtils"; + + // used by android.support.v4.provider.DocumentFile + public static final String TAG_DOCFILE = "DocumentFile"; + private static final String SAFROOTPREF_KEY_SAF_ROOT_PREFIX = "safroot-"; private static DocumentFileTranslator rootsettings = null; private final Context context; diff --git a/app/src/main/java/de/k3b/android/widget/ActivityWithAutoCloseDialogs.java b/app/src/main/java/de/k3b/android/widget/ActivityWithAutoCloseDialogs.java index 0124da27..17a6785b 100644 --- a/app/src/main/java/de/k3b/android/widget/ActivityWithAutoCloseDialogs.java +++ b/app/src/main/java/de/k3b/android/widget/ActivityWithAutoCloseDialogs.java @@ -41,7 +41,7 @@ public abstract class ActivityWithAutoCloseDialogs extends LocalizedActivity { private Closeable mCloseable; private DialogInterface mCurrentDialog; - protected void closeDialogIfNeeded() { + public void closeDialogIfNeeded() { // close dialog. else crash in onResume if (mCurrentDialog != null) mCurrentDialog.dismiss(); diff --git a/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java b/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java index 8d55a66a..db79f29e 100644 --- a/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java +++ b/app/src/main/java/de/k3b/android/widget/BaseQueryActivity.java @@ -723,7 +723,7 @@ public void onDirectoryCancel(int queryTypeId) { } @Override - protected void closeDialogIfNeeded() { + public void closeDialogIfNeeded() { super.closeDialogIfNeeded(); getFolderApi().mDirPicker = null; } diff --git a/app/src/main/java/de/k3b/android/widget/FilePermissionActivity.java b/app/src/main/java/de/k3b/android/widget/FilePermissionActivity.java index bc32ab2f..bfa1b086 100644 --- a/app/src/main/java/de/k3b/android/widget/FilePermissionActivity.java +++ b/app/src/main/java/de/k3b/android/widget/FilePermissionActivity.java @@ -21,6 +21,7 @@ import android.Manifest; import android.app.Dialog; +import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageManager; @@ -55,6 +56,8 @@ public abstract class FilePermissionActivity extends ActivityWithAutoCloseDialog private static File currentRootFileRequest = null; private DocumentFileTranslator documentFileTranslator = null; + public static void init(Context context) { + } // workflow onCreate() => requestPermission(PERMISSION_WRITE_EXTERNAL_STORAGE) => onRequestPermissionsResult() => abstract onCreateEx() @Override protected void onCreate(Bundle savedInstanceState) { @@ -134,7 +137,7 @@ private static void execRequestRootUri( * @return null if all permissions are granted or * the root file that has not permissions yet. */ - protected File getMissingRootDirFileOrNull(File... dirs) { + public File getMissingRootDirFileOrNull(File... dirs) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { for (int i = dirs.length - 1; i >= 0; i--) { if (null == getOrCreateDirectory(dirs[i])) { @@ -178,7 +181,7 @@ public DocumentFileTranslator getDocumentFileTranslator() { // ... -> requestRootUriDialog -> execRequestRootUri // -> onActivityResult -> onRootUriResult -> IOnDirectoryPermissionGrantedHandler.afterGrant() - protected void requestRootUriDialog(File root, final CharSequence title, IOnDirectoryPermissionGrantedHandler permissionGrantedHandler) { + public void requestRootUriDialog(File root, final CharSequence title, IOnDirectoryPermissionGrantedHandler permissionGrantedHandler) { requestRootUriDialog(this, root, title, getString(R.string.select_folder_root_rationale), diff --git a/fotolib2/src/main/java/de/k3b/io/FileApi.java b/fotolib2/src/main/java/de/k3b/io/FileApi.java index dfad1da8..f42403a7 100644 --- a/fotolib2/src/main/java/de/k3b/io/FileApi.java +++ b/fotolib2/src/main/java/de/k3b/io/FileApi.java @@ -32,4 +32,8 @@ protected boolean osRenameTo(File dest, File source) { return (source != null) && (dest != null) && source.renameTo(dest); } + protected boolean osDeleteFile(File file) { + return (file != null) && (file.canWrite()) && file.delete(); + } + } diff --git a/fotolib2/src/main/java/de/k3b/io/FileCommands.java b/fotolib2/src/main/java/de/k3b/io/FileCommands.java index e3604500..cb0a0fc3 100644 --- a/fotolib2/src/main/java/de/k3b/io/FileCommands.java +++ b/fotolib2/src/main/java/de/k3b/io/FileCommands.java @@ -528,9 +528,8 @@ private static boolean _osFileCopy(File targetFullPath, File sourceFullPath, Fil return result; } - /** to be replaced by mock/stub in unittests */ protected boolean osDeleteFile(File file) { - final boolean result = file.delete(); + final boolean result = fileApi.osDeleteFile(file); if (LibGlobal.debugEnabledJpg) logger.info("osDeleteFile '" + file + "' success=" + result); return result; }