diff --git a/app/build.gradle b/app/build.gradle index a94e34c7..71592065 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 83eadc0a..728a26c4 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + 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 diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt index 67f3bc68..e5aaf931 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/adapters/ItemsAdapter.kt @@ -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) { 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, callback: (success: Boolean) -> Unit) { - sourcePaths.forEach { - try { - val zipFile = ZipFile(it) - val entries = zipFile.entries() - val fileDirItems = ArrayList() - 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() + 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, conflictResolutions: LinkedHashMap, 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, targetPath: String): Boolean { - val queue = LinkedList() + val queue = LinkedList() 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) } diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/dialogs/CreateNewItemDialog.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/dialogs/CreateNewItemDialog.kt index 2131dd0d..86fb1370 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/dialogs/CreateNewItemDialog.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/dialogs/CreateNewItemDialog.kt @@ -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) } diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/ItemsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/ItemsFragment.kt index 4f766373..9917bf05 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/ItemsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/ItemsFragment.kt @@ -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) diff --git a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/RecentsFragment.kt b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/RecentsFragment.kt index 297b6975..49438aa6 100644 --- a/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/RecentsFragment.kt +++ b/app/src/main/kotlin/com/simplemobiletools/filemanager/pro/fragments/RecentsFragment.kt @@ -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 {