forked from hardcoresushi/DroidFS
"Copy" feature, FAB icons & GitHub link
This commit is contained in:
parent
34d7f19927
commit
5a444f2829
@ -26,7 +26,6 @@ android {
|
||||
release {
|
||||
minifyEnabled true
|
||||
shrinkResources true
|
||||
useProguard true
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
@ -120,7 +120,9 @@ func openBackingDir(sessionID int, relPath string) (dirfd int, cName string, err
|
||||
if i == len(parts)-1 {
|
||||
cache_dirfd, err := syscall.Dup(dirfd)
|
||||
if err == nil {
|
||||
sessions[sessionID].dirCache[dirRelPath] = Directory{cache_dirfd, iv}
|
||||
var dirRelPathCopy strings.Builder
|
||||
dirRelPathCopy.WriteString(dirRelPath)
|
||||
sessions[sessionID].dirCache[dirRelPathCopy.String()] = Directory{cache_dirfd, iv}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<application
|
||||
android:name=".ColoredApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:icon="@mipmap/icon_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme"
|
||||
@ -28,6 +28,7 @@
|
||||
<activity android:name=".explorers.ExplorerActivityDrop" />
|
||||
<activity
|
||||
android:name=".OpenActivity"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:screenOrientation="nosensor"
|
||||
android:windowSoftInputMode="adjustPan">
|
||||
<intent-filter android:label="@string/share_menu_label">
|
||||
@ -39,9 +40,11 @@
|
||||
</activity>
|
||||
<activity
|
||||
android:name=".CreateActivity"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:screenOrientation="nosensor" />
|
||||
<activity
|
||||
android:name=".ChangePasswordActivity"
|
||||
android:parentActivityName=".MainActivity"
|
||||
android:screenOrientation="nosensor"
|
||||
android:windowSoftInputMode="adjustPan" />
|
||||
<activity
|
||||
|
@ -33,6 +33,7 @@ class ChangePasswordActivity : BaseActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_change_password)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
|
||||
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)
|
||||
|
@ -9,6 +9,7 @@ class ConstValues {
|
||||
const val saved_volumes_key = "saved_volumes"
|
||||
const val sort_order_key = "sort_order"
|
||||
val fakeUri: Uri = Uri.parse("fakeuri://droidfs")
|
||||
const val MAX_KERNEL_WRITE = 128*1024
|
||||
const val wipe_passes = 2
|
||||
private val fileExtensions = mapOf(
|
||||
Pair("image", listOf("png", "jpg", "jpeg")),
|
||||
|
@ -31,6 +31,7 @@ class CreateActivity : BaseActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_create)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
|
||||
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)
|
||||
|
@ -38,6 +38,7 @@ class OpenActivity : BaseActivity() {
|
||||
super.onCreate(savedInstanceState)
|
||||
setContentView(R.layout.activity_open)
|
||||
setSupportActionBar(toolbar)
|
||||
supportActionBar?.setDisplayHomeAsUpEnabled(true)
|
||||
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
|
||||
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)
|
||||
|
@ -26,7 +26,6 @@ class SettingsActivity : BaseActivity(), PreferenceFragmentCompat.OnPreferenceSt
|
||||
.beginTransaction()
|
||||
.replace(R.id.settings, fragment)
|
||||
.commit()
|
||||
setTheme(R.style.AppTheme)
|
||||
}
|
||||
|
||||
class SettingsFragment : PreferenceFragmentCompat() {
|
||||
|
@ -63,11 +63,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
list_explorer.adapter = explorerAdapter
|
||||
list_explorer.onItemClickListener = OnItemClickListener { _, _, position, _ -> onExplorerItemClick(position) }
|
||||
list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ ->
|
||||
explorerAdapter.onItemLongClick(position)
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ -> onExplorerItemLongClick(position); true }
|
||||
refresher.setOnRefreshListener {
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
refresher.isRefreshing = false
|
||||
@ -90,13 +86,13 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
explorerAdapter.onItemClick(position)
|
||||
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||
if (!wasSelecting) {
|
||||
val fullPath = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name)
|
||||
val fullPath = explorerElements[position].getFullPath()
|
||||
when {
|
||||
explorerElements[position].isDirectory -> {
|
||||
setCurrentPath(fullPath)
|
||||
}
|
||||
explorerElements[position].isParentFolder -> {
|
||||
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||
setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
|
||||
}
|
||||
isImage(fullPath) -> {
|
||||
startFileViewer(ImageViewer::class.java, fullPath)
|
||||
@ -128,8 +124,14 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
.show()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun onExplorerItemLongClick(position: Int) {
|
||||
explorerAdapter.onItemLongClick(position)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
@ -169,7 +171,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
text_dir_empty.visibility = if (explorerElements.size == 0) View.VISIBLE else View.INVISIBLE
|
||||
sortExplorerElements()
|
||||
if (path.isNotEmpty()) { //not root
|
||||
explorerElements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1))
|
||||
explorerElements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1, currentDirectoryPath))
|
||||
}
|
||||
explorerAdapter.setExplorerElements(explorerElements)
|
||||
currentDirectoryPath = path
|
||||
@ -192,16 +194,16 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
|
||||
protected open fun closeVolumeOnDestroy() {
|
||||
gocryptfsVolume.close()
|
||||
RestrictedFileProvider.wipeAll() //additional security
|
||||
RestrictedFileProvider.wipeAll(this) //additional security
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||
val parentPath = PathUtils.get_parent_path(currentDirectoryPath)
|
||||
val parentPath = PathUtils.getParentPath(currentDirectoryPath)
|
||||
if (parentPath == currentDirectoryPath) {
|
||||
askCloseVolume()
|
||||
} else {
|
||||
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||
setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
|
||||
}
|
||||
} else {
|
||||
unselectAll()
|
||||
@ -266,23 +268,21 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
fun handleMenuItems(menu: Menu){
|
||||
menu.findItem(R.id.explorer_menu_rename).isVisible = false
|
||||
menu.findItem(R.id.rename).isVisible = false
|
||||
if (usf_open){
|
||||
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = false
|
||||
menu.findItem(R.id.external_open)?.isVisible = false
|
||||
}
|
||||
val selectedItems = explorerAdapter.selectedItems
|
||||
if (selectedItems.isEmpty()){
|
||||
val noItemSelected = explorerAdapter.selectedItems.isEmpty()
|
||||
menu.findItem(R.id.sort).isVisible = noItemSelected
|
||||
menu.findItem(R.id.close).isVisible = noItemSelected
|
||||
if (noItemSelected){
|
||||
toolbar.navigationIcon = null
|
||||
menu.findItem(R.id.explorer_menu_close).isVisible = true
|
||||
menu.findItem(R.id.explorer_menu_sort).isVisible = true
|
||||
} else {
|
||||
toolbar.setNavigationIcon(R.drawable.icon_arrow_back)
|
||||
menu.findItem(R.id.explorer_menu_close).isVisible = false
|
||||
menu.findItem(R.id.explorer_menu_sort).isVisible = false
|
||||
if (selectedItems.size == 1) {
|
||||
menu.findItem(R.id.explorer_menu_rename).isVisible = true
|
||||
if (usf_open && explorerElements[selectedItems[0]].isRegularFile) {
|
||||
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = true
|
||||
if (explorerAdapter.selectedItems.size == 1) {
|
||||
menu.findItem(R.id.rename).isVisible = true
|
||||
if (usf_open && explorerElements[explorerAdapter.selectedItems[0]].isRegularFile) {
|
||||
menu.findItem(R.id.external_open)?.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -294,7 +294,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
unselectAll()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_sort -> {
|
||||
R.id.sort -> {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.sort_order)
|
||||
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, sortModesEntries), currentSortModeIndex) { dialog, which ->
|
||||
@ -306,7 +306,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
.show()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_rename -> {
|
||||
R.id.rename -> {
|
||||
val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null)
|
||||
val oldName = explorerElements[explorerAdapter.selectedItems[0]].name
|
||||
val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text)
|
||||
@ -331,14 +331,14 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
dialog.show()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_external_open -> {
|
||||
R.id.external_open -> {
|
||||
if (usf_open){
|
||||
ExternalProvider.open(this, gocryptfsVolume, PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name))
|
||||
unselectAll()
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_close -> {
|
||||
R.id.close -> {
|
||||
askCloseVolume()
|
||||
true
|
||||
}
|
||||
|
@ -8,9 +8,9 @@ import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import com.github.clans.fab.FloatingActionButton
|
||||
import com.github.clans.fab.FloatingActionMenu
|
||||
import kotlinx.android.synthetic.main.activity_explorer.*
|
||||
import sushi.hardcore.droidfs.OpenActivity
|
||||
@ -18,7 +18,7 @@ import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.util.*
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
class ExplorerActivity : BaseExplorerActivity() {
|
||||
private val PICK_DIRECTORY_REQUEST_CODE = 1
|
||||
@ -26,12 +26,20 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
private val PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE = 3
|
||||
private var usf_decrypt = false
|
||||
private var usf_share = false
|
||||
private var modeSelectLocation = false
|
||||
private val filesToCopy = ArrayList<ExplorerElement>()
|
||||
override fun init() {
|
||||
setContentView(R.layout.activity_explorer)
|
||||
usf_decrypt = sharedPrefs.getBoolean("usf_decrypt", false)
|
||||
usf_share = sharedPrefs.getBoolean("usf_share", false)
|
||||
}
|
||||
|
||||
override fun onExplorerItemLongClick(position: Int) {
|
||||
cancelCopy()
|
||||
explorerAdapter.onItemLongClick(position)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
|
||||
private fun createNewFile(fileName: String){
|
||||
if (fileName.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
@ -230,7 +238,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
failedItem = if (types[i] == 0) { //directory
|
||||
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
|
||||
} else {
|
||||
if (importFileFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)) null else paths[i]
|
||||
if (importFileFromOtherVolume(remoteGocryptfsVolume, paths[i], PathUtils.path_join(currentDirectoryPath, File(paths[i]).name))) null else paths[i]
|
||||
}
|
||||
if (failedItem != null) {
|
||||
break
|
||||
@ -238,7 +246,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
failedItem = if (importFileFromOtherVolume(remoteGocryptfsVolume, path, currentDirectoryPath)) null else path
|
||||
failedItem = if (importFileFromOtherVolume(remoteGocryptfsVolume, path, PathUtils.path_join(currentDirectoryPath, File(path).name))) null else path
|
||||
}
|
||||
if (failedItem == null) {
|
||||
stopTask {
|
||||
@ -269,24 +277,30 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.explorer, menu)
|
||||
handleMenuItems(menu)
|
||||
if (usf_share){
|
||||
menu.findItem(R.id.explorer_menu_share).isVisible = false
|
||||
}
|
||||
val anyItemSelected = explorerAdapter.selectedItems.isNotEmpty()
|
||||
menu.findItem(R.id.explorer_menu_select_all).isVisible = anyItemSelected
|
||||
menu.findItem(R.id.explorer_menu_delete).isVisible = anyItemSelected
|
||||
menu.findItem(R.id.explorer_menu_decrypt).isVisible = anyItemSelected && usf_decrypt
|
||||
if (anyItemSelected && usf_share){
|
||||
var containsDir = false
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
if (explorerElements[i].isDirectory) {
|
||||
containsDir = true
|
||||
break
|
||||
}
|
||||
if (modeSelectLocation) {
|
||||
menu.findItem(R.id.validate).isVisible = true
|
||||
menu.findItem(R.id.close).isVisible = false
|
||||
} else {
|
||||
handleMenuItems(menu)
|
||||
if (usf_share){
|
||||
menu.findItem(R.id.share).isVisible = false
|
||||
}
|
||||
if (!containsDir) {
|
||||
menu.findItem(R.id.explorer_menu_share).isVisible = true
|
||||
val anyItemSelected = explorerAdapter.selectedItems.isNotEmpty()
|
||||
menu.findItem(R.id.select_all).isVisible = anyItemSelected
|
||||
menu.findItem(R.id.delete).isVisible = anyItemSelected
|
||||
menu.findItem(R.id.copy).isVisible = anyItemSelected
|
||||
menu.findItem(R.id.decrypt).isVisible = anyItemSelected && usf_decrypt
|
||||
if (anyItemSelected && usf_share){
|
||||
var containsDir = false
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
if (explorerElements[i].isDirectory) {
|
||||
containsDir = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if (!containsDir) {
|
||||
menu.findItem(R.id.share).isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
@ -294,12 +308,67 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.explorer_menu_select_all -> {
|
||||
android.R.id.home -> {
|
||||
cancelCopy()
|
||||
super.onOptionsItemSelected(item)
|
||||
}
|
||||
R.id.select_all -> {
|
||||
explorerAdapter.selectAll()
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_delete -> {
|
||||
R.id.copy -> {
|
||||
for (i in explorerAdapter.selectedItems){
|
||||
filesToCopy.add(explorerElements[i])
|
||||
}
|
||||
modeSelectLocation = true
|
||||
unselectAll()
|
||||
findViewById<FloatingActionButton>(R.id.fab_add_file).visibility = View.GONE
|
||||
findViewById<FloatingActionButton>(R.id.fab_import_file).visibility = View.GONE
|
||||
findViewById<FloatingActionButton>(R.id.fab_import_file_from_other_volume).visibility = View.GONE
|
||||
true
|
||||
}
|
||||
R.id.validate -> {
|
||||
object : LoadingTask(this, R.string.loading_msg_copy){
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
var failedItem: String? = null
|
||||
for (element in filesToCopy) {
|
||||
val originalPath = element.getFullPath()
|
||||
failedItem = if (element.isDirectory) {
|
||||
recursiveCopyDirectory(originalPath, currentDirectoryPath)
|
||||
} else {
|
||||
if (copyFile(originalPath, PathUtils.path_join(currentDirectoryPath, element.name))) null else originalPath
|
||||
}
|
||||
if (failedItem != null) {
|
||||
stopTask {
|
||||
ColoredAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.copy_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
if (failedItem == null) {
|
||||
stopTask {
|
||||
ColoredAlertDialogBuilder(activity)
|
||||
.setTitle(getString(R.string.copy_success))
|
||||
.setMessage(getString(R.string.copy_success_msg))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun doFinally(activity: AppCompatActivity) {
|
||||
cancelCopy()
|
||||
unselectAll()
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
R.id.delete -> {
|
||||
val size = explorerAdapter.selectedItems.size
|
||||
val dialog = ColoredAlertDialogBuilder(this)
|
||||
dialog.setTitle(R.string.warning)
|
||||
@ -313,17 +382,16 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
dialog.show()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_share -> {
|
||||
R.id.share -> {
|
||||
val paths: MutableList<String> = ArrayList()
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val e = explorerElements[i]
|
||||
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
|
||||
paths.add(explorerElements[i].getFullPath())
|
||||
}
|
||||
ExternalProvider.share(this, gocryptfsVolume, paths)
|
||||
unselectAll()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_decrypt -> {
|
||||
R.id.decrypt -> {
|
||||
val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
|
||||
startActivityForResult(i, PICK_DIRECTORY_REQUEST_CODE)
|
||||
true
|
||||
@ -332,12 +400,86 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun importFileFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, full_path: String, output_dir: String): Boolean {
|
||||
val outputPath = PathUtils.path_join(output_dir, File(full_path).name)
|
||||
private fun recursiveMapFiles(rootPath: String): MutableList<ExplorerElement> {
|
||||
val result = mutableListOf<ExplorerElement>()
|
||||
val explorerElements = gocryptfsVolume.list_dir(rootPath)
|
||||
result.addAll(explorerElements)
|
||||
for (e in explorerElements){
|
||||
if (e.isDirectory){
|
||||
result.addAll(recursiveMapFiles(e.getFullPath()))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private fun cancelCopy() {
|
||||
if (modeSelectLocation){
|
||||
modeSelectLocation = false
|
||||
findViewById<FloatingActionButton>(R.id.fab_add_file).visibility = View.VISIBLE
|
||||
findViewById<FloatingActionButton>(R.id.fab_import_file).visibility = View.VISIBLE
|
||||
findViewById<FloatingActionButton>(R.id.fab_import_file_from_other_volume).visibility = View.VISIBLE
|
||||
filesToCopy.clear()
|
||||
}
|
||||
}
|
||||
|
||||
private fun copyFile(srcPath: String, dstPath: String): Boolean {
|
||||
var success = true
|
||||
val srcHandleID = remote_gocryptfsVolume.open_read_mode(full_path)
|
||||
val originalHandleId = gocryptfsVolume.open_read_mode(srcPath)
|
||||
if (originalHandleId != -1){
|
||||
val newHandleId = gocryptfsVolume.open_write_mode(dstPath)
|
||||
if (newHandleId != -1){
|
||||
var offset: Long = 0
|
||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
var length: Int
|
||||
while (gocryptfsVolume.read_file(originalHandleId, offset, ioBuffer).also { length = it } > 0) {
|
||||
val written = gocryptfsVolume.write_file(newHandleId, offset, ioBuffer, length).toLong()
|
||||
if (written == length.toLong()) {
|
||||
offset += written
|
||||
} else {
|
||||
success = false
|
||||
break
|
||||
}
|
||||
}
|
||||
gocryptfsVolume.close_file(newHandleId)
|
||||
} else {
|
||||
success = false
|
||||
}
|
||||
gocryptfsVolume.close_file(originalHandleId)
|
||||
} else {
|
||||
success = false
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
private fun recursiveCopyDirectory(srcDirectoryPath: String, outputPath: String): String? {
|
||||
val mappedElements = recursiveMapFiles(srcDirectoryPath)
|
||||
val dstDirectoryPath = PathUtils.path_join(outputPath, File(srcDirectoryPath).name)
|
||||
if (!gocryptfsVolume.path_exists(dstDirectoryPath)) {
|
||||
if (!gocryptfsVolume.mkdir(dstDirectoryPath)) {
|
||||
return dstDirectoryPath
|
||||
}
|
||||
}
|
||||
for (e in mappedElements) {
|
||||
val srcPath = e.getFullPath()
|
||||
val dstPath = PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, srcPath))
|
||||
if (e.isDirectory) {
|
||||
if (!gocryptfsVolume.mkdir(dstPath)){
|
||||
return srcPath
|
||||
}
|
||||
} else {
|
||||
if (!copyFile(srcPath, dstPath)) {
|
||||
return srcPath
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun importFileFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, srcPath: String, dstPath: String): Boolean {
|
||||
var success = true
|
||||
val srcHandleID = remote_gocryptfsVolume.open_read_mode(srcPath)
|
||||
if (srcHandleID != -1) {
|
||||
val dstHandleID = gocryptfsVolume.open_write_mode(outputPath)
|
||||
val dstHandleID = gocryptfsVolume.open_write_mode(dstPath)
|
||||
if (dstHandleID != -1) {
|
||||
var length: Int
|
||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
@ -358,22 +500,24 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
return success
|
||||
}
|
||||
|
||||
private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, output_dir: String): String? {
|
||||
val directoryPath = PathUtils.path_join(output_dir, File(remote_directory_path).name)
|
||||
if (!gocryptfsVolume.path_exists(directoryPath)) {
|
||||
if (!gocryptfsVolume.mkdir(directoryPath)) {
|
||||
return directoryPath
|
||||
private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, outputPath: String): String? {
|
||||
val mappedElements = recursiveMapFiles(remote_directory_path)
|
||||
val dstDirectoryPath = PathUtils.path_join(outputPath, File(remote_directory_path).name)
|
||||
if (!gocryptfsVolume.path_exists(dstDirectoryPath)) {
|
||||
if (!gocryptfsVolume.mkdir(dstDirectoryPath)) {
|
||||
return dstDirectoryPath
|
||||
}
|
||||
}
|
||||
val explorerElements = remote_gocryptfsVolume.list_dir(remote_directory_path)
|
||||
for (e in explorerElements) {
|
||||
val fullPath = PathUtils.path_join(remote_directory_path, e.name)
|
||||
for (e in mappedElements) {
|
||||
val srcPath = e.getFullPath()
|
||||
val dstPath = PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(remote_directory_path, srcPath))
|
||||
if (e.isDirectory) {
|
||||
val failedItem = recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)
|
||||
failedItem?.let { return it }
|
||||
if (!gocryptfsVolume.mkdir(dstPath)){
|
||||
return srcPath
|
||||
}
|
||||
} else {
|
||||
if (!importFileFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)) {
|
||||
return fullPath
|
||||
if (!importFileFromOtherVolume(remote_gocryptfsVolume, srcPath, dstPath)) {
|
||||
return srcPath
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import sushi.hardcore.droidfs.R
|
||||
@ -16,48 +17,48 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.explorer_drop, menu)
|
||||
handleMenuItems(menu)
|
||||
menu.findItem(R.id.explorer_menu_validate).isVisible = explorerAdapter.selectedItems.isEmpty()
|
||||
menu.findItem(R.id.validate).isVisible = explorerAdapter.selectedItems.isEmpty()
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.explorer_menu_validate -> {
|
||||
R.id.validate -> {
|
||||
val alertDialog = ColoredAlertDialogBuilder(this)
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
||||
var error_msg: String? = null
|
||||
var errorMsg: String? = null
|
||||
val extras = intent.extras
|
||||
if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){
|
||||
if (intent.action == Intent.ACTION_SEND) {
|
||||
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
val output_path = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri))
|
||||
error_msg = if (gocryptfsVolume.import_file(this, uri, output_path)) null else getString(R.string.import_failed, output_path)
|
||||
val outputPath = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri))
|
||||
errorMsg = if (gocryptfsVolume.import_file(this, uri, outputPath)) null else getString(R.string.import_failed, outputPath)
|
||||
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
|
||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
if (uris != null){
|
||||
for (uri in uris) {
|
||||
val output_path = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri))
|
||||
if (!gocryptfsVolume.import_file(this, uri, output_path)) {
|
||||
error_msg = getString(R.string.import_failed, output_path)
|
||||
val outputPath = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri))
|
||||
if (!gocryptfsVolume.import_file(this, uri, outputPath)) {
|
||||
errorMsg = getString(R.string.import_failed, outputPath)
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
error_msg = getString(R.string.share_intent_parsing_failed)
|
||||
errorMsg = getString(R.string.share_intent_parsing_failed)
|
||||
}
|
||||
} else {
|
||||
error_msg = getString(R.string.share_intent_parsing_failed)
|
||||
errorMsg = getString(R.string.share_intent_parsing_failed)
|
||||
}
|
||||
} else {
|
||||
error_msg = getString(R.string.share_intent_parsing_failed)
|
||||
errorMsg = getString(R.string.share_intent_parsing_failed)
|
||||
}
|
||||
if (error_msg == null) {
|
||||
if (errorMsg == null) {
|
||||
alertDialog.setTitle(R.string.success_import)
|
||||
alertDialog.setMessage(R.string.success_import_msg)
|
||||
} else {
|
||||
alertDialog.setTitle(R.string.error)
|
||||
alertDialog.setMessage(error_msg)
|
||||
alertDialog.setMessage(errorMsg)
|
||||
}
|
||||
alertDialog.show()
|
||||
true
|
||||
|
@ -27,7 +27,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
setCurrentPath(full_path)
|
||||
}
|
||||
explorerElements[position].isParentFolder -> {
|
||||
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||
setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
|
||||
}
|
||||
else -> {
|
||||
result_intent.putExtra("path", full_path)
|
||||
@ -43,19 +43,19 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
menuInflater.inflate(R.menu.explorer_pick, menu)
|
||||
handleMenuItems(menu)
|
||||
val any_item_selected = explorerAdapter.selectedItems.isNotEmpty()
|
||||
menu.findItem(R.id.explorer_menu_select_all).isVisible = any_item_selected
|
||||
menu.findItem(R.id.explorer_menu_validate).isVisible = any_item_selected
|
||||
menu.findItem(R.id.select_all).isVisible = any_item_selected
|
||||
menu.findItem(R.id.validate).isVisible = any_item_selected
|
||||
return true
|
||||
}
|
||||
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.explorer_menu_select_all -> {
|
||||
R.id.select_all -> {
|
||||
explorerAdapter.selectAll()
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_validate -> {
|
||||
R.id.validate -> {
|
||||
val paths = ArrayList<String>()
|
||||
val types = ArrayList<Int>()
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
@ -79,7 +79,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
|
||||
override fun closeVolumeOnDestroy() {
|
||||
//don't close volume
|
||||
RestrictedFileProvider.wipeAll()
|
||||
RestrictedFileProvider.wipeAll(this)
|
||||
}
|
||||
|
||||
override fun closeVolumeOnUserExit() {
|
||||
|
@ -1,8 +1,9 @@
|
||||
package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import java.util.*
|
||||
|
||||
class ExplorerElement(val name: String, val elementType: Short, val size: Long, mtime: Long) {
|
||||
class ExplorerElement(val name: String, val elementType: Short, val size: Long, mtime: Long, private val parentPath: String) {
|
||||
val mTime = Date((mtime * 1000).toString().toLong())
|
||||
|
||||
val isDirectory: Boolean
|
||||
@ -14,4 +15,7 @@ class ExplorerElement(val name: String, val elementType: Short, val size: Long,
|
||||
val isRegularFile: Boolean
|
||||
get() = elementType.toInt() == 1
|
||||
|
||||
fun getFullPath(): String {
|
||||
return PathUtils.path_join(parentPath, name)
|
||||
}
|
||||
}
|
@ -4,6 +4,8 @@ import android.net.Uri
|
||||
import com.google.android.exoplayer2.upstream.*
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.min
|
||||
|
||||
class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource {
|
||||
private var handleID = -1
|
||||
@ -34,15 +36,20 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
||||
if (fileOffset >= fileSize){
|
||||
return -1
|
||||
}
|
||||
val tmpBuff = if (fileOffset+readLength > fileSize){
|
||||
ByteArray((fileSize-fileOffset).toInt())
|
||||
} else {
|
||||
ByteArray(readLength)
|
||||
var totalRead = 0
|
||||
for (i in 0 until ceil(readLength.toDouble()/ConstValues.MAX_KERNEL_WRITE).toInt()){
|
||||
val tmpReadLength = min(readLength-totalRead, ConstValues.MAX_KERNEL_WRITE)
|
||||
val tmpBuff = if (fileOffset+tmpReadLength > fileSize){
|
||||
ByteArray((fileSize-fileOffset).toInt())
|
||||
} else {
|
||||
ByteArray(tmpReadLength)
|
||||
}
|
||||
val read = gocryptfsVolume.read_file(handleID, fileOffset, tmpBuff)
|
||||
System.arraycopy(tmpBuff, 0, buffer, offset+totalRead, read)
|
||||
fileOffset += read
|
||||
totalRead += read
|
||||
}
|
||||
val read = gocryptfsVolume.read_file(handleID, fileOffset, tmpBuff)
|
||||
fileOffset += read
|
||||
System.arraycopy(tmpBuff, 0, buffer, offset, read)
|
||||
return read
|
||||
return totalRead
|
||||
}
|
||||
|
||||
class Factory(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource.Factory{
|
||||
|
@ -2,12 +2,17 @@ package sushi.hardcore.droidfs.provider
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.content.Context
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import sushi.hardcore.droidfs.BuildConfig
|
||||
import sushi.hardcore.droidfs.util.SQLUtil.appendSelectionArgs
|
||||
import sushi.hardcore.droidfs.util.SQLUtil.concatenateWhere
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
@ -15,48 +20,105 @@ import java.util.regex.Pattern
|
||||
|
||||
class RestrictedFileProvider: ContentProvider() {
|
||||
companion object {
|
||||
private const val DB_NAME = "temporary_files.db"
|
||||
private const val TABLE_FILES = "files"
|
||||
private const val DB_VERSION = 3
|
||||
private var dbHelper: RestrictedDatabaseHelper? = null
|
||||
private const val AUTHORITY = BuildConfig.APPLICATION_ID + ".temporary_provider"
|
||||
private val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY")
|
||||
const val TEMPORARY_FILES_DIR_NAME = "temp"
|
||||
private val UUID_PATTERN = Pattern.compile("[a-fA-F0-9-]+")
|
||||
|
||||
private lateinit var tempFilesDir: File
|
||||
private val tempFiles = mutableMapOf<String, TemporaryFile>()
|
||||
|
||||
class TemporaryFile(val fileName: String, val file: File)
|
||||
internal class TemporaryFileColumns {
|
||||
companion object {
|
||||
const val COLUMN_UUID = "uuid"
|
||||
const val COLUMN_NAME = "name"
|
||||
}
|
||||
}
|
||||
|
||||
internal class RestrictedDatabaseHelper(context: Context?): SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
|
||||
override fun onCreate(db: SQLiteDatabase) {
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
|
||||
TemporaryFileColumns.COLUMN_UUID + " TEXT PRIMARY KEY, " +
|
||||
TemporaryFileColumns.COLUMN_NAME + " TEXT" +
|
||||
");"
|
||||
)
|
||||
}
|
||||
|
||||
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
|
||||
if (oldVersion == 1) {
|
||||
db.execSQL("DROP TABLE IF EXISTS files")
|
||||
db.execSQL(
|
||||
"CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
|
||||
TemporaryFileColumns.COLUMN_UUID + " TEXT PRIMARY KEY, " +
|
||||
TemporaryFileColumns.COLUMN_NAME + " TEXT" +
|
||||
");"
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun newFile(fileName: String): Uri? {
|
||||
val uuid = UUID.randomUUID().toString()
|
||||
val file = File(tempFilesDir, uuid)
|
||||
return if (file.createNewFile()){
|
||||
tempFiles[uuid] = TemporaryFile(fileName, file)
|
||||
Uri.withAppendedPath(CONTENT_URI, uuid)
|
||||
val contentValues = ContentValues()
|
||||
contentValues.put(TemporaryFileColumns.COLUMN_UUID, uuid)
|
||||
contentValues.put(TemporaryFileColumns.COLUMN_NAME, fileName)
|
||||
if (dbHelper?.writableDatabase?.insert(TABLE_FILES, null, contentValues)?.toInt() != -1){
|
||||
Uri.withAppendedPath(CONTENT_URI, uuid)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun wipeAll() {
|
||||
fun wipeAll(context: Context) {
|
||||
tempFilesDir.listFiles()?.let{
|
||||
for (file in it) {
|
||||
Wiper.wipe(file)
|
||||
}
|
||||
}
|
||||
context.deleteDatabase(DB_NAME)
|
||||
}
|
||||
|
||||
private fun getFileFromUri(uri: Uri): TemporaryFile? {
|
||||
private fun isValidUUID(uuid: String): Boolean {
|
||||
return UUID_PATTERN.matcher(uuid).matches()
|
||||
}
|
||||
|
||||
private fun getUuidFromUri(uri: Uri): String? {
|
||||
val uuid = uri.lastPathSegment
|
||||
if (uuid != null) {
|
||||
if (UUID_PATTERN.matcher(uuid).matches()) {
|
||||
return tempFiles[uuid]
|
||||
if (isValidUUID(uuid)) {
|
||||
return uuid
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getFileFromUUID(uuid: String): File? {
|
||||
if (isValidUUID(uuid)){
|
||||
return File(tempFilesDir, uuid)
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun getFileFromUri(uri: Uri): File? {
|
||||
getUuidFromUri(uri)?.let {
|
||||
return getFileFromUUID(it)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
context?.let {
|
||||
dbHelper = RestrictedDatabaseHelper(it)
|
||||
tempFilesDir = File(it.cacheDir, TEMPORARY_FILES_DIR_NAME)
|
||||
return tempFilesDir.mkdirs()
|
||||
}
|
||||
@ -73,28 +135,47 @@ class RestrictedFileProvider: ContentProvider() {
|
||||
|
||||
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
|
||||
val temporaryFile = getFileFromUri(uri)
|
||||
if (temporaryFile != null) {
|
||||
val cursor = MatrixCursor(
|
||||
arrayOf(
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.SIZE
|
||||
)
|
||||
)
|
||||
cursor.newRow()
|
||||
.add(temporaryFile.fileName)
|
||||
.add(temporaryFile.file.length())
|
||||
return cursor
|
||||
temporaryFile?.let{
|
||||
val fileName =
|
||||
dbHelper?.readableDatabase?.query(TABLE_FILES, arrayOf(TemporaryFileColumns.COLUMN_NAME), TemporaryFileColumns.COLUMN_UUID + "=?", arrayOf(uri.lastPathSegment), null, null, null)
|
||||
fileName?.let{
|
||||
if (fileName.moveToNext()) {
|
||||
val cursor = MatrixCursor(
|
||||
arrayOf(
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.SIZE
|
||||
)
|
||||
)
|
||||
cursor.newRow()
|
||||
.add(fileName.getString(0))
|
||||
.add(temporaryFile.length())
|
||||
fileName.close()
|
||||
return cursor
|
||||
}
|
||||
fileName.close()
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
val temporaryFile = getFileFromUri(uri)
|
||||
if (temporaryFile != null) {
|
||||
Wiper.wipe(temporaryFile.file)
|
||||
tempFiles.remove(uri.lastPathSegment)
|
||||
override fun delete(uri: Uri, givenSelection: String?, givenSelectionArgs: Array<String>?): Int {
|
||||
val uuid = getUuidFromUri(uri)
|
||||
uuid?.let{
|
||||
val selection = concatenateWhere(givenSelection ?: "" , TemporaryFileColumns.COLUMN_UUID + "=?")
|
||||
val selectionArgs = appendSelectionArgs(givenSelectionArgs, arrayOf(it))
|
||||
|
||||
val files = dbHelper?.readableDatabase?.query(TABLE_FILES, arrayOf(TemporaryFileColumns.COLUMN_UUID), selection, selectionArgs, null, null, null)
|
||||
if (files != null) {
|
||||
while (files.moveToNext()) {
|
||||
getFileFromUUID(files.getString(0))?.let { file ->
|
||||
Wiper.wipe(file)
|
||||
}
|
||||
}
|
||||
files.close()
|
||||
return dbHelper?.writableDatabase?.delete(TABLE_FILES, selection, selectionArgs) ?: 0
|
||||
}
|
||||
}
|
||||
return 1
|
||||
return 0
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String {
|
||||
@ -104,7 +185,7 @@ class RestrictedFileProvider: ContentProvider() {
|
||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
||||
if (("w" in mode && callingPackage == BuildConfig.APPLICATION_ID) || "w" !in mode) {
|
||||
getFileFromUri(uri)?.let{
|
||||
return ParcelFileDescriptor.open(it.file, ParcelFileDescriptor.parseMode(mode))
|
||||
return ParcelFileDescriptor.open(it, ParcelFileDescriptor.parseMode(mode))
|
||||
}
|
||||
} else {
|
||||
throw SecurityException("Read-only access")
|
||||
|
@ -9,7 +9,6 @@ import sushi.hardcore.droidfs.provider.RestrictedFileProvider
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.net.URLConnection
|
||||
import java.util.*
|
||||
import kotlin.collections.ArrayList
|
||||
|
||||
object ExternalProvider {
|
||||
@ -102,13 +101,13 @@ object ExternalProvider {
|
||||
|
||||
fun removeFiles(context: Context) {
|
||||
Thread{
|
||||
val wiped = ArrayList<Uri>()
|
||||
for (uri in storedFiles) {
|
||||
if (Wiper.wipe(context, uri) == null){
|
||||
wiped.add(uri)
|
||||
val success = ArrayList<Uri>()
|
||||
for (uri in storedFiles){
|
||||
if (context.contentResolver.delete(uri, null, null) == 1){
|
||||
success.add(uri)
|
||||
}
|
||||
}
|
||||
for (uri in wiped){
|
||||
for (uri in success){
|
||||
storedFiles.remove(uri)
|
||||
}
|
||||
}.start()
|
||||
|
@ -2,7 +2,6 @@ package sushi.hardcore.droidfs.util
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.Log
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||
import java.io.*
|
||||
|
||||
@ -93,10 +92,10 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
|
||||
fun export_file(handleID: Int, os: OutputStream): Boolean {
|
||||
var offset: Long = 0
|
||||
val io_buffer = ByteArray(DefaultBS)
|
||||
val ioBuffer = ByteArray(DefaultBS)
|
||||
var length: Int
|
||||
while (native_read_file(sessionID, handleID, offset, io_buffer).also { length = it } > 0){
|
||||
os.write(io_buffer, 0, length)
|
||||
while (native_read_file(sessionID, handleID, offset, ioBuffer).also { length = it } > 0){
|
||||
os.write(ioBuffer, 0, length)
|
||||
offset += length.toLong()
|
||||
}
|
||||
os.close()
|
||||
@ -105,10 +104,10 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
|
||||
fun export_file(src_path: String, os: OutputStream): Boolean {
|
||||
var success = false
|
||||
val src_handleID = open_read_mode(src_path)
|
||||
if (src_handleID != -1) {
|
||||
success = export_file(src_handleID, os)
|
||||
close_file(src_handleID)
|
||||
val srcHandleId = open_read_mode(src_path)
|
||||
if (srcHandleId != -1) {
|
||||
success = export_file(srcHandleId, os)
|
||||
close_file(srcHandleId)
|
||||
}
|
||||
return success
|
||||
}
|
||||
@ -127,10 +126,10 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
|
||||
fun import_file(inputStream: InputStream, handleID: Int): Boolean {
|
||||
var offset: Long = 0
|
||||
val io_buffer = ByteArray(DefaultBS)
|
||||
val ioBuffer = ByteArray(DefaultBS)
|
||||
var length: Int
|
||||
while (inputStream.read(io_buffer).also { length = it } > 0) {
|
||||
val written = native_write_file(sessionID, handleID, offset, io_buffer, length).toLong()
|
||||
while (inputStream.read(ioBuffer).also { length = it } > 0) {
|
||||
val written = native_write_file(sessionID, handleID, offset, ioBuffer, length).toLong()
|
||||
if (written == length.toLong()) {
|
||||
offset += written
|
||||
} else {
|
||||
@ -145,10 +144,10 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
|
||||
fun import_file(inputStream: InputStream, dst_path: String): Boolean {
|
||||
var success = false
|
||||
val dst_handleID = open_write_mode(dst_path)
|
||||
if (dst_handleID != -1) {
|
||||
success = import_file(inputStream, dst_handleID)
|
||||
close_file(dst_handleID)
|
||||
val dstHandleId = open_write_mode(dst_path)
|
||||
if (dstHandleId != -1) {
|
||||
success = import_file(inputStream, dstHandleId)
|
||||
close_file(dstHandleId)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ import java.text.DecimalFormat;
|
||||
|
||||
public class PathUtils {
|
||||
|
||||
public static String get_parent_path(String path){
|
||||
public static String getParentPath(String path){
|
||||
if (path.endsWith("/")){
|
||||
String a = path.substring(0, path.length()-2);
|
||||
if (a.contains("/")){
|
||||
@ -44,16 +44,22 @@ public class PathUtils {
|
||||
return result.substring(0, result.length()-1);
|
||||
}
|
||||
|
||||
public static String getRelativePath(String parentPath, String childPath){
|
||||
return childPath.substring(parentPath.length()+1);
|
||||
}
|
||||
|
||||
public static String getFilenameFromURI(Context context, Uri uri){
|
||||
String result = null;
|
||||
if (uri.getScheme().equals("content")){
|
||||
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
|
||||
try {
|
||||
if (cursor != null && cursor.moveToFirst()){
|
||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
if (cursor != null){
|
||||
try {
|
||||
if (cursor.moveToFirst()){
|
||||
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
if (result == null){
|
||||
|
24
app/src/main/java/sushi/hardcore/droidfs/util/SQLUtil.kt
Normal file
24
app/src/main/java/sushi/hardcore/droidfs/util/SQLUtil.kt
Normal file
@ -0,0 +1,24 @@
|
||||
package sushi.hardcore.droidfs.util
|
||||
|
||||
object SQLUtil {
|
||||
@JvmStatic
|
||||
fun concatenateWhere(a: String, b: String): String {
|
||||
if (a.isEmpty()) {
|
||||
return b
|
||||
}
|
||||
return if (b.isEmpty()) {
|
||||
a
|
||||
} else "($a) AND ($b)"
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
fun appendSelectionArgs(originalValues: Array<String>?, newValues: Array<String>): Array<String> {
|
||||
if (originalValues == null || originalValues.isEmpty()) {
|
||||
return newValues
|
||||
}
|
||||
val result = Array(originalValues.size + newValues.size){ "it = $it" }
|
||||
System.arraycopy(originalValues, 0, result, 0, originalValues.size)
|
||||
System.arraycopy(newValues, 0, result, originalValues.size, newValues.size)
|
||||
return result
|
||||
}
|
||||
}
|
@ -1,8 +1,6 @@
|
||||
package sushi.hardcore.droidfs.widgets
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.Color
|
||||
import android.graphics.drawable.Drawable
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.graphics.drawable.DrawableCompat
|
||||
import androidx.preference.Preference
|
||||
@ -20,9 +18,8 @@ object ThemeColor {
|
||||
for (i in 0 until preference.preferenceCount) {
|
||||
tintPreferenceIcons(preference.getPreference(i), color)
|
||||
}
|
||||
} else {
|
||||
val icon: Drawable = preference.icon
|
||||
DrawableCompat.setTint(icon, color)
|
||||
} else if (preference.icon != null) {
|
||||
DrawableCompat.setTint(preference.icon, color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -227,7 +227,7 @@ Java_sushi_hardcore_droidfs_util_GocryptfsVolume_native_1list_1dir(JNIEnv *env,
|
||||
jmethodID java_util_ArrayList_add = (*env)->GetMethodID(env, java_util_ArrayList, "add", "(Ljava/lang/Object;)Z");
|
||||
|
||||
jclass classExplorerElement = (*env)->NewGlobalRef(env, (*env)->FindClass(env, "sushi/hardcore/droidfs/explorers/ExplorerElement"));
|
||||
jmethodID classExplorerElement_init = (*env)->GetMethodID(env, classExplorerElement, "<init>", "(Ljava/lang/String;SJJ)V");
|
||||
jmethodID classExplorerElement_init = (*env)->GetMethodID(env, classExplorerElement, "<init>", "(Ljava/lang/String;SJJLjava/lang/String;)V");
|
||||
|
||||
jobject element_list = (*env)->NewObject(env, java_util_ArrayList, java_util_ArrayList_init, elements.r2);
|
||||
unsigned int c = 0;
|
||||
@ -254,7 +254,7 @@ Java_sushi_hardcore_droidfs_util_GocryptfsVolume_native_1list_1dir(JNIEnv *env,
|
||||
type = 1; //regular file
|
||||
}
|
||||
jstring jname = (*env)->NewStringUTF(env, name);
|
||||
jobject explorerElement = (*env)->NewObject(env, classExplorerElement, classExplorerElement_init, jname, type, (long long)attrs.r0, attrs.r1);
|
||||
jobject explorerElement = (*env)->NewObject(env, classExplorerElement, classExplorerElement_init, jname, type, (long long)attrs.r0, attrs.r1, jplain_dir);
|
||||
(*env)->CallBooleanMethod(env, element_list, java_util_ArrayList_add, explorerElement);
|
||||
c += name_len+1;
|
||||
}
|
||||
|
5
app/src/main/res/drawable/icon_copy.xml
Normal file
5
app/src/main/res/drawable/icon_copy.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M16,1L4,1c-1.1,0 -2,0.9 -2,2v14h2L4,3h12L16,1zM15,5l6,6v10c0,1.1 -0.9,2 -2,2L7.99,23C6.89,23 6,22.1 6,21l0.01,-14c0,-1.1 0.89,-2 1.99,-2h7zM14,12h5.5L14,6.5L14,12z"/>
|
||||
</vector>
|
@ -1,12 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="1024"
|
||||
android:viewportHeight="1024"
|
||||
android:tint="#FFFFFF">
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M749.5,417.3H715V283.8c0-54.4-21.1-105.3-59.4-143.6c-38.4-38.4-89.3-59.5-143.5-59.5 c-54.3,0-105.3,21.3-143.6,59.6c-38.3,38.4-59.5,88.9-59.5,143.9h60c0-79,64.2-143.4,143.1-143.4c38.2,0,74.1,14.7,101.1,41.7 c27,27,41.8,62.9,41.8,101.2v133.5H274.5c-41.1,0-74.5,33.4-74.5,74.5v376.5c0,41.3,33.4,74.9,74.5,74.9h475 c41.1,0,74.5-33.6,74.5-74.9V491.8C824,450.7,790.6,417.3,749.5,417.3z M532,742.7v55.5h-40v-55.5c-23-8.2-39.5-30.2-39.5-56 c0-32.8,26.7-59.5,59.5-59.5s59.5,26.7,59.5,59.5C571.5,712.5,555,734.5,532,742.7z" />
|
||||
<vector android:height="24dp" android:viewportHeight="500"
|
||||
android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#ffffff" android:fillType="evenOdd" android:pathData="M68.29,431.711c0,20.078 16.264,36.34 36.343,36.34h290.734c20.078,0 36.345,-16.262 36.345,-36.34V250c0,-20.079 -16.267,-36.342 -36.345,-36.342H177.317v-63.597c0,-40.157 32.525,-72.685 72.683,-72.685c40.158,0 72.685,32.528 72.685,72.685v4.541c0,12.538 10.176,22.715 22.711,22.715c12.537,0 22.717,-10.177 22.717,-22.715v-4.541c0,-65.232 -52.882,-118.111 -118.112,-118.111c-65.24,0 -118.111,52.879 -118.111,118.111v63.597h-27.256c-20.079,0 -36.343,16.263 -36.343,36.342V431.711zM213.658,313.599c0,-20.078 16.263,-36.341 36.342,-36.341s36.341,16.263 36.341,36.341c0,12.812 -6.634,24.079 -16.625,30.529c0,0 3.55,21.446 7.542,46.699c0,7.538 -6.087,13.625 -13.629,13.625h-27.258c-7.541,0 -13.627,-6.087 -13.627,-13.625l7.542,-46.699C220.294,337.678 213.658,326.41 213.658,313.599z"/>
|
||||
</vector>
|
11
app/src/main/res/drawable/icon_encrypt.xml
Normal file
11
app/src/main/res/drawable/icon_encrypt.xml
Normal file
@ -0,0 +1,11 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="500"
|
||||
android:viewportHeight="500">
|
||||
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:pathData="M131.889,150.061v63.597h-27.256 c-20.079,0-36.343,16.263-36.343,36.342v181.711c0,20.078,16.264,36.34,36.343,36.34h290.734c20.078,0,36.345-16.262,36.345-36.34 V250c0-20.079-16.267-36.342-36.345-36.342h-27.254v-63.597c0-65.232-52.882-118.111-118.112-118.111 S131.889,84.828,131.889,150.061z M177.317,213.658v-63.597c0-40.157,32.525-72.685,72.683-72.685 c40.158,0,72.685,32.528,72.685,72.685v63.597H177.317z M213.658,313.599c0-20.078,16.263-36.341,36.342-36.341 s36.341,16.263,36.341,36.341c0,12.812-6.634,24.079-16.625,30.529c0,0,3.55,21.446,7.542,46.699 c0,7.538-6.087,13.625-13.629,13.625h-27.258c-7.541,0-13.627-6.087-13.627-13.625l7.542-46.699 C220.294,337.678,213.658,326.41,213.658,313.599z" />
|
||||
</vector>
|
10
app/src/main/res/drawable/icon_github.xml
Normal file
10
app/src/main/res/drawable/icon_github.xml
Normal file
@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff" android:pathData="M10.9,2.1c-4.6,0.5-8.3,4.2-8.8,8.7c-0.6,5,2.5,9.3,6.9,10.7v-2.3c0,0-0.4,0.1-0.9,0.1c-1.4,0-2-1.2-2.1-1.9 c-0.1-0.4-0.3-0.7-0.6-1C5.1,16.3,5,16.3,5,16.2C5,16,5.3,16,5.4,16c0.6,0,1.1,0.7,1.3,1c0.5,0.8,1.1,1,1.4,1c0.4,0,0.7-0.1,0.9-0.2 c0.1-0.7,0.4-1.4,1-1.8c-2.3-0.5-4-1.8-4-4c0-1.1,0.5-2.2,1.2-3C7.1,8.8,7,8.3,7,7.6C7,7.2,7,6.6,7.3,6c0,0,1.4,0,2.8,1.3 C10.6,7.1,11.3,7,12,7s1.4,0.1,2,0.3C15.3,6,16.8,6,16.8,6C17,6.6,17,7.2,17,7.6c0,0.8-0.1,1.2-0.2,1.4c0.7,0.8,1.2,1.8,1.2,3 c0,2.2-1.7,3.5-4,4c0.6,0.5,1,1.4,1,2.3v3.3c4.1-1.3,7-5.1,7-9.5C22,6.1,16.9,1.4,10.9,2.1z" />
|
||||
</vector>
|
5
app/src/main/res/drawable/icon_transfert.xml
Normal file
5
app/src/main/res/drawable/icon_transfert.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z"/>
|
||||
</vector>
|
Binary file not shown.
Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 49 KiB |
@ -47,37 +47,39 @@
|
||||
app:menu_labels_style="@style/fab_label">
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredFAB
|
||||
android:id="@+id/fab_import_file_from_other_volume"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fab_size="mini"
|
||||
app:fab_label="@string/import_from_other_volume"
|
||||
android:src="@drawable/icon_add"
|
||||
android:src="@drawable/icon_transfert"
|
||||
android:onClick="onClickAddFileFromOtherVolume"/>
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredFAB
|
||||
android:id="@+id/fab_import_file"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fab_size="mini"
|
||||
app:fab_label="@string/import_files"
|
||||
android:src="@drawable/icon_add"
|
||||
android:src="@drawable/icon_encrypt"
|
||||
android:onClick="onClickAddFile"/>
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredFAB
|
||||
android:id="@+id/fab_explorer_add_file"
|
||||
android:id="@+id/fab_add_file"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fab_size="mini"
|
||||
app:fab_label="@string/new_file"
|
||||
android:src="@drawable/icon_add"
|
||||
android:src="@drawable/icon_file_unknown"
|
||||
android:onClick="onClickCreateFile"/>
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredFAB
|
||||
android:id="@+id/fab_explorer_add_folder"
|
||||
android:id="@+id/fab_add_folder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fab_size="mini"
|
||||
app:fab_label="@string/mkdir"
|
||||
android:src="@drawable/icon_add"
|
||||
android:src="@drawable/icon_folder"
|
||||
android:onClick="onClickAddFolder"
|
||||
tools:ignore="OnClick" />
|
||||
|
||||
|
@ -45,12 +45,12 @@
|
||||
app:menu_labels_style="@style/fab_label">
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredFAB
|
||||
android:id="@+id/fab_explorer_add_folder"
|
||||
android:id="@+id/fab_add_folder"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:fab_size="mini"
|
||||
app:fab_label="@string/mkdir"
|
||||
android:src="@drawable/icon_add"
|
||||
android:src="@drawable/icon_folder"
|
||||
android:onClick="onClickAddFolder"/>
|
||||
|
||||
</sushi.hardcore.droidfs.widgets.ColoredFAM>
|
||||
|
@ -3,51 +3,62 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_select_all"
|
||||
android:id="@+id/select_all"
|
||||
app:showAsAction="always"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_select_all"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_delete"
|
||||
android:id="@+id/copy"
|
||||
app:showAsAction="always"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_copy"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/delete"
|
||||
app:showAsAction="always"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_delete" />
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_share"
|
||||
app:showAsAction="always"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_share"
|
||||
app:iconTint="@color/menuIcon"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_decrypt"
|
||||
app:showAsAction="always"
|
||||
android:id="@+id/decrypt"
|
||||
app:showAsAction="ifRoom"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_decrypt"
|
||||
app:iconTint="@color/menuIcon"/>
|
||||
android:title="@string/decrypt"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_external_open"
|
||||
android:id="@+id/share"
|
||||
app:showAsAction="ifRoom"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_share"
|
||||
android:title="@string/share"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/external_open"
|
||||
app:showAsAction="never"
|
||||
android:visible="false"
|
||||
android:title="@string/external_open"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_rename"
|
||||
android:id="@+id/rename"
|
||||
app:showAsAction="never"
|
||||
android:visible="false"
|
||||
android:title="@string/rename"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_sort"
|
||||
app:showAsAction="always"
|
||||
android:icon="@drawable/icon_sort"
|
||||
app:iconTint="@color/menuIcon"/>
|
||||
android:id="@+id/validate"
|
||||
app:showAsAction="ifRoom"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_check"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_close"
|
||||
android:id="@+id/sort"
|
||||
app:showAsAction="always"
|
||||
android:icon="@drawable/icon_sort"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/close"
|
||||
app:showAsAction="always"
|
||||
android:icon="@drawable/icon_close"/>
|
||||
|
||||
|
@ -3,30 +3,29 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_external_open"
|
||||
android:id="@+id/external_open"
|
||||
app:showAsAction="never"
|
||||
android:visible="false"
|
||||
android:title="@string/external_open"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_rename"
|
||||
android:id="@+id/rename"
|
||||
app:showAsAction="never"
|
||||
android:visible="false"
|
||||
android:title="@string/rename"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_validate"
|
||||
app:showAsAction="always"
|
||||
android:id="@+id/validate"
|
||||
app:showAsAction="ifRoom"
|
||||
android:icon="@drawable/icon_check"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_sort"
|
||||
android:id="@+id/sort"
|
||||
app:showAsAction="always"
|
||||
android:icon="@drawable/icon_sort"
|
||||
app:iconTint="@color/menuIcon"/>
|
||||
android:icon="@drawable/icon_sort"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_close"
|
||||
android:id="@+id/close"
|
||||
app:showAsAction="always"
|
||||
android:icon="@drawable/icon_close"/>
|
||||
|
||||
|
@ -3,31 +3,30 @@
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_validate"
|
||||
app:showAsAction="always"
|
||||
android:id="@+id/validate"
|
||||
app:showAsAction="ifRoom"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_check"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_select_all"
|
||||
app:showAsAction="always"
|
||||
android:id="@+id/select_all"
|
||||
app:showAsAction="ifRoom"
|
||||
android:visible="false"
|
||||
android:icon="@drawable/icon_select_all"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_rename"
|
||||
android:id="@+id/rename"
|
||||
app:showAsAction="never"
|
||||
android:visible="false"
|
||||
android:title="@string/rename"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_sort"
|
||||
android:id="@+id/sort"
|
||||
app:showAsAction="always"
|
||||
android:icon="@drawable/icon_sort"
|
||||
app:iconTint="@color/menuIcon"/>
|
||||
android:icon="@drawable/icon_sort"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/explorer_menu_close"
|
||||
android:id="@+id/close"
|
||||
app:showAsAction="always"
|
||||
android:icon="@drawable/icon_close"/>
|
||||
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 9.5 KiB |
BIN
app/src/main/res/mipmap/icon_launcher.png
Normal file
BIN
app/src/main/res/mipmap/icon_launcher.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 8.5 KiB |
@ -12,7 +12,6 @@
|
||||
<color name="textColor">#FFFFFF</color>
|
||||
<color name="buttonBackgroundColor">#5B5A5C</color>
|
||||
<color name="item_selected">#66666666</color>
|
||||
<color name="menuIcon">#FFFFFF</color>
|
||||
<color name="infoBarBackgroundColor">#111111</color>
|
||||
<color name="fingerprint_wait">#FFFFFF</color>
|
||||
<color name="fingerprint_success">#6CC341</color>
|
||||
|
@ -21,7 +21,7 @@
|
||||
<string name="success_import_msg">The selected files have been successfully imported.</string>
|
||||
<string name="import_failed">Import of %1$s failed.</string>
|
||||
<string name="export_failed">Export of %1$s failed.</string>
|
||||
<string name="success_export">Export Successful !</string>
|
||||
<string name="success_export">Export successful !</string>
|
||||
<string name="success_export_msg">The selected files have been successfully exported.</string>
|
||||
<string name="remove_failed">Deletion of %1$s failed</string>
|
||||
<string name="passwords_mismatch">Passwords don\'t match</string>
|
||||
@ -130,4 +130,13 @@
|
||||
<string name="loading_msg_wipe">Wiping original files…</string>
|
||||
<string name="loading_msg_export">Exporting files…</string>
|
||||
<string name="query_cursor_null_error_msg">Unable to access this file</string>
|
||||
<string name="about">About</string>
|
||||
<string name="github">GitHub</string>
|
||||
<string name="github_summary">Want to read the documentation, request feature, report bug, read the source code… Check the DroidFS\'s repository !</string>
|
||||
<string name="share">Share</string>
|
||||
<string name="decrypt">Decrypt</string>
|
||||
<string name="loading_msg_copy">Copying selected items...</string>
|
||||
<string name="copy_failed">Copy of %1$s failed.</string>
|
||||
<string name="copy_success_msg">The selected items have been successfully copied.</string>
|
||||
<string name="copy_success">Copy successful !</string>
|
||||
</resources>
|
||||
|
@ -24,11 +24,24 @@
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/unsafe_features">
|
||||
|
||||
<Preference
|
||||
android:title="@string/manage_unsafe_features"
|
||||
android:summary="@string/manage_unsafe_features_summary"
|
||||
android:icon="@drawable/icon_warning"
|
||||
android:fragment="sushi.hardcore.droidfs.SettingsActivity$UnsafeFeaturesSettingsFragment"/>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
<PreferenceCategory android:title="@string/about">
|
||||
|
||||
<Preference
|
||||
android:title="@string/github"
|
||||
android:summary="@string/github_summary"
|
||||
android:icon="@drawable/icon_github">
|
||||
<intent android:action="android.intent.action.VIEW" android:data="https://github.com/hardcore-sushi/DroidFS"/>
|
||||
</Preference>
|
||||
|
||||
</PreferenceCategory>
|
||||
|
||||
</PreferenceScreen>
|
Loading…
Reference in New Issue
Block a user