forked from hardcoresushi/DroidFS
RestrictedFileProvider & Playing media without disk writes
This commit is contained in:
parent
ce74aad5cf
commit
24cfc1093e
@ -6,12 +6,16 @@ android {
|
||||
compileSdkVersion 29
|
||||
buildToolsVersion "30.0.0"
|
||||
|
||||
compileOptions {
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "sushi.hardcore.droidfs"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
targetSdkVersion 29
|
||||
versionCode 1
|
||||
versionName "1.0.1"
|
||||
versionName "1.1.0"
|
||||
|
||||
ndk {
|
||||
abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||
@ -35,7 +39,7 @@ android {
|
||||
dependencies {
|
||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
implementation 'androidx.core:core-ktx:1.3.0'
|
||||
implementation 'androidx.core:core-ktx:1.3.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||
testImplementation 'junit:junit:4.12'
|
||||
|
||||
@ -44,4 +48,6 @@ dependencies {
|
||||
implementation 'com.github.clans:fab:1.6.4'
|
||||
implementation 'com.jaredrummler:cyanea:1.0.2'
|
||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-core:2.11.7'
|
||||
implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.7'
|
||||
}
|
||||
|
@ -771,7 +771,6 @@ func gcf_open_read_mode(sessionID int, path string) int {
|
||||
}
|
||||
defer syscall.Close(dirfd)
|
||||
fd, err := syscallcompat.Openat(dirfd, cName, newFlags, 0)
|
||||
// Handle a few specific errors
|
||||
if err != nil {
|
||||
return -1
|
||||
}
|
||||
|
@ -2,6 +2,10 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="sushi.hardcore.droidfs">
|
||||
|
||||
<permission
|
||||
android:name="${applicationId}.WRITE_TEMPORARY_STORAGE"
|
||||
android:protectionLevel="signature"/>
|
||||
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
@ -13,7 +17,8 @@
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
android:theme="@style/AppTheme"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
<activity
|
||||
android:name=".SettingsActivity"
|
||||
android:label="@string/title_activity_settings"
|
||||
@ -53,10 +58,10 @@
|
||||
<activity android:name=".file_viewers.TextEditor" android:configChanges="screenSize|orientation" />
|
||||
|
||||
<provider
|
||||
android:name=".provider.TemporaryFileProvider"
|
||||
android:authorities="${applicationId}.tempstorage"
|
||||
android:name=".provider.RestrictedFileProvider"
|
||||
android:authorities="${applicationId}.temporary_provider"
|
||||
android:exported="true"
|
||||
android:writePermission="${applicationId}.READ_TEMPORARY_STORAGE" />
|
||||
android:writePermission="${applicationId}.WRITE_TEMPORARY_STORAGE"/>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
@ -3,7 +3,6 @@ package sushi.hardcore.droidfs
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.WindowManager
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.preference.PreferenceManager
|
||||
import com.jaredrummler.cyanea.app.CyaneaAppCompatActivity
|
||||
|
@ -7,7 +7,6 @@ import android.os.Bundle
|
||||
import android.text.Editable
|
||||
import android.text.TextWatcher
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_change_password.*
|
||||
@ -18,7 +17,7 @@ import kotlinx.android.synthetic.main.activity_change_password.saved_path_listvi
|
||||
import kotlinx.android.synthetic.main.toolbar.*
|
||||
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
|
||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
@ -80,7 +79,7 @@ class ChangePasswordActivity : BaseActivity() {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||
if (data != null) {
|
||||
val path = FilesUtils.getFullPathFromTreeUri(data.data, this)
|
||||
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
|
||||
edit_volume_path.setText(path)
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package sushi.hardcore.droidfs
|
||||
|
||||
import android.net.Uri
|
||||
import java.io.File
|
||||
|
||||
class ConstValues {
|
||||
@ -7,6 +8,7 @@ class ConstValues {
|
||||
const val creator = "DroidFS"
|
||||
const val saved_volumes_key = "saved_volumes"
|
||||
const val sort_order_key = "sort_order"
|
||||
val fakeUri = Uri.parse("fakeuri://droidfs")
|
||||
const val wipe_passes = 2
|
||||
const val seek_bar_inc = 200
|
||||
private val fileExtensions = mapOf(
|
||||
|
@ -5,7 +5,6 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_create.*
|
||||
import kotlinx.android.synthetic.main.activity_create.checkbox_remember_path
|
||||
@ -15,7 +14,7 @@ import kotlinx.android.synthetic.main.activity_create.edit_volume_path
|
||||
import kotlinx.android.synthetic.main.toolbar.*
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
@ -57,7 +56,7 @@ class CreateActivity : BaseActivity() {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||
if (data != null) {
|
||||
val path = FilesUtils.getFullPathFromTreeUri(data.data, this)
|
||||
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
|
||||
edit_volume_path.setText(path)
|
||||
}
|
||||
}
|
||||
|
@ -35,14 +35,14 @@ class MainActivity : BaseActivity() {
|
||||
if (!storageAvailable) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.storage_unavailable)
|
||||
.setMessage(getString(R.string.storage_unavailable_msg))
|
||||
.setMessage(R.string.storage_unavailable_msg)
|
||||
.setPositiveButton(R.string.ok
|
||||
) { _, _ -> finish() }.show()
|
||||
}
|
||||
if (!sharedPrefs.getBoolean("alreadyLaunched", false)){
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(getString(R.string.usf_home_warning_msg))
|
||||
.setMessage(R.string.usf_home_warning_msg)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.see_unsafe_features)){ _, _ ->
|
||||
val intent = Intent(this, SettingsActivity::class.java)
|
||||
@ -65,7 +65,7 @@ class MainActivity : BaseActivity() {
|
||||
if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.storage_perm_denied)
|
||||
.setMessage(getString(R.string.storage_perm_denied_msg))
|
||||
.setMessage(R.string.storage_perm_denied_msg)
|
||||
.setPositiveButton(R.string.ok
|
||||
) { _, _ -> finish() }.show()
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.AdapterView.OnItemClickListener
|
||||
import android.widget.Toast
|
||||
import kotlinx.android.synthetic.main.activity_open.checkbox_remember_path
|
||||
@ -19,7 +18,7 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
@ -76,7 +75,7 @@ class OpenActivity : BaseActivity() {
|
||||
if (resultCode == Activity.RESULT_OK) {
|
||||
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||
if (data != null) {
|
||||
val path = FilesUtils.getFullPathFromTreeUri(data.data, this)
|
||||
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
|
||||
edit_volume_path.setText(path)
|
||||
}
|
||||
}
|
||||
@ -128,7 +127,7 @@ class OpenActivity : BaseActivity() {
|
||||
} else {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.open_volume_failed)
|
||||
.setMessage(getString(R.string.open_failed_hash_msg))
|
||||
.setMessage(R.string.open_failed_hash_msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
|
@ -14,23 +14,23 @@ import androidx.core.content.ContextCompat
|
||||
import sushi.hardcore.droidfs.ConstValues.Companion.getAssociatedDrawable
|
||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.widgets.ThemeColor
|
||||
import java.text.DateFormat
|
||||
import java.util.*
|
||||
|
||||
class ExplorerElementAdapter(private val context: Context) : BaseAdapter() {
|
||||
private val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, context.resources.configuration.locale)
|
||||
private lateinit var explorer_elements: List<ExplorerElement>
|
||||
private lateinit var explorerElements: List<ExplorerElement>
|
||||
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||
val selectedItems: MutableList<Int> = ArrayList()
|
||||
private val themeColor = ThemeColor.getThemeColor(context)
|
||||
override fun getCount(): Int {
|
||||
return explorer_elements.size
|
||||
return explorerElements.size
|
||||
}
|
||||
|
||||
override fun getItem(position: Int): ExplorerElement {
|
||||
return explorer_elements[position]
|
||||
return explorerElements[position]
|
||||
}
|
||||
|
||||
override fun getItemId(position: Int): Long {
|
||||
@ -55,7 +55,7 @@ class ExplorerElementAdapter(private val context: Context) : BaseAdapter() {
|
||||
}
|
||||
else -> {
|
||||
textElementMtime.text = dateFormat.format(currentElement.mTime)
|
||||
textElementSize.text = FilesUtils.formatSize(currentElement.size)
|
||||
textElementSize.text = PathUtils.formatSize(currentElement.size)
|
||||
drawableId = getAssociatedDrawable(currentElement.name)
|
||||
}
|
||||
}
|
||||
@ -73,56 +73,55 @@ class ExplorerElementAdapter(private val context: Context) : BaseAdapter() {
|
||||
|
||||
fun onItemClick(position: Int) {
|
||||
if (selectedItems.isNotEmpty()) {
|
||||
if (!explorer_elements[position].isParentFolder) {
|
||||
if (!explorerElements[position].isParentFolder) {
|
||||
if (selectedItems.contains(position)) {
|
||||
selectedItems.remove(position)
|
||||
} else {
|
||||
selectedItems.add(position)
|
||||
}
|
||||
notifyDataSetInvalidated()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onItemLongClick(position: Int) {
|
||||
if (!explorer_elements[position].isParentFolder) {
|
||||
if (!explorerElements[position].isParentFolder) {
|
||||
if (!selectedItems.contains(position)) {
|
||||
selectedItems.add(position)
|
||||
} else {
|
||||
selectedItems.remove(position)
|
||||
}
|
||||
notifyDataSetInvalidated()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
}
|
||||
|
||||
fun selectAll() {
|
||||
for (i in explorer_elements.indices) {
|
||||
if (!selectedItems.contains(i) && !explorer_elements[i].isParentFolder) {
|
||||
for (i in explorerElements.indices) {
|
||||
if (!selectedItems.contains(i) && !explorerElements[i].isParentFolder) {
|
||||
selectedItems.add(i)
|
||||
}
|
||||
}
|
||||
notifyDataSetInvalidated()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun unSelectAll() {
|
||||
selectedItems.clear()
|
||||
notifyDataSetInvalidated()
|
||||
notifyDataSetChanged()
|
||||
}
|
||||
|
||||
fun setExplorerElements(explorer_elements: List<ExplorerElement>) {
|
||||
unSelectAll()
|
||||
this.explorer_elements = explorer_elements
|
||||
this.explorerElements = explorer_elements
|
||||
}
|
||||
|
||||
val currentDirectoryTotalSize: Long
|
||||
get() {
|
||||
var total_size: Long = 0
|
||||
for (e in explorer_elements) {
|
||||
var totalSize: Long = 0
|
||||
for (e in explorerElements) {
|
||||
if (e.isRegularFile) {
|
||||
total_size += e.size
|
||||
totalSize += e.size
|
||||
}
|
||||
}
|
||||
return total_size
|
||||
return totalSize
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import android.content.Intent
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Bundle
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
@ -29,50 +28,48 @@ import sushi.hardcore.droidfs.file_viewers.AudioPlayer
|
||||
import sushi.hardcore.droidfs.file_viewers.ImageViewer
|
||||
import sushi.hardcore.droidfs.file_viewers.TextEditor
|
||||
import sushi.hardcore.droidfs.file_viewers.VideoPlayer
|
||||
import sushi.hardcore.droidfs.provider.TemporaryFileProvider
|
||||
import sushi.hardcore.droidfs.provider.RestrictedFileProvider
|
||||
import sushi.hardcore.droidfs.util.ExternalProvider
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
import java.util.*
|
||||
|
||||
open class BaseExplorerActivity : BaseActivity() {
|
||||
private lateinit var shared_prefs_editor: SharedPreferences.Editor
|
||||
private lateinit var sort_modes_entries: Array<String>
|
||||
private lateinit var sort_modes_values: Array<String>
|
||||
private var current_sort_mode_index = 0
|
||||
private lateinit var sortModesEntries: Array<String>
|
||||
private lateinit var sortModesValues: Array<String>
|
||||
private var currentSortModeIndex = 0
|
||||
protected lateinit var gocryptfsVolume: GocryptfsVolume
|
||||
private lateinit var volume_name: String
|
||||
protected var current_path = ""
|
||||
protected lateinit var explorer_elements: MutableList<ExplorerElement>
|
||||
protected lateinit var explorer_adapter: ExplorerElementAdapter
|
||||
private lateinit var volumeName: String
|
||||
protected var currentDirectoryPath = ""
|
||||
protected lateinit var explorerElements: MutableList<ExplorerElement>
|
||||
protected lateinit var explorerAdapter: ExplorerElementAdapter
|
||||
private var usf_open = false
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
||||
val intent = intent
|
||||
volume_name = intent.getStringExtra("volume_name") ?: ""
|
||||
volumeName = intent.getStringExtra("volume_name") ?: ""
|
||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
||||
gocryptfsVolume = GocryptfsVolume(sessionID)
|
||||
sort_modes_entries = resources.getStringArray(R.array.sort_orders_entries)
|
||||
sort_modes_values = resources.getStringArray(R.array.sort_orders_values)
|
||||
current_sort_mode_index = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.sort_order_key, "name"))
|
||||
shared_prefs_editor = sharedPrefs.edit()
|
||||
sortModesEntries = resources.getStringArray(R.array.sort_orders_entries)
|
||||
sortModesValues = resources.getStringArray(R.array.sort_orders_values)
|
||||
currentSortModeIndex = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.sort_order_key, "name"))
|
||||
init()
|
||||
setSupportActionBar(toolbar)
|
||||
title = ""
|
||||
title_text.text = getString(R.string.volume, volume_name)
|
||||
explorer_adapter = ExplorerElementAdapter(this)
|
||||
setCurrentPath(current_path)
|
||||
list_explorer.adapter = explorer_adapter
|
||||
title_text.text = getString(R.string.volume, volumeName)
|
||||
explorerAdapter = ExplorerElementAdapter(this)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
list_explorer.adapter = explorerAdapter
|
||||
list_explorer.onItemClickListener = OnItemClickListener { _, _, position, _ -> onExplorerItemClick(position) }
|
||||
list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ ->
|
||||
explorer_adapter.onItemLongClick(position)
|
||||
explorerAdapter.onItemLongClick(position)
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
refresher.setOnRefreshListener {
|
||||
setCurrentPath(current_path)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
refresher.isRefreshing = false
|
||||
}
|
||||
}
|
||||
@ -89,29 +86,29 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
protected open fun onExplorerItemClick(position: Int) {
|
||||
val wasSelecting = explorer_adapter.selectedItems.isNotEmpty()
|
||||
explorer_adapter.onItemClick(position)
|
||||
if (explorer_adapter.selectedItems.isEmpty()) {
|
||||
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
|
||||
explorerAdapter.onItemClick(position)
|
||||
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||
if (!wasSelecting) {
|
||||
val full_path = FilesUtils.path_join(current_path, explorer_elements[position].name)
|
||||
val fullPath = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name)
|
||||
when {
|
||||
explorer_elements[position].isDirectory -> {
|
||||
setCurrentPath(full_path)
|
||||
explorerElements[position].isDirectory -> {
|
||||
setCurrentPath(fullPath)
|
||||
}
|
||||
explorer_elements[position].isParentFolder -> {
|
||||
setCurrentPath(FilesUtils.get_parent_path(current_path))
|
||||
explorerElements[position].isParentFolder -> {
|
||||
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||
}
|
||||
isImage(full_path) -> {
|
||||
startFileViewer(ImageViewer::class.java, full_path)
|
||||
isImage(fullPath) -> {
|
||||
startFileViewer(ImageViewer::class.java, fullPath)
|
||||
}
|
||||
isVideo(full_path) -> {
|
||||
startFileViewer(VideoPlayer::class.java, full_path)
|
||||
isVideo(fullPath) -> {
|
||||
startFileViewer(VideoPlayer::class.java, fullPath)
|
||||
}
|
||||
isText(full_path) -> {
|
||||
startFileViewer(TextEditor::class.java, full_path)
|
||||
isText(fullPath) -> {
|
||||
startFileViewer(TextEditor::class.java, fullPath)
|
||||
}
|
||||
isAudio(full_path) -> {
|
||||
startFileViewer(AudioPlayer::class.java, full_path)
|
||||
isAudio(fullPath) -> {
|
||||
startFileViewer(AudioPlayer::class.java, fullPath)
|
||||
}
|
||||
else -> {
|
||||
val dialogListView = layoutInflater.inflate(R.layout.dialog_listview, null)
|
||||
@ -125,10 +122,10 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
.create()
|
||||
listView.setOnItemClickListener{_, _, fileTypePosition, _ ->
|
||||
when (adapter.getItem(fileTypePosition)){
|
||||
"image" -> startFileViewer(ImageViewer::class.java, full_path)
|
||||
"video" -> startFileViewer(VideoPlayer::class.java, full_path)
|
||||
"audio" -> startFileViewer(AudioPlayer::class.java, full_path)
|
||||
"text" -> startFileViewer(TextEditor::class.java, full_path)
|
||||
"image" -> startFileViewer(ImageViewer::class.java, fullPath)
|
||||
"video" -> startFileViewer(VideoPlayer::class.java, fullPath)
|
||||
"audio" -> startFileViewer(AudioPlayer::class.java, fullPath)
|
||||
"text" -> startFileViewer(TextEditor::class.java, fullPath)
|
||||
}
|
||||
dialog.dismiss()
|
||||
}
|
||||
@ -141,41 +138,42 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
private fun sortExplorerElements() {
|
||||
when (sort_modes_values[current_sort_mode_index]) {
|
||||
when (sortModesValues[currentSortModeIndex]) {
|
||||
"name" -> {
|
||||
explorer_elements.sortWith(Comparator { o1, o2 -> o1.name.compareTo(o2.name) })
|
||||
explorerElements.sortWith(Comparator { o1, o2 -> o1.name.compareTo(o2.name) })
|
||||
}
|
||||
"size" -> {
|
||||
explorer_elements.sortWith(Comparator { o1, o2 -> (o1.size - o2.size).toInt() })
|
||||
explorerElements.sortWith(Comparator { o1, o2 -> (o1.size - o2.size).toInt() })
|
||||
}
|
||||
"date" -> {
|
||||
explorer_elements.sortWith(Comparator { o1, o2 -> o1.mTime.compareTo(o2.mTime) })
|
||||
explorerElements.sortWith(Comparator { o1, o2 -> o1.mTime.compareTo(o2.mTime) })
|
||||
}
|
||||
"name_desc" -> {
|
||||
explorer_elements.sortWith(Comparator { o1, o2 -> o2.name.compareTo(o1.name) })
|
||||
explorerElements.sortWith(Comparator { o1, o2 -> o2.name.compareTo(o1.name) })
|
||||
}
|
||||
"size_desc" -> {
|
||||
explorer_elements.sortWith(Comparator { o1, o2 -> (o2.size - o1.size).toInt() })
|
||||
explorerElements.sortWith(Comparator { o1, o2 -> (o2.size - o1.size).toInt() })
|
||||
}
|
||||
"date_desc" -> {
|
||||
explorer_elements.sortWith(Comparator { o1, o2 -> o2.mTime.compareTo(o1.mTime) })
|
||||
explorerElements.sortWith(Comparator { o1, o2 -> o2.mTime.compareTo(o1.mTime) })
|
||||
}
|
||||
}
|
||||
shared_prefs_editor.putString(ConstValues.sort_order_key, sort_modes_values[current_sort_mode_index])
|
||||
shared_prefs_editor.apply()
|
||||
val sharedPrefsEditor = sharedPrefs.edit()
|
||||
sharedPrefsEditor.putString(ConstValues.sort_order_key, sortModesValues[currentSortModeIndex])
|
||||
sharedPrefsEditor.apply()
|
||||
}
|
||||
|
||||
protected fun setCurrentPath(path: String) {
|
||||
explorer_elements = gocryptfsVolume.list_dir(path)
|
||||
text_dir_empty.visibility = if (explorer_elements.size == 0) View.VISIBLE else View.INVISIBLE
|
||||
explorerElements = gocryptfsVolume.list_dir(path)
|
||||
text_dir_empty.visibility = if (explorerElements.size == 0) View.VISIBLE else View.INVISIBLE
|
||||
sortExplorerElements()
|
||||
if (path.isNotEmpty()) { //not root
|
||||
explorer_elements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1))
|
||||
explorerElements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1))
|
||||
}
|
||||
explorer_adapter.setExplorerElements(explorer_elements)
|
||||
current_path = path
|
||||
current_path_text.text = getString(R.string.location, current_path)
|
||||
total_size_text.text = getString(R.string.total_size, FilesUtils.formatSize(explorer_adapter.currentDirectoryTotalSize))
|
||||
explorerAdapter.setExplorerElements(explorerElements)
|
||||
currentDirectoryPath = path
|
||||
current_path_text.text = getString(R.string.location, currentDirectoryPath)
|
||||
total_size_text.text = getString(R.string.total_size, PathUtils.formatSize(explorerAdapter.currentDirectoryTotalSize))
|
||||
}
|
||||
|
||||
private fun askCloseVolume() {
|
||||
@ -193,19 +191,19 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
|
||||
protected open fun closeVolumeOnDestroy() {
|
||||
gocryptfsVolume.close()
|
||||
TemporaryFileProvider.wipeAll() //additional security
|
||||
RestrictedFileProvider.wipeAll() //additional security
|
||||
}
|
||||
|
||||
override fun onBackPressed() {
|
||||
if (explorer_adapter.selectedItems.isEmpty()) {
|
||||
val parent_path = FilesUtils.get_parent_path(current_path)
|
||||
if (parent_path == current_path) {
|
||||
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||
val parentPath = PathUtils.get_parent_path(currentDirectoryPath)
|
||||
if (parentPath == currentDirectoryPath) {
|
||||
askCloseVolume()
|
||||
} else {
|
||||
setCurrentPath(FilesUtils.get_parent_path(current_path))
|
||||
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||
}
|
||||
} else {
|
||||
explorer_adapter.unSelectAll()
|
||||
explorerAdapter.unSelectAll()
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
@ -214,14 +212,14 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
if (folder_name.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (!gocryptfsVolume.mkdir(FilesUtils.path_join(current_path, folder_name))) {
|
||||
if (!gocryptfsVolume.mkdir(PathUtils.path_join(currentDirectoryPath, folder_name))) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.error_mkdir)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
setCurrentPath(current_path)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
@ -254,14 +252,14 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
if (new_name.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
if (!gocryptfsVolume.rename(FilesUtils.path_join(current_path, old_name), FilesUtils.path_join(current_path, new_name))) {
|
||||
if (!gocryptfsVolume.rename(PathUtils.path_join(currentDirectoryPath, old_name), PathUtils.path_join(currentDirectoryPath, new_name))) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.rename_failed, old_name))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
setCurrentPath(current_path)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
@ -272,7 +270,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
if (usf_open){
|
||||
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = false
|
||||
}
|
||||
val selectedItems = explorer_adapter.selectedItems
|
||||
val selectedItems = explorerAdapter.selectedItems
|
||||
if (selectedItems.isEmpty()){
|
||||
toolbar.navigationIcon = null
|
||||
menu.findItem(R.id.explorer_menu_close).isVisible = true
|
||||
@ -283,7 +281,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
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 && explorer_elements[selectedItems[0]].isRegularFile) {
|
||||
if (usf_open && explorerElements[selectedItems[0]].isRegularFile) {
|
||||
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = true
|
||||
}
|
||||
}
|
||||
@ -293,39 +291,39 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
android.R.id.home -> {
|
||||
explorer_adapter.unSelectAll()
|
||||
explorerAdapter.unSelectAll()
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_sort -> {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.sort_order)
|
||||
.setSingleChoiceItems(sort_modes_entries, current_sort_mode_index) { dialog, which ->
|
||||
current_sort_mode_index = which
|
||||
setCurrentPath(current_path)
|
||||
.setSingleChoiceItems(sortModesEntries, currentSortModeIndex) { dialog, which ->
|
||||
currentSortModeIndex = which
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
dialog.dismiss()
|
||||
}.show()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_rename -> {
|
||||
val dialog_edit_text_view = layoutInflater.inflate(R.layout.dialog_edit_text, null)
|
||||
val old_name = explorer_elements[explorer_adapter.selectedItems[0]].name
|
||||
val dialog_edit_text = dialog_edit_text_view.findViewById<EditText>(R.id.dialog_edit_text)
|
||||
dialog_edit_text.setText(old_name)
|
||||
dialog_edit_text.selectAll()
|
||||
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)
|
||||
dialogEditText.setText(oldName)
|
||||
dialogEditText.selectAll()
|
||||
val dialog = ColoredAlertDialog(this)
|
||||
.setView(dialog_edit_text_view)
|
||||
.setView(dialogEditTextView)
|
||||
.setTitle(R.string.rename_title)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
val new_name = dialog_edit_text.text.toString()
|
||||
rename(old_name, new_name)
|
||||
val newName = dialogEditText.text.toString()
|
||||
rename(oldName, newName)
|
||||
}
|
||||
.setNegativeButton(R.string.cancel, null)
|
||||
.create()
|
||||
dialog_edit_text.setOnEditorActionListener { _, _, _ ->
|
||||
val new_name = dialog_edit_text.text.toString()
|
||||
dialogEditText.setOnEditorActionListener { _, _, _ ->
|
||||
val newName = dialogEditText.text.toString()
|
||||
dialog.dismiss()
|
||||
rename(old_name, new_name)
|
||||
rename(oldName, newName)
|
||||
true
|
||||
}
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
@ -334,8 +332,8 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
}
|
||||
R.id.explorer_menu_external_open -> {
|
||||
if (usf_open){
|
||||
ExternalProvider.open(this, gocryptfsVolume, FilesUtils.path_join(current_path, explorer_elements[explorer_adapter.selectedItems[0]].name))
|
||||
explorer_adapter.unSelectAll()
|
||||
ExternalProvider.open(this, gocryptfsVolume, PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name))
|
||||
explorerAdapter.unSelectAll()
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
true
|
||||
@ -357,6 +355,6 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
ExternalProvider.clear_cache(this)
|
||||
ExternalProvider.removeFiles(this)
|
||||
}
|
||||
}
|
@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.activity_explorer.*
|
||||
import sushi.hardcore.droidfs.OpenActivity
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.util.ExternalProvider
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
@ -37,16 +37,16 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
if (fileName.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
val handleID = gocryptfsVolume.open_write_mode(FilesUtils.path_join(current_path, fileName))
|
||||
val handleID = gocryptfsVolume.open_write_mode(PathUtils.path_join(currentDirectoryPath, fileName))
|
||||
if (handleID == -1) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.file_creation_failed))
|
||||
.setMessage(R.string.file_creation_failed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
gocryptfsVolume.close_file(handleID)
|
||||
setCurrentPath(current_path)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
if (uris.isNotEmpty()){
|
||||
var success = true
|
||||
for (uri in uris) {
|
||||
val dstPath = FilesUtils.path_join(current_path, FilesUtils.getFilenameFromURI(this, uri))
|
||||
val dstPath = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri))
|
||||
contentResolver.openInputStream(uri)?.let {
|
||||
success = gocryptfsVolume.import_file(it, dstPath)
|
||||
}
|
||||
@ -154,32 +154,32 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
.setNegativeButton(getString(R.string.no), null)
|
||||
.show()
|
||||
}
|
||||
setCurrentPath(current_path)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
}
|
||||
} else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
val uri = data.data
|
||||
val output_dir = FilesUtils.getFullPathFromTreeUri(uri, this)
|
||||
var failed_item: String? = null
|
||||
for (i in explorer_adapter.selectedItems) {
|
||||
val element = explorer_adapter.getItem(i)
|
||||
val full_path = FilesUtils.path_join(current_path, element.name)
|
||||
failed_item = if (element.isDirectory) {
|
||||
recursive_export_directory(full_path, output_dir)
|
||||
val outputDir = PathUtils.getFullPathFromTreeUri(uri, this)
|
||||
var failedItem: String? = null
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val element = explorerAdapter.getItem(i)
|
||||
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
|
||||
failedItem = if (element.isDirectory) {
|
||||
recursiveExportDirectory(fullPath, outputDir)
|
||||
} else {
|
||||
if (gocryptfsVolume.export_file(full_path, FilesUtils.path_join(output_dir, element.name))) null else full_path
|
||||
if (gocryptfsVolume.export_file(fullPath, PathUtils.path_join(outputDir, element.name))) null else fullPath
|
||||
}
|
||||
if (failed_item != null) {
|
||||
if (failedItem != null) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.export_failed, failed_item))
|
||||
.setMessage(getString(R.string.export_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
break
|
||||
}
|
||||
}
|
||||
if (failed_item == null) {
|
||||
if (failedItem == null) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.success_export)
|
||||
.setMessage(R.string.success_export_msg)
|
||||
@ -187,33 +187,33 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
.show()
|
||||
}
|
||||
}
|
||||
explorer_adapter.unSelectAll()
|
||||
explorerAdapter.unSelectAll()
|
||||
invalidateOptionsMenu()
|
||||
} else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
val remote_sessionID = data.getIntExtra("sessionID", -1)
|
||||
val remote_gocryptfsVolume = GocryptfsVolume(remote_sessionID)
|
||||
val remoteSessionID = data.getIntExtra("sessionID", -1)
|
||||
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
|
||||
val path = data.getStringExtra("path")
|
||||
var failed_item: String? = null
|
||||
var failedItem: String? = null
|
||||
if (path == null) {
|
||||
val paths = data.getStringArrayListExtra("paths")
|
||||
val types = data.getIntegerArrayListExtra("types")
|
||||
if (types != null && paths != null){
|
||||
for (i in paths.indices) {
|
||||
failed_item = if (types[i] == 0) { //directory
|
||||
recursive_import_directory_from_other_volume(remote_gocryptfsVolume, paths[i], current_path)
|
||||
failedItem = if (types[i] == 0) { //directory
|
||||
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
|
||||
} else {
|
||||
if (import_file_from_other_volume(remote_gocryptfsVolume, paths[i], current_path)) null else paths[i]
|
||||
if (importFileFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)) null else paths[i]
|
||||
}
|
||||
if (failed_item != null) {
|
||||
if (failedItem != null) {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
failed_item = if (import_file_from_other_volume(remote_gocryptfsVolume, path, current_path)) null else path
|
||||
failedItem = if (importFileFromOtherVolume(remoteGocryptfsVolume, path, currentDirectoryPath)) null else path
|
||||
}
|
||||
if (failed_item == null) {
|
||||
if (failedItem == null) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.success_import)
|
||||
.setMessage(R.string.success_import_msg)
|
||||
@ -222,12 +222,12 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
} else {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.import_failed, failed_item))
|
||||
.setMessage(getString(R.string.import_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
remote_gocryptfsVolume.close()
|
||||
setCurrentPath(current_path)
|
||||
remoteGocryptfsVolume.close()
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -238,14 +238,14 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
if (usf_share){
|
||||
menu.findItem(R.id.explorer_menu_share).isVisible = false
|
||||
}
|
||||
val any_item_selected = explorer_adapter.selectedItems.isNotEmpty()
|
||||
menu.findItem(R.id.explorer_menu_select_all).isVisible = any_item_selected
|
||||
menu.findItem(R.id.explorer_menu_delete).isVisible = any_item_selected
|
||||
menu.findItem(R.id.explorer_menu_decrypt).isVisible = any_item_selected && usf_decrypt
|
||||
if (any_item_selected && usf_share){
|
||||
val anyItemSelected = explorerAdapter.selectedItems.isNotEmpty()
|
||||
menu.findItem(R.id.explorer_menu_select_all).isVisible = anyItemSelected
|
||||
menu.findItem(R.id.explorer_menu_delete).isVisible = anyItemSelected
|
||||
menu.findItem(R.id.explorer_menu_decrypt).isVisible = anyItemSelected && usf_decrypt
|
||||
if (anyItemSelected && usf_share){
|
||||
var containsDir = false
|
||||
for (i in explorer_adapter.selectedItems) {
|
||||
if (explorer_elements[i].isDirectory) {
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
if (explorerElements[i].isDirectory) {
|
||||
containsDir = true
|
||||
break
|
||||
}
|
||||
@ -260,32 +260,32 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.explorer_menu_select_all -> {
|
||||
explorer_adapter.selectAll()
|
||||
explorerAdapter.selectAll()
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_delete -> {
|
||||
val size = explorer_adapter.selectedItems.size
|
||||
val size = explorerAdapter.selectedItems.size
|
||||
val dialog = ColoredAlertDialog(this)
|
||||
dialog.setTitle(R.string.warning)
|
||||
dialog.setPositiveButton(R.string.ok) { _, _ -> remove_selected_items() }
|
||||
dialog.setPositiveButton(R.string.ok) { _, _ -> removeSelectedItems() }
|
||||
dialog.setNegativeButton(R.string.cancel, null)
|
||||
if (size > 1) {
|
||||
dialog.setMessage(getString(R.string.multiple_delete_confirm, explorer_adapter.selectedItems.size.toString()))
|
||||
dialog.setMessage(getString(R.string.multiple_delete_confirm, explorerAdapter.selectedItems.size.toString()))
|
||||
} else {
|
||||
dialog.setMessage(getString(R.string.single_delete_confirm, explorer_adapter.getItem(explorer_adapter.selectedItems[0]).name))
|
||||
dialog.setMessage(getString(R.string.single_delete_confirm, explorerAdapter.getItem(explorerAdapter.selectedItems[0]).name))
|
||||
}
|
||||
dialog.show()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_share -> {
|
||||
val paths: MutableList<String> = ArrayList()
|
||||
for (i in explorer_adapter.selectedItems) {
|
||||
val e = explorer_elements[i]
|
||||
paths.add(FilesUtils.path_join(current_path, e.name))
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val e = explorerElements[i]
|
||||
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
|
||||
}
|
||||
ExternalProvider.share(this, gocryptfsVolume, paths)
|
||||
explorer_adapter.unSelectAll()
|
||||
explorerAdapter.unSelectAll()
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
@ -298,18 +298,18 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun import_file_from_other_volume(remote_gocryptfsVolume: GocryptfsVolume, full_path: String, output_dir: String): Boolean {
|
||||
val output_path = FilesUtils.path_join(output_dir, File(full_path).name)
|
||||
private fun importFileFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, full_path: String, output_dir: String): Boolean {
|
||||
val outputPath = PathUtils.path_join(output_dir, File(full_path).name)
|
||||
var success = true
|
||||
val src_handleID = remote_gocryptfsVolume.open_read_mode(full_path)
|
||||
if (src_handleID != -1) {
|
||||
val dst_handleID = gocryptfsVolume.open_write_mode(output_path)
|
||||
if (dst_handleID != -1) {
|
||||
val srcHandleID = remote_gocryptfsVolume.open_read_mode(full_path)
|
||||
if (srcHandleID != -1) {
|
||||
val dstHandleID = gocryptfsVolume.open_write_mode(outputPath)
|
||||
if (dstHandleID != -1) {
|
||||
var length: Int
|
||||
val io_buffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
var offset: Long = 0
|
||||
while (remote_gocryptfsVolume.read_file(src_handleID, offset, io_buffer).also { length = it } > 0){
|
||||
val written = gocryptfsVolume.write_file(dst_handleID, offset, io_buffer, length).toLong()
|
||||
while (remote_gocryptfsVolume.read_file(srcHandleID, offset, ioBuffer).also { length = it } > 0){
|
||||
val written = gocryptfsVolume.write_file(dstHandleID, offset, ioBuffer, length).toLong()
|
||||
if (written == length.toLong()) {
|
||||
offset += length.toLong()
|
||||
} else {
|
||||
@ -317,46 +317,46 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
break
|
||||
}
|
||||
}
|
||||
gocryptfsVolume.close_file(dst_handleID)
|
||||
gocryptfsVolume.close_file(dstHandleID)
|
||||
}
|
||||
remote_gocryptfsVolume.close_file(src_handleID)
|
||||
remote_gocryptfsVolume.close_file(srcHandleID)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
private fun recursive_import_directory_from_other_volume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, output_dir: String): String? {
|
||||
val directory_path = FilesUtils.path_join(output_dir, File(remote_directory_path).name)
|
||||
if (!gocryptfsVolume.path_exists(directory_path)) {
|
||||
if (!gocryptfsVolume.mkdir(directory_path)) {
|
||||
return directory_path
|
||||
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
|
||||
}
|
||||
}
|
||||
val explorer_elements = remote_gocryptfsVolume.list_dir(remote_directory_path)
|
||||
for (e in explorer_elements) {
|
||||
val full_path = FilesUtils.path_join(remote_directory_path, e.name)
|
||||
val explorerElements = remote_gocryptfsVolume.list_dir(remote_directory_path)
|
||||
for (e in explorerElements) {
|
||||
val fullPath = PathUtils.path_join(remote_directory_path, e.name)
|
||||
if (e.isDirectory) {
|
||||
val failed_item = recursive_import_directory_from_other_volume(remote_gocryptfsVolume, full_path, directory_path)
|
||||
failed_item?.let { return it }
|
||||
val failedItem = recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)
|
||||
failedItem?.let { return it }
|
||||
} else {
|
||||
if (!import_file_from_other_volume(remote_gocryptfsVolume, full_path, directory_path)) {
|
||||
return full_path
|
||||
if (!importFileFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)) {
|
||||
return fullPath
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
private fun recursive_export_directory(plain_directory_path: String, output_dir: String?): String? {
|
||||
if (File(FilesUtils.path_join(output_dir, plain_directory_path)).mkdir()) {
|
||||
val explorer_elements = gocryptfsVolume.list_dir(plain_directory_path)
|
||||
for (e in explorer_elements) {
|
||||
val full_path = FilesUtils.path_join(plain_directory_path, e.name)
|
||||
private fun recursiveExportDirectory(plain_directory_path: String, output_dir: String?): String? {
|
||||
if (File(PathUtils.path_join(output_dir, plain_directory_path)).mkdir()) {
|
||||
val explorerElements = gocryptfsVolume.list_dir(plain_directory_path)
|
||||
for (e in explorerElements) {
|
||||
val fullPath = PathUtils.path_join(plain_directory_path, e.name)
|
||||
if (e.isDirectory) {
|
||||
val failed_item = recursive_export_directory(full_path, output_dir)
|
||||
failed_item?.let { return it }
|
||||
val failedItem = recursiveExportDirectory(fullPath, output_dir)
|
||||
failedItem?.let { return it }
|
||||
} else {
|
||||
if (!gocryptfsVolume.export_file(full_path, FilesUtils.path_join(output_dir, full_path))) {
|
||||
return full_path
|
||||
if (!gocryptfsVolume.export_file(fullPath, PathUtils.path_join(output_dir, fullPath))) {
|
||||
return fullPath
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -365,16 +365,16 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
return output_dir
|
||||
}
|
||||
|
||||
private fun recursive_remove_directory(plain_directory_path: String): String? {
|
||||
val explorer_elements = gocryptfsVolume.list_dir(plain_directory_path)
|
||||
for (e in explorer_elements) {
|
||||
val full_path = FilesUtils.path_join(plain_directory_path, e.name)
|
||||
private fun recursiveRemoveDirectory(plain_directory_path: String): String? {
|
||||
val explorerElements = gocryptfsVolume.list_dir(plain_directory_path)
|
||||
for (e in explorerElements) {
|
||||
val fullPath = PathUtils.path_join(plain_directory_path, e.name)
|
||||
if (e.isDirectory) {
|
||||
val result = recursive_remove_directory(full_path)
|
||||
val result = recursiveRemoveDirectory(fullPath)
|
||||
result?.let { return it }
|
||||
} else {
|
||||
if (!gocryptfsVolume.remove_file(full_path)) {
|
||||
return full_path
|
||||
if (!gocryptfsVolume.remove_file(fullPath)) {
|
||||
return fullPath
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -385,30 +385,30 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
private fun remove_selected_items() {
|
||||
var failed_item: String? = null
|
||||
for (i in explorer_adapter.selectedItems) {
|
||||
val element = explorer_adapter.getItem(i)
|
||||
val full_path = FilesUtils.path_join(current_path, element.name)
|
||||
private fun removeSelectedItems() {
|
||||
var failedItem: String? = null
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val element = explorerAdapter.getItem(i)
|
||||
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
|
||||
if (element.isDirectory) {
|
||||
val result = recursive_remove_directory(full_path)
|
||||
result?.let{ failed_item = it }
|
||||
val result = recursiveRemoveDirectory(fullPath)
|
||||
result?.let{ failedItem = it }
|
||||
} else {
|
||||
if (!gocryptfsVolume.remove_file(full_path)) {
|
||||
failed_item = full_path
|
||||
if (!gocryptfsVolume.remove_file(fullPath)) {
|
||||
failedItem = fullPath
|
||||
}
|
||||
}
|
||||
if (failed_item != null) {
|
||||
if (failedItem != null) {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.remove_failed, failed_item))
|
||||
.setMessage(getString(R.string.remove_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
break
|
||||
}
|
||||
}
|
||||
explorer_adapter.unSelectAll()
|
||||
explorerAdapter.unSelectAll()
|
||||
invalidateOptionsMenu()
|
||||
setCurrentPath(current_path) //refresh
|
||||
setCurrentPath(currentDirectoryPath) //refresh
|
||||
}
|
||||
}
|
@ -5,7 +5,7 @@ import android.net.Uri
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
|
||||
class ExplorerActivityDrop : BaseExplorerActivity() {
|
||||
@ -16,7 +16,7 @@ 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 = explorer_adapter.selectedItems.isEmpty()
|
||||
menu.findItem(R.id.explorer_menu_validate).isVisible = explorerAdapter.selectedItems.isEmpty()
|
||||
return true
|
||||
}
|
||||
|
||||
@ -31,13 +31,13 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
|
||||
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 = FilesUtils.path_join(current_path, FilesUtils.getFilenameFromURI(this, uri))
|
||||
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)
|
||||
} 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 = FilesUtils.path_join(current_path, FilesUtils.getFilenameFromURI(this, uri))
|
||||
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)
|
||||
break
|
||||
|
@ -5,8 +5,8 @@ import android.content.Intent
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.provider.TemporaryFileProvider
|
||||
import sushi.hardcore.droidfs.util.FilesUtils
|
||||
import sushi.hardcore.droidfs.provider.RestrictedFileProvider
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import java.util.*
|
||||
|
||||
class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
@ -17,21 +17,21 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
}
|
||||
|
||||
override fun onExplorerItemClick(position: Int) {
|
||||
val wasSelecting = explorer_adapter.selectedItems.isNotEmpty()
|
||||
explorer_adapter.onItemClick(position)
|
||||
if (explorer_adapter.selectedItems.isEmpty()) {
|
||||
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
|
||||
explorerAdapter.onItemClick(position)
|
||||
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||
if (!wasSelecting) {
|
||||
val full_path = FilesUtils.path_join(current_path, explorer_elements[position].name)
|
||||
val full_path = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name)
|
||||
when {
|
||||
explorer_elements[position].isDirectory -> {
|
||||
explorerElements[position].isDirectory -> {
|
||||
setCurrentPath(full_path)
|
||||
}
|
||||
explorer_elements[position].isParentFolder -> {
|
||||
setCurrentPath(FilesUtils.get_parent_path(current_path))
|
||||
explorerElements[position].isParentFolder -> {
|
||||
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||
}
|
||||
else -> {
|
||||
result_intent.putExtra("path", full_path)
|
||||
return_activity_result()
|
||||
returnActivityResult()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -42,7 +42,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.explorer_pick, menu)
|
||||
handleMenuItems(menu)
|
||||
val any_item_selected = explorer_adapter.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.explorer_menu_validate).isVisible = any_item_selected
|
||||
return true
|
||||
@ -51,35 +51,35 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.explorer_menu_select_all -> {
|
||||
explorer_adapter.selectAll()
|
||||
explorerAdapter.selectAll()
|
||||
invalidateOptionsMenu()
|
||||
true
|
||||
}
|
||||
R.id.explorer_menu_validate -> {
|
||||
val paths = ArrayList<String>()
|
||||
val types = ArrayList<Int>()
|
||||
for (i in explorer_adapter.selectedItems) {
|
||||
val e = explorer_elements[i]
|
||||
paths.add(FilesUtils.path_join(current_path, e.name))
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val e = explorerElements[i]
|
||||
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
|
||||
types.add(e.elementType.toInt())
|
||||
}
|
||||
result_intent.putStringArrayListExtra("paths", paths)
|
||||
result_intent.putIntegerArrayListExtra("types", types)
|
||||
return_activity_result()
|
||||
returnActivityResult()
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
}
|
||||
}
|
||||
|
||||
private fun return_activity_result() {
|
||||
private fun returnActivityResult() {
|
||||
setResult(Activity.RESULT_OK, result_intent)
|
||||
finish()
|
||||
}
|
||||
|
||||
override fun closeVolumeOnDestroy() {
|
||||
//don't close volume
|
||||
TemporaryFileProvider.wipeAll()
|
||||
RestrictedFileProvider.wipeAll()
|
||||
}
|
||||
|
||||
override fun closeVolumeOnUserExit() {
|
||||
|
@ -1,20 +1,13 @@
|
||||
package sushi.hardcore.droidfs.file_viewers
|
||||
|
||||
import android.media.MediaPlayer
|
||||
import android.os.Handler
|
||||
import android.widget.SeekBar
|
||||
import androidx.core.content.ContextCompat
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import kotlinx.android.synthetic.main.activity_audio_player.*
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
import java.io.File
|
||||
import java.io.IOException
|
||||
|
||||
class AudioPlayer: FileViewerActivity(){
|
||||
private lateinit var player: MediaPlayer
|
||||
private var isPrepared = false
|
||||
class AudioPlayer: MediaPlayer(){
|
||||
override fun viewFile() {
|
||||
super.viewFile()
|
||||
setContentView(R.layout.activity_audio_player)
|
||||
val filename = File(filePath).name
|
||||
val pos = filename.lastIndexOf('.')
|
||||
@ -23,65 +16,9 @@ class AudioPlayer: FileViewerActivity(){
|
||||
} else {
|
||||
filename
|
||||
}
|
||||
val tmpFileUri = exportFile(filePath)
|
||||
tmpFileUri?.let {
|
||||
player = MediaPlayer()
|
||||
player.setDataSource(this, tmpFileUri)
|
||||
try {
|
||||
player.prepare()
|
||||
isPrepared = true
|
||||
} catch (e: IOException){
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.media_player_prepare_failed))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
||||
.show()
|
||||
}
|
||||
if (isPrepared){
|
||||
player.isLooping = true
|
||||
button_pause.setOnClickListener {
|
||||
if (player.isPlaying) {
|
||||
player.pause()
|
||||
button_pause.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.icon_play))
|
||||
} else {
|
||||
player.start()
|
||||
button_pause.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.icon_pause))
|
||||
}
|
||||
}
|
||||
button_stop.setOnClickListener { finish() }
|
||||
seekbar.max = player.duration / ConstValues.seek_bar_inc
|
||||
val handler = Handler()
|
||||
runOnUiThread(object : Runnable {
|
||||
override fun run() {
|
||||
if (isPrepared) {
|
||||
seekbar.progress = player.currentPosition / ConstValues.seek_bar_inc
|
||||
}
|
||||
handler.postDelayed(this, ConstValues.seek_bar_inc.toLong())
|
||||
}
|
||||
})
|
||||
seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener {
|
||||
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
|
||||
if (::player.isInitialized && fromUser) {
|
||||
player.seekTo(progress * ConstValues.seek_bar_inc)
|
||||
}
|
||||
}
|
||||
override fun onStartTrackingTouch(seekBar: SeekBar) {}
|
||||
override fun onStopTrackingTouch(seekBar: SeekBar) {}
|
||||
})
|
||||
player.start()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
if (::player.isInitialized) {
|
||||
if (player.isPlaying) {
|
||||
player.stop()
|
||||
}
|
||||
isPrepared = false
|
||||
player.release()
|
||||
}
|
||||
override fun bindPlayer(player: SimpleExoPlayer) {
|
||||
audio_controller.player = player
|
||||
}
|
||||
}
|
@ -1,21 +1,13 @@
|
||||
package sushi.hardcore.droidfs.file_viewers
|
||||
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import androidx.preference.PreferenceManager
|
||||
import sushi.hardcore.droidfs.BaseActivity
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.provider.TemporaryFileProvider
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
import java.io.File
|
||||
import java.util.ArrayList
|
||||
|
||||
abstract class FileViewerActivity: BaseActivity() {
|
||||
private var cachedFiles: MutableList<Uri> = ArrayList()
|
||||
lateinit var gocryptfsVolume: GocryptfsVolume
|
||||
lateinit var filePath: String
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
@ -23,15 +15,17 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
filePath = intent.getStringExtra("path")!!
|
||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
||||
gocryptfsVolume = GocryptfsVolume(sessionID)
|
||||
toggleFullscreen()
|
||||
hideSystemUi()
|
||||
viewFile()
|
||||
}
|
||||
open fun toggleFullscreen(){
|
||||
var uiOptions = window.decorView.systemUiVisibility
|
||||
//uiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
||||
uiOptions = uiOptions xor View.SYSTEM_UI_FLAG_FULLSCREEN
|
||||
uiOptions = uiOptions xor View.SYSTEM_UI_FLAG_IMMERSIVE
|
||||
window.decorView.systemUiVisibility = uiOptions
|
||||
open fun hideSystemUi(){
|
||||
window.decorView.systemUiVisibility =
|
||||
View.SYSTEM_UI_FLAG_LOW_PROFILE or
|
||||
View.SYSTEM_UI_FLAG_FULLSCREEN/* or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION*/
|
||||
}
|
||||
abstract fun viewFile()
|
||||
fun loadWholeFile(path: String): ByteArray? {
|
||||
@ -43,10 +37,10 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
val handleID = gocryptfsVolume.open_read_mode(path)
|
||||
if (handleID != -1) {
|
||||
var offset: Long = 0
|
||||
val io_buffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||
var length: Int
|
||||
while (gocryptfsVolume.read_file(handleID, offset, io_buffer).also { length = it } > 0){
|
||||
System.arraycopy(io_buffer, 0, fileBuff, offset.toInt(), length)
|
||||
while (gocryptfsVolume.read_file(handleID, offset, ioBuffer).also { length = it } > 0){
|
||||
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
|
||||
offset += length.toLong()
|
||||
}
|
||||
gocryptfsVolume.close_file(handleID)
|
||||
@ -65,7 +59,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
} catch (e: OutOfMemoryError){
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.outofmemoryerror_msg))
|
||||
.setMessage(R.string.outofmemoryerror_msg)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.ok)) { _, _ -> finish() }
|
||||
.show()
|
||||
@ -81,30 +75,4 @@ abstract class FileViewerActivity: BaseActivity() {
|
||||
}
|
||||
return null
|
||||
}
|
||||
fun exportFile(path: String): Uri? {
|
||||
val tmpFileUri = TemporaryFileProvider.createFile(this, File(path).name)
|
||||
cachedFiles.add(tmpFileUri)
|
||||
return if (gocryptfsVolume.export_file(this, path, tmpFileUri)) {
|
||||
tmpFileUri
|
||||
} else {
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.export_failed, path))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
||||
.show()
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
Thread{
|
||||
for (uri in cachedFiles) {
|
||||
if (Wiper.wipe(this, uri)){
|
||||
cachedFiles.remove(uri)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
package sushi.hardcore.droidfs.file_viewers
|
||||
|
||||
import android.net.Uri
|
||||
import com.google.android.exoplayer2.upstream.*
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
|
||||
class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource {
|
||||
private var handleID = -1
|
||||
private var fileSize: Long = -1
|
||||
private var fileOffset: Long = 0
|
||||
override fun open(dataSpec: DataSpec?): Long {
|
||||
dataSpec?.let {
|
||||
fileOffset = dataSpec.position
|
||||
}
|
||||
handleID = gocryptfsVolume.open_read_mode(filePath)
|
||||
fileSize = gocryptfsVolume.get_size(filePath)
|
||||
return fileSize
|
||||
}
|
||||
|
||||
override fun getUri(): Uri {
|
||||
return ConstValues.fakeUri
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
gocryptfsVolume.close_file(handleID)
|
||||
}
|
||||
|
||||
override fun addTransferListener(transferListener: TransferListener?) {
|
||||
//too lazy to implement this
|
||||
}
|
||||
|
||||
override fun read(buffer: ByteArray, offset: Int, readLength: Int): Int {
|
||||
if (fileOffset >= fileSize){
|
||||
return -1
|
||||
}
|
||||
val tmpBuff = if (fileOffset+readLength > fileSize){
|
||||
ByteArray((fileSize-fileOffset).toInt())
|
||||
} else {
|
||||
ByteArray(readLength)
|
||||
}
|
||||
val read = gocryptfsVolume.read_file(handleID, fileOffset, tmpBuff)
|
||||
fileOffset += read
|
||||
System.arraycopy(tmpBuff, 0, buffer, offset, read)
|
||||
return read
|
||||
}
|
||||
|
||||
class Factory(private val gocryptfsVolume: GocryptfsVolume, private val filePath: String): DataSource.Factory{
|
||||
override fun createDataSource(): DataSource {
|
||||
return GocryptfsDataSource(gocryptfsVolume, filePath)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
package sushi.hardcore.droidfs.file_viewers
|
||||
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.google.android.exoplayer2.ExoPlaybackException
|
||||
import com.google.android.exoplayer2.Player
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import com.google.android.exoplayer2.source.LoopingMediaSource
|
||||
import com.google.android.exoplayer2.source.ProgressiveMediaSource
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
|
||||
abstract class MediaPlayer: FileViewerActivity() {
|
||||
private lateinit var player: SimpleExoPlayer
|
||||
private var currentWindow = 0
|
||||
private var playbackPosition: Long = 0
|
||||
private lateinit var errorDialog: AlertDialog.Builder
|
||||
|
||||
override fun viewFile() {
|
||||
errorDialog = ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.playing_failed)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
||||
}
|
||||
|
||||
abstract fun bindPlayer(player: SimpleExoPlayer)
|
||||
|
||||
private fun initializePlayer(){
|
||||
player = SimpleExoPlayer.Builder(this).build()
|
||||
bindPlayer(player)
|
||||
val dataSourceFactory = GocryptfsDataSource.Factory(gocryptfsVolume, filePath)
|
||||
val mediaSource = ProgressiveMediaSource.Factory(dataSourceFactory).createMediaSource(ConstValues.fakeUri)
|
||||
player.seekTo(currentWindow, playbackPosition)
|
||||
player.playWhenReady = true
|
||||
player.addListener(object : Player.EventListener{
|
||||
override fun onPlayerError(error: ExoPlaybackException) {
|
||||
if (error.type == ExoPlaybackException.TYPE_SOURCE){
|
||||
errorDialog.show()
|
||||
}
|
||||
}
|
||||
})
|
||||
player.prepare(LoopingMediaSource(mediaSource), false, false)
|
||||
}
|
||||
|
||||
private fun releasePlayer(){
|
||||
if (::player.isInitialized) {
|
||||
playbackPosition = player.currentPosition
|
||||
currentWindow = player.currentWindowIndex
|
||||
player.release()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
hideSystemUi()
|
||||
initializePlayer()
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
releasePlayer()
|
||||
}
|
||||
}
|
@ -21,8 +21,8 @@ class TextEditor: FileViewerActivity() {
|
||||
private lateinit var titleText: TextView
|
||||
private var changedSinceLastSave = false
|
||||
private var wordWrap = true
|
||||
override fun toggleFullscreen() {
|
||||
//don't toggle fullscreen
|
||||
override fun hideSystemUi() {
|
||||
//don't hide system ui
|
||||
}
|
||||
override fun viewFile() {
|
||||
loadWholeFile(filePath)?.let {
|
||||
@ -32,7 +32,7 @@ class TextEditor: FileViewerActivity() {
|
||||
} catch (e: OutOfMemoryError){
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.outofmemoryerror_msg))
|
||||
.setMessage(R.string.outofmemoryerror_msg)
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.ok)) { _, _ -> finish() }
|
||||
.show()
|
||||
@ -100,7 +100,7 @@ class TextEditor: FileViewerActivity() {
|
||||
if (changedSinceLastSave){
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.warning)
|
||||
.setMessage(getString(R.string.ask_save))
|
||||
.setMessage(R.string.ask_save)
|
||||
.setPositiveButton(getString(R.string.save)) { _, _ ->
|
||||
if (save()){
|
||||
finish()
|
||||
|
@ -1,27 +1,16 @@
|
||||
package sushi.hardcore.droidfs.file_viewers
|
||||
|
||||
import android.widget.MediaController
|
||||
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||
import kotlinx.android.synthetic.main.activity_video_player.*
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
|
||||
class VideoPlayer: FileViewerActivity() {
|
||||
class VideoPlayer: MediaPlayer() {
|
||||
override fun viewFile() {
|
||||
val mc = MediaController(this)
|
||||
super.viewFile()
|
||||
setContentView(R.layout.activity_video_player)
|
||||
mc.setAnchorView(video_player)
|
||||
video_player.setOnErrorListener { _, _, _ ->
|
||||
ColoredAlertDialog(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.video_play_failed))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
||||
.show()
|
||||
true
|
||||
}
|
||||
val tmpFileUri = exportFile(filePath)
|
||||
video_player.setVideoURI(tmpFileUri)
|
||||
video_player.setMediaController(mc)
|
||||
video_player.start()
|
||||
}
|
||||
|
||||
override fun bindPlayer(player: SimpleExoPlayer) {
|
||||
video_player.player = player
|
||||
}
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
package sushi.hardcore.droidfs.provider
|
||||
|
||||
import android.content.ContentProvider
|
||||
import android.content.ContentValues
|
||||
import android.database.Cursor
|
||||
import android.database.MatrixCursor
|
||||
import android.net.Uri
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.provider.MediaStore
|
||||
import sushi.hardcore.droidfs.BuildConfig
|
||||
import sushi.hardcore.droidfs.util.Wiper
|
||||
import java.io.File
|
||||
import java.util.*
|
||||
import java.util.regex.Pattern
|
||||
|
||||
class RestrictedFileProvider: ContentProvider() {
|
||||
companion object {
|
||||
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)
|
||||
|
||||
fun newFile(fileName: String): Uri? {
|
||||
val uuid = UUID.randomUUID().toString()
|
||||
val file = File(tempFilesDir, uuid)
|
||||
return if (file.createNewFile()){
|
||||
tempFiles[uuid] = TemporaryFile(fileName, file)
|
||||
Uri.withAppendedPath(CONTENT_URI, uuid)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun wipeAll() {
|
||||
tempFilesDir.listFiles()?.let{
|
||||
for (file in it) {
|
||||
Wiper.wipe(file)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getFileFromUri(uri: Uri): TemporaryFile? {
|
||||
val uuid = uri.lastPathSegment
|
||||
if (uuid != null) {
|
||||
if (UUID_PATTERN.matcher(uuid).matches()) {
|
||||
return tempFiles[uuid]
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCreate(): Boolean {
|
||||
context?.let {
|
||||
tempFilesDir = File(it.cacheDir, TEMPORARY_FILES_DIR_NAME)
|
||||
return tempFilesDir.mkdirs()
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
override fun insert(uri: Uri, values: ContentValues?): Uri? {
|
||||
throw RuntimeException("Operation not supported")
|
||||
}
|
||||
|
||||
override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<String>?): Int {
|
||||
throw RuntimeException("Operation not supported")
|
||||
}
|
||||
|
||||
override fun query(uri: Uri, projection: Array<String>?, selection: String?, selectionArgs: Array<String>?, sortOrder: String?): Cursor? {
|
||||
val temporaryFile = getFileFromUri(uri)
|
||||
if (temporaryFile != null) {
|
||||
val cursor = MatrixCursor(
|
||||
arrayOf(
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.SIZE
|
||||
)
|
||||
)
|
||||
cursor.newRow()
|
||||
.add(temporaryFile.fileName)
|
||||
.add(temporaryFile.file.length())
|
||||
return cursor
|
||||
}
|
||||
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)
|
||||
}
|
||||
return 1
|
||||
}
|
||||
|
||||
override fun getType(uri: Uri): String {
|
||||
return "application/octet-stream"
|
||||
}
|
||||
|
||||
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))
|
||||
}
|
||||
} else {
|
||||
throw SecurityException("Read-only access")
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
@ -1,326 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package sushi.hardcore.droidfs.provider;
|
||||
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.Application;
|
||||
import android.content.ClipDescription;
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
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.Build;
|
||||
import android.os.ParcelFileDescriptor;
|
||||
import android.provider.MediaStore;
|
||||
import android.util.Log;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import sushi.hardcore.droidfs.BuildConfig;
|
||||
|
||||
import sushi.hardcore.droidfs.util.DatabaseUtil;
|
||||
import sushi.hardcore.droidfs.util.Wiper;
|
||||
|
||||
/**
|
||||
* Borrowed from OpenKeyChain
|
||||
* I removed the scheduled cleanup because it requires unwanted permissions and doesn't work very well.
|
||||
* But don't panic ! The "clear_cache" function from ExternalProvider do the same job when needed.
|
||||
**/
|
||||
|
||||
|
||||
/**
|
||||
* TemporaryStorageProvider stores decrypted files inside the app's cache directory previously to
|
||||
* sharing them with other applications.
|
||||
* <p/>
|
||||
* Security:
|
||||
* - It is writable by OpenKeychain only (see Manifest), but exported for reading files
|
||||
* - It uses UUIDs as identifiers which makes predicting files from outside impossible
|
||||
* - Querying a number of files is not allowed, only querying single files
|
||||
* -> You can only open a file if you know the Uri containing the precise UUID, this Uri is only
|
||||
* revealed when the user shares a decrypted file with another app.
|
||||
* <p/>
|
||||
* Why is support lib's FileProvider not used?
|
||||
* Because granting Uri permissions temporarily does not work correctly. See
|
||||
* - https://code.google.com/p/android/issues/detail?id=76683
|
||||
* - https://github.com/nmr8acme/FileProvider-permission-bug
|
||||
* - http://stackoverflow.com/q/24467696
|
||||
* - http://stackoverflow.com/q/18249007
|
||||
* - Comments at http://www.blogc.at/2014/03/23/share-private-files-with-other-apps-fileprovider/
|
||||
*/
|
||||
public class TemporaryFileProvider extends ContentProvider {
|
||||
|
||||
private static final int TEMPFILE_TTL = 10 * 60 * 1000; // 10 minutes
|
||||
|
||||
private static final String DB_NAME = "tempstorage.db";
|
||||
private static final String TABLE_FILES = "files";
|
||||
public static final String AUTHORITY = BuildConfig.APPLICATION_ID + ".tempstorage";
|
||||
public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY);
|
||||
private static final int DB_VERSION = 3;
|
||||
|
||||
interface TemporaryFileColumns {
|
||||
String COLUMN_UUID = "id";
|
||||
String COLUMN_NAME = "name";
|
||||
String COLUMN_TIME = "time";
|
||||
String COLUMN_TYPE = "mimetype";
|
||||
}
|
||||
|
||||
private static final String TEMP_FILES_DIR = "temp";
|
||||
private static File tempFilesDir;
|
||||
|
||||
private static Pattern UUID_PATTERN = Pattern.compile("[a-fA-F0-9-]+");
|
||||
|
||||
public static void wipeAll(){
|
||||
for (File f: tempFilesDir.listFiles()){
|
||||
Wiper.wipe(f);
|
||||
}
|
||||
}
|
||||
|
||||
public static Uri createFile(Context context, String targetName, String mimeType) {
|
||||
ContentResolver contentResolver = context.getContentResolver();
|
||||
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(TemporaryFileColumns.COLUMN_NAME, targetName);
|
||||
contentValues.put(TemporaryFileColumns.COLUMN_TYPE, mimeType);
|
||||
contentValues.put(TemporaryFileColumns.COLUMN_TIME, System.currentTimeMillis());
|
||||
Uri resultUri = contentResolver.insert(CONTENT_URI, contentValues);
|
||||
|
||||
//scheduleCleanupAfterTtl(context);
|
||||
return resultUri;
|
||||
}
|
||||
|
||||
public static Uri createFile(Context context, String targetName) {
|
||||
return createFile(context, targetName, null);
|
||||
}
|
||||
|
||||
public static Uri createFile(Context context) {
|
||||
ContentValues contentValues = new ContentValues();
|
||||
return context.getContentResolver().insert(CONTENT_URI, contentValues);
|
||||
}
|
||||
|
||||
public static int setName(Context context, Uri uri, String name) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(TemporaryFileColumns.COLUMN_NAME, name);
|
||||
return context.getContentResolver().update(uri, values, null, null);
|
||||
}
|
||||
|
||||
public static int setMimeType(Context context, Uri uri, String mimetype) {
|
||||
ContentValues values = new ContentValues();
|
||||
values.put(TemporaryFileColumns.COLUMN_TYPE, mimetype);
|
||||
return context.getContentResolver().update(uri, values, null, null);
|
||||
}
|
||||
|
||||
private static class TemporaryStorageDatabase extends SQLiteOpenHelper {
|
||||
|
||||
public TemporaryStorageDatabase(Context context) {
|
||||
super(context, DB_NAME, null, DB_VERSION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate(SQLiteDatabase db) {
|
||||
db.execSQL("CREATE TABLE IF NOT EXISTS " + TABLE_FILES + " (" +
|
||||
TemporaryFileColumns.COLUMN_UUID + " TEXT PRIMARY KEY, " +
|
||||
TemporaryFileColumns.COLUMN_NAME + " TEXT, " +
|
||||
TemporaryFileColumns.COLUMN_TYPE + " TEXT, " +
|
||||
TemporaryFileColumns.COLUMN_TIME + " INTEGER" +
|
||||
");");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
||||
|
||||
switch (oldVersion) {
|
||||
case 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, " +
|
||||
TemporaryFileColumns.COLUMN_TIME + " INTEGER" +
|
||||
");");
|
||||
case 2:
|
||||
db.execSQL("ALTER TABLE files ADD COLUMN " + TemporaryFileColumns.COLUMN_TYPE + " TEXT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static TemporaryStorageDatabase db;
|
||||
|
||||
private File getFile(Uri uri) throws FileNotFoundException {
|
||||
try {
|
||||
return getFile(uri.getLastPathSegment());
|
||||
} catch (NumberFormatException e) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
private File getFile(String id) {
|
||||
Matcher m = UUID_PATTERN.matcher(id);
|
||||
if (!m.matches()) {
|
||||
throw new SecurityException("Can only open temporary files with UUIDs!");
|
||||
}
|
||||
|
||||
return new File(tempFilesDir, id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
db = new TemporaryStorageDatabase(getContext());
|
||||
tempFilesDir = new File(getContext().getCacheDir(), TEMP_FILES_DIR);
|
||||
return tempFilesDir.mkdirs();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
|
||||
if (uri.getLastPathSegment() == null) {
|
||||
throw new SecurityException("Listing temporary files is not allowed, only querying single files.");
|
||||
}
|
||||
|
||||
File file;
|
||||
try {
|
||||
file = getFile(uri);
|
||||
} catch (FileNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Cursor fileName = db.getReadableDatabase().query(TABLE_FILES,
|
||||
new String[]{TemporaryFileColumns.COLUMN_NAME},
|
||||
TemporaryFileColumns.COLUMN_UUID + "=?",
|
||||
new String[]{uri.getLastPathSegment()}, null, null, null);
|
||||
if (fileName != null) {
|
||||
if (fileName.moveToNext()) {
|
||||
MatrixCursor cursor = new MatrixCursor(new String[]{
|
||||
MediaStore.MediaColumns.DISPLAY_NAME,
|
||||
MediaStore.MediaColumns.SIZE,
|
||||
MediaStore.MediaColumns.DATA,
|
||||
});
|
||||
cursor.newRow()
|
||||
.add(fileName.getString(0))
|
||||
.add(file.length())
|
||||
.add(file.getAbsolutePath());
|
||||
fileName.close();
|
||||
return cursor;
|
||||
}
|
||||
fileName.close();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(Uri uri) {
|
||||
Cursor cursor = db.getReadableDatabase().query(TABLE_FILES,
|
||||
new String[]{TemporaryFileColumns.COLUMN_TYPE},
|
||||
TemporaryFileColumns.COLUMN_UUID + "=?",
|
||||
new String[]{uri.getLastPathSegment()}, null, null, null);
|
||||
if (cursor != null) {
|
||||
try {
|
||||
if (cursor.moveToNext()) {
|
||||
if (!cursor.isNull(0)) {
|
||||
return cursor.getString(0);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
return "application/octet-stream";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getStreamTypes(Uri uri, String mimeTypeFilter) {
|
||||
String type = getType(uri);
|
||||
if (ClipDescription.compareMimeTypes(type, mimeTypeFilter)) {
|
||||
return new String[]{type};
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(Uri uri, ContentValues values) {
|
||||
String uuid = UUID.randomUUID().toString();
|
||||
values.put(TemporaryFileColumns.COLUMN_UUID, uuid);
|
||||
int insert = (int) db.getWritableDatabase().insert(TABLE_FILES, null, values);
|
||||
if (insert == -1) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
getFile(uuid).createNewFile();
|
||||
} catch (IOException e) {
|
||||
return null;
|
||||
}
|
||||
return Uri.withAppendedPath(CONTENT_URI, uuid);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(Uri uri, String selection, String[] selectionArgs) {
|
||||
if (uri == null) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
String fileUuidFromUri = uri.getLastPathSegment();
|
||||
if (fileUuidFromUri != null) {
|
||||
selection = DatabaseUtil.concatenateWhere(selection, TemporaryFileColumns.COLUMN_UUID + "=?");
|
||||
selectionArgs = DatabaseUtil.appendSelectionArgs(selectionArgs, new String[]{ fileUuidFromUri });
|
||||
}
|
||||
|
||||
Cursor files = db.getReadableDatabase().query(TABLE_FILES, new String[]{TemporaryFileColumns.COLUMN_UUID}, selection,
|
||||
selectionArgs, null, null, null);
|
||||
if (files != null) {
|
||||
while (files.moveToNext()) {
|
||||
getFile(files.getString(0)).delete();
|
||||
}
|
||||
files.close();
|
||||
return db.getWritableDatabase().delete(TABLE_FILES, selection, selectionArgs);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
|
||||
if (values.size() != 1) {
|
||||
throw new UnsupportedOperationException("Update supported only for one field at a time!");
|
||||
}
|
||||
if (!values.containsKey(TemporaryFileColumns.COLUMN_NAME) && !values.containsKey(TemporaryFileColumns.COLUMN_TYPE)) {
|
||||
throw new UnsupportedOperationException("Update supported only for name and type field!");
|
||||
}
|
||||
if (selection != null || selectionArgs != null) {
|
||||
throw new UnsupportedOperationException("Update supported only for plain uri!");
|
||||
}
|
||||
return db.getWritableDatabase().update(TABLE_FILES, values,
|
||||
TemporaryFileColumns.COLUMN_UUID + " = ?", new String[]{uri.getLastPathSegment()});
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {
|
||||
return openFileHelper(uri, mode);
|
||||
}
|
||||
}
|
@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package sushi.hardcore.droidfs.util;
|
||||
|
||||
|
||||
import androidx.sqlite.db.SupportSQLiteDatabase;
|
||||
import android.database.Cursor;
|
||||
import android.text.TextUtils;
|
||||
|
||||
|
||||
/**
|
||||
* Borrowed from OpenKeyChain
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Shamelessly copied from android.database.DatabaseUtils
|
||||
*/
|
||||
public class DatabaseUtil {
|
||||
/**
|
||||
* Concatenates two SQL WHERE clauses, handling empty or null values.
|
||||
*/
|
||||
public static String concatenateWhere(String a, String b) {
|
||||
if (TextUtils.isEmpty(a)) {
|
||||
return b;
|
||||
}
|
||||
if (TextUtils.isEmpty(b)) {
|
||||
return a;
|
||||
}
|
||||
|
||||
return "(" + a + ") AND (" + b + ")";
|
||||
}
|
||||
|
||||
/**
|
||||
* Appends one set of selection args to another. This is useful when adding a selection
|
||||
* argument to a user provided set.
|
||||
*/
|
||||
public static String[] appendSelectionArgs(String[] originalValues, String[] newValues) {
|
||||
if (originalValues == null || originalValues.length == 0) {
|
||||
return newValues;
|
||||
}
|
||||
String[] result = new String[originalValues.length + newValues.length ];
|
||||
System.arraycopy(originalValues, 0, result, 0, originalValues.length);
|
||||
System.arraycopy(newValues, 0, result, originalValues.length, newValues.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static void explainQuery(SupportSQLiteDatabase db, String sql) {
|
||||
Cursor explainCursor = db.query("EXPLAIN QUERY PLAN " + sql, new String[0]);
|
||||
|
||||
// this is a debugging feature, we can be a little careless
|
||||
explainCursor.moveToFirst();
|
||||
|
||||
StringBuilder line = new StringBuilder();
|
||||
for (int i = 0; i < explainCursor.getColumnCount(); i++) {
|
||||
line.append(explainCursor.getColumnName(i)).append(", ");
|
||||
}
|
||||
|
||||
while (!explainCursor.isAfterLast()) {
|
||||
line = new StringBuilder();
|
||||
for (int i = 0; i < explainCursor.getColumnCount(); i++) {
|
||||
line.append(explainCursor.getString(i)).append(", ");
|
||||
}
|
||||
explainCursor.moveToNext();
|
||||
}
|
||||
|
||||
explainCursor.close();
|
||||
}
|
||||
}
|
@ -3,9 +3,8 @@ package sushi.hardcore.droidfs.util
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.provider.TemporaryFileProvider
|
||||
import sushi.hardcore.droidfs.provider.RestrictedFileProvider
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||
import java.io.File
|
||||
import java.net.URLConnection
|
||||
@ -13,51 +12,53 @@ import java.util.*
|
||||
|
||||
object ExternalProvider {
|
||||
private const val content_type_all = "*/*"
|
||||
private var cached_files: MutableList<Uri> = ArrayList()
|
||||
private fun get_content_type(filename: String, previous_content_type: String?): String? {
|
||||
private var storedFiles: MutableList<Uri> = ArrayList()
|
||||
private fun getContentType(filename: String, previous_content_type: String?): String? {
|
||||
if (content_type_all != previous_content_type) {
|
||||
var content_type = URLConnection.guessContentTypeFromName(filename)
|
||||
if (content_type == null) {
|
||||
content_type = content_type_all
|
||||
var contentType = URLConnection.guessContentTypeFromName(filename)
|
||||
if (contentType == null) {
|
||||
contentType = content_type_all
|
||||
}
|
||||
if (previous_content_type == null) {
|
||||
return content_type
|
||||
} else if (previous_content_type != content_type) {
|
||||
return contentType
|
||||
} else if (previous_content_type != contentType) {
|
||||
return content_type_all
|
||||
}
|
||||
}
|
||||
return previous_content_type
|
||||
}
|
||||
|
||||
private fun export_file(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Export_file_result {
|
||||
val filename = File(file_path).name
|
||||
val tmp_file_uri = TemporaryFileProvider.createFile(context, filename)
|
||||
cached_files.add(tmp_file_uri)
|
||||
if (gocryptfsVolume.export_file(context, file_path, tmp_file_uri)) {
|
||||
return Export_file_result(tmp_file_uri, get_content_type(filename, previous_content_type))
|
||||
private fun exportFile(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Pair<Uri?, String?> {
|
||||
val fileName = File(file_path).name
|
||||
val tmpFileUri = RestrictedFileProvider.newFile(fileName)
|
||||
if (tmpFileUri != null){
|
||||
storedFiles.add(tmpFileUri)
|
||||
if (gocryptfsVolume.export_file(context, file_path, tmpFileUri)) {
|
||||
return Pair(tmpFileUri, getContentType(fileName, previous_content_type))
|
||||
}
|
||||
}
|
||||
ColoredAlertDialog(context)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(context.getString(R.string.export_failed, file_path))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
return Export_file_result(null, null)
|
||||
return Pair(null, null)
|
||||
}
|
||||
|
||||
fun share(context: Context, gocryptfsVolume: GocryptfsVolume, file_paths: List<String>) {
|
||||
var content_type: String? = null
|
||||
var contentType: String? = null
|
||||
val uris = ArrayList<Uri>()
|
||||
for (path in file_paths) {
|
||||
val result = export_file(context, gocryptfsVolume, path, content_type)
|
||||
content_type = if (result.uri == null) {
|
||||
return
|
||||
val result = exportFile(context, gocryptfsVolume, path, contentType)
|
||||
contentType = if (result.first != null) {
|
||||
uris.add(result.first!!)
|
||||
result.second
|
||||
} else {
|
||||
uris.add(result.uri!!)
|
||||
result.content_type
|
||||
return
|
||||
}
|
||||
}
|
||||
val shareIntent = Intent()
|
||||
shareIntent.type = content_type
|
||||
shareIntent.type = contentType
|
||||
if (uris.size == 1) {
|
||||
shareIntent.action = Intent.ACTION_SEND
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uris[0])
|
||||
@ -69,24 +70,22 @@ object ExternalProvider {
|
||||
}
|
||||
|
||||
fun open(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String) {
|
||||
val result = export_file(context, gocryptfsVolume, file_path, null)
|
||||
result.uri?.let {
|
||||
val result = exportFile(context, gocryptfsVolume, file_path, null)
|
||||
result.first?.let {
|
||||
val openIntent = Intent()
|
||||
openIntent.action = Intent.ACTION_VIEW
|
||||
openIntent.setDataAndType(result.uri, result.content_type)
|
||||
openIntent.setDataAndType(result.first, result.second)
|
||||
context.startActivity(openIntent)
|
||||
}
|
||||
}
|
||||
|
||||
fun clear_cache(context: Context) {
|
||||
fun removeFiles(context: Context) {
|
||||
Thread{
|
||||
for (uri in cached_files) {
|
||||
for (uri in storedFiles) {
|
||||
if (Wiper.wipe(context, uri)){
|
||||
cached_files.remove(uri)
|
||||
storedFiles.remove(uri)
|
||||
}
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private class Export_file_result(var uri: Uri?, var content_type: String?)
|
||||
}
|
@ -26,7 +26,6 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
const val KeyLen = 32
|
||||
const val ScryptDefaultLogN = 16
|
||||
const val DefaultBS = 4096
|
||||
//external fun scrypt_hash(data: CharArray, logN: Int): ByteArray
|
||||
external fun create_volume(root_cipher_dir: String, password: CharArray, logN: Int, creator: String): Boolean
|
||||
external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
|
||||
external fun change_password(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
|
||||
|
@ -1,42 +1,18 @@
|
||||
package sushi.hardcore.droidfs.util;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.annotation.TargetApi;
|
||||
import android.content.ContentUris;
|
||||
import android.content.Context;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Environment;
|
||||
import android.os.storage.StorageManager;
|
||||
import android.provider.DocumentsContract;
|
||||
import android.provider.MediaStore;
|
||||
import android.provider.OpenableColumns;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.ArrayList;
|
||||
|
||||
public final class FilesUtils {
|
||||
|
||||
/*public static ArrayList<File> recursive_get_files(File root_path){
|
||||
ArrayList<File> results_files = new ArrayList<>();
|
||||
final File[] elements = root_path.listFiles();
|
||||
if (elements != null){
|
||||
for (File item : elements){
|
||||
if (item.isDirectory()){
|
||||
results_files.addAll(recursive_get_files(item));
|
||||
} else if (item.isFile()){
|
||||
results_files.add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
return results_files;
|
||||
}*/
|
||||
public class PathUtils {
|
||||
|
||||
public static String get_parent_path(String path){
|
||||
if (path.endsWith("/")){
|
@ -2,6 +2,7 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:gravity="center_vertical"
|
||||
android:background="@color/fullScreenBackgroundColor"
|
||||
android:orientation="vertical">
|
||||
@ -14,33 +15,11 @@
|
||||
android:textSize="18sp"
|
||||
android:padding="10dp"/>
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredSeekBar
|
||||
android:id="@+id/seekbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
<com.google.android.exoplayer2.ui.PlayerControlView
|
||||
android:id="@+id/audio_controller"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_horizontal">
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredImageButton
|
||||
android:id="@+id/button_pause"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:src="@drawable/icon_pause"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="#00000000"/>
|
||||
|
||||
<sushi.hardcore.droidfs.widgets.ColoredImageButton
|
||||
android:id="@+id/button_stop"
|
||||
android:layout_width="50dp"
|
||||
android:layout_height="50dp"
|
||||
android:src="@drawable/icon_stop"
|
||||
android:scaleType="fitCenter"
|
||||
android:background="#00000000"/>
|
||||
|
||||
</LinearLayout>
|
||||
app:controller_layout_id="@layout/exo_custom_playback_control"
|
||||
app:show_timeout="0"/>
|
||||
|
||||
</LinearLayout>
|
@ -2,13 +2,15 @@
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:gravity="center"
|
||||
android:background="@color/fullScreenBackgroundColor">
|
||||
|
||||
<VideoView
|
||||
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
|
||||
android:id="@+id/video_player"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"/>
|
||||
android:layout_gravity="center"
|
||||
app:controller_layout_id="@layout/exo_custom_playback_control"/>
|
||||
|
||||
</LinearLayout>
|
74
app/src/main/res/layout/exo_custom_playback_control.xml
Normal file
74
app/src/main/res/layout/exo_custom_playback_control.xml
Normal file
@ -0,0 +1,74 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="bottom"
|
||||
android:layoutDirection="ltr"
|
||||
android:background="#CC000000"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:paddingTop="4dp"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageButton android:id="@id/exo_rew"
|
||||
style="@style/ExoMediaButton.Rewind"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_shuffle"
|
||||
style="@style/ExoMediaButton"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_repeat_toggle"
|
||||
style="@style/ExoMediaButton"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_play"
|
||||
style="@style/ExoMediaButton.Play"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_pause"
|
||||
style="@style/ExoMediaButton.Pause"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_ffwd"
|
||||
style="@style/ExoMediaButton.FastForward"/>
|
||||
|
||||
<ImageButton android:id="@id/exo_vr"
|
||||
style="@style/ExoMediaButton.VR"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:gravity="center_vertical"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView android:id="@id/exo_position"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="#FFBEBEBE"/>
|
||||
|
||||
<View android:id="@id/exo_progress_placeholder"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:layout_height="26dp"/>
|
||||
|
||||
<TextView android:id="@id/exo_duration"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="14sp"
|
||||
android:textStyle="bold"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:includeFontPadding="false"
|
||||
android:textColor="#FFBEBEBE"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
@ -109,7 +109,7 @@
|
||||
<string name="video">Video</string>
|
||||
<string name="audio">Audio</string>
|
||||
<string name="media_player_prepare_failed">Failed to initialize the media player.</string>
|
||||
<string name="video_play_failed">Failed to play this video.</string>
|
||||
<string name="playing_failed">Failed to play this file.</string>
|
||||
<string name="text">Text</string>
|
||||
<string name="save_failed">Save failed</string>
|
||||
<string name="file_saved">File saved !</string>
|
||||
|
@ -2,11 +2,8 @@
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.Cyanea.Dark.NoActionBar">
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<!--<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>-->
|
||||
<item name="android:colorBackground">@color/backgroundColor</item>
|
||||
<item name="android:textColor">@color/textColor</item>
|
||||
<!--<item name="buttonStyle">@style/button</item>-->
|
||||
<item name="checkboxStyle">@style/checkbox</item>
|
||||
<!--<item name="actionOverflowMenuStyle">@style/menu_overflow</item>-->
|
||||
</style>
|
||||
|
Loading…
Reference in New Issue
Block a user