mirror of
				https://github.com/SimpleMobileTools/Simple-File-Manager.git
				synced 2025-06-05 22:09:15 +02:00 
			
		
		
		
	Merge branch 'feat/scoped-storage' of https://github.com/KryptKode/Simple-File-Manager into KryptKode-feat/scoped-storage
This commit is contained in:
		| @@ -9,13 +9,12 @@ if (keystorePropertiesFile.exists()) { | ||||
| } | ||||
|  | ||||
| android { | ||||
|     compileSdkVersion 29 | ||||
|     buildToolsVersion "29.0.3" | ||||
|     compileSdkVersion 30 | ||||
|  | ||||
|     defaultConfig { | ||||
|         applicationId "com.simplemobiletools.filemanager.pro" | ||||
|         minSdkVersion 21 | ||||
|         targetSdkVersion 29 | ||||
|         targetSdkVersion 30 | ||||
|         versionCode 110 | ||||
|         versionName "6.10.1" | ||||
|         multiDexEnabled true | ||||
| @@ -58,7 +57,7 @@ android { | ||||
| } | ||||
|  | ||||
| dependencies { | ||||
|     implementation 'com.github.SimpleMobileTools:Simple-Commons:c184f98ca8' | ||||
|     implementation 'com.github.SimpleMobileTools:Simple-Commons:4e6eeb901f' | ||||
|     implementation 'com.github.Stericson:RootTools:df729dcb13' | ||||
|     implementation 'com.github.Stericson:RootShell:1.6' | ||||
|     implementation 'com.alexvasilkov:gesture-views:2.5.2' | ||||
|   | ||||
| @@ -6,6 +6,7 @@ | ||||
|  | ||||
|     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> | ||||
|     <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> | ||||
|     <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/> | ||||
|  | ||||
|     <uses-feature | ||||
|         android:name="android.hardware.faketouch" | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| package com.simplemobiletools.filemanager.pro.activities | ||||
|  | ||||
| import android.annotation.SuppressLint | ||||
| import android.app.Activity | ||||
| import android.app.SearchManager | ||||
| import android.content.ClipData | ||||
| @@ -8,13 +9,18 @@ import android.content.Intent | ||||
| import android.graphics.drawable.ColorDrawable | ||||
| import android.graphics.drawable.Drawable | ||||
| import android.media.RingtoneManager | ||||
| import android.net.Uri | ||||
| import android.os.Bundle | ||||
| import android.os.Environment | ||||
| import android.os.Handler | ||||
| import android.provider.Settings | ||||
| import android.view.Menu | ||||
| import android.view.MenuItem | ||||
| import androidx.appcompat.widget.SearchView | ||||
| import androidx.core.view.MenuItemCompat | ||||
| import androidx.viewpager.widget.ViewPager | ||||
| import com.simplemobiletools.commons.dialogs.ConfirmationAdvancedDialog | ||||
| import com.simplemobiletools.commons.dialogs.ConfirmationDialog | ||||
| import com.simplemobiletools.commons.dialogs.RadioGroupDialog | ||||
| import com.simplemobiletools.commons.extensions.* | ||||
| import com.simplemobiletools.commons.helpers.* | ||||
| @@ -46,6 +52,7 @@ import java.util.* | ||||
|  | ||||
| class MainActivity : SimpleActivity() { | ||||
|     private val BACK_PRESS_TIMEOUT = 5000 | ||||
|     private val MANAGE_STORAGE_RC = 201 | ||||
|     private val PICKED_PATH = "picked_path" | ||||
|     private var isSearchOpen = false | ||||
|     private var wasBackJustPressed = false | ||||
| @@ -282,8 +289,8 @@ class MainActivity : SimpleActivity() { | ||||
|     } | ||||
|  | ||||
|     private fun tryInitFileManager() { | ||||
|         val hadPermission = hasPermission(PERMISSION_WRITE_STORAGE) | ||||
|         handlePermission(PERMISSION_WRITE_STORAGE) { | ||||
|         val hadPermission = hasStoragePermission() | ||||
|         handleStoragePermission { | ||||
|             checkOTGPath() | ||||
|             if (it) { | ||||
|                 if (main_view_pager.adapter == null) { | ||||
| @@ -300,6 +307,56 @@ class MainActivity : SimpleActivity() { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @SuppressLint("InlinedApi") | ||||
|     private fun handleStoragePermission(callback: (granted: Boolean) -> Unit) { | ||||
|         actionOnPermission = null | ||||
|         if (hasStoragePermission()) { | ||||
|             callback(true) | ||||
|         } else { | ||||
|             if (isRPlus()) { | ||||
|                 ConfirmationAdvancedDialog(this, "", R.string.access_storage_prompt, R.string.ok, 0) { success -> | ||||
|                     if (success ) { | ||||
|                         isAskingPermissions = true | ||||
|                         actionOnPermission = callback | ||||
|                         try { | ||||
|                             val intent = Intent(Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION) | ||||
|                             intent.addCategory("android.intent.category.DEFAULT") | ||||
|                             intent.data = Uri.parse("package:$packageName") | ||||
|                             startActivityForResult(intent, MANAGE_STORAGE_RC) | ||||
|                         } catch (e: Exception) { | ||||
|                             showErrorToast(e) | ||||
|                             val intent = Intent() | ||||
|                             intent.action = Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION | ||||
|                             startActivityForResult(intent, MANAGE_STORAGE_RC) | ||||
|                         } | ||||
|                     } else { | ||||
|                         finish() | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 handlePermission(PERMISSION_WRITE_STORAGE, callback) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @SuppressLint("NewApi") | ||||
|     private fun hasStoragePermission(): Boolean { | ||||
|         return if (isRPlus()) { | ||||
|             Environment.isExternalStorageManager() | ||||
|         } else { | ||||
|             hasPermission(PERMISSION_WRITE_STORAGE) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @SuppressLint("NewApi") | ||||
|     override fun onActivityResult(requestCode: Int, resultCode: Int, resultData: Intent?) { | ||||
|         super.onActivityResult(requestCode, resultCode, resultData) | ||||
|         isAskingPermissions = false | ||||
|         if (requestCode == MANAGE_STORAGE_RC && isRPlus()) { | ||||
|             actionOnPermission?.invoke(Environment.isExternalStorageManager()) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun initFileManager(refreshRecents: Boolean) { | ||||
|         if (intent.action == Intent.ACTION_VIEW && intent.data != null) { | ||||
|             val data = intent.data | ||||
|   | ||||
| @@ -41,6 +41,7 @@ import com.simplemobiletools.filemanager.pro.helpers.* | ||||
| import com.simplemobiletools.filemanager.pro.interfaces.ItemOperationsListener | ||||
| import com.simplemobiletools.filemanager.pro.models.ListItem | ||||
| import com.stericson.RootTools.RootTools | ||||
| import java.io.BufferedInputStream | ||||
| import kotlinx.android.synthetic.main.item_file_dir_grid.view.* | ||||
| import kotlinx.android.synthetic.main.item_file_dir_list.view.* | ||||
| import kotlinx.android.synthetic.main.item_file_dir_list.view.item_frame | ||||
| @@ -49,10 +50,9 @@ import kotlinx.android.synthetic.main.item_file_dir_list.view.item_name | ||||
| import kotlinx.android.synthetic.main.item_section.view.* | ||||
| import java.io.Closeable | ||||
| import java.io.File | ||||
| import java.io.FileInputStream | ||||
| import java.util.* | ||||
| import java.util.zip.ZipEntry | ||||
| import java.util.zip.ZipFile | ||||
| import java.util.zip.ZipInputStream | ||||
| import java.util.zip.ZipOutputStream | ||||
|  | ||||
| class ItemsAdapter( | ||||
| @@ -329,13 +329,25 @@ class ItemsAdapter( | ||||
|     private fun addFileUris(path: String, paths: ArrayList<String>) { | ||||
|         if (activity.getIsPathDirectory(path)) { | ||||
|             val shouldShowHidden = activity.config.shouldShowHidden | ||||
|             if (activity.isPathOnOTG(path)) { | ||||
|                 activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") }?.forEach { | ||||
|                     addFileUris(it.uri.toString(), paths) | ||||
|             when { | ||||
|                 activity.isRestrictedSAFOnlyRoot(path) -> { | ||||
|                     activity.getAndroidSAFFileItems(path, shouldShowHidden, false) { files -> | ||||
|                         files.forEach { | ||||
|                             addFileUris(activity.getAndroidSAFUri(it.path).toString(), paths) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|             } else { | ||||
|                 File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') }?.forEach { | ||||
|                     addFileUris(it.absolutePath, paths) | ||||
|  | ||||
|                 activity.isPathOnOTG(path) -> { | ||||
|                     activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") }?.forEach { | ||||
|                         addFileUris(it.uri.toString(), paths) | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 else -> { | ||||
|                     File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') }?.forEach { | ||||
|                         addFileUris(it.absolutePath, paths) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
| @@ -390,20 +402,30 @@ class ItemsAdapter( | ||||
|                 activity.copyMoveFilesTo(files, source, it, isCopyOperation, false, activity.config.shouldShowHidden) { | ||||
|                     if (!isCopyOperation) { | ||||
|                         files.forEach { sourceFileDir -> | ||||
|                             val sourceFile = File(sourceFileDir.path) | ||||
|                             if (activity.getDoesFilePathExist(source) && activity.getIsPathDirectory(source) && | ||||
|                                 sourceFile.list()?.isEmpty() == true && sourceFile.getProperSize(true) == 0L && sourceFile.getFileCount(true) == 0 | ||||
|                             ) { | ||||
|                                 val sourceFolder = sourceFile.toFileDirItem(activity) | ||||
|                                 activity.deleteFile(sourceFolder, true) { | ||||
|                             val sourcePath = sourceFileDir.path | ||||
|                             if (activity.isRestrictedSAFOnlyRoot(sourcePath) && activity.getDoesFilePathExist(sourcePath)) { | ||||
|                                 activity.deleteFile(sourceFileDir, true) { | ||||
|                                     listener?.refreshFragment() | ||||
|                                     activity.runOnUiThread { | ||||
|                                         finishActMode() | ||||
|                                     } | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 listener?.refreshFragment() | ||||
|                                 finishActMode() | ||||
|                                 val sourceFile = File(sourcePath) | ||||
|                                 if (activity.getDoesFilePathExist(source) && activity.getIsPathDirectory(source) && | ||||
|                                     sourceFile.list()?.isEmpty() == true && sourceFile.getProperSize(true) == 0L && sourceFile.getFileCount(true) == 0 | ||||
|                                 ) { | ||||
|                                     val sourceFolder = sourceFile.toFileDirItem(activity) | ||||
|                                     activity.deleteFile(sourceFolder, true) { | ||||
|                                         listener?.refreshFragment() | ||||
|                                         activity.runOnUiThread { | ||||
|                                             finishActMode() | ||||
|                                         } | ||||
|                                     } | ||||
|                                 } else { | ||||
|                                     listener?.refreshFragment() | ||||
|                                     finishActMode() | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
| @@ -443,22 +465,27 @@ class ItemsAdapter( | ||||
|  | ||||
|         CompressAsDialog(activity, firstPath) { | ||||
|             val destination = it | ||||
|             activity.handleSAFDialog(firstPath) { | ||||
|                 if (!it) { | ||||
|                     return@handleSAFDialog | ||||
|             activity.handleAndroidSAFDialog(firstPath) { granted -> | ||||
|                 if (!granted) { | ||||
|                     return@handleAndroidSAFDialog | ||||
|                 } | ||||
|                 activity.handleSAFDialog(firstPath) { | ||||
|                     if (!it) { | ||||
|                         return@handleSAFDialog | ||||
|                     } | ||||
|  | ||||
|                 activity.toast(R.string.compressing) | ||||
|                 val paths = getSelectedFileDirItems().map { it.path } | ||||
|                 ensureBackgroundThread { | ||||
|                     if (compressPaths(paths, destination)) { | ||||
|                         activity.runOnUiThread { | ||||
|                             activity.toast(R.string.compression_successful) | ||||
|                             listener?.refreshFragment() | ||||
|                             finishActMode() | ||||
|                     activity.toast(R.string.compressing) | ||||
|                     val paths = getSelectedFileDirItems().map { it.path } | ||||
|                     ensureBackgroundThread { | ||||
|                         if (compressPaths(paths, destination)) { | ||||
|                             activity.runOnUiThread { | ||||
|                                 activity.toast(R.string.compression_successful) | ||||
|                                 listener?.refreshFragment() | ||||
|                                 finishActMode() | ||||
|                             } | ||||
|                         } else { | ||||
|                             activity.toast(R.string.compressing_failed) | ||||
|                         } | ||||
|                     } else { | ||||
|                         activity.toast(R.string.compressing_failed) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
| @@ -478,103 +505,108 @@ class ItemsAdapter( | ||||
|             } | ||||
|  | ||||
|             val paths = getSelectedFileDirItems().asSequence().map { it.path }.filter { it.isZipFile() }.toList() | ||||
|             tryDecompressingPaths(paths) { | ||||
|                 if (it) { | ||||
|                     activity.toast(R.string.decompression_successful) | ||||
|             ensureBackgroundThread { | ||||
|                 tryDecompressingPaths(paths) { success -> | ||||
|                     activity.runOnUiThread { | ||||
|                         listener?.refreshFragment() | ||||
|                         finishActMode() | ||||
|                         if (success) { | ||||
|                             activity.toast(R.string.decompression_successful) | ||||
|                             listener?.refreshFragment() | ||||
|                             finishActMode() | ||||
|                         } else { | ||||
|                             activity.toast(R.string.decompressing_failed) | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     activity.toast(R.string.decompressing_failed) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun tryDecompressingPaths(sourcePaths: List<String>, callback: (success: Boolean) -> Unit) { | ||||
|         sourcePaths.forEach { | ||||
|             try { | ||||
|                 val zipFile = ZipFile(it) | ||||
|                 val entries = zipFile.entries() | ||||
|                 val fileDirItems = ArrayList<FileDirItem>() | ||||
|                 while (entries.hasMoreElements()) { | ||||
|                     val entry = entries.nextElement() | ||||
|                     val currPath = if (entry.isDirectory) it else "${it.getParentPath().trimEnd('/')}/${entry.name}" | ||||
|                     val fileDirItem = FileDirItem(currPath, entry.name, entry.isDirectory, 0, entry.size) | ||||
|                     fileDirItems.add(fileDirItem) | ||||
|                 } | ||||
|  | ||||
|                 val destinationPath = fileDirItems.first().getParentPath().trimEnd('/') | ||||
|                 activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) { | ||||
|                     ensureBackgroundThread { | ||||
|                         decompressPaths(sourcePaths, it, callback) | ||||
|         sourcePaths.forEach { path -> | ||||
|             ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path))).use { zipInputStream -> | ||||
|                 try { | ||||
|                     val fileDirItems = ArrayList<FileDirItem>() | ||||
|                     var entry = zipInputStream.nextEntry | ||||
|                     while (entry != null) { | ||||
|                         val currPath = if (entry.isDirectory) path else "${path.getParentPath().trimEnd('/')}/${entry.name}" | ||||
|                         val fileDirItem = FileDirItem(currPath, entry.name, entry.isDirectory, 0, entry.size) | ||||
|                         fileDirItems.add(fileDirItem) | ||||
|                         zipInputStream.closeEntry() | ||||
|                         entry = zipInputStream.nextEntry | ||||
|                     } | ||||
|                     zipInputStream.closeEntry() | ||||
|                     val destinationPath = fileDirItems.first().getParentPath().trimEnd('/') | ||||
|                     activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) { | ||||
|                         ensureBackgroundThread { | ||||
|                             decompressPaths(sourcePaths, it, callback) | ||||
|                         } | ||||
|                     } | ||||
|                 } catch (exception: Exception) { | ||||
|                     activity.showErrorToast(exception) | ||||
|                 } | ||||
|             } catch (exception: Exception) { | ||||
|                 activity.showErrorToast(exception) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun decompressPaths(paths: List<String>, conflictResolutions: LinkedHashMap<String, Int>, callback: (success: Boolean) -> Unit) { | ||||
|         paths.forEach { | ||||
|             try { | ||||
|                 val zipFile = ZipFile(it) | ||||
|                 val entries = zipFile.entries() | ||||
|                 val zipFileName = it.getFilenameFromPath() | ||||
|                 val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4) | ||||
|                 while (entries.hasMoreElements()) { | ||||
|                     val entry = entries.nextElement() | ||||
|                     val parentPath = it.getParentPath() | ||||
|                     val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}" | ||||
|         paths.forEach { path -> | ||||
|             val zipInputStream = ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path))) | ||||
|             zipInputStream.use { | ||||
|                 try { | ||||
|                     var entry = zipInputStream.nextEntry | ||||
|                     val zipFileName = path.getFilenameFromPath() | ||||
|                     val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4) | ||||
|                     while (entry != null) { | ||||
|                         val parentPath = path.getParentPath() | ||||
|                         val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}" | ||||
|  | ||||
|                     val resolution = getConflictResolution(conflictResolutions, newPath) | ||||
|                     val doesPathExist = activity.getDoesFilePathExist(newPath) | ||||
|                     if (doesPathExist && resolution == CONFLICT_OVERWRITE) { | ||||
|                         val fileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), entry.isDirectory) | ||||
|                         if (activity.getIsPathDirectory(it)) { | ||||
|                             activity.deleteFolderBg(fileDirItem, false) { | ||||
|                                 if (it) { | ||||
|                                     extractEntry(newPath, entry, zipFile) | ||||
|                                 } else { | ||||
|                                     callback(false) | ||||
|                                 } | ||||
|                             } | ||||
|                         } else { | ||||
|                             activity.deleteFileBg(fileDirItem, false) { | ||||
|                                 if (it) { | ||||
|                                     extractEntry(newPath, entry, zipFile) | ||||
|                                 } else { | ||||
|                                     callback(false) | ||||
|                         val resolution = getConflictResolution(conflictResolutions, newPath) | ||||
|                         val doesPathExist = activity.getDoesFilePathExist(newPath) | ||||
|                         if (doesPathExist && resolution == CONFLICT_OVERWRITE) { | ||||
|                             val fileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), entry.isDirectory) | ||||
|                             if (activity.getIsPathDirectory(path)) { | ||||
|                                 activity.deleteFolderBg(fileDirItem, false) { | ||||
|                                     if (it) { | ||||
|                                         extractEntry(newPath, entry, zipInputStream) | ||||
|                                     } else { | ||||
|                                         callback(false) | ||||
|                                     } | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 activity.deleteFileBg(fileDirItem, false) { | ||||
|                                     if (it) { | ||||
|                                         extractEntry(newPath, entry, zipInputStream) | ||||
|                                     } else { | ||||
|                                         callback(false) | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } else if (!doesPathExist) { | ||||
|                             extractEntry(newPath, entry, zipInputStream) | ||||
|                         } | ||||
|                     } else if (!doesPathExist) { | ||||
|                         extractEntry(newPath, entry, zipFile) | ||||
|  | ||||
|                         zipInputStream.closeEntry() | ||||
|                         entry = zipInputStream.nextEntry | ||||
|                     } | ||||
|                     callback(true) | ||||
|                 } catch (e: Exception) { | ||||
|                     activity.showErrorToast(e) | ||||
|                     callback(false) | ||||
|                 } | ||||
|                 callback(true) | ||||
|             } catch (e: Exception) { | ||||
|                 activity.showErrorToast(e) | ||||
|                 callback(false) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private fun extractEntry(newPath: String, entry: ZipEntry, zipFile: ZipFile) { | ||||
|     private fun extractEntry(newPath: String, entry: ZipEntry, zipInputStream: ZipInputStream) { | ||||
|         if (entry.isDirectory) { | ||||
|             if (!activity.createDirectorySync(newPath) && !activity.getDoesFilePathExist(newPath)) { | ||||
|                 val error = String.format(activity.getString(R.string.could_not_create_file), newPath) | ||||
|                 activity.showErrorToast(error) | ||||
|             } | ||||
|         } else { | ||||
|             val ins = zipFile.getInputStream(entry) | ||||
|             ins.use { | ||||
|                 val fos = activity.getFileOutputStreamSync(newPath, newPath.getMimeType()) | ||||
|                 if (fos != null) { | ||||
|                     ins.copyTo(fos) | ||||
|                 } | ||||
|             val fos = activity.getFileOutputStreamSync(newPath, newPath.getMimeType()) | ||||
|             if (fos != null) { | ||||
|                 zipInputStream.copyTo(fos) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| @@ -590,43 +622,62 @@ class ItemsAdapter( | ||||
|     } | ||||
|  | ||||
|     private fun compressPaths(sourcePaths: List<String>, targetPath: String): Boolean { | ||||
|         val queue = LinkedList<File>() | ||||
|         val queue = LinkedList<String>() | ||||
|         val fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false | ||||
|  | ||||
|         val zout = ZipOutputStream(fos) | ||||
|         var res: Closeable = fos | ||||
|  | ||||
|         try { | ||||
|             sourcePaths.forEach { | ||||
|             sourcePaths.forEach { currentPath -> | ||||
|                 var name: String | ||||
|                 var mainFile = File(it) | ||||
|                 val base = mainFile.parentFile.toURI() | ||||
|                 var mainFilePath = currentPath | ||||
|                 val base = "${mainFilePath.getParentPath()}/" | ||||
|                 res = zout | ||||
|                 queue.push(mainFile) | ||||
|                 if (activity.getIsPathDirectory(mainFile.absolutePath)) { | ||||
|                     name = "${mainFile.name.trimEnd('/')}/" | ||||
|                 queue.push(mainFilePath) | ||||
|                 if (activity.getIsPathDirectory(mainFilePath)) { | ||||
|                     name = "${mainFilePath.getFilenameFromPath()}/" | ||||
|                     zout.putNextEntry(ZipEntry(name)) | ||||
|                 } | ||||
|  | ||||
|                 while (!queue.isEmpty()) { | ||||
|                     mainFile = queue.pop() | ||||
|                     if (activity.getIsPathDirectory(mainFile.absolutePath)) { | ||||
|                         for (file in mainFile.listFiles()) { | ||||
|                             name = base.relativize(file.toURI()).path | ||||
|                             if (activity.getIsPathDirectory(file.absolutePath)) { | ||||
|                                 queue.push(file) | ||||
|                                 name = "${name.trimEnd('/')}/" | ||||
|                                 zout.putNextEntry(ZipEntry(name)) | ||||
|                             } else { | ||||
|                                 zout.putNextEntry(ZipEntry(name)) | ||||
|                                 FileInputStream(file).copyTo(zout) | ||||
|                                 zout.closeEntry() | ||||
|                     mainFilePath = queue.pop() | ||||
|                     if (activity.getIsPathDirectory(mainFilePath)) { | ||||
|                         if (activity.isRestrictedSAFOnlyRoot(mainFilePath)) { | ||||
|                             activity.getAndroidSAFFileItems(mainFilePath, true) { files -> | ||||
|                                 for (file in files) { | ||||
|                                     name = file.path.relativizeWith(base) | ||||
|                                     if (activity.getIsPathDirectory(file.path)) { | ||||
|                                         queue.push(file.path) | ||||
|                                         name = "${name.trimEnd('/')}/" | ||||
|                                         zout.putNextEntry(ZipEntry(name)) | ||||
|                                     } else { | ||||
|                                         zout.putNextEntry(ZipEntry(name)) | ||||
|                                         activity.getFileInputStreamSync(file.path)!!.copyTo(zout) | ||||
|                                         zout.closeEntry() | ||||
|                                     } | ||||
|                                 } | ||||
|                             } | ||||
|                         } else { | ||||
|                             val mainFile = File(mainFilePath) | ||||
|                             for (file in mainFile.listFiles()) { | ||||
|                                 name = file.path.relativizeWith(base) | ||||
|                                 if (activity.getIsPathDirectory(file.absolutePath)) { | ||||
|                                     queue.push(file.absolutePath) | ||||
|                                     name = "${name.trimEnd('/')}/" | ||||
|                                     zout.putNextEntry(ZipEntry(name)) | ||||
|                                 } else { | ||||
|                                     zout.putNextEntry(ZipEntry(name)) | ||||
|                                     activity.getFileInputStreamSync(file.path)!!.copyTo(zout) | ||||
|                                     zout.closeEntry() | ||||
|                                 } | ||||
|                             } | ||||
|                         } | ||||
|  | ||||
|                     } else { | ||||
|                         name = if (base.path == it) it.getFilenameFromPath() else base.relativize(mainFile.toURI()).path | ||||
|                         name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base) | ||||
|                         zout.putNextEntry(ZipEntry(name)) | ||||
|                         FileInputStream(mainFile).copyTo(zout) | ||||
|                         activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout) | ||||
|                         zout.closeEntry() | ||||
|                     } | ||||
|                 } | ||||
| @@ -833,7 +884,9 @@ class ItemsAdapter( | ||||
|             path | ||||
|         } | ||||
|  | ||||
|         if (hasOTGConnected && itemToLoad is String && activity.isPathOnOTG(itemToLoad) && baseConfig.OTGTreeUri.isNotEmpty() && baseConfig.OTGPartition.isNotEmpty()) { | ||||
|         if (activity.isRestrictedSAFOnlyRoot(path)) { | ||||
|             itemToLoad = activity.getAndroidSAFUri(path) | ||||
|         } else if (hasOTGConnected && itemToLoad is String && activity.isPathOnOTG(itemToLoad) && baseConfig.OTGTreeUri.isNotEmpty() && baseConfig.OTGPartition.isNotEmpty()) { | ||||
|             itemToLoad = getOTGPublicPath(itemToLoad) | ||||
|         } | ||||
|  | ||||
|   | ||||
| @@ -3,6 +3,7 @@ package com.simplemobiletools.filemanager.pro.dialogs | ||||
| import android.view.View | ||||
| import androidx.appcompat.app.AlertDialog | ||||
| import com.simplemobiletools.commons.extensions.* | ||||
| import com.simplemobiletools.commons.helpers.isRPlus | ||||
| import com.simplemobiletools.filemanager.pro.R | ||||
| import com.simplemobiletools.filemanager.pro.activities.SimpleActivity | ||||
| import com.simplemobiletools.filemanager.pro.helpers.RootHelpers | ||||
| @@ -15,41 +16,62 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca | ||||
|  | ||||
|     init { | ||||
|         AlertDialog.Builder(activity) | ||||
|                 .setPositiveButton(R.string.ok, null) | ||||
|                 .setNegativeButton(R.string.cancel, null) | ||||
|                 .create().apply { | ||||
|                     activity.setupDialogStuff(view, this, R.string.create_new) { | ||||
|                         showKeyboard(view.item_name) | ||||
|                         getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener { | ||||
|                             val name = view.item_name.value | ||||
|                             if (name.isEmpty()) { | ||||
|                                 activity.toast(R.string.empty_name) | ||||
|                             } else if (name.isAValidFilename()) { | ||||
|                                 val newPath = "$path/$name" | ||||
|                                 if (activity.getDoesFilePathExist(newPath)) { | ||||
|                                     activity.toast(R.string.name_taken) | ||||
|                                     return@OnClickListener | ||||
|                                 } | ||||
|             .setPositiveButton(R.string.ok, null) | ||||
|             .setNegativeButton(R.string.cancel, null) | ||||
|             .create().apply { | ||||
|                 activity.setupDialogStuff(view, this, R.string.create_new) { | ||||
|                     showKeyboard(view.item_name) | ||||
|                     getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener(View.OnClickListener { | ||||
|                         val name = view.item_name.value | ||||
|                         if (name.isEmpty()) { | ||||
|                             activity.toast(R.string.empty_name) | ||||
|                         } else if (name.isAValidFilename()) { | ||||
|                             val newPath = "$path/$name" | ||||
|                             if (activity.getDoesFilePathExist(newPath)) { | ||||
|                                 activity.toast(R.string.name_taken) | ||||
|                                 return@OnClickListener | ||||
|                             } | ||||
|  | ||||
|                                 if (view.dialog_radio_group.checkedRadioButtonId == R.id.dialog_radio_directory) { | ||||
|                                     createDirectory(newPath, this) { | ||||
|                                         callback(it) | ||||
|                                     } | ||||
|                                 } else { | ||||
|                                     createFile(newPath, this) { | ||||
|                                         callback(it) | ||||
|                                     } | ||||
|                             if (view.dialog_radio_group.checkedRadioButtonId == R.id.dialog_radio_directory) { | ||||
|                                 createDirectory(newPath, this) { | ||||
|                                     callback(it) | ||||
|                                 } | ||||
|                             } else { | ||||
|                                 activity.toast(R.string.invalid_name) | ||||
|                                 createFile(newPath, this) { | ||||
|                                     callback(it) | ||||
|                                 } | ||||
|                             } | ||||
|                         }) | ||||
|                     } | ||||
|                         } else { | ||||
|                             activity.toast(R.string.invalid_name) | ||||
|                         } | ||||
|                     }) | ||||
|                 } | ||||
|             } | ||||
|     } | ||||
|  | ||||
|     private fun createDirectory(path: String, alertDialog: AlertDialog, callback: (Boolean) -> Unit) { | ||||
|         when { | ||||
|             isRPlus() || path.startsWith(activity.internalStoragePath, true) -> { | ||||
|                 if (activity.isRestrictedSAFOnlyRoot(path)) { | ||||
|                     activity.handleAndroidSAFDialog(path) { | ||||
|                         if (!it) { | ||||
|                             callback(false) | ||||
|                             return@handleAndroidSAFDialog | ||||
|                         } | ||||
|                         if (activity.createAndroidSAFDirectory(path)) { | ||||
|                             success(alertDialog) | ||||
|                         } else { | ||||
|                             val error = String.format(activity.getString(R.string.could_not_create_folder), path) | ||||
|                             activity.showErrorToast(error) | ||||
|                             callback(false) | ||||
|                         } | ||||
|                     } | ||||
|                 } else { | ||||
|                     if (File(path).mkdirs()) { | ||||
|                         success(alertDialog) | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             activity.needsStupidWritePermissions(path) -> activity.handleSAFDialog(path) { | ||||
|                 if (!it) { | ||||
|                     return@handleSAFDialog | ||||
| @@ -65,11 +87,6 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca | ||||
|                 documentFile.createDirectory(path.getFilenameFromPath()) | ||||
|                 success(alertDialog) | ||||
|             } | ||||
|             path.startsWith(activity.internalStoragePath, true) -> { | ||||
|                 if (File(path).mkdirs()) { | ||||
|                     success(alertDialog) | ||||
|                 } | ||||
|             } | ||||
|             else -> { | ||||
|                 RootHelpers(activity).createFileFolder(path, false) { | ||||
|                     if (it) { | ||||
| @@ -85,6 +102,22 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca | ||||
|     private fun createFile(path: String, alertDialog: AlertDialog, callback: (Boolean) -> Unit) { | ||||
|         try { | ||||
|             when { | ||||
|                 activity.isRestrictedSAFOnlyRoot(path) -> { | ||||
|                     activity.handleAndroidSAFDialog(path) { | ||||
|                         if (!it) { | ||||
|                             callback(false) | ||||
|                             return@handleAndroidSAFDialog | ||||
|                         } | ||||
|                         if (activity.createAndroidSAFFile(path)) { | ||||
|                             success(alertDialog) | ||||
|                         } else { | ||||
|                             val error = String.format(activity.getString(R.string.could_not_create_file), path) | ||||
|                             activity.showErrorToast(error) | ||||
|                             callback(false) | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 activity.needsStupidWritePermissions(path) -> { | ||||
|                     activity.handleSAFDialog(path) { | ||||
|                         if (!it) { | ||||
| @@ -102,7 +135,8 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca | ||||
|                         success(alertDialog) | ||||
|                     } | ||||
|                 } | ||||
|                 path.startsWith(activity.internalStoragePath, true) -> { | ||||
|  | ||||
|                 isRPlus() || path.startsWith(activity.internalStoragePath, true) -> { | ||||
|                     if (File(path).createNewFile()) { | ||||
|                         success(alertDialog) | ||||
|                     } | ||||
|   | ||||
| @@ -23,10 +23,10 @@ import com.simplemobiletools.filemanager.pro.helpers.MAX_COLUMN_COUNT | ||||
| import com.simplemobiletools.filemanager.pro.helpers.RootHelpers | ||||
| import com.simplemobiletools.filemanager.pro.interfaces.ItemOperationsListener | ||||
| import com.simplemobiletools.filemanager.pro.models.ListItem | ||||
| import kotlinx.android.synthetic.main.items_fragment.view.* | ||||
| import java.io.File | ||||
| import java.util.* | ||||
| import kotlin.collections.ArrayList | ||||
| import kotlinx.android.synthetic.main.items_fragment.view.* | ||||
|  | ||||
| class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), ItemOperationsListener, | ||||
|     Breadcrumbs.BreadcrumbsListener { | ||||
| @@ -162,7 +162,18 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF | ||||
|         ensureBackgroundThread { | ||||
|             if (activity?.isDestroyed == false && activity?.isFinishing == false) { | ||||
|                 val config = context!!.config | ||||
|                 if (context!!.isPathOnOTG(path) && config.OTGTreeUri.isNotEmpty()) { | ||||
|                 if (context.isRestrictedSAFOnlyRoot(path)) { | ||||
|                     activity?.handleAndroidSAFDialog(path) { | ||||
|                         if (!it) { | ||||
|                             activity?.toast(R.string.no_storage_permissions) | ||||
|                             return@handleAndroidSAFDialog | ||||
|                         } | ||||
|                         val getProperChildCount = context!!.config.getFolderViewType(currentPath) == VIEW_TYPE_LIST | ||||
|                         context.getAndroidSAFFileItems(path, context.config.shouldShowHidden, getProperChildCount) { fileItems -> | ||||
|                             callback(path, getListItemsFromFileDirItems(fileItems)) | ||||
|                         } | ||||
|                     } | ||||
|                 } else if (context!!.isPathOnOTG(path) && config.OTGTreeUri.isNotEmpty()) { | ||||
|                     val getProperFileSize = context!!.config.getFolderSorting(currentPath) and SORT_BY_SIZE != 0 | ||||
|                     context!!.getOTGItems(path, config.shouldShowHidden, getProperFileSize) { | ||||
|                         callback(path, getListItemsFromFileDirItems(it)) | ||||
| @@ -201,7 +212,7 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF | ||||
|         if (getProperChildCount) { | ||||
|             items.filter { it.mIsDirectory }.forEach { | ||||
|                 if (context != null) { | ||||
|                     val childrenCount = it.getDirectChildrenCount(context!!, showHidden) | ||||
|                     val childrenCount = it.getDirectChildrenCount(activity as BaseSimpleActivity, showHidden) | ||||
|                     if (childrenCount != 0) { | ||||
|                         activity?.runOnUiThread { | ||||
|                             getRecyclerAdapter()?.updateChildCount(it.mPath, childrenCount) | ||||
|   | ||||
| @@ -1,14 +1,14 @@ | ||||
| package com.simplemobiletools.filemanager.pro.fragments | ||||
|  | ||||
| import android.content.ContentResolver | ||||
| import android.content.Context | ||||
| import android.provider.MediaStore.Files | ||||
| import android.provider.MediaStore.Files.FileColumns | ||||
| import android.util.AttributeSet | ||||
| import androidx.core.os.bundleOf | ||||
| import androidx.recyclerview.widget.GridLayoutManager | ||||
| import com.simplemobiletools.commons.extensions.* | ||||
| import com.simplemobiletools.commons.helpers.VIEW_TYPE_GRID | ||||
| import com.simplemobiletools.commons.helpers.VIEW_TYPE_LIST | ||||
| import com.simplemobiletools.commons.helpers.ensureBackgroundThread | ||||
| import com.simplemobiletools.commons.helpers.* | ||||
| import com.simplemobiletools.commons.models.FileDirItem | ||||
| import com.simplemobiletools.commons.views.MyGridLayoutManager | ||||
| import com.simplemobiletools.filemanager.pro.R | ||||
| @@ -24,6 +24,8 @@ import kotlinx.android.synthetic.main.recents_fragment.view.* | ||||
| import java.util.* | ||||
|  | ||||
| class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), ItemOperationsListener { | ||||
|     private val RECENTS_LIMIT = 50 | ||||
|  | ||||
|     override fun setupFragment(activity: SimpleActivity) { | ||||
|         if (this.activity == null) { | ||||
|             this.activity = activity | ||||
| @@ -120,17 +122,33 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage | ||||
|             FileColumns.SIZE | ||||
|         ) | ||||
|  | ||||
|         val sortOrder = "${FileColumns.DATE_MODIFIED} DESC LIMIT 50" | ||||
|  | ||||
|         context?.queryCursor(uri, projection, sortOrder = sortOrder, showErrors = true) { cursor -> | ||||
|             val path = cursor.getStringValue(FileColumns.DATA) | ||||
|             val name = cursor.getStringValue(FileColumns.DISPLAY_NAME) ?: path.getFilenameFromPath() | ||||
|             val size = cursor.getLongValue(FileColumns.SIZE) | ||||
|             val modified = cursor.getLongValue(FileColumns.DATE_MODIFIED) * 1000 | ||||
|             val fileDirItem = ListItem(path, name, false, 0, size, modified, false) | ||||
|             if ((showHidden || !name.startsWith(".")) && activity?.getDoesFilePathExist(path) == true) { | ||||
|                 listItems.add(fileDirItem) | ||||
|         try { | ||||
|             if (isOreoPlus()) { | ||||
|                 val queryArgs = bundleOf( | ||||
|                     ContentResolver.QUERY_ARG_LIMIT to RECENTS_LIMIT, | ||||
|                     ContentResolver.QUERY_ARG_SORT_COLUMNS to arrayOf(FileColumns.DATE_MODIFIED), | ||||
|                     ContentResolver.QUERY_ARG_SORT_DIRECTION to ContentResolver.QUERY_SORT_DIRECTION_DESCENDING | ||||
|                 ) | ||||
|                 context?.contentResolver?.query(uri, projection, queryArgs, null) | ||||
|             } else { | ||||
|                 val sortOrder = "${FileColumns.DATE_MODIFIED} DESC LIMIT $RECENTS_LIMIT" | ||||
|                 context?.contentResolver?.query(uri, projection, null, null, sortOrder) | ||||
|             }?.use { cursor -> | ||||
|                 if (cursor.moveToFirst()) { | ||||
|                     do { | ||||
|                         val path = cursor.getStringValue(FileColumns.DATA) | ||||
|                         val name = cursor.getStringValue(FileColumns.DISPLAY_NAME) ?: path.getFilenameFromPath() | ||||
|                         val size = cursor.getLongValue(FileColumns.SIZE) | ||||
|                         val modified = cursor.getLongValue(FileColumns.DATE_MODIFIED) * 1000 | ||||
|                         val fileDirItem = ListItem(path, name, false, 0, size, modified, false) | ||||
|                         if ((showHidden || !name.startsWith(".")) && activity?.getDoesFilePathExist(path) == true) { | ||||
|                             listItems.add(fileDirItem) | ||||
|                         } | ||||
|                     } while (cursor.moveToNext()) | ||||
|                 } | ||||
|             } | ||||
|         } catch (e: Exception) { | ||||
|             activity?.showErrorToast(e) | ||||
|         } | ||||
|  | ||||
|         activity?.runOnUiThread { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user