Merge pull request #520 from KryptKode/feat/scoped-storage

Feat/scoped storage
This commit is contained in:
Tibor Kaputa 2021-11-22 11:39:01 +01:00 committed by GitHub
commit ba186c0d7f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 350 additions and 172 deletions

View File

@ -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'

View File

@ -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"

View File

@ -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

View File

@ -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)
}

View File

@ -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)
}

View File

@ -58,13 +58,18 @@ class ItemsFragment(context: Context, attributeSet: AttributeSet) : MyViewPagerF
initDrawables()
}
breadcrumbs.updateColor(textColor)
items_fastscroller.updateBubbleColors()
if (currentPath != "") {
breadcrumbs.updateColor(textColor)
}
}
override fun setupFontSize() {
getRecyclerAdapter()?.updateFontSizes()
breadcrumbs.updateFontSize(context!!.getTextSize())
if (currentPath != "") {
breadcrumbs.updateFontSize(context!!.getTextSize())
}
}
override fun setupDateTimeFormat() {
@ -162,7 +167,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 +217,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)

View File

@ -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 {