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 {