Merge branch 'feat/scoped-storage' of https://github.com/KryptKode/Simple-File-Manager into KryptKode-feat/scoped-storage

This commit is contained in:
tibbi 2021-11-21 23:10:08 +01:00
commit 2ba6ac5f35
7 changed files with 344 additions and 171 deletions

View File

@ -9,13 +9,12 @@ if (keystorePropertiesFile.exists()) {
} }
android { android {
compileSdkVersion 29 compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig { defaultConfig {
applicationId "com.simplemobiletools.filemanager.pro" applicationId "com.simplemobiletools.filemanager.pro"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 30
versionCode 110 versionCode 110
versionName "6.10.1" versionName "6.10.1"
multiDexEnabled true multiDexEnabled true
@ -58,7 +57,7 @@ android {
} }
dependencies { 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:RootTools:df729dcb13'
implementation 'com.github.Stericson:RootShell:1.6' implementation 'com.github.Stericson:RootShell:1.6'
implementation 'com.alexvasilkov:gesture-views:2.5.2' implementation 'com.alexvasilkov:gesture-views:2.5.2'

View File

@ -6,6 +6,7 @@
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE"/>
<uses-feature <uses-feature
android:name="android.hardware.faketouch" android:name="android.hardware.faketouch"

View File

@ -1,5 +1,6 @@
package com.simplemobiletools.filemanager.pro.activities package com.simplemobiletools.filemanager.pro.activities
import android.annotation.SuppressLint
import android.app.Activity import android.app.Activity
import android.app.SearchManager import android.app.SearchManager
import android.content.ClipData import android.content.ClipData
@ -8,13 +9,18 @@ import android.content.Intent
import android.graphics.drawable.ColorDrawable import android.graphics.drawable.ColorDrawable
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.media.RingtoneManager import android.media.RingtoneManager
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.os.Handler import android.os.Handler
import android.provider.Settings
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.core.view.MenuItemCompat import androidx.core.view.MenuItemCompat
import androidx.viewpager.widget.ViewPager 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.dialogs.RadioGroupDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.* import com.simplemobiletools.commons.helpers.*
@ -46,6 +52,7 @@ import java.util.*
class MainActivity : SimpleActivity() { class MainActivity : SimpleActivity() {
private val BACK_PRESS_TIMEOUT = 5000 private val BACK_PRESS_TIMEOUT = 5000
private val MANAGE_STORAGE_RC = 201
private val PICKED_PATH = "picked_path" private val PICKED_PATH = "picked_path"
private var isSearchOpen = false private var isSearchOpen = false
private var wasBackJustPressed = false private var wasBackJustPressed = false
@ -282,8 +289,8 @@ class MainActivity : SimpleActivity() {
} }
private fun tryInitFileManager() { private fun tryInitFileManager() {
val hadPermission = hasPermission(PERMISSION_WRITE_STORAGE) val hadPermission = hasStoragePermission()
handlePermission(PERMISSION_WRITE_STORAGE) { handleStoragePermission {
checkOTGPath() checkOTGPath()
if (it) { if (it) {
if (main_view_pager.adapter == null) { 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) { private fun initFileManager(refreshRecents: Boolean) {
if (intent.action == Intent.ACTION_VIEW && intent.data != null) { if (intent.action == Intent.ACTION_VIEW && intent.data != null) {
val data = intent.data val data = intent.data

View File

@ -41,6 +41,7 @@ import com.simplemobiletools.filemanager.pro.helpers.*
import com.simplemobiletools.filemanager.pro.interfaces.ItemOperationsListener import com.simplemobiletools.filemanager.pro.interfaces.ItemOperationsListener
import com.simplemobiletools.filemanager.pro.models.ListItem import com.simplemobiletools.filemanager.pro.models.ListItem
import com.stericson.RootTools.RootTools 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_grid.view.*
import kotlinx.android.synthetic.main.item_file_dir_list.view.* import kotlinx.android.synthetic.main.item_file_dir_list.view.*
import kotlinx.android.synthetic.main.item_file_dir_list.view.item_frame 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 kotlinx.android.synthetic.main.item_section.view.*
import java.io.Closeable import java.io.Closeable
import java.io.File import java.io.File
import java.io.FileInputStream
import java.util.* import java.util.*
import java.util.zip.ZipEntry import java.util.zip.ZipEntry
import java.util.zip.ZipFile import java.util.zip.ZipInputStream
import java.util.zip.ZipOutputStream import java.util.zip.ZipOutputStream
class ItemsAdapter( class ItemsAdapter(
@ -329,15 +329,27 @@ class ItemsAdapter(
private fun addFileUris(path: String, paths: ArrayList<String>) { private fun addFileUris(path: String, paths: ArrayList<String>) {
if (activity.getIsPathDirectory(path)) { if (activity.getIsPathDirectory(path)) {
val shouldShowHidden = activity.config.shouldShowHidden val shouldShowHidden = activity.config.shouldShowHidden
if (activity.isPathOnOTG(path)) { when {
activity.isRestrictedSAFOnlyRoot(path) -> {
activity.getAndroidSAFFileItems(path, shouldShowHidden, false) { files ->
files.forEach {
addFileUris(activity.getAndroidSAFUri(it.path).toString(), paths)
}
}
}
activity.isPathOnOTG(path) -> {
activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") }?.forEach { activity.getDocumentFile(path)?.listFiles()?.filter { if (shouldShowHidden) true else !it.name!!.startsWith(".") }?.forEach {
addFileUris(it.uri.toString(), paths) addFileUris(it.uri.toString(), paths)
} }
} else { }
else -> {
File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') }?.forEach { File(path).listFiles()?.filter { if (shouldShowHidden) true else !it.name.startsWith('.') }?.forEach {
addFileUris(it.absolutePath, paths) addFileUris(it.absolutePath, paths)
} }
} }
}
} else { } else {
paths.add(path) paths.add(path)
} }
@ -390,7 +402,16 @@ class ItemsAdapter(
activity.copyMoveFilesTo(files, source, it, isCopyOperation, false, activity.config.shouldShowHidden) { activity.copyMoveFilesTo(files, source, it, isCopyOperation, false, activity.config.shouldShowHidden) {
if (!isCopyOperation) { if (!isCopyOperation) {
files.forEach { sourceFileDir -> files.forEach { sourceFileDir ->
val sourceFile = File(sourceFileDir.path) val sourcePath = sourceFileDir.path
if (activity.isRestrictedSAFOnlyRoot(sourcePath) && activity.getDoesFilePathExist(sourcePath)) {
activity.deleteFile(sourceFileDir, true) {
listener?.refreshFragment()
activity.runOnUiThread {
finishActMode()
}
}
} else {
val sourceFile = File(sourcePath)
if (activity.getDoesFilePathExist(source) && activity.getIsPathDirectory(source) && if (activity.getDoesFilePathExist(source) && activity.getIsPathDirectory(source) &&
sourceFile.list()?.isEmpty() == true && sourceFile.getProperSize(true) == 0L && sourceFile.getFileCount(true) == 0 sourceFile.list()?.isEmpty() == true && sourceFile.getProperSize(true) == 0L && sourceFile.getFileCount(true) == 0
) { ) {
@ -406,6 +427,7 @@ class ItemsAdapter(
finishActMode() finishActMode()
} }
} }
}
} else { } else {
listener?.refreshFragment() listener?.refreshFragment()
finishActMode() finishActMode()
@ -443,6 +465,10 @@ class ItemsAdapter(
CompressAsDialog(activity, firstPath) { CompressAsDialog(activity, firstPath) {
val destination = it val destination = it
activity.handleAndroidSAFDialog(firstPath) { granted ->
if (!granted) {
return@handleAndroidSAFDialog
}
activity.handleSAFDialog(firstPath) { activity.handleSAFDialog(firstPath) {
if (!it) { if (!it) {
return@handleSAFDialog return@handleSAFDialog
@ -464,6 +490,7 @@ class ItemsAdapter(
} }
} }
} }
}
private fun decompressSelection() { private fun decompressSelection() {
val firstPath = getFirstSelectedItemPath() val firstPath = getFirstSelectedItemPath()
@ -478,33 +505,36 @@ class ItemsAdapter(
} }
val paths = getSelectedFileDirItems().asSequence().map { it.path }.filter { it.isZipFile() }.toList() val paths = getSelectedFileDirItems().asSequence().map { it.path }.filter { it.isZipFile() }.toList()
tryDecompressingPaths(paths) { ensureBackgroundThread {
if (it) { tryDecompressingPaths(paths) { success ->
activity.toast(R.string.decompression_successful)
activity.runOnUiThread { activity.runOnUiThread {
if (success) {
activity.toast(R.string.decompression_successful)
listener?.refreshFragment() listener?.refreshFragment()
finishActMode() finishActMode()
}
} else { } else {
activity.toast(R.string.decompressing_failed) 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)
} }
private fun tryDecompressingPaths(sourcePaths: List<String>, callback: (success: Boolean) -> Unit) {
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('/') val destinationPath = fileDirItems.first().getParentPath().trimEnd('/')
activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) { activity.checkConflicts(fileDirItems, destinationPath, 0, LinkedHashMap()) {
ensureBackgroundThread { ensureBackgroundThread {
@ -516,27 +546,28 @@ class ItemsAdapter(
} }
} }
} }
}
private fun decompressPaths(paths: List<String>, conflictResolutions: LinkedHashMap<String, Int>, callback: (success: Boolean) -> Unit) { private fun decompressPaths(paths: List<String>, conflictResolutions: LinkedHashMap<String, Int>, callback: (success: Boolean) -> Unit) {
paths.forEach { paths.forEach { path ->
val zipInputStream = ZipInputStream(BufferedInputStream(activity.getFileInputStreamSync(path)))
zipInputStream.use {
try { try {
val zipFile = ZipFile(it) var entry = zipInputStream.nextEntry
val entries = zipFile.entries() val zipFileName = path.getFilenameFromPath()
val zipFileName = it.getFilenameFromPath()
val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4) val newFolderName = zipFileName.subSequence(0, zipFileName.length - 4)
while (entries.hasMoreElements()) { while (entry != null) {
val entry = entries.nextElement() val parentPath = path.getParentPath()
val parentPath = it.getParentPath()
val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}" val newPath = "$parentPath/$newFolderName/${entry.name.trimEnd('/')}"
val resolution = getConflictResolution(conflictResolutions, newPath) val resolution = getConflictResolution(conflictResolutions, newPath)
val doesPathExist = activity.getDoesFilePathExist(newPath) val doesPathExist = activity.getDoesFilePathExist(newPath)
if (doesPathExist && resolution == CONFLICT_OVERWRITE) { if (doesPathExist && resolution == CONFLICT_OVERWRITE) {
val fileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), entry.isDirectory) val fileDirItem = FileDirItem(newPath, newPath.getFilenameFromPath(), entry.isDirectory)
if (activity.getIsPathDirectory(it)) { if (activity.getIsPathDirectory(path)) {
activity.deleteFolderBg(fileDirItem, false) { activity.deleteFolderBg(fileDirItem, false) {
if (it) { if (it) {
extractEntry(newPath, entry, zipFile) extractEntry(newPath, entry, zipInputStream)
} else { } else {
callback(false) callback(false)
} }
@ -544,15 +575,18 @@ class ItemsAdapter(
} else { } else {
activity.deleteFileBg(fileDirItem, false) { activity.deleteFileBg(fileDirItem, false) {
if (it) { if (it) {
extractEntry(newPath, entry, zipFile) extractEntry(newPath, entry, zipInputStream)
} else { } else {
callback(false) callback(false)
} }
} }
} }
} else if (!doesPathExist) { } else if (!doesPathExist) {
extractEntry(newPath, entry, zipFile) extractEntry(newPath, entry, zipInputStream)
} }
zipInputStream.closeEntry()
entry = zipInputStream.nextEntry
} }
callback(true) callback(true)
} catch (e: Exception) { } catch (e: Exception) {
@ -561,20 +595,18 @@ class ItemsAdapter(
} }
} }
} }
}
private fun extractEntry(newPath: String, entry: ZipEntry, zipFile: ZipFile) { private fun extractEntry(newPath: String, entry: ZipEntry, zipInputStream: ZipInputStream) {
if (entry.isDirectory) { if (entry.isDirectory) {
if (!activity.createDirectorySync(newPath) && !activity.getDoesFilePathExist(newPath)) { if (!activity.createDirectorySync(newPath) && !activity.getDoesFilePathExist(newPath)) {
val error = String.format(activity.getString(R.string.could_not_create_file), newPath) val error = String.format(activity.getString(R.string.could_not_create_file), newPath)
activity.showErrorToast(error) activity.showErrorToast(error)
} }
} else { } else {
val ins = zipFile.getInputStream(entry)
ins.use {
val fos = activity.getFileOutputStreamSync(newPath, newPath.getMimeType()) val fos = activity.getFileOutputStreamSync(newPath, newPath.getMimeType())
if (fos != null) { if (fos != null) {
ins.copyTo(fos) zipInputStream.copyTo(fos)
}
} }
} }
} }
@ -590,43 +622,62 @@ class ItemsAdapter(
} }
private fun compressPaths(sourcePaths: List<String>, targetPath: String): Boolean { 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 fos = activity.getFileOutputStreamSync(targetPath, "application/zip") ?: return false
val zout = ZipOutputStream(fos) val zout = ZipOutputStream(fos)
var res: Closeable = fos var res: Closeable = fos
try { try {
sourcePaths.forEach { sourcePaths.forEach { currentPath ->
var name: String var name: String
var mainFile = File(it) var mainFilePath = currentPath
val base = mainFile.parentFile.toURI() val base = "${mainFilePath.getParentPath()}/"
res = zout res = zout
queue.push(mainFile) queue.push(mainFilePath)
if (activity.getIsPathDirectory(mainFile.absolutePath)) { if (activity.getIsPathDirectory(mainFilePath)) {
name = "${mainFile.name.trimEnd('/')}/" name = "${mainFilePath.getFilenameFromPath()}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(ZipEntry(name))
} }
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
mainFile = queue.pop() mainFilePath = queue.pop()
if (activity.getIsPathDirectory(mainFile.absolutePath)) { if (activity.getIsPathDirectory(mainFilePath)) {
for (file in mainFile.listFiles()) { if (activity.isRestrictedSAFOnlyRoot(mainFilePath)) {
name = base.relativize(file.toURI()).path activity.getAndroidSAFFileItems(mainFilePath, true) { files ->
if (activity.getIsPathDirectory(file.absolutePath)) { for (file in files) {
queue.push(file) name = file.path.relativizeWith(base)
if (activity.getIsPathDirectory(file.path)) {
queue.push(file.path)
name = "${name.trimEnd('/')}/" name = "${name.trimEnd('/')}/"
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(ZipEntry(name))
} else { } else {
zout.putNextEntry(ZipEntry(name)) zout.putNextEntry(ZipEntry(name))
FileInputStream(file).copyTo(zout) activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }
} }
}
} else { } else {
name = if (base.path == it) it.getFilenameFromPath() else base.relativize(mainFile.toURI()).path 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)) zout.putNextEntry(ZipEntry(name))
FileInputStream(mainFile).copyTo(zout) } else {
zout.putNextEntry(ZipEntry(name))
activity.getFileInputStreamSync(file.path)!!.copyTo(zout)
zout.closeEntry()
}
}
}
} else {
name = if (base == currentPath) currentPath.getFilenameFromPath() else mainFilePath.relativizeWith(base)
zout.putNextEntry(ZipEntry(name))
activity.getFileInputStreamSync(mainFilePath)!!.copyTo(zout)
zout.closeEntry() zout.closeEntry()
} }
} }
@ -833,7 +884,9 @@ class ItemsAdapter(
path 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) itemToLoad = getOTGPublicPath(itemToLoad)
} }

View File

@ -3,6 +3,7 @@ package com.simplemobiletools.filemanager.pro.dialogs
import android.view.View import android.view.View
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.isRPlus
import com.simplemobiletools.filemanager.pro.R import com.simplemobiletools.filemanager.pro.R
import com.simplemobiletools.filemanager.pro.activities.SimpleActivity import com.simplemobiletools.filemanager.pro.activities.SimpleActivity
import com.simplemobiletools.filemanager.pro.helpers.RootHelpers import com.simplemobiletools.filemanager.pro.helpers.RootHelpers
@ -50,6 +51,27 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca
private fun createDirectory(path: String, alertDialog: AlertDialog, callback: (Boolean) -> Unit) { private fun createDirectory(path: String, alertDialog: AlertDialog, callback: (Boolean) -> Unit) {
when { 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) { activity.needsStupidWritePermissions(path) -> activity.handleSAFDialog(path) {
if (!it) { if (!it) {
return@handleSAFDialog return@handleSAFDialog
@ -65,11 +87,6 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca
documentFile.createDirectory(path.getFilenameFromPath()) documentFile.createDirectory(path.getFilenameFromPath())
success(alertDialog) success(alertDialog)
} }
path.startsWith(activity.internalStoragePath, true) -> {
if (File(path).mkdirs()) {
success(alertDialog)
}
}
else -> { else -> {
RootHelpers(activity).createFileFolder(path, false) { RootHelpers(activity).createFileFolder(path, false) {
if (it) { 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) { private fun createFile(path: String, alertDialog: AlertDialog, callback: (Boolean) -> Unit) {
try { try {
when { 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.needsStupidWritePermissions(path) -> {
activity.handleSAFDialog(path) { activity.handleSAFDialog(path) {
if (!it) { if (!it) {
@ -102,7 +135,8 @@ class CreateNewItemDialog(val activity: SimpleActivity, val path: String, val ca
success(alertDialog) success(alertDialog)
} }
} }
path.startsWith(activity.internalStoragePath, true) -> {
isRPlus() || path.startsWith(activity.internalStoragePath, true) -> {
if (File(path).createNewFile()) { if (File(path).createNewFile()) {
success(alertDialog) success(alertDialog)
} }

View File

@ -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.helpers.RootHelpers
import com.simplemobiletools.filemanager.pro.interfaces.ItemOperationsListener import com.simplemobiletools.filemanager.pro.interfaces.ItemOperationsListener
import com.simplemobiletools.filemanager.pro.models.ListItem import com.simplemobiletools.filemanager.pro.models.ListItem
import kotlinx.android.synthetic.main.items_fragment.view.*
import java.io.File import java.io.File
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
import kotlinx.android.synthetic.main.items_fragment.view.*
class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), ItemOperationsListener, class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), ItemOperationsListener,
Breadcrumbs.BreadcrumbsListener { Breadcrumbs.BreadcrumbsListener {
@ -162,7 +162,18 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF
ensureBackgroundThread { ensureBackgroundThread {
if (activity?.isDestroyed == false && activity?.isFinishing == false) { if (activity?.isDestroyed == false && activity?.isFinishing == false) {
val config = context!!.config 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 val getProperFileSize = context!!.config.getFolderSorting(currentPath) and SORT_BY_SIZE != 0
context!!.getOTGItems(path, config.shouldShowHidden, getProperFileSize) { context!!.getOTGItems(path, config.shouldShowHidden, getProperFileSize) {
callback(path, getListItemsFromFileDirItems(it)) callback(path, getListItemsFromFileDirItems(it))
@ -201,7 +212,7 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF
if (getProperChildCount) { if (getProperChildCount) {
items.filter { it.mIsDirectory }.forEach { items.filter { it.mIsDirectory }.forEach {
if (context != null) { if (context != null) {
val childrenCount = it.getDirectChildrenCount(context!!, showHidden) val childrenCount = it.getDirectChildrenCount(activity as BaseSimpleActivity, showHidden)
if (childrenCount != 0) { if (childrenCount != 0) {
activity?.runOnUiThread { activity?.runOnUiThread {
getRecyclerAdapter()?.updateChildCount(it.mPath, childrenCount) getRecyclerAdapter()?.updateChildCount(it.mPath, childrenCount)

View File

@ -1,14 +1,14 @@
package com.simplemobiletools.filemanager.pro.fragments package com.simplemobiletools.filemanager.pro.fragments
import android.content.ContentResolver
import android.content.Context import android.content.Context
import android.provider.MediaStore.Files import android.provider.MediaStore.Files
import android.provider.MediaStore.Files.FileColumns import android.provider.MediaStore.Files.FileColumns
import android.util.AttributeSet import android.util.AttributeSet
import androidx.core.os.bundleOf
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import com.simplemobiletools.commons.extensions.* import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.VIEW_TYPE_GRID import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.helpers.VIEW_TYPE_LIST
import com.simplemobiletools.commons.helpers.ensureBackgroundThread
import com.simplemobiletools.commons.models.FileDirItem import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.views.MyGridLayoutManager import com.simplemobiletools.commons.views.MyGridLayoutManager
import com.simplemobiletools.filemanager.pro.R import com.simplemobiletools.filemanager.pro.R
@ -24,6 +24,8 @@ import kotlinx.android.synthetic.main.recents_fragment.view.*
import java.util.* import java.util.*
class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), ItemOperationsListener { class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerFragment(context, attributeSet), ItemOperationsListener {
private val RECENTS_LIMIT = 50
override fun setupFragment(activity: SimpleActivity) { override fun setupFragment(activity: SimpleActivity) {
if (this.activity == null) { if (this.activity == null) {
this.activity = activity this.activity = activity
@ -120,9 +122,20 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage
FileColumns.SIZE FileColumns.SIZE
) )
val sortOrder = "${FileColumns.DATE_MODIFIED} DESC LIMIT 50" try {
if (isOreoPlus()) {
context?.queryCursor(uri, projection, sortOrder = sortOrder, showErrors = true) { cursor -> 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 path = cursor.getStringValue(FileColumns.DATA)
val name = cursor.getStringValue(FileColumns.DISPLAY_NAME) ?: path.getFilenameFromPath() val name = cursor.getStringValue(FileColumns.DISPLAY_NAME) ?: path.getFilenameFromPath()
val size = cursor.getLongValue(FileColumns.SIZE) val size = cursor.getLongValue(FileColumns.SIZE)
@ -131,6 +144,11 @@ class RecentsFragment(context: Context, attributeSet: AttributeSet) : MyViewPage
if ((showHidden || !name.startsWith(".")) && activity?.getDoesFilePathExist(path) == true) { if ((showHidden || !name.startsWith(".")) && activity?.getDoesFilePathExist(path) == true) {
listItems.add(fileDirItem) listItems.add(fileDirItem)
} }
} while (cursor.moveToNext())
}
}
} catch (e: Exception) {
activity?.showErrorToast(e)
} }
activity?.runOnUiThread { activity?.runOnUiThread {