"Copy" feature, FAB icons & GitHub link

This commit is contained in:
Hardcore Sushi 2020-08-01 15:39:00 +02:00
parent 34d7f19927
commit 5a444f2829
37 changed files with 547 additions and 223 deletions

View File

@ -26,7 +26,6 @@ android {
release { release {
minifyEnabled true minifyEnabled true
shrinkResources true shrinkResources true
useProguard true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
} }
} }

View File

@ -120,7 +120,9 @@ func openBackingDir(sessionID int, relPath string) (dirfd int, cName string, err
if i == len(parts)-1 { if i == len(parts)-1 {
cache_dirfd, err := syscall.Dup(dirfd) cache_dirfd, err := syscall.Dup(dirfd)
if err == nil { 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 break
} }

View File

@ -14,7 +14,7 @@
<application <application
android:name=".ColoredApplication" android:name=".ColoredApplication"
android:allowBackup="false" android:allowBackup="false"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/icon_launcher"
android:label="@string/app_name" android:label="@string/app_name"
android:supportsRtl="true" android:supportsRtl="true"
android:theme="@style/AppTheme" android:theme="@style/AppTheme"
@ -28,6 +28,7 @@
<activity android:name=".explorers.ExplorerActivityDrop" /> <activity android:name=".explorers.ExplorerActivityDrop" />
<activity <activity
android:name=".OpenActivity" android:name=".OpenActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="nosensor" android:screenOrientation="nosensor"
android:windowSoftInputMode="adjustPan"> android:windowSoftInputMode="adjustPan">
<intent-filter android:label="@string/share_menu_label"> <intent-filter android:label="@string/share_menu_label">
@ -39,9 +40,11 @@
</activity> </activity>
<activity <activity
android:name=".CreateActivity" android:name=".CreateActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="nosensor" /> android:screenOrientation="nosensor" />
<activity <activity
android:name=".ChangePasswordActivity" android:name=".ChangePasswordActivity"
android:parentActivityName=".MainActivity"
android:screenOrientation="nosensor" android:screenOrientation="nosensor"
android:windowSoftInputMode="adjustPan" /> android:windowSoftInputMode="adjustPan" />
<activity <activity

View File

@ -33,6 +33,7 @@ class ChangePasswordActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_change_password) setContentView(R.layout.activity_change_password)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false) usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs) fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)

View File

@ -9,6 +9,7 @@ class ConstValues {
const val saved_volumes_key = "saved_volumes" const val saved_volumes_key = "saved_volumes"
const val sort_order_key = "sort_order" const val sort_order_key = "sort_order"
val fakeUri: Uri = Uri.parse("fakeuri://droidfs") val fakeUri: Uri = Uri.parse("fakeuri://droidfs")
const val MAX_KERNEL_WRITE = 128*1024
const val wipe_passes = 2 const val wipe_passes = 2
private val fileExtensions = mapOf( private val fileExtensions = mapOf(
Pair("image", listOf("png", "jpg", "jpeg")), Pair("image", listOf("png", "jpg", "jpeg")),

View File

@ -31,6 +31,7 @@ class CreateActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_create) setContentView(R.layout.activity_create)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false) usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs) fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)

View File

@ -38,6 +38,7 @@ class OpenActivity : BaseActivity() {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_open) setContentView(R.layout.activity_open)
setSupportActionBar(toolbar) setSupportActionBar(toolbar)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false) usf_fingerprint = sharedPrefs.getBoolean("usf_fingerprint", false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && usf_fingerprint) {
fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs) fingerprintPasswordHashSaver = FingerprintPasswordHashSaver(this, sharedPrefs)

View File

@ -26,7 +26,6 @@ class SettingsActivity : BaseActivity(), PreferenceFragmentCompat.OnPreferenceSt
.beginTransaction() .beginTransaction()
.replace(R.id.settings, fragment) .replace(R.id.settings, fragment)
.commit() .commit()
setTheme(R.style.AppTheme)
} }
class SettingsFragment : PreferenceFragmentCompat() { class SettingsFragment : PreferenceFragmentCompat() {

View File

@ -63,11 +63,7 @@ open class BaseExplorerActivity : BaseActivity() {
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
list_explorer.adapter = explorerAdapter list_explorer.adapter = explorerAdapter
list_explorer.onItemClickListener = OnItemClickListener { _, _, position, _ -> onExplorerItemClick(position) } list_explorer.onItemClickListener = OnItemClickListener { _, _, position, _ -> onExplorerItemClick(position) }
list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ -> list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ -> onExplorerItemLongClick(position); true }
explorerAdapter.onItemLongClick(position)
invalidateOptionsMenu()
true
}
refresher.setOnRefreshListener { refresher.setOnRefreshListener {
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
refresher.isRefreshing = false refresher.isRefreshing = false
@ -90,13 +86,13 @@ open class BaseExplorerActivity : BaseActivity() {
explorerAdapter.onItemClick(position) explorerAdapter.onItemClick(position)
if (explorerAdapter.selectedItems.isEmpty()) { if (explorerAdapter.selectedItems.isEmpty()) {
if (!wasSelecting) { if (!wasSelecting) {
val fullPath = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name) val fullPath = explorerElements[position].getFullPath()
when { when {
explorerElements[position].isDirectory -> { explorerElements[position].isDirectory -> {
setCurrentPath(fullPath) setCurrentPath(fullPath)
} }
explorerElements[position].isParentFolder -> { explorerElements[position].isParentFolder -> {
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath)) setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
} }
isImage(fullPath) -> { isImage(fullPath) -> {
startFileViewer(ImageViewer::class.java, fullPath) startFileViewer(ImageViewer::class.java, fullPath)
@ -128,8 +124,14 @@ open class BaseExplorerActivity : BaseActivity() {
.show() .show()
} }
} }
} else {
invalidateOptionsMenu()
} }
} }
}
protected open fun onExplorerItemLongClick(position: Int) {
explorerAdapter.onItemLongClick(position)
invalidateOptionsMenu() invalidateOptionsMenu()
} }
@ -169,7 +171,7 @@ open class BaseExplorerActivity : BaseActivity() {
text_dir_empty.visibility = if (explorerElements.size == 0) View.VISIBLE else View.INVISIBLE text_dir_empty.visibility = if (explorerElements.size == 0) View.VISIBLE else View.INVISIBLE
sortExplorerElements() sortExplorerElements()
if (path.isNotEmpty()) { //not root 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) explorerAdapter.setExplorerElements(explorerElements)
currentDirectoryPath = path currentDirectoryPath = path
@ -192,16 +194,16 @@ open class BaseExplorerActivity : BaseActivity() {
protected open fun closeVolumeOnDestroy() { protected open fun closeVolumeOnDestroy() {
gocryptfsVolume.close() gocryptfsVolume.close()
RestrictedFileProvider.wipeAll() //additional security RestrictedFileProvider.wipeAll(this) //additional security
} }
override fun onBackPressed() { override fun onBackPressed() {
if (explorerAdapter.selectedItems.isEmpty()) { if (explorerAdapter.selectedItems.isEmpty()) {
val parentPath = PathUtils.get_parent_path(currentDirectoryPath) val parentPath = PathUtils.getParentPath(currentDirectoryPath)
if (parentPath == currentDirectoryPath) { if (parentPath == currentDirectoryPath) {
askCloseVolume() askCloseVolume()
} else { } else {
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath)) setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
} }
} else { } else {
unselectAll() unselectAll()
@ -266,23 +268,21 @@ open class BaseExplorerActivity : BaseActivity() {
} }
fun handleMenuItems(menu: Menu){ fun handleMenuItems(menu: Menu){
menu.findItem(R.id.explorer_menu_rename).isVisible = false menu.findItem(R.id.rename).isVisible = false
if (usf_open){ 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 val noItemSelected = explorerAdapter.selectedItems.isEmpty()
if (selectedItems.isEmpty()){ menu.findItem(R.id.sort).isVisible = noItemSelected
menu.findItem(R.id.close).isVisible = noItemSelected
if (noItemSelected){
toolbar.navigationIcon = null toolbar.navigationIcon = null
menu.findItem(R.id.explorer_menu_close).isVisible = true
menu.findItem(R.id.explorer_menu_sort).isVisible = true
} else { } else {
toolbar.setNavigationIcon(R.drawable.icon_arrow_back) toolbar.setNavigationIcon(R.drawable.icon_arrow_back)
menu.findItem(R.id.explorer_menu_close).isVisible = false if (explorerAdapter.selectedItems.size == 1) {
menu.findItem(R.id.explorer_menu_sort).isVisible = false menu.findItem(R.id.rename).isVisible = true
if (selectedItems.size == 1) { if (usf_open && explorerElements[explorerAdapter.selectedItems[0]].isRegularFile) {
menu.findItem(R.id.explorer_menu_rename).isVisible = true menu.findItem(R.id.external_open)?.isVisible = true
if (usf_open && explorerElements[selectedItems[0]].isRegularFile) {
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = true
} }
} }
} }
@ -294,7 +294,7 @@ open class BaseExplorerActivity : BaseActivity() {
unselectAll() unselectAll()
true true
} }
R.id.explorer_menu_sort -> { R.id.sort -> {
ColoredAlertDialogBuilder(this) ColoredAlertDialogBuilder(this)
.setTitle(R.string.sort_order) .setTitle(R.string.sort_order)
.setSingleChoiceItems(DialogSingleChoiceAdapter(this, sortModesEntries), currentSortModeIndex) { dialog, which -> .setSingleChoiceItems(DialogSingleChoiceAdapter(this, sortModesEntries), currentSortModeIndex) { dialog, which ->
@ -306,7 +306,7 @@ open class BaseExplorerActivity : BaseActivity() {
.show() .show()
true true
} }
R.id.explorer_menu_rename -> { R.id.rename -> {
val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null) val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null)
val oldName = explorerElements[explorerAdapter.selectedItems[0]].name val oldName = explorerElements[explorerAdapter.selectedItems[0]].name
val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text) val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text)
@ -331,14 +331,14 @@ open class BaseExplorerActivity : BaseActivity() {
dialog.show() dialog.show()
true true
} }
R.id.explorer_menu_external_open -> { R.id.external_open -> {
if (usf_open){ if (usf_open){
ExternalProvider.open(this, gocryptfsVolume, PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name)) ExternalProvider.open(this, gocryptfsVolume, PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name))
unselectAll() unselectAll()
} }
true true
} }
R.id.explorer_menu_close -> { R.id.close -> {
askCloseVolume() askCloseVolume()
true true
} }

View File

@ -8,9 +8,9 @@ import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.EditText import android.widget.EditText
import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import com.github.clans.fab.FloatingActionButton
import com.github.clans.fab.FloatingActionMenu import com.github.clans.fab.FloatingActionMenu
import kotlinx.android.synthetic.main.activity_explorer.* import kotlinx.android.synthetic.main.activity_explorer.*
import sushi.hardcore.droidfs.OpenActivity import sushi.hardcore.droidfs.OpenActivity
@ -18,7 +18,7 @@ import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.util.* import sushi.hardcore.droidfs.util.*
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File import java.io.File
import java.util.* import kotlin.collections.ArrayList
class ExplorerActivity : BaseExplorerActivity() { class ExplorerActivity : BaseExplorerActivity() {
private val PICK_DIRECTORY_REQUEST_CODE = 1 private val PICK_DIRECTORY_REQUEST_CODE = 1
@ -26,12 +26,20 @@ class ExplorerActivity : BaseExplorerActivity() {
private val PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE = 3 private val PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE = 3
private var usf_decrypt = false private var usf_decrypt = false
private var usf_share = false private var usf_share = false
private var modeSelectLocation = false
private val filesToCopy = ArrayList<ExplorerElement>()
override fun init() { override fun init() {
setContentView(R.layout.activity_explorer) setContentView(R.layout.activity_explorer)
usf_decrypt = sharedPrefs.getBoolean("usf_decrypt", false) usf_decrypt = sharedPrefs.getBoolean("usf_decrypt", false)
usf_share = sharedPrefs.getBoolean("usf_share", false) usf_share = sharedPrefs.getBoolean("usf_share", false)
} }
override fun onExplorerItemLongClick(position: Int) {
cancelCopy()
explorerAdapter.onItemLongClick(position)
invalidateOptionsMenu()
}
private fun createNewFile(fileName: String){ private fun createNewFile(fileName: String){
if (fileName.isEmpty()) { if (fileName.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() 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 failedItem = if (types[i] == 0) { //directory
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath) recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
} else { } 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) { if (failedItem != null) {
break break
@ -238,7 +246,7 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
} }
} else { } 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) { if (failedItem == null) {
stopTask { stopTask {
@ -269,24 +277,30 @@ class ExplorerActivity : BaseExplorerActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.explorer, menu) menuInflater.inflate(R.menu.explorer, menu)
handleMenuItems(menu) if (modeSelectLocation) {
if (usf_share){ menu.findItem(R.id.validate).isVisible = true
menu.findItem(R.id.explorer_menu_share).isVisible = false menu.findItem(R.id.close).isVisible = false
} } else {
val anyItemSelected = explorerAdapter.selectedItems.isNotEmpty() handleMenuItems(menu)
menu.findItem(R.id.explorer_menu_select_all).isVisible = anyItemSelected if (usf_share){
menu.findItem(R.id.explorer_menu_delete).isVisible = anyItemSelected menu.findItem(R.id.share).isVisible = false
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 (!containsDir) { val anyItemSelected = explorerAdapter.selectedItems.isNotEmpty()
menu.findItem(R.id.explorer_menu_share).isVisible = true 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 return true
@ -294,12 +308,67 @@ class ExplorerActivity : BaseExplorerActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.explorer_menu_select_all -> { android.R.id.home -> {
cancelCopy()
super.onOptionsItemSelected(item)
}
R.id.select_all -> {
explorerAdapter.selectAll() explorerAdapter.selectAll()
invalidateOptionsMenu() invalidateOptionsMenu()
true 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 size = explorerAdapter.selectedItems.size
val dialog = ColoredAlertDialogBuilder(this) val dialog = ColoredAlertDialogBuilder(this)
dialog.setTitle(R.string.warning) dialog.setTitle(R.string.warning)
@ -313,17 +382,16 @@ class ExplorerActivity : BaseExplorerActivity() {
dialog.show() dialog.show()
true true
} }
R.id.explorer_menu_share -> { R.id.share -> {
val paths: MutableList<String> = ArrayList() val paths: MutableList<String> = ArrayList()
for (i in explorerAdapter.selectedItems) { for (i in explorerAdapter.selectedItems) {
val e = explorerElements[i] paths.add(explorerElements[i].getFullPath())
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
} }
ExternalProvider.share(this, gocryptfsVolume, paths) ExternalProvider.share(this, gocryptfsVolume, paths)
unselectAll() unselectAll()
true true
} }
R.id.explorer_menu_decrypt -> { R.id.decrypt -> {
val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE) val i = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE)
startActivityForResult(i, PICK_DIRECTORY_REQUEST_CODE) startActivityForResult(i, PICK_DIRECTORY_REQUEST_CODE)
true true
@ -332,12 +400,86 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
} }
private fun importFileFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, full_path: String, output_dir: String): Boolean { private fun recursiveMapFiles(rootPath: String): MutableList<ExplorerElement> {
val outputPath = PathUtils.path_join(output_dir, File(full_path).name) 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 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) { if (srcHandleID != -1) {
val dstHandleID = gocryptfsVolume.open_write_mode(outputPath) val dstHandleID = gocryptfsVolume.open_write_mode(dstPath)
if (dstHandleID != -1) { if (dstHandleID != -1) {
var length: Int var length: Int
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS) val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
@ -358,22 +500,24 @@ class ExplorerActivity : BaseExplorerActivity() {
return success return success
} }
private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, output_dir: String): String? { private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, outputPath: String): String? {
val directoryPath = PathUtils.path_join(output_dir, File(remote_directory_path).name) val mappedElements = recursiveMapFiles(remote_directory_path)
if (!gocryptfsVolume.path_exists(directoryPath)) { val dstDirectoryPath = PathUtils.path_join(outputPath, File(remote_directory_path).name)
if (!gocryptfsVolume.mkdir(directoryPath)) { if (!gocryptfsVolume.path_exists(dstDirectoryPath)) {
return directoryPath if (!gocryptfsVolume.mkdir(dstDirectoryPath)) {
return dstDirectoryPath
} }
} }
val explorerElements = remote_gocryptfsVolume.list_dir(remote_directory_path) for (e in mappedElements) {
for (e in explorerElements) { val srcPath = e.getFullPath()
val fullPath = PathUtils.path_join(remote_directory_path, e.name) val dstPath = PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(remote_directory_path, srcPath))
if (e.isDirectory) { if (e.isDirectory) {
val failedItem = recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath) if (!gocryptfsVolume.mkdir(dstPath)){
failedItem?.let { return it } return srcPath
}
} else { } else {
if (!importFileFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)) { if (!importFileFromOtherVolume(remote_gocryptfsVolume, srcPath, dstPath)) {
return fullPath return srcPath
} }
} }
} }

View File

@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.explorers
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.util.Log
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
@ -16,48 +17,48 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.explorer_drop, menu) menuInflater.inflate(R.menu.explorer_drop, menu)
handleMenuItems(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 return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.explorer_menu_validate -> { R.id.validate -> {
val alertDialog = ColoredAlertDialogBuilder(this) val alertDialog = ColoredAlertDialogBuilder(this)
alertDialog.setCancelable(false) alertDialog.setCancelable(false)
alertDialog.setPositiveButton(R.string.ok) { _, _ -> finish() } alertDialog.setPositiveButton(R.string.ok) { _, _ -> finish() }
var error_msg: String? = null var errorMsg: String? = null
val extras = intent.extras val extras = intent.extras
if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){ if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){
if (intent.action == Intent.ACTION_SEND) { if (intent.action == Intent.ACTION_SEND) {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM) val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
val output_path = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri)) val outputPath = 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) errorMsg = if (gocryptfsVolume.import_file(this, uri, outputPath)) null else getString(R.string.import_failed, outputPath)
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE) { } else if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM) val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
if (uris != null){ if (uris != null){
for (uri in uris) { for (uri in uris) {
val output_path = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri)) val outputPath = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri))
if (!gocryptfsVolume.import_file(this, uri, output_path)) { if (!gocryptfsVolume.import_file(this, uri, outputPath)) {
error_msg = getString(R.string.import_failed, output_path) errorMsg = getString(R.string.import_failed, outputPath)
break break
} }
} }
} else { } else {
error_msg = getString(R.string.share_intent_parsing_failed) errorMsg = getString(R.string.share_intent_parsing_failed)
} }
} else { } else {
error_msg = getString(R.string.share_intent_parsing_failed) errorMsg = getString(R.string.share_intent_parsing_failed)
} }
} else { } 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.setTitle(R.string.success_import)
alertDialog.setMessage(R.string.success_import_msg) alertDialog.setMessage(R.string.success_import_msg)
} else { } else {
alertDialog.setTitle(R.string.error) alertDialog.setTitle(R.string.error)
alertDialog.setMessage(error_msg) alertDialog.setMessage(errorMsg)
} }
alertDialog.show() alertDialog.show()
true true

View File

@ -27,7 +27,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
setCurrentPath(full_path) setCurrentPath(full_path)
} }
explorerElements[position].isParentFolder -> { explorerElements[position].isParentFolder -> {
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath)) setCurrentPath(PathUtils.getParentPath(currentDirectoryPath))
} }
else -> { else -> {
result_intent.putExtra("path", full_path) result_intent.putExtra("path", full_path)
@ -43,19 +43,19 @@ class ExplorerActivityPick : BaseExplorerActivity() {
menuInflater.inflate(R.menu.explorer_pick, menu) menuInflater.inflate(R.menu.explorer_pick, menu)
handleMenuItems(menu) handleMenuItems(menu)
val any_item_selected = explorerAdapter.selectedItems.isNotEmpty() val any_item_selected = explorerAdapter.selectedItems.isNotEmpty()
menu.findItem(R.id.explorer_menu_select_all).isVisible = any_item_selected menu.findItem(R.id.select_all).isVisible = any_item_selected
menu.findItem(R.id.explorer_menu_validate).isVisible = any_item_selected menu.findItem(R.id.validate).isVisible = any_item_selected
return true return true
} }
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.explorer_menu_select_all -> { R.id.select_all -> {
explorerAdapter.selectAll() explorerAdapter.selectAll()
invalidateOptionsMenu() invalidateOptionsMenu()
true true
} }
R.id.explorer_menu_validate -> { R.id.validate -> {
val paths = ArrayList<String>() val paths = ArrayList<String>()
val types = ArrayList<Int>() val types = ArrayList<Int>()
for (i in explorerAdapter.selectedItems) { for (i in explorerAdapter.selectedItems) {
@ -79,7 +79,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
override fun closeVolumeOnDestroy() { override fun closeVolumeOnDestroy() {
//don't close volume //don't close volume
RestrictedFileProvider.wipeAll() RestrictedFileProvider.wipeAll(this)
} }
override fun closeVolumeOnUserExit() { override fun closeVolumeOnUserExit() {

View File

@ -1,8 +1,9 @@
package sushi.hardcore.droidfs.explorers package sushi.hardcore.droidfs.explorers
import sushi.hardcore.droidfs.util.PathUtils
import java.util.* 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 mTime = Date((mtime * 1000).toString().toLong())
val isDirectory: Boolean val isDirectory: Boolean
@ -14,4 +15,7 @@ class ExplorerElement(val name: String, val elementType: Short, val size: Long,
val isRegularFile: Boolean val isRegularFile: Boolean
get() = elementType.toInt() == 1 get() = elementType.toInt() == 1
fun getFullPath(): String {
return PathUtils.path_join(parentPath, name)
}
} }

View File

@ -4,6 +4,8 @@ import android.net.Uri
import com.google.android.exoplayer2.upstream.* import com.google.android.exoplayer2.upstream.*
import sushi.hardcore.droidfs.ConstValues import sushi.hardcore.droidfs.ConstValues
import sushi.hardcore.droidfs.util.GocryptfsVolume 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 { class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource {
private var handleID = -1 private var handleID = -1
@ -34,15 +36,20 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
if (fileOffset >= fileSize){ if (fileOffset >= fileSize){
return -1 return -1
} }
val tmpBuff = if (fileOffset+readLength > fileSize){ var totalRead = 0
ByteArray((fileSize-fileOffset).toInt()) for (i in 0 until ceil(readLength.toDouble()/ConstValues.MAX_KERNEL_WRITE).toInt()){
} else { val tmpReadLength = min(readLength-totalRead, ConstValues.MAX_KERNEL_WRITE)
ByteArray(readLength) 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) return totalRead
fileOffset += read
System.arraycopy(tmpBuff, 0, buffer, offset, read)
return read
} }
class Factory(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource.Factory{ class Factory(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource.Factory{

View File

@ -2,12 +2,17 @@ package sushi.hardcore.droidfs.provider
import android.content.ContentProvider import android.content.ContentProvider
import android.content.ContentValues import android.content.ContentValues
import android.content.Context
import android.database.Cursor import android.database.Cursor
import android.database.MatrixCursor import android.database.MatrixCursor
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
import android.net.Uri import android.net.Uri
import android.os.ParcelFileDescriptor import android.os.ParcelFileDescriptor
import android.provider.MediaStore import android.provider.MediaStore
import sushi.hardcore.droidfs.BuildConfig 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 sushi.hardcore.droidfs.util.Wiper
import java.io.File import java.io.File
import java.util.* import java.util.*
@ -15,48 +20,105 @@ import java.util.regex.Pattern
class RestrictedFileProvider: ContentProvider() { class RestrictedFileProvider: ContentProvider() {
companion object { 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 const val AUTHORITY = BuildConfig.APPLICATION_ID + ".temporary_provider"
private val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY") private val CONTENT_URI: Uri = Uri.parse("content://$AUTHORITY")
const val TEMPORARY_FILES_DIR_NAME = "temp" const val TEMPORARY_FILES_DIR_NAME = "temp"
private val UUID_PATTERN = Pattern.compile("[a-fA-F0-9-]+") private val UUID_PATTERN = Pattern.compile("[a-fA-F0-9-]+")
private lateinit var tempFilesDir: File 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? { fun newFile(fileName: String): Uri? {
val uuid = UUID.randomUUID().toString() val uuid = UUID.randomUUID().toString()
val file = File(tempFilesDir, uuid) val file = File(tempFilesDir, uuid)
return if (file.createNewFile()){ return if (file.createNewFile()){
tempFiles[uuid] = TemporaryFile(fileName, file) val contentValues = ContentValues()
Uri.withAppendedPath(CONTENT_URI, uuid) 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 { } else {
null null
} }
} }
fun wipeAll() { fun wipeAll(context: Context) {
tempFilesDir.listFiles()?.let{ tempFilesDir.listFiles()?.let{
for (file in it) { for (file in it) {
Wiper.wipe(file) 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 val uuid = uri.lastPathSegment
if (uuid != null) { if (uuid != null) {
if (UUID_PATTERN.matcher(uuid).matches()) { if (isValidUUID(uuid)) {
return tempFiles[uuid] return uuid
} }
} }
return null 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 { override fun onCreate(): Boolean {
context?.let { context?.let {
dbHelper = RestrictedDatabaseHelper(it)
tempFilesDir = File(it.cacheDir, TEMPORARY_FILES_DIR_NAME) tempFilesDir = File(it.cacheDir, TEMPORARY_FILES_DIR_NAME)
return tempFilesDir.mkdirs() 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? { override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
val temporaryFile = getFileFromUri(uri) val temporaryFile = getFileFromUri(uri)
if (temporaryFile != null) { temporaryFile?.let{
val cursor = MatrixCursor( val fileName =
arrayOf( dbHelper?.readableDatabase?.query(TABLE_FILES, arrayOf(TemporaryFileColumns.COLUMN_NAME), TemporaryFileColumns.COLUMN_UUID + "=?", arrayOf(uri.lastPathSegment), null, null, null)
MediaStore.MediaColumns.DISPLAY_NAME, fileName?.let{
MediaStore.MediaColumns.SIZE if (fileName.moveToNext()) {
) val cursor = MatrixCursor(
) arrayOf(
cursor.newRow() MediaStore.MediaColumns.DISPLAY_NAME,
.add(temporaryFile.fileName) MediaStore.MediaColumns.SIZE
.add(temporaryFile.file.length()) )
return cursor )
cursor.newRow()
.add(fileName.getString(0))
.add(temporaryFile.length())
fileName.close()
return cursor
}
fileName.close()
}
} }
return null return null
} }
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int { override fun delete(uri: Uri, givenSelection: String?, givenSelectionArgs: Array<String>?): Int {
val temporaryFile = getFileFromUri(uri) val uuid = getUuidFromUri(uri)
if (temporaryFile != null) { uuid?.let{
Wiper.wipe(temporaryFile.file) val selection = concatenateWhere(givenSelection ?: "" , TemporaryFileColumns.COLUMN_UUID + "=?")
tempFiles.remove(uri.lastPathSegment) 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 { override fun getType(uri: Uri): String {
@ -104,7 +185,7 @@ class RestrictedFileProvider: ContentProvider() {
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? { override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
if (("w" in mode && callingPackage == BuildConfig.APPLICATION_ID) || "w" !in mode) { if (("w" in mode && callingPackage == BuildConfig.APPLICATION_ID) || "w" !in mode) {
getFileFromUri(uri)?.let{ getFileFromUri(uri)?.let{
return ParcelFileDescriptor.open(it.file, ParcelFileDescriptor.parseMode(mode)) return ParcelFileDescriptor.open(it, ParcelFileDescriptor.parseMode(mode))
} }
} else { } else {
throw SecurityException("Read-only access") throw SecurityException("Read-only access")

View File

@ -9,7 +9,6 @@ import sushi.hardcore.droidfs.provider.RestrictedFileProvider
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File import java.io.File
import java.net.URLConnection import java.net.URLConnection
import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
object ExternalProvider { object ExternalProvider {
@ -102,13 +101,13 @@ object ExternalProvider {
fun removeFiles(context: Context) { fun removeFiles(context: Context) {
Thread{ Thread{
val wiped = ArrayList<Uri>() val success = ArrayList<Uri>()
for (uri in storedFiles) { for (uri in storedFiles){
if (Wiper.wipe(context, uri) == null){ if (context.contentResolver.delete(uri, null, null) == 1){
wiped.add(uri) success.add(uri)
} }
} }
for (uri in wiped){ for (uri in success){
storedFiles.remove(uri) storedFiles.remove(uri)
} }
}.start() }.start()

View File

@ -2,7 +2,6 @@ package sushi.hardcore.droidfs.util
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import java.io.* import java.io.*
@ -93,10 +92,10 @@ class GocryptfsVolume(var sessionID: Int) {
fun export_file(handleID: Int, os: OutputStream): Boolean { fun export_file(handleID: Int, os: OutputStream): Boolean {
var offset: Long = 0 var offset: Long = 0
val io_buffer = ByteArray(DefaultBS) val ioBuffer = ByteArray(DefaultBS)
var length: Int var length: Int
while (native_read_file(sessionID, handleID, offset, io_buffer).also { length = it } > 0){ while (native_read_file(sessionID, handleID, offset, ioBuffer).also { length = it } > 0){
os.write(io_buffer, 0, length) os.write(ioBuffer, 0, length)
offset += length.toLong() offset += length.toLong()
} }
os.close() os.close()
@ -105,10 +104,10 @@ class GocryptfsVolume(var sessionID: Int) {
fun export_file(src_path: String, os: OutputStream): Boolean { fun export_file(src_path: String, os: OutputStream): Boolean {
var success = false var success = false
val src_handleID = open_read_mode(src_path) val srcHandleId = open_read_mode(src_path)
if (src_handleID != -1) { if (srcHandleId != -1) {
success = export_file(src_handleID, os) success = export_file(srcHandleId, os)
close_file(src_handleID) close_file(srcHandleId)
} }
return success return success
} }
@ -127,10 +126,10 @@ class GocryptfsVolume(var sessionID: Int) {
fun import_file(inputStream: InputStream, handleID: Int): Boolean { fun import_file(inputStream: InputStream, handleID: Int): Boolean {
var offset: Long = 0 var offset: Long = 0
val io_buffer = ByteArray(DefaultBS) val ioBuffer = ByteArray(DefaultBS)
var length: Int var length: Int
while (inputStream.read(io_buffer).also { length = it } > 0) { while (inputStream.read(ioBuffer).also { length = it } > 0) {
val written = native_write_file(sessionID, handleID, offset, io_buffer, length).toLong() val written = native_write_file(sessionID, handleID, offset, ioBuffer, length).toLong()
if (written == length.toLong()) { if (written == length.toLong()) {
offset += written offset += written
} else { } else {
@ -145,10 +144,10 @@ class GocryptfsVolume(var sessionID: Int) {
fun import_file(inputStream: InputStream, dst_path: String): Boolean { fun import_file(inputStream: InputStream, dst_path: String): Boolean {
var success = false var success = false
val dst_handleID = open_write_mode(dst_path) val dstHandleId = open_write_mode(dst_path)
if (dst_handleID != -1) { if (dstHandleId != -1) {
success = import_file(inputStream, dst_handleID) success = import_file(inputStream, dstHandleId)
close_file(dst_handleID) close_file(dstHandleId)
} }
return success return success
} }

View File

@ -14,7 +14,7 @@ import java.text.DecimalFormat;
public class PathUtils { public class PathUtils {
public static String get_parent_path(String path){ public static String getParentPath(String path){
if (path.endsWith("/")){ if (path.endsWith("/")){
String a = path.substring(0, path.length()-2); String a = path.substring(0, path.length()-2);
if (a.contains("/")){ if (a.contains("/")){
@ -44,16 +44,22 @@ public class PathUtils {
return result.substring(0, result.length()-1); 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){ public static String getFilenameFromURI(Context context, Uri uri){
String result = null; String result = null;
if (uri.getScheme().equals("content")){ if (uri.getScheme().equals("content")){
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null); Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
try { if (cursor != null){
if (cursor != null && cursor.moveToFirst()){ try {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); if (cursor.moveToFirst()){
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
} }
} finally {
cursor.close();
} }
} }
if (result == null){ if (result == null){

View 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
}
}

View File

@ -1,8 +1,6 @@
package sushi.hardcore.droidfs.widgets package sushi.hardcore.droidfs.widgets
import android.content.Context import android.content.Context
import android.graphics.Color
import android.graphics.drawable.Drawable
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.core.graphics.drawable.DrawableCompat import androidx.core.graphics.drawable.DrawableCompat
import androidx.preference.Preference import androidx.preference.Preference
@ -20,9 +18,8 @@ object ThemeColor {
for (i in 0 until preference.preferenceCount) { for (i in 0 until preference.preferenceCount) {
tintPreferenceIcons(preference.getPreference(i), color) tintPreferenceIcons(preference.getPreference(i), color)
} }
} else { } else if (preference.icon != null) {
val icon: Drawable = preference.icon DrawableCompat.setTint(preference.icon, color)
DrawableCompat.setTint(icon, color)
} }
} }
} }

View File

@ -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"); 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")); 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); jobject element_list = (*env)->NewObject(env, java_util_ArrayList, java_util_ArrayList_init, elements.r2);
unsigned int c = 0; unsigned int c = 0;
@ -254,7 +254,7 @@ Java_sushi_hardcore_droidfs_util_GocryptfsVolume_native_1list_1dir(JNIEnv *env,
type = 1; //regular file type = 1; //regular file
} }
jstring jname = (*env)->NewStringUTF(env, name); 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); (*env)->CallBooleanMethod(env, element_list, java_util_ArrayList_add, explorerElement);
c += name_len+1; c += name_len+1;
} }

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

View File

@ -1,12 +1,4 @@
<?xml version="1.0" encoding="utf-8"?> <vector android:height="24dp" android:viewportHeight="500"
<vector xmlns:android="http://schemas.android.com/apk/res/android" android:viewportWidth="500" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
android:width="24dp" <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"/>
android:height="24dp" </vector>
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>

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

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

View 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

View File

@ -47,37 +47,39 @@
app:menu_labels_style="@style/fab_label"> app:menu_labels_style="@style/fab_label">
<sushi.hardcore.droidfs.widgets.ColoredFAB <sushi.hardcore.droidfs.widgets.ColoredFAB
android:id="@+id/fab_import_file_from_other_volume"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_size="mini" app:fab_size="mini"
app:fab_label="@string/import_from_other_volume" app:fab_label="@string/import_from_other_volume"
android:src="@drawable/icon_add" android:src="@drawable/icon_transfert"
android:onClick="onClickAddFileFromOtherVolume"/> android:onClick="onClickAddFileFromOtherVolume"/>
<sushi.hardcore.droidfs.widgets.ColoredFAB <sushi.hardcore.droidfs.widgets.ColoredFAB
android:id="@+id/fab_import_file"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_size="mini" app:fab_size="mini"
app:fab_label="@string/import_files" app:fab_label="@string/import_files"
android:src="@drawable/icon_add" android:src="@drawable/icon_encrypt"
android:onClick="onClickAddFile"/> android:onClick="onClickAddFile"/>
<sushi.hardcore.droidfs.widgets.ColoredFAB <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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_size="mini" app:fab_size="mini"
app:fab_label="@string/new_file" app:fab_label="@string/new_file"
android:src="@drawable/icon_add" android:src="@drawable/icon_file_unknown"
android:onClick="onClickCreateFile"/> android:onClick="onClickCreateFile"/>
<sushi.hardcore.droidfs.widgets.ColoredFAB <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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_size="mini" app:fab_size="mini"
app:fab_label="@string/mkdir" app:fab_label="@string/mkdir"
android:src="@drawable/icon_add" android:src="@drawable/icon_folder"
android:onClick="onClickAddFolder" android:onClick="onClickAddFolder"
tools:ignore="OnClick" /> tools:ignore="OnClick" />

View File

@ -45,12 +45,12 @@
app:menu_labels_style="@style/fab_label"> app:menu_labels_style="@style/fab_label">
<sushi.hardcore.droidfs.widgets.ColoredFAB <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_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
app:fab_size="mini" app:fab_size="mini"
app:fab_label="@string/mkdir" app:fab_label="@string/mkdir"
android:src="@drawable/icon_add" android:src="@drawable/icon_folder"
android:onClick="onClickAddFolder"/> android:onClick="onClickAddFolder"/>
</sushi.hardcore.droidfs.widgets.ColoredFAM> </sushi.hardcore.droidfs.widgets.ColoredFAM>

View File

@ -3,52 +3,63 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/explorer_menu_select_all" android:id="@+id/select_all"
app:showAsAction="always" app:showAsAction="always"
android:visible="false" android:visible="false"
android:icon="@drawable/icon_select_all"/> android:icon="@drawable/icon_select_all"/>
<item <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" app:showAsAction="always"
android:visible="false" android:visible="false"
android:icon="@drawable/icon_delete" /> android:icon="@drawable/icon_delete" />
<item <item
android:id="@+id/explorer_menu_share" android:id="@+id/decrypt"
app:showAsAction="always" app:showAsAction="ifRoom"
android:visible="false"
android:icon="@drawable/icon_share"
app:iconTint="@color/menuIcon"/>
<item
android:id="@+id/explorer_menu_decrypt"
app:showAsAction="always"
android:visible="false" android:visible="false"
android:icon="@drawable/icon_decrypt" android:icon="@drawable/icon_decrypt"
app:iconTint="@color/menuIcon"/> android:title="@string/decrypt"/>
<item <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" app:showAsAction="never"
android:visible="false" android:visible="false"
android:title="@string/external_open"/> android:title="@string/external_open"/>
<item <item
android:id="@+id/explorer_menu_rename" android:id="@+id/rename"
app:showAsAction="never" app:showAsAction="never"
android:visible="false" android:visible="false"
android:title="@string/rename"/> android:title="@string/rename"/>
<item <item
android:id="@+id/explorer_menu_sort" android:id="@+id/validate"
app:showAsAction="always" app:showAsAction="ifRoom"
android:icon="@drawable/icon_sort" android:visible="false"
app:iconTint="@color/menuIcon"/> android:icon="@drawable/icon_check"/>
<item <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" app:showAsAction="always"
android:icon="@drawable/icon_close"/> android:icon="@drawable/icon_close"/>
</menu> </menu>

View File

@ -3,31 +3,30 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/explorer_menu_external_open" android:id="@+id/external_open"
app:showAsAction="never" app:showAsAction="never"
android:visible="false" android:visible="false"
android:title="@string/external_open"/> android:title="@string/external_open"/>
<item <item
android:id="@+id/explorer_menu_rename" android:id="@+id/rename"
app:showAsAction="never" app:showAsAction="never"
android:visible="false" android:visible="false"
android:title="@string/rename"/> android:title="@string/rename"/>
<item <item
android:id="@+id/explorer_menu_validate" android:id="@+id/validate"
app:showAsAction="always" app:showAsAction="ifRoom"
android:icon="@drawable/icon_check"/> android:icon="@drawable/icon_check"/>
<item <item
android:id="@+id/explorer_menu_sort" android:id="@+id/sort"
app:showAsAction="always" app:showAsAction="always"
android:icon="@drawable/icon_sort" android:icon="@drawable/icon_sort"/>
app:iconTint="@color/menuIcon"/>
<item <item
android:id="@+id/explorer_menu_close" android:id="@+id/close"
app:showAsAction="always" app:showAsAction="always"
android:icon="@drawable/icon_close"/> android:icon="@drawable/icon_close"/>
</menu> </menu>

View File

@ -3,32 +3,31 @@
xmlns:app="http://schemas.android.com/apk/res-auto"> xmlns:app="http://schemas.android.com/apk/res-auto">
<item <item
android:id="@+id/explorer_menu_validate" android:id="@+id/validate"
app:showAsAction="always" app:showAsAction="ifRoom"
android:visible="false" android:visible="false"
android:icon="@drawable/icon_check"/> android:icon="@drawable/icon_check"/>
<item <item
android:id="@+id/explorer_menu_select_all" android:id="@+id/select_all"
app:showAsAction="always" app:showAsAction="ifRoom"
android:visible="false" android:visible="false"
android:icon="@drawable/icon_select_all"/> android:icon="@drawable/icon_select_all"/>
<item <item
android:id="@+id/explorer_menu_rename" android:id="@+id/rename"
app:showAsAction="never" app:showAsAction="never"
android:visible="false" android:visible="false"
android:title="@string/rename"/> android:title="@string/rename"/>
<item <item
android:id="@+id/explorer_menu_sort" android:id="@+id/sort"
app:showAsAction="always" app:showAsAction="always"
android:icon="@drawable/icon_sort" android:icon="@drawable/icon_sort"/>
app:iconTint="@color/menuIcon"/>
<item <item
android:id="@+id/explorer_menu_close" android:id="@+id/close"
app:showAsAction="always" app:showAsAction="always"
android:icon="@drawable/icon_close"/> android:icon="@drawable/icon_close"/>
</menu> </menu>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

@ -12,7 +12,6 @@
<color name="textColor">#FFFFFF</color> <color name="textColor">#FFFFFF</color>
<color name="buttonBackgroundColor">#5B5A5C</color> <color name="buttonBackgroundColor">#5B5A5C</color>
<color name="item_selected">#66666666</color> <color name="item_selected">#66666666</color>
<color name="menuIcon">#FFFFFF</color>
<color name="infoBarBackgroundColor">#111111</color> <color name="infoBarBackgroundColor">#111111</color>
<color name="fingerprint_wait">#FFFFFF</color> <color name="fingerprint_wait">#FFFFFF</color>
<color name="fingerprint_success">#6CC341</color> <color name="fingerprint_success">#6CC341</color>

View File

@ -21,7 +21,7 @@
<string name="success_import_msg">The selected files have been successfully imported.</string> <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="import_failed">Import of %1$s failed.</string>
<string name="export_failed">Export 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="success_export_msg">The selected files have been successfully exported.</string>
<string name="remove_failed">Deletion of %1$s failed</string> <string name="remove_failed">Deletion of %1$s failed</string>
<string name="passwords_mismatch">Passwords don\'t match</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_wipe">Wiping original files…</string>
<string name="loading_msg_export">Exporting 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="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> </resources>

View File

@ -24,11 +24,24 @@
</PreferenceCategory> </PreferenceCategory>
<PreferenceCategory android:title="@string/unsafe_features"> <PreferenceCategory android:title="@string/unsafe_features">
<Preference <Preference
android:title="@string/manage_unsafe_features" android:title="@string/manage_unsafe_features"
android:summary="@string/manage_unsafe_features_summary" android:summary="@string/manage_unsafe_features_summary"
android:icon="@drawable/icon_warning" android:icon="@drawable/icon_warning"
android:fragment="sushi.hardcore.droidfs.SettingsActivity$UnsafeFeaturesSettingsFragment"/> 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> </PreferenceCategory>
</PreferenceScreen> </PreferenceScreen>