"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 {
minifyEnabled true
shrinkResources true
useProguard true
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 {
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
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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,14 +277,19 @@ class ExplorerActivity : BaseExplorerActivity() {
override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.explorer, menu)
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.explorer_menu_share).isVisible = false
menu.findItem(R.id.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
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) {
@ -286,7 +299,8 @@ class ExplorerActivity : BaseExplorerActivity() {
}
}
if (!containsDir) {
menu.findItem(R.id.explorer_menu_share).isVisible = true
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
}
}
}

View File

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

View File

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

View File

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

View File

@ -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){
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(readLength)
ByteArray(tmpReadLength)
}
val read = gocryptfsVolume.read_file(handleID, fileOffset, tmpBuff)
System.arraycopy(tmpBuff, 0, buffer, offset+totalRead, read)
fileOffset += read
System.arraycopy(tmpBuff, 0, buffer, offset, read)
return read
totalRead += read
}
return totalRead
}
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.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)
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,7 +135,11 @@ 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) {
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,
@ -81,20 +147,35 @@ class RestrictedFileProvider: ContentProvider() {
)
)
cursor.newRow()
.add(temporaryFile.fileName)
.add(temporaryFile.file.length())
.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)
}
return 1
}
files.close()
return dbHelper?.writableDatabase?.delete(TABLE_FILES, selection, selectionArgs) ?: 0
}
}
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")

View File

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

View File

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

View File

@ -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,18 +44,24 @@ 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);
if (cursor != null){
try {
if (cursor != null && cursor.moveToFirst()){
if (cursor.moveToFirst()){
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
}
if (result == null){
result = uri.getPath();
int cut = result.lastIndexOf('/');

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

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");
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;
}

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

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

View File

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

View File

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

View File

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

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

View File

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

View File

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

View File

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