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
|
compileSdkVersion 29
|
||||||
buildToolsVersion "30.0.0"
|
buildToolsVersion "30.0.0"
|
||||||
|
|
||||||
|
compileOptions {
|
||||||
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
|
}
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "sushi.hardcore.droidfs"
|
applicationId "sushi.hardcore.droidfs"
|
||||||
minSdkVersion 21
|
minSdkVersion 21
|
||||||
targetSdkVersion 28
|
targetSdkVersion 29
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "1.0.1"
|
versionName "1.1.0"
|
||||||
|
|
||||||
ndk {
|
ndk {
|
||||||
abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a'
|
||||||
@ -35,7 +39,7 @@ android {
|
|||||||
dependencies {
|
dependencies {
|
||||||
implementation fileTree(dir: "libs", include: ["*.jar"])
|
implementation fileTree(dir: "libs", include: ["*.jar"])
|
||||||
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
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'
|
implementation 'androidx.appcompat:appcompat:1.1.0'
|
||||||
testImplementation 'junit:junit:4.12'
|
testImplementation 'junit:junit:4.12'
|
||||||
|
|
||||||
@ -44,4 +48,6 @@ dependencies {
|
|||||||
implementation 'com.github.clans:fab:1.6.4'
|
implementation 'com.github.clans:fab:1.6.4'
|
||||||
implementation 'com.jaredrummler:cyanea:1.0.2'
|
implementation 'com.jaredrummler:cyanea:1.0.2'
|
||||||
implementation 'com.github.bumptech.glide:glide:4.11.0'
|
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)
|
defer syscall.Close(dirfd)
|
||||||
fd, err := syscallcompat.Openat(dirfd, cName, newFlags, 0)
|
fd, err := syscallcompat.Openat(dirfd, cName, newFlags, 0)
|
||||||
// Handle a few specific errors
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return -1
|
return -1
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,10 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="sushi.hardcore.droidfs">
|
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.READ_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||||
@ -13,7 +17,8 @@
|
|||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@style/AppTheme">
|
android:theme="@style/AppTheme"
|
||||||
|
android:requestLegacyExternalStorage="true">
|
||||||
<activity
|
<activity
|
||||||
android:name=".SettingsActivity"
|
android:name=".SettingsActivity"
|
||||||
android:label="@string/title_activity_settings"
|
android:label="@string/title_activity_settings"
|
||||||
@ -53,10 +58,10 @@
|
|||||||
<activity android:name=".file_viewers.TextEditor" android:configChanges="screenSize|orientation" />
|
<activity android:name=".file_viewers.TextEditor" android:configChanges="screenSize|orientation" />
|
||||||
|
|
||||||
<provider
|
<provider
|
||||||
android:name=".provider.TemporaryFileProvider"
|
android:name=".provider.RestrictedFileProvider"
|
||||||
android:authorities="${applicationId}.tempstorage"
|
android:authorities="${applicationId}.temporary_provider"
|
||||||
android:exported="true"
|
android:exported="true"
|
||||||
android:writePermission="${applicationId}.READ_TEMPORARY_STORAGE" />
|
android:writePermission="${applicationId}.WRITE_TEMPORARY_STORAGE"/>
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
|
@ -3,7 +3,6 @@ package sushi.hardcore.droidfs
|
|||||||
import android.content.SharedPreferences
|
import android.content.SharedPreferences
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.WindowManager
|
import android.view.WindowManager
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.preference.PreferenceManager
|
import androidx.preference.PreferenceManager
|
||||||
import com.jaredrummler.cyanea.app.CyaneaAppCompatActivity
|
import com.jaredrummler.cyanea.app.CyaneaAppCompatActivity
|
||||||
|
@ -7,7 +7,6 @@ import android.os.Bundle
|
|||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.AdapterView.OnItemClickListener
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import kotlinx.android.synthetic.main.activity_change_password.*
|
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 kotlinx.android.synthetic.main.toolbar.*
|
||||||
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
|
import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter
|
||||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
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.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||||
import sushi.hardcore.droidfs.util.Wiper
|
import sushi.hardcore.droidfs.util.Wiper
|
||||||
@ -80,7 +79,7 @@ class ChangePasswordActivity : BaseActivity() {
|
|||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
val path = FilesUtils.getFullPathFromTreeUri(data.data, this)
|
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
|
||||||
edit_volume_path.setText(path)
|
edit_volume_path.setText(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package sushi.hardcore.droidfs
|
package sushi.hardcore.droidfs
|
||||||
|
|
||||||
|
import android.net.Uri
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
class ConstValues {
|
class ConstValues {
|
||||||
@ -7,6 +8,7 @@ class ConstValues {
|
|||||||
const val creator = "DroidFS"
|
const val creator = "DroidFS"
|
||||||
const val saved_volumes_key = "saved_volumes"
|
const val saved_volumes_key = "saved_volumes"
|
||||||
const val sort_order_key = "sort_order"
|
const val sort_order_key = "sort_order"
|
||||||
|
val fakeUri = Uri.parse("fakeuri://droidfs")
|
||||||
const val wipe_passes = 2
|
const val wipe_passes = 2
|
||||||
const val seek_bar_inc = 200
|
const val seek_bar_inc = 200
|
||||||
private val fileExtensions = mapOf(
|
private val fileExtensions = mapOf(
|
||||||
|
@ -5,7 +5,6 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import kotlinx.android.synthetic.main.activity_create.*
|
import kotlinx.android.synthetic.main.activity_create.*
|
||||||
import kotlinx.android.synthetic.main.activity_create.checkbox_remember_path
|
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 kotlinx.android.synthetic.main.toolbar.*
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
import sushi.hardcore.droidfs.explorers.ExplorerActivity
|
||||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
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.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||||
import sushi.hardcore.droidfs.util.Wiper
|
import sushi.hardcore.droidfs.util.Wiper
|
||||||
@ -57,7 +56,7 @@ class CreateActivity : BaseActivity() {
|
|||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
val path = FilesUtils.getFullPathFromTreeUri(data.data, this)
|
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
|
||||||
edit_volume_path.setText(path)
|
edit_volume_path.setText(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -35,14 +35,14 @@ class MainActivity : BaseActivity() {
|
|||||||
if (!storageAvailable) {
|
if (!storageAvailable) {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.storage_unavailable)
|
.setTitle(R.string.storage_unavailable)
|
||||||
.setMessage(getString(R.string.storage_unavailable_msg))
|
.setMessage(R.string.storage_unavailable_msg)
|
||||||
.setPositiveButton(R.string.ok
|
.setPositiveButton(R.string.ok
|
||||||
) { _, _ -> finish() }.show()
|
) { _, _ -> finish() }.show()
|
||||||
}
|
}
|
||||||
if (!sharedPrefs.getBoolean("alreadyLaunched", false)){
|
if (!sharedPrefs.getBoolean("alreadyLaunched", false)){
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(getString(R.string.usf_home_warning_msg))
|
.setMessage(R.string.usf_home_warning_msg)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(getString(R.string.see_unsafe_features)){ _, _ ->
|
.setPositiveButton(getString(R.string.see_unsafe_features)){ _, _ ->
|
||||||
val intent = Intent(this, SettingsActivity::class.java)
|
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) {
|
if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.storage_perm_denied)
|
.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
|
.setPositiveButton(R.string.ok
|
||||||
) { _, _ -> finish() }.show()
|
) { _, _ -> finish() }.show()
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import android.content.Intent
|
|||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
|
||||||
import android.widget.AdapterView.OnItemClickListener
|
import android.widget.AdapterView.OnItemClickListener
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import kotlinx.android.synthetic.main.activity_open.checkbox_remember_path
|
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.ExplorerActivityDrop
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
import sushi.hardcore.droidfs.explorers.ExplorerActivityPick
|
||||||
import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver
|
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.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.WidgetUtil
|
import sushi.hardcore.droidfs.util.WidgetUtil
|
||||||
import sushi.hardcore.droidfs.util.Wiper
|
import sushi.hardcore.droidfs.util.Wiper
|
||||||
@ -76,7 +75,7 @@ class OpenActivity : BaseActivity() {
|
|||||||
if (resultCode == Activity.RESULT_OK) {
|
if (resultCode == Activity.RESULT_OK) {
|
||||||
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
val path = FilesUtils.getFullPathFromTreeUri(data.data, this)
|
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
|
||||||
edit_volume_path.setText(path)
|
edit_volume_path.setText(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -128,7 +127,7 @@ class OpenActivity : BaseActivity() {
|
|||||||
} else {
|
} else {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.open_volume_failed)
|
.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)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
|
@ -14,23 +14,23 @@ import androidx.core.content.ContextCompat
|
|||||||
import sushi.hardcore.droidfs.ConstValues.Companion.getAssociatedDrawable
|
import sushi.hardcore.droidfs.ConstValues.Companion.getAssociatedDrawable
|
||||||
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
import sushi.hardcore.droidfs.explorers.ExplorerElement
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.util.FilesUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.ThemeColor
|
import sushi.hardcore.droidfs.widgets.ThemeColor
|
||||||
import java.text.DateFormat
|
import java.text.DateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ExplorerElementAdapter(private val context: Context) : BaseAdapter() {
|
class ExplorerElementAdapter(private val context: Context) : BaseAdapter() {
|
||||||
private val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, context.resources.configuration.locale)
|
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)
|
private val inflater: LayoutInflater = LayoutInflater.from(context)
|
||||||
val selectedItems: MutableList<Int> = ArrayList()
|
val selectedItems: MutableList<Int> = ArrayList()
|
||||||
private val themeColor = ThemeColor.getThemeColor(context)
|
private val themeColor = ThemeColor.getThemeColor(context)
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return explorer_elements.size
|
return explorerElements.size
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItem(position: Int): ExplorerElement {
|
override fun getItem(position: Int): ExplorerElement {
|
||||||
return explorer_elements[position]
|
return explorerElements[position]
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getItemId(position: Int): Long {
|
override fun getItemId(position: Int): Long {
|
||||||
@ -55,7 +55,7 @@ class ExplorerElementAdapter(private val context: Context) : BaseAdapter() {
|
|||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
textElementMtime.text = dateFormat.format(currentElement.mTime)
|
textElementMtime.text = dateFormat.format(currentElement.mTime)
|
||||||
textElementSize.text = FilesUtils.formatSize(currentElement.size)
|
textElementSize.text = PathUtils.formatSize(currentElement.size)
|
||||||
drawableId = getAssociatedDrawable(currentElement.name)
|
drawableId = getAssociatedDrawable(currentElement.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,56 +73,55 @@ class ExplorerElementAdapter(private val context: Context) : BaseAdapter() {
|
|||||||
|
|
||||||
fun onItemClick(position: Int) {
|
fun onItemClick(position: Int) {
|
||||||
if (selectedItems.isNotEmpty()) {
|
if (selectedItems.isNotEmpty()) {
|
||||||
if (!explorer_elements[position].isParentFolder) {
|
if (!explorerElements[position].isParentFolder) {
|
||||||
if (selectedItems.contains(position)) {
|
if (selectedItems.contains(position)) {
|
||||||
selectedItems.remove(position)
|
selectedItems.remove(position)
|
||||||
} else {
|
} else {
|
||||||
selectedItems.add(position)
|
selectedItems.add(position)
|
||||||
}
|
}
|
||||||
notifyDataSetInvalidated()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onItemLongClick(position: Int) {
|
fun onItemLongClick(position: Int) {
|
||||||
if (!explorer_elements[position].isParentFolder) {
|
if (!explorerElements[position].isParentFolder) {
|
||||||
if (!selectedItems.contains(position)) {
|
if (!selectedItems.contains(position)) {
|
||||||
selectedItems.add(position)
|
selectedItems.add(position)
|
||||||
} else {
|
} else {
|
||||||
selectedItems.remove(position)
|
selectedItems.remove(position)
|
||||||
}
|
}
|
||||||
notifyDataSetInvalidated()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun selectAll() {
|
fun selectAll() {
|
||||||
for (i in explorer_elements.indices) {
|
for (i in explorerElements.indices) {
|
||||||
if (!selectedItems.contains(i) && !explorer_elements[i].isParentFolder) {
|
if (!selectedItems.contains(i) && !explorerElements[i].isParentFolder) {
|
||||||
selectedItems.add(i)
|
selectedItems.add(i)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
notifyDataSetInvalidated()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun unSelectAll() {
|
fun unSelectAll() {
|
||||||
selectedItems.clear()
|
selectedItems.clear()
|
||||||
notifyDataSetInvalidated()
|
notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setExplorerElements(explorer_elements: List<ExplorerElement>) {
|
fun setExplorerElements(explorer_elements: List<ExplorerElement>) {
|
||||||
unSelectAll()
|
unSelectAll()
|
||||||
this.explorer_elements = explorer_elements
|
this.explorerElements = explorer_elements
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentDirectoryTotalSize: Long
|
val currentDirectoryTotalSize: Long
|
||||||
get() {
|
get() {
|
||||||
var total_size: Long = 0
|
var totalSize: Long = 0
|
||||||
for (e in explorer_elements) {
|
for (e in explorerElements) {
|
||||||
if (e.isRegularFile) {
|
if (e.isRegularFile) {
|
||||||
total_size += e.size
|
totalSize += e.size
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return total_size
|
return totalSize
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -1,7 +1,6 @@
|
|||||||
package sushi.hardcore.droidfs.explorers
|
package sushi.hardcore.droidfs.explorers
|
||||||
|
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.content.SharedPreferences
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
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.ImageViewer
|
||||||
import sushi.hardcore.droidfs.file_viewers.TextEditor
|
import sushi.hardcore.droidfs.file_viewers.TextEditor
|
||||||
import sushi.hardcore.droidfs.file_viewers.VideoPlayer
|
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.ExternalProvider
|
||||||
import sushi.hardcore.droidfs.util.FilesUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
open class BaseExplorerActivity : BaseActivity() {
|
open class BaseExplorerActivity : BaseActivity() {
|
||||||
private lateinit var shared_prefs_editor: SharedPreferences.Editor
|
private lateinit var sortModesEntries: Array<String>
|
||||||
private lateinit var sort_modes_entries: Array<String>
|
private lateinit var sortModesValues: Array<String>
|
||||||
private lateinit var sort_modes_values: Array<String>
|
private var currentSortModeIndex = 0
|
||||||
private var current_sort_mode_index = 0
|
|
||||||
protected lateinit var gocryptfsVolume: GocryptfsVolume
|
protected lateinit var gocryptfsVolume: GocryptfsVolume
|
||||||
private lateinit var volume_name: String
|
private lateinit var volumeName: String
|
||||||
protected var current_path = ""
|
protected var currentDirectoryPath = ""
|
||||||
protected lateinit var explorer_elements: MutableList<ExplorerElement>
|
protected lateinit var explorerElements: MutableList<ExplorerElement>
|
||||||
protected lateinit var explorer_adapter: ExplorerElementAdapter
|
protected lateinit var explorerAdapter: ExplorerElementAdapter
|
||||||
private var usf_open = false
|
private var usf_open = false
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
usf_open = sharedPrefs.getBoolean("usf_open", false)
|
||||||
val intent = intent
|
val intent = intent
|
||||||
volume_name = intent.getStringExtra("volume_name") ?: ""
|
volumeName = intent.getStringExtra("volume_name") ?: ""
|
||||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
val sessionID = intent.getIntExtra("sessionID", -1)
|
||||||
gocryptfsVolume = GocryptfsVolume(sessionID)
|
gocryptfsVolume = GocryptfsVolume(sessionID)
|
||||||
sort_modes_entries = resources.getStringArray(R.array.sort_orders_entries)
|
sortModesEntries = resources.getStringArray(R.array.sort_orders_entries)
|
||||||
sort_modes_values = resources.getStringArray(R.array.sort_orders_values)
|
sortModesValues = 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"))
|
currentSortModeIndex = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.sort_order_key, "name"))
|
||||||
shared_prefs_editor = sharedPrefs.edit()
|
|
||||||
init()
|
init()
|
||||||
setSupportActionBar(toolbar)
|
setSupportActionBar(toolbar)
|
||||||
title = ""
|
title = ""
|
||||||
title_text.text = getString(R.string.volume, volume_name)
|
title_text.text = getString(R.string.volume, volumeName)
|
||||||
explorer_adapter = ExplorerElementAdapter(this)
|
explorerAdapter = ExplorerElementAdapter(this)
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
list_explorer.adapter = explorer_adapter
|
list_explorer.adapter = explorerAdapter
|
||||||
list_explorer.onItemClickListener = OnItemClickListener { _, _, position, _ -> onExplorerItemClick(position) }
|
list_explorer.onItemClickListener = OnItemClickListener { _, _, position, _ -> onExplorerItemClick(position) }
|
||||||
list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ ->
|
list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ ->
|
||||||
explorer_adapter.onItemLongClick(position)
|
explorerAdapter.onItemLongClick(position)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
refresher.setOnRefreshListener {
|
refresher.setOnRefreshListener {
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
refresher.isRefreshing = false
|
refresher.isRefreshing = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -89,29 +86,29 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected open fun onExplorerItemClick(position: Int) {
|
protected open fun onExplorerItemClick(position: Int) {
|
||||||
val wasSelecting = explorer_adapter.selectedItems.isNotEmpty()
|
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
|
||||||
explorer_adapter.onItemClick(position)
|
explorerAdapter.onItemClick(position)
|
||||||
if (explorer_adapter.selectedItems.isEmpty()) {
|
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||||
if (!wasSelecting) {
|
if (!wasSelecting) {
|
||||||
val full_path = FilesUtils.path_join(current_path, explorer_elements[position].name)
|
val fullPath = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name)
|
||||||
when {
|
when {
|
||||||
explorer_elements[position].isDirectory -> {
|
explorerElements[position].isDirectory -> {
|
||||||
setCurrentPath(full_path)
|
setCurrentPath(fullPath)
|
||||||
}
|
}
|
||||||
explorer_elements[position].isParentFolder -> {
|
explorerElements[position].isParentFolder -> {
|
||||||
setCurrentPath(FilesUtils.get_parent_path(current_path))
|
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||||
}
|
}
|
||||||
isImage(full_path) -> {
|
isImage(fullPath) -> {
|
||||||
startFileViewer(ImageViewer::class.java, full_path)
|
startFileViewer(ImageViewer::class.java, fullPath)
|
||||||
}
|
}
|
||||||
isVideo(full_path) -> {
|
isVideo(fullPath) -> {
|
||||||
startFileViewer(VideoPlayer::class.java, full_path)
|
startFileViewer(VideoPlayer::class.java, fullPath)
|
||||||
}
|
}
|
||||||
isText(full_path) -> {
|
isText(fullPath) -> {
|
||||||
startFileViewer(TextEditor::class.java, full_path)
|
startFileViewer(TextEditor::class.java, fullPath)
|
||||||
}
|
}
|
||||||
isAudio(full_path) -> {
|
isAudio(fullPath) -> {
|
||||||
startFileViewer(AudioPlayer::class.java, full_path)
|
startFileViewer(AudioPlayer::class.java, fullPath)
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
val dialogListView = layoutInflater.inflate(R.layout.dialog_listview, null)
|
val dialogListView = layoutInflater.inflate(R.layout.dialog_listview, null)
|
||||||
@ -125,10 +122,10 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
.create()
|
.create()
|
||||||
listView.setOnItemClickListener{_, _, fileTypePosition, _ ->
|
listView.setOnItemClickListener{_, _, fileTypePosition, _ ->
|
||||||
when (adapter.getItem(fileTypePosition)){
|
when (adapter.getItem(fileTypePosition)){
|
||||||
"image" -> startFileViewer(ImageViewer::class.java, full_path)
|
"image" -> startFileViewer(ImageViewer::class.java, fullPath)
|
||||||
"video" -> startFileViewer(VideoPlayer::class.java, full_path)
|
"video" -> startFileViewer(VideoPlayer::class.java, fullPath)
|
||||||
"audio" -> startFileViewer(AudioPlayer::class.java, full_path)
|
"audio" -> startFileViewer(AudioPlayer::class.java, fullPath)
|
||||||
"text" -> startFileViewer(TextEditor::class.java, full_path)
|
"text" -> startFileViewer(TextEditor::class.java, fullPath)
|
||||||
}
|
}
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}
|
}
|
||||||
@ -141,41 +138,42 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun sortExplorerElements() {
|
private fun sortExplorerElements() {
|
||||||
when (sort_modes_values[current_sort_mode_index]) {
|
when (sortModesValues[currentSortModeIndex]) {
|
||||||
"name" -> {
|
"name" -> {
|
||||||
explorer_elements.sortWith(Comparator { o1, o2 -> o1.name.compareTo(o2.name) })
|
explorerElements.sortWith(Comparator { o1, o2 -> o1.name.compareTo(o2.name) })
|
||||||
}
|
}
|
||||||
"size" -> {
|
"size" -> {
|
||||||
explorer_elements.sortWith(Comparator { o1, o2 -> (o1.size - o2.size).toInt() })
|
explorerElements.sortWith(Comparator { o1, o2 -> (o1.size - o2.size).toInt() })
|
||||||
}
|
}
|
||||||
"date" -> {
|
"date" -> {
|
||||||
explorer_elements.sortWith(Comparator { o1, o2 -> o1.mTime.compareTo(o2.mTime) })
|
explorerElements.sortWith(Comparator { o1, o2 -> o1.mTime.compareTo(o2.mTime) })
|
||||||
}
|
}
|
||||||
"name_desc" -> {
|
"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" -> {
|
"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" -> {
|
"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])
|
val sharedPrefsEditor = sharedPrefs.edit()
|
||||||
shared_prefs_editor.apply()
|
sharedPrefsEditor.putString(ConstValues.sort_order_key, sortModesValues[currentSortModeIndex])
|
||||||
|
sharedPrefsEditor.apply()
|
||||||
}
|
}
|
||||||
|
|
||||||
protected fun setCurrentPath(path: String) {
|
protected fun setCurrentPath(path: String) {
|
||||||
explorer_elements = gocryptfsVolume.list_dir(path)
|
explorerElements = gocryptfsVolume.list_dir(path)
|
||||||
text_dir_empty.visibility = if (explorer_elements.size == 0) View.VISIBLE else View.INVISIBLE
|
text_dir_empty.visibility = if (explorerElements.size == 0) View.VISIBLE else View.INVISIBLE
|
||||||
sortExplorerElements()
|
sortExplorerElements()
|
||||||
if (path.isNotEmpty()) { //not root
|
if (path.isNotEmpty()) { //not root
|
||||||
explorer_elements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1))
|
explorerElements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1))
|
||||||
}
|
}
|
||||||
explorer_adapter.setExplorerElements(explorer_elements)
|
explorerAdapter.setExplorerElements(explorerElements)
|
||||||
current_path = path
|
currentDirectoryPath = path
|
||||||
current_path_text.text = getString(R.string.location, current_path)
|
current_path_text.text = getString(R.string.location, currentDirectoryPath)
|
||||||
total_size_text.text = getString(R.string.total_size, FilesUtils.formatSize(explorer_adapter.currentDirectoryTotalSize))
|
total_size_text.text = getString(R.string.total_size, PathUtils.formatSize(explorerAdapter.currentDirectoryTotalSize))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun askCloseVolume() {
|
private fun askCloseVolume() {
|
||||||
@ -193,19 +191,19 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
|
|
||||||
protected open fun closeVolumeOnDestroy() {
|
protected open fun closeVolumeOnDestroy() {
|
||||||
gocryptfsVolume.close()
|
gocryptfsVolume.close()
|
||||||
TemporaryFileProvider.wipeAll() //additional security
|
RestrictedFileProvider.wipeAll() //additional security
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onBackPressed() {
|
override fun onBackPressed() {
|
||||||
if (explorer_adapter.selectedItems.isEmpty()) {
|
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||||
val parent_path = FilesUtils.get_parent_path(current_path)
|
val parentPath = PathUtils.get_parent_path(currentDirectoryPath)
|
||||||
if (parent_path == current_path) {
|
if (parentPath == currentDirectoryPath) {
|
||||||
askCloseVolume()
|
askCloseVolume()
|
||||||
} else {
|
} else {
|
||||||
setCurrentPath(FilesUtils.get_parent_path(current_path))
|
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
explorer_adapter.unSelectAll()
|
explorerAdapter.unSelectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -214,14 +212,14 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
if (folder_name.isEmpty()) {
|
if (folder_name.isEmpty()) {
|
||||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} else {
|
||||||
if (!gocryptfsVolume.mkdir(FilesUtils.path_join(current_path, folder_name))) {
|
if (!gocryptfsVolume.mkdir(PathUtils.path_join(currentDirectoryPath, folder_name))) {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(R.string.error_mkdir)
|
.setMessage(R.string.error_mkdir)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -254,14 +252,14 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
if (new_name.isEmpty()) {
|
if (new_name.isEmpty()) {
|
||||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} 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)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.rename_failed, old_name))
|
.setMessage(getString(R.string.rename_failed, old_name))
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -272,7 +270,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
if (usf_open){
|
if (usf_open){
|
||||||
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = false
|
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = false
|
||||||
}
|
}
|
||||||
val selectedItems = explorer_adapter.selectedItems
|
val selectedItems = explorerAdapter.selectedItems
|
||||||
if (selectedItems.isEmpty()){
|
if (selectedItems.isEmpty()){
|
||||||
toolbar.navigationIcon = null
|
toolbar.navigationIcon = null
|
||||||
menu.findItem(R.id.explorer_menu_close).isVisible = true
|
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
|
menu.findItem(R.id.explorer_menu_sort).isVisible = false
|
||||||
if (selectedItems.size == 1) {
|
if (selectedItems.size == 1) {
|
||||||
menu.findItem(R.id.explorer_menu_rename).isVisible = true
|
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
|
menu.findItem(R.id.explorer_menu_external_open)?.isVisible = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -293,39 +291,39 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
android.R.id.home -> {
|
android.R.id.home -> {
|
||||||
explorer_adapter.unSelectAll()
|
explorerAdapter.unSelectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.explorer_menu_sort -> {
|
R.id.explorer_menu_sort -> {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.sort_order)
|
.setTitle(R.string.sort_order)
|
||||||
.setSingleChoiceItems(sort_modes_entries, current_sort_mode_index) { dialog, which ->
|
.setSingleChoiceItems(sortModesEntries, currentSortModeIndex) { dialog, which ->
|
||||||
current_sort_mode_index = which
|
currentSortModeIndex = which
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
}.show()
|
}.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.explorer_menu_rename -> {
|
R.id.explorer_menu_rename -> {
|
||||||
val dialog_edit_text_view = layoutInflater.inflate(R.layout.dialog_edit_text, null)
|
val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null)
|
||||||
val old_name = explorer_elements[explorer_adapter.selectedItems[0]].name
|
val oldName = explorerElements[explorerAdapter.selectedItems[0]].name
|
||||||
val dialog_edit_text = dialog_edit_text_view.findViewById<EditText>(R.id.dialog_edit_text)
|
val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text)
|
||||||
dialog_edit_text.setText(old_name)
|
dialogEditText.setText(oldName)
|
||||||
dialog_edit_text.selectAll()
|
dialogEditText.selectAll()
|
||||||
val dialog = ColoredAlertDialog(this)
|
val dialog = ColoredAlertDialog(this)
|
||||||
.setView(dialog_edit_text_view)
|
.setView(dialogEditTextView)
|
||||||
.setTitle(R.string.rename_title)
|
.setTitle(R.string.rename_title)
|
||||||
.setPositiveButton(R.string.ok) { _, _ ->
|
.setPositiveButton(R.string.ok) { _, _ ->
|
||||||
val new_name = dialog_edit_text.text.toString()
|
val newName = dialogEditText.text.toString()
|
||||||
rename(old_name, new_name)
|
rename(oldName, newName)
|
||||||
}
|
}
|
||||||
.setNegativeButton(R.string.cancel, null)
|
.setNegativeButton(R.string.cancel, null)
|
||||||
.create()
|
.create()
|
||||||
dialog_edit_text.setOnEditorActionListener { _, _, _ ->
|
dialogEditText.setOnEditorActionListener { _, _, _ ->
|
||||||
val new_name = dialog_edit_text.text.toString()
|
val newName = dialogEditText.text.toString()
|
||||||
dialog.dismiss()
|
dialog.dismiss()
|
||||||
rename(old_name, new_name)
|
rename(oldName, newName)
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||||
@ -334,8 +332,8 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
}
|
}
|
||||||
R.id.explorer_menu_external_open -> {
|
R.id.explorer_menu_external_open -> {
|
||||||
if (usf_open){
|
if (usf_open){
|
||||||
ExternalProvider.open(this, gocryptfsVolume, FilesUtils.path_join(current_path, explorer_elements[explorer_adapter.selectedItems[0]].name))
|
ExternalProvider.open(this, gocryptfsVolume, PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name))
|
||||||
explorer_adapter.unSelectAll()
|
explorerAdapter.unSelectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
true
|
true
|
||||||
@ -357,6 +355,6 @@ open class BaseExplorerActivity : BaseActivity() {
|
|||||||
|
|
||||||
override fun onResume() {
|
override fun onResume() {
|
||||||
super.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.OpenActivity
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.util.ExternalProvider
|
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.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.Wiper
|
import sushi.hardcore.droidfs.util.Wiper
|
||||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||||
@ -37,16 +37,16 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
if (fileName.isEmpty()) {
|
if (fileName.isEmpty()) {
|
||||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||||
} else {
|
} 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) {
|
if (handleID == -1) {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.file_creation_failed))
|
.setMessage(R.string.file_creation_failed)
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
} else {
|
} else {
|
||||||
gocryptfsVolume.close_file(handleID)
|
gocryptfsVolume.close_file(handleID)
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -110,7 +110,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
if (uris.isNotEmpty()){
|
if (uris.isNotEmpty()){
|
||||||
var success = true
|
var success = true
|
||||||
for (uri in uris) {
|
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 {
|
contentResolver.openInputStream(uri)?.let {
|
||||||
success = gocryptfsVolume.import_file(it, dstPath)
|
success = gocryptfsVolume.import_file(it, dstPath)
|
||||||
}
|
}
|
||||||
@ -154,32 +154,32 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
.setNegativeButton(getString(R.string.no), null)
|
.setNegativeButton(getString(R.string.no), null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
} else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
val uri = data.data
|
val uri = data.data
|
||||||
val output_dir = FilesUtils.getFullPathFromTreeUri(uri, this)
|
val outputDir = PathUtils.getFullPathFromTreeUri(uri, this)
|
||||||
var failed_item: String? = null
|
var failedItem: String? = null
|
||||||
for (i in explorer_adapter.selectedItems) {
|
for (i in explorerAdapter.selectedItems) {
|
||||||
val element = explorer_adapter.getItem(i)
|
val element = explorerAdapter.getItem(i)
|
||||||
val full_path = FilesUtils.path_join(current_path, element.name)
|
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
|
||||||
failed_item = if (element.isDirectory) {
|
failedItem = if (element.isDirectory) {
|
||||||
recursive_export_directory(full_path, output_dir)
|
recursiveExportDirectory(fullPath, outputDir)
|
||||||
} else {
|
} 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)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.export_failed, failed_item))
|
.setMessage(getString(R.string.export_failed, failedItem))
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failed_item == null) {
|
if (failedItem == null) {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.success_export)
|
.setTitle(R.string.success_export)
|
||||||
.setMessage(R.string.success_export_msg)
|
.setMessage(R.string.success_export_msg)
|
||||||
@ -187,33 +187,33 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
explorer_adapter.unSelectAll()
|
explorerAdapter.unSelectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
} else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) {
|
} else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) {
|
||||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||||
val remote_sessionID = data.getIntExtra("sessionID", -1)
|
val remoteSessionID = data.getIntExtra("sessionID", -1)
|
||||||
val remote_gocryptfsVolume = GocryptfsVolume(remote_sessionID)
|
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
|
||||||
val path = data.getStringExtra("path")
|
val path = data.getStringExtra("path")
|
||||||
var failed_item: String? = null
|
var failedItem: String? = null
|
||||||
if (path == null) {
|
if (path == null) {
|
||||||
val paths = data.getStringArrayListExtra("paths")
|
val paths = data.getStringArrayListExtra("paths")
|
||||||
val types = data.getIntegerArrayListExtra("types")
|
val types = data.getIntegerArrayListExtra("types")
|
||||||
if (types != null && paths != null){
|
if (types != null && paths != null){
|
||||||
for (i in paths.indices) {
|
for (i in paths.indices) {
|
||||||
failed_item = if (types[i] == 0) { //directory
|
failedItem = if (types[i] == 0) { //directory
|
||||||
recursive_import_directory_from_other_volume(remote_gocryptfsVolume, paths[i], current_path)
|
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
|
||||||
} else {
|
} 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
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.success_import)
|
.setTitle(R.string.success_import)
|
||||||
.setMessage(R.string.success_import_msg)
|
.setMessage(R.string.success_import_msg)
|
||||||
@ -222,12 +222,12 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
} else {
|
} else {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.import_failed, failed_item))
|
.setMessage(getString(R.string.import_failed, failedItem))
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
}
|
}
|
||||||
remote_gocryptfsVolume.close()
|
remoteGocryptfsVolume.close()
|
||||||
setCurrentPath(current_path)
|
setCurrentPath(currentDirectoryPath)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -238,14 +238,14 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
if (usf_share){
|
if (usf_share){
|
||||||
menu.findItem(R.id.explorer_menu_share).isVisible = false
|
menu.findItem(R.id.explorer_menu_share).isVisible = false
|
||||||
}
|
}
|
||||||
val any_item_selected = explorer_adapter.selectedItems.isNotEmpty()
|
val anyItemSelected = explorerAdapter.selectedItems.isNotEmpty()
|
||||||
menu.findItem(R.id.explorer_menu_select_all).isVisible = any_item_selected
|
menu.findItem(R.id.explorer_menu_select_all).isVisible = anyItemSelected
|
||||||
menu.findItem(R.id.explorer_menu_delete).isVisible = any_item_selected
|
menu.findItem(R.id.explorer_menu_delete).isVisible = anyItemSelected
|
||||||
menu.findItem(R.id.explorer_menu_decrypt).isVisible = any_item_selected && usf_decrypt
|
menu.findItem(R.id.explorer_menu_decrypt).isVisible = anyItemSelected && usf_decrypt
|
||||||
if (any_item_selected && usf_share){
|
if (anyItemSelected && usf_share){
|
||||||
var containsDir = false
|
var containsDir = false
|
||||||
for (i in explorer_adapter.selectedItems) {
|
for (i in explorerAdapter.selectedItems) {
|
||||||
if (explorer_elements[i].isDirectory) {
|
if (explorerElements[i].isDirectory) {
|
||||||
containsDir = true
|
containsDir = true
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@ -260,32 +260,32 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.explorer_menu_select_all -> {
|
R.id.explorer_menu_select_all -> {
|
||||||
explorer_adapter.selectAll()
|
explorerAdapter.selectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.explorer_menu_delete -> {
|
R.id.explorer_menu_delete -> {
|
||||||
val size = explorer_adapter.selectedItems.size
|
val size = explorerAdapter.selectedItems.size
|
||||||
val dialog = ColoredAlertDialog(this)
|
val dialog = ColoredAlertDialog(this)
|
||||||
dialog.setTitle(R.string.warning)
|
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)
|
dialog.setNegativeButton(R.string.cancel, null)
|
||||||
if (size > 1) {
|
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 {
|
} 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()
|
dialog.show()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.explorer_menu_share -> {
|
R.id.explorer_menu_share -> {
|
||||||
val paths: MutableList<String> = ArrayList()
|
val paths: MutableList<String> = ArrayList()
|
||||||
for (i in explorer_adapter.selectedItems) {
|
for (i in explorerAdapter.selectedItems) {
|
||||||
val e = explorer_elements[i]
|
val e = explorerElements[i]
|
||||||
paths.add(FilesUtils.path_join(current_path, e.name))
|
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
|
||||||
}
|
}
|
||||||
ExternalProvider.share(this, gocryptfsVolume, paths)
|
ExternalProvider.share(this, gocryptfsVolume, paths)
|
||||||
explorer_adapter.unSelectAll()
|
explorerAdapter.unSelectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
true
|
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 {
|
private fun importFileFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, full_path: String, output_dir: String): Boolean {
|
||||||
val output_path = FilesUtils.path_join(output_dir, File(full_path).name)
|
val outputPath = PathUtils.path_join(output_dir, File(full_path).name)
|
||||||
var success = true
|
var success = true
|
||||||
val src_handleID = remote_gocryptfsVolume.open_read_mode(full_path)
|
val srcHandleID = remote_gocryptfsVolume.open_read_mode(full_path)
|
||||||
if (src_handleID != -1) {
|
if (srcHandleID != -1) {
|
||||||
val dst_handleID = gocryptfsVolume.open_write_mode(output_path)
|
val dstHandleID = gocryptfsVolume.open_write_mode(outputPath)
|
||||||
if (dst_handleID != -1) {
|
if (dstHandleID != -1) {
|
||||||
var length: Int
|
var length: Int
|
||||||
val io_buffer = ByteArray(GocryptfsVolume.DefaultBS)
|
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||||
var offset: Long = 0
|
var offset: Long = 0
|
||||||
while (remote_gocryptfsVolume.read_file(src_handleID, offset, io_buffer).also { length = it } > 0){
|
while (remote_gocryptfsVolume.read_file(srcHandleID, offset, ioBuffer).also { length = it } > 0){
|
||||||
val written = gocryptfsVolume.write_file(dst_handleID, offset, io_buffer, length).toLong()
|
val written = gocryptfsVolume.write_file(dstHandleID, offset, ioBuffer, length).toLong()
|
||||||
if (written == length.toLong()) {
|
if (written == length.toLong()) {
|
||||||
offset += length.toLong()
|
offset += length.toLong()
|
||||||
} else {
|
} else {
|
||||||
@ -317,46 +317,46 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
gocryptfsVolume.close_file(dst_handleID)
|
gocryptfsVolume.close_file(dstHandleID)
|
||||||
}
|
}
|
||||||
remote_gocryptfsVolume.close_file(src_handleID)
|
remote_gocryptfsVolume.close_file(srcHandleID)
|
||||||
}
|
}
|
||||||
return success
|
return success
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recursive_import_directory_from_other_volume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, output_dir: String): String? {
|
private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, output_dir: String): String? {
|
||||||
val directory_path = FilesUtils.path_join(output_dir, File(remote_directory_path).name)
|
val directoryPath = PathUtils.path_join(output_dir, File(remote_directory_path).name)
|
||||||
if (!gocryptfsVolume.path_exists(directory_path)) {
|
if (!gocryptfsVolume.path_exists(directoryPath)) {
|
||||||
if (!gocryptfsVolume.mkdir(directory_path)) {
|
if (!gocryptfsVolume.mkdir(directoryPath)) {
|
||||||
return directory_path
|
return directoryPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val explorer_elements = remote_gocryptfsVolume.list_dir(remote_directory_path)
|
val explorerElements = remote_gocryptfsVolume.list_dir(remote_directory_path)
|
||||||
for (e in explorer_elements) {
|
for (e in explorerElements) {
|
||||||
val full_path = FilesUtils.path_join(remote_directory_path, e.name)
|
val fullPath = PathUtils.path_join(remote_directory_path, e.name)
|
||||||
if (e.isDirectory) {
|
if (e.isDirectory) {
|
||||||
val failed_item = recursive_import_directory_from_other_volume(remote_gocryptfsVolume, full_path, directory_path)
|
val failedItem = recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)
|
||||||
failed_item?.let { return it }
|
failedItem?.let { return it }
|
||||||
} else {
|
} else {
|
||||||
if (!import_file_from_other_volume(remote_gocryptfsVolume, full_path, directory_path)) {
|
if (!importFileFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)) {
|
||||||
return full_path
|
return fullPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recursive_export_directory(plain_directory_path: String, output_dir: String?): String? {
|
private fun recursiveExportDirectory(plain_directory_path: String, output_dir: String?): String? {
|
||||||
if (File(FilesUtils.path_join(output_dir, plain_directory_path)).mkdir()) {
|
if (File(PathUtils.path_join(output_dir, plain_directory_path)).mkdir()) {
|
||||||
val explorer_elements = gocryptfsVolume.list_dir(plain_directory_path)
|
val explorerElements = gocryptfsVolume.list_dir(plain_directory_path)
|
||||||
for (e in explorer_elements) {
|
for (e in explorerElements) {
|
||||||
val full_path = FilesUtils.path_join(plain_directory_path, e.name)
|
val fullPath = PathUtils.path_join(plain_directory_path, e.name)
|
||||||
if (e.isDirectory) {
|
if (e.isDirectory) {
|
||||||
val failed_item = recursive_export_directory(full_path, output_dir)
|
val failedItem = recursiveExportDirectory(fullPath, output_dir)
|
||||||
failed_item?.let { return it }
|
failedItem?.let { return it }
|
||||||
} else {
|
} else {
|
||||||
if (!gocryptfsVolume.export_file(full_path, FilesUtils.path_join(output_dir, full_path))) {
|
if (!gocryptfsVolume.export_file(fullPath, PathUtils.path_join(output_dir, fullPath))) {
|
||||||
return full_path
|
return fullPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -365,16 +365,16 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
return output_dir
|
return output_dir
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun recursive_remove_directory(plain_directory_path: String): String? {
|
private fun recursiveRemoveDirectory(plain_directory_path: String): String? {
|
||||||
val explorer_elements = gocryptfsVolume.list_dir(plain_directory_path)
|
val explorerElements = gocryptfsVolume.list_dir(plain_directory_path)
|
||||||
for (e in explorer_elements) {
|
for (e in explorerElements) {
|
||||||
val full_path = FilesUtils.path_join(plain_directory_path, e.name)
|
val fullPath = PathUtils.path_join(plain_directory_path, e.name)
|
||||||
if (e.isDirectory) {
|
if (e.isDirectory) {
|
||||||
val result = recursive_remove_directory(full_path)
|
val result = recursiveRemoveDirectory(fullPath)
|
||||||
result?.let { return it }
|
result?.let { return it }
|
||||||
} else {
|
} else {
|
||||||
if (!gocryptfsVolume.remove_file(full_path)) {
|
if (!gocryptfsVolume.remove_file(fullPath)) {
|
||||||
return full_path
|
return fullPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -385,30 +385,30 @@ class ExplorerActivity : BaseExplorerActivity() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun remove_selected_items() {
|
private fun removeSelectedItems() {
|
||||||
var failed_item: String? = null
|
var failedItem: String? = null
|
||||||
for (i in explorer_adapter.selectedItems) {
|
for (i in explorerAdapter.selectedItems) {
|
||||||
val element = explorer_adapter.getItem(i)
|
val element = explorerAdapter.getItem(i)
|
||||||
val full_path = FilesUtils.path_join(current_path, element.name)
|
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
|
||||||
if (element.isDirectory) {
|
if (element.isDirectory) {
|
||||||
val result = recursive_remove_directory(full_path)
|
val result = recursiveRemoveDirectory(fullPath)
|
||||||
result?.let{ failed_item = it }
|
result?.let{ failedItem = it }
|
||||||
} else {
|
} else {
|
||||||
if (!gocryptfsVolume.remove_file(full_path)) {
|
if (!gocryptfsVolume.remove_file(fullPath)) {
|
||||||
failed_item = full_path
|
failedItem = fullPath
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (failed_item != null) {
|
if (failedItem != null) {
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.remove_failed, failed_item))
|
.setMessage(getString(R.string.remove_failed, failedItem))
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
explorer_adapter.unSelectAll()
|
explorerAdapter.unSelectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
setCurrentPath(current_path) //refresh
|
setCurrentPath(currentDirectoryPath) //refresh
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,7 +5,7 @@ import android.net.Uri
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.util.FilesUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||||
|
|
||||||
class ExplorerActivityDrop : BaseExplorerActivity() {
|
class ExplorerActivityDrop : BaseExplorerActivity() {
|
||||||
@ -16,7 +16,7 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
|
|||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.explorer_drop, menu)
|
menuInflater.inflate(R.menu.explorer_drop, menu)
|
||||||
handleMenuItems(menu)
|
handleMenuItems(menu)
|
||||||
menu.findItem(R.id.explorer_menu_validate).isVisible = explorer_adapter.selectedItems.isEmpty()
|
menu.findItem(R.id.explorer_menu_validate).isVisible = explorerAdapter.selectedItems.isEmpty()
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -31,13 +31,13 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
|
|||||||
if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){
|
if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){
|
||||||
if (intent.action == Intent.ACTION_SEND) {
|
if (intent.action == Intent.ACTION_SEND) {
|
||||||
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||||
val output_path = 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)
|
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) {
|
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
|
||||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||||
if (uris != null){
|
if (uris != null){
|
||||||
for (uri in uris) {
|
for (uri in uris) {
|
||||||
val output_path = 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)) {
|
if (!gocryptfsVolume.import_file(this, uri, output_path)) {
|
||||||
error_msg = getString(R.string.import_failed, output_path)
|
error_msg = getString(R.string.import_failed, output_path)
|
||||||
break
|
break
|
||||||
|
@ -5,8 +5,8 @@ import android.content.Intent
|
|||||||
import android.view.Menu
|
import android.view.Menu
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.provider.TemporaryFileProvider
|
import sushi.hardcore.droidfs.provider.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.util.FilesUtils
|
import sushi.hardcore.droidfs.util.PathUtils
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class ExplorerActivityPick : BaseExplorerActivity() {
|
class ExplorerActivityPick : BaseExplorerActivity() {
|
||||||
@ -17,21 +17,21 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onExplorerItemClick(position: Int) {
|
override fun onExplorerItemClick(position: Int) {
|
||||||
val wasSelecting = explorer_adapter.selectedItems.isNotEmpty()
|
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
|
||||||
explorer_adapter.onItemClick(position)
|
explorerAdapter.onItemClick(position)
|
||||||
if (explorer_adapter.selectedItems.isEmpty()) {
|
if (explorerAdapter.selectedItems.isEmpty()) {
|
||||||
if (!wasSelecting) {
|
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 {
|
when {
|
||||||
explorer_elements[position].isDirectory -> {
|
explorerElements[position].isDirectory -> {
|
||||||
setCurrentPath(full_path)
|
setCurrentPath(full_path)
|
||||||
}
|
}
|
||||||
explorer_elements[position].isParentFolder -> {
|
explorerElements[position].isParentFolder -> {
|
||||||
setCurrentPath(FilesUtils.get_parent_path(current_path))
|
setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath))
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
result_intent.putExtra("path", full_path)
|
result_intent.putExtra("path", full_path)
|
||||||
return_activity_result()
|
returnActivityResult()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
|||||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||||
menuInflater.inflate(R.menu.explorer_pick, menu)
|
menuInflater.inflate(R.menu.explorer_pick, menu)
|
||||||
handleMenuItems(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_select_all).isVisible = any_item_selected
|
||||||
menu.findItem(R.id.explorer_menu_validate).isVisible = any_item_selected
|
menu.findItem(R.id.explorer_menu_validate).isVisible = any_item_selected
|
||||||
return true
|
return true
|
||||||
@ -51,35 +51,35 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
|||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.explorer_menu_select_all -> {
|
R.id.explorer_menu_select_all -> {
|
||||||
explorer_adapter.selectAll()
|
explorerAdapter.selectAll()
|
||||||
invalidateOptionsMenu()
|
invalidateOptionsMenu()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
R.id.explorer_menu_validate -> {
|
R.id.explorer_menu_validate -> {
|
||||||
val paths = ArrayList<String>()
|
val paths = ArrayList<String>()
|
||||||
val types = ArrayList<Int>()
|
val types = ArrayList<Int>()
|
||||||
for (i in explorer_adapter.selectedItems) {
|
for (i in explorerAdapter.selectedItems) {
|
||||||
val e = explorer_elements[i]
|
val e = explorerElements[i]
|
||||||
paths.add(FilesUtils.path_join(current_path, e.name))
|
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
|
||||||
types.add(e.elementType.toInt())
|
types.add(e.elementType.toInt())
|
||||||
}
|
}
|
||||||
result_intent.putStringArrayListExtra("paths", paths)
|
result_intent.putStringArrayListExtra("paths", paths)
|
||||||
result_intent.putIntegerArrayListExtra("types", types)
|
result_intent.putIntegerArrayListExtra("types", types)
|
||||||
return_activity_result()
|
returnActivityResult()
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
else -> super.onOptionsItemSelected(item)
|
else -> super.onOptionsItemSelected(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun return_activity_result() {
|
private fun returnActivityResult() {
|
||||||
setResult(Activity.RESULT_OK, result_intent)
|
setResult(Activity.RESULT_OK, result_intent)
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun closeVolumeOnDestroy() {
|
override fun closeVolumeOnDestroy() {
|
||||||
//don't close volume
|
//don't close volume
|
||||||
TemporaryFileProvider.wipeAll()
|
RestrictedFileProvider.wipeAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun closeVolumeOnUserExit() {
|
override fun closeVolumeOnUserExit() {
|
||||||
|
@ -1,20 +1,13 @@
|
|||||||
package sushi.hardcore.droidfs.file_viewers
|
package sushi.hardcore.droidfs.file_viewers
|
||||||
|
|
||||||
import android.media.MediaPlayer
|
import com.google.android.exoplayer2.SimpleExoPlayer
|
||||||
import android.os.Handler
|
|
||||||
import android.widget.SeekBar
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import kotlinx.android.synthetic.main.activity_audio_player.*
|
import kotlinx.android.synthetic.main.activity_audio_player.*
|
||||||
import sushi.hardcore.droidfs.ConstValues
|
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.IOException
|
|
||||||
|
|
||||||
class AudioPlayer: FileViewerActivity(){
|
class AudioPlayer: MediaPlayer(){
|
||||||
private lateinit var player: MediaPlayer
|
|
||||||
private var isPrepared = false
|
|
||||||
override fun viewFile() {
|
override fun viewFile() {
|
||||||
|
super.viewFile()
|
||||||
setContentView(R.layout.activity_audio_player)
|
setContentView(R.layout.activity_audio_player)
|
||||||
val filename = File(filePath).name
|
val filename = File(filePath).name
|
||||||
val pos = filename.lastIndexOf('.')
|
val pos = filename.lastIndexOf('.')
|
||||||
@ -23,65 +16,9 @@ class AudioPlayer: FileViewerActivity(){
|
|||||||
} else {
|
} else {
|
||||||
filename
|
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() {
|
override fun bindPlayer(player: SimpleExoPlayer) {
|
||||||
super.onDestroy()
|
audio_controller.player = player
|
||||||
if (::player.isInitialized) {
|
|
||||||
if (player.isPlaying) {
|
|
||||||
player.stop()
|
|
||||||
}
|
|
||||||
isPrepared = false
|
|
||||||
player.release()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,21 +1,13 @@
|
|||||||
package sushi.hardcore.droidfs.file_viewers
|
package sushi.hardcore.droidfs.file_viewers
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.WindowManager
|
|
||||||
import androidx.preference.PreferenceManager
|
|
||||||
import sushi.hardcore.droidfs.BaseActivity
|
import sushi.hardcore.droidfs.BaseActivity
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.provider.TemporaryFileProvider
|
|
||||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||||
import sushi.hardcore.droidfs.util.Wiper
|
|
||||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||||
import java.io.File
|
|
||||||
import java.util.ArrayList
|
|
||||||
|
|
||||||
abstract class FileViewerActivity: BaseActivity() {
|
abstract class FileViewerActivity: BaseActivity() {
|
||||||
private var cachedFiles: MutableList<Uri> = ArrayList()
|
|
||||||
lateinit var gocryptfsVolume: GocryptfsVolume
|
lateinit var gocryptfsVolume: GocryptfsVolume
|
||||||
lateinit var filePath: String
|
lateinit var filePath: String
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
@ -23,15 +15,17 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
filePath = intent.getStringExtra("path")!!
|
filePath = intent.getStringExtra("path")!!
|
||||||
val sessionID = intent.getIntExtra("sessionID", -1)
|
val sessionID = intent.getIntExtra("sessionID", -1)
|
||||||
gocryptfsVolume = GocryptfsVolume(sessionID)
|
gocryptfsVolume = GocryptfsVolume(sessionID)
|
||||||
toggleFullscreen()
|
hideSystemUi()
|
||||||
viewFile()
|
viewFile()
|
||||||
}
|
}
|
||||||
open fun toggleFullscreen(){
|
open fun hideSystemUi(){
|
||||||
var uiOptions = window.decorView.systemUiVisibility
|
window.decorView.systemUiVisibility =
|
||||||
//uiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
|
View.SYSTEM_UI_FLAG_LOW_PROFILE or
|
||||||
uiOptions = uiOptions xor View.SYSTEM_UI_FLAG_FULLSCREEN
|
View.SYSTEM_UI_FLAG_FULLSCREEN/* or
|
||||||
uiOptions = uiOptions xor View.SYSTEM_UI_FLAG_IMMERSIVE
|
View.SYSTEM_UI_FLAG_LAYOUT_STABLE or
|
||||||
window.decorView.systemUiVisibility = uiOptions
|
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or
|
||||||
|
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or
|
||||||
|
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION*/
|
||||||
}
|
}
|
||||||
abstract fun viewFile()
|
abstract fun viewFile()
|
||||||
fun loadWholeFile(path: String): ByteArray? {
|
fun loadWholeFile(path: String): ByteArray? {
|
||||||
@ -43,10 +37,10 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
val handleID = gocryptfsVolume.open_read_mode(path)
|
val handleID = gocryptfsVolume.open_read_mode(path)
|
||||||
if (handleID != -1) {
|
if (handleID != -1) {
|
||||||
var offset: Long = 0
|
var offset: Long = 0
|
||||||
val io_buffer = ByteArray(GocryptfsVolume.DefaultBS)
|
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
|
||||||
var length: Int
|
var length: Int
|
||||||
while (gocryptfsVolume.read_file(handleID, offset, io_buffer).also { length = it } > 0){
|
while (gocryptfsVolume.read_file(handleID, offset, ioBuffer).also { length = it } > 0){
|
||||||
System.arraycopy(io_buffer, 0, fileBuff, offset.toInt(), length)
|
System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length)
|
||||||
offset += length.toLong()
|
offset += length.toLong()
|
||||||
}
|
}
|
||||||
gocryptfsVolume.close_file(handleID)
|
gocryptfsVolume.close_file(handleID)
|
||||||
@ -65,7 +59,7 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
} catch (e: OutOfMemoryError){
|
} catch (e: OutOfMemoryError){
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.outofmemoryerror_msg))
|
.setMessage(R.string.outofmemoryerror_msg)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(getString(R.string.ok)) { _, _ -> finish() }
|
.setPositiveButton(getString(R.string.ok)) { _, _ -> finish() }
|
||||||
.show()
|
.show()
|
||||||
@ -81,30 +75,4 @@ abstract class FileViewerActivity: BaseActivity() {
|
|||||||
}
|
}
|
||||||
return null
|
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 lateinit var titleText: TextView
|
||||||
private var changedSinceLastSave = false
|
private var changedSinceLastSave = false
|
||||||
private var wordWrap = true
|
private var wordWrap = true
|
||||||
override fun toggleFullscreen() {
|
override fun hideSystemUi() {
|
||||||
//don't toggle fullscreen
|
//don't hide system ui
|
||||||
}
|
}
|
||||||
override fun viewFile() {
|
override fun viewFile() {
|
||||||
loadWholeFile(filePath)?.let {
|
loadWholeFile(filePath)?.let {
|
||||||
@ -32,7 +32,7 @@ class TextEditor: FileViewerActivity() {
|
|||||||
} catch (e: OutOfMemoryError){
|
} catch (e: OutOfMemoryError){
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(getString(R.string.outofmemoryerror_msg))
|
.setMessage(R.string.outofmemoryerror_msg)
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setPositiveButton(getString(R.string.ok)) { _, _ -> finish() }
|
.setPositiveButton(getString(R.string.ok)) { _, _ -> finish() }
|
||||||
.show()
|
.show()
|
||||||
@ -100,7 +100,7 @@ class TextEditor: FileViewerActivity() {
|
|||||||
if (changedSinceLastSave){
|
if (changedSinceLastSave){
|
||||||
ColoredAlertDialog(this)
|
ColoredAlertDialog(this)
|
||||||
.setTitle(R.string.warning)
|
.setTitle(R.string.warning)
|
||||||
.setMessage(getString(R.string.ask_save))
|
.setMessage(R.string.ask_save)
|
||||||
.setPositiveButton(getString(R.string.save)) { _, _ ->
|
.setPositiveButton(getString(R.string.save)) { _, _ ->
|
||||||
if (save()){
|
if (save()){
|
||||||
finish()
|
finish()
|
||||||
|
@ -1,27 +1,16 @@
|
|||||||
package sushi.hardcore.droidfs.file_viewers
|
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 kotlinx.android.synthetic.main.activity_video_player.*
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
|
||||||
|
|
||||||
class VideoPlayer: FileViewerActivity() {
|
class VideoPlayer: MediaPlayer() {
|
||||||
override fun viewFile() {
|
override fun viewFile() {
|
||||||
val mc = MediaController(this)
|
super.viewFile()
|
||||||
setContentView(R.layout.activity_video_player)
|
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)
|
override fun bindPlayer(player: SimpleExoPlayer) {
|
||||||
video_player.setMediaController(mc)
|
video_player.player = player
|
||||||
video_player.start()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -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.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import sushi.hardcore.droidfs.R
|
import sushi.hardcore.droidfs.R
|
||||||
import sushi.hardcore.droidfs.provider.TemporaryFileProvider
|
import sushi.hardcore.droidfs.provider.RestrictedFileProvider
|
||||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
import sushi.hardcore.droidfs.widgets.ColoredAlertDialog
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.net.URLConnection
|
import java.net.URLConnection
|
||||||
@ -13,51 +12,53 @@ import java.util.*
|
|||||||
|
|
||||||
object ExternalProvider {
|
object ExternalProvider {
|
||||||
private const val content_type_all = "*/*"
|
private const val content_type_all = "*/*"
|
||||||
private var cached_files: MutableList<Uri> = ArrayList()
|
private var storedFiles: MutableList<Uri> = ArrayList()
|
||||||
private fun get_content_type(filename: String, previous_content_type: String?): String? {
|
private fun getContentType(filename: String, previous_content_type: String?): String? {
|
||||||
if (content_type_all != previous_content_type) {
|
if (content_type_all != previous_content_type) {
|
||||||
var content_type = URLConnection.guessContentTypeFromName(filename)
|
var contentType = URLConnection.guessContentTypeFromName(filename)
|
||||||
if (content_type == null) {
|
if (contentType == null) {
|
||||||
content_type = content_type_all
|
contentType = content_type_all
|
||||||
}
|
}
|
||||||
if (previous_content_type == null) {
|
if (previous_content_type == null) {
|
||||||
return content_type
|
return contentType
|
||||||
} else if (previous_content_type != content_type) {
|
} else if (previous_content_type != contentType) {
|
||||||
return content_type_all
|
return content_type_all
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return previous_content_type
|
return previous_content_type
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun export_file(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Export_file_result {
|
private fun exportFile(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Pair<Uri?, String?> {
|
||||||
val filename = File(file_path).name
|
val fileName = File(file_path).name
|
||||||
val tmp_file_uri = TemporaryFileProvider.createFile(context, filename)
|
val tmpFileUri = RestrictedFileProvider.newFile(fileName)
|
||||||
cached_files.add(tmp_file_uri)
|
if (tmpFileUri != null){
|
||||||
if (gocryptfsVolume.export_file(context, file_path, tmp_file_uri)) {
|
storedFiles.add(tmpFileUri)
|
||||||
return Export_file_result(tmp_file_uri, get_content_type(filename, previous_content_type))
|
if (gocryptfsVolume.export_file(context, file_path, tmpFileUri)) {
|
||||||
|
return Pair(tmpFileUri, getContentType(fileName, previous_content_type))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
ColoredAlertDialog(context)
|
ColoredAlertDialog(context)
|
||||||
.setTitle(R.string.error)
|
.setTitle(R.string.error)
|
||||||
.setMessage(context.getString(R.string.export_failed, file_path))
|
.setMessage(context.getString(R.string.export_failed, file_path))
|
||||||
.setPositiveButton(R.string.ok, null)
|
.setPositiveButton(R.string.ok, null)
|
||||||
.show()
|
.show()
|
||||||
return Export_file_result(null, null)
|
return Pair(null, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun share(context: Context, gocryptfsVolume: GocryptfsVolume, file_paths: List<String>) {
|
fun share(context: Context, gocryptfsVolume: GocryptfsVolume, file_paths: List<String>) {
|
||||||
var content_type: String? = null
|
var contentType: String? = null
|
||||||
val uris = ArrayList<Uri>()
|
val uris = ArrayList<Uri>()
|
||||||
for (path in file_paths) {
|
for (path in file_paths) {
|
||||||
val result = export_file(context, gocryptfsVolume, path, content_type)
|
val result = exportFile(context, gocryptfsVolume, path, contentType)
|
||||||
content_type = if (result.uri == null) {
|
contentType = if (result.first != null) {
|
||||||
return
|
uris.add(result.first!!)
|
||||||
|
result.second
|
||||||
} else {
|
} else {
|
||||||
uris.add(result.uri!!)
|
return
|
||||||
result.content_type
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val shareIntent = Intent()
|
val shareIntent = Intent()
|
||||||
shareIntent.type = content_type
|
shareIntent.type = contentType
|
||||||
if (uris.size == 1) {
|
if (uris.size == 1) {
|
||||||
shareIntent.action = Intent.ACTION_SEND
|
shareIntent.action = Intent.ACTION_SEND
|
||||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uris[0])
|
shareIntent.putExtra(Intent.EXTRA_STREAM, uris[0])
|
||||||
@ -69,24 +70,22 @@ object ExternalProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun open(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String) {
|
fun open(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String) {
|
||||||
val result = export_file(context, gocryptfsVolume, file_path, null)
|
val result = exportFile(context, gocryptfsVolume, file_path, null)
|
||||||
result.uri?.let {
|
result.first?.let {
|
||||||
val openIntent = Intent()
|
val openIntent = Intent()
|
||||||
openIntent.action = Intent.ACTION_VIEW
|
openIntent.action = Intent.ACTION_VIEW
|
||||||
openIntent.setDataAndType(result.uri, result.content_type)
|
openIntent.setDataAndType(result.first, result.second)
|
||||||
context.startActivity(openIntent)
|
context.startActivity(openIntent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clear_cache(context: Context) {
|
fun removeFiles(context: Context) {
|
||||||
Thread{
|
Thread{
|
||||||
for (uri in cached_files) {
|
for (uri in storedFiles) {
|
||||||
if (Wiper.wipe(context, uri)){
|
if (Wiper.wipe(context, uri)){
|
||||||
cached_files.remove(uri)
|
storedFiles.remove(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}.start()
|
}.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 KeyLen = 32
|
||||||
const val ScryptDefaultLogN = 16
|
const val ScryptDefaultLogN = 16
|
||||||
const val DefaultBS = 4096
|
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 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 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
|
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;
|
package sushi.hardcore.droidfs.util;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
|
||||||
import android.annotation.TargetApi;
|
|
||||||
import android.content.ContentUris;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.database.Cursor;
|
import android.database.Cursor;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.os.Environment;
|
|
||||||
import android.os.storage.StorageManager;
|
import android.os.storage.StorageManager;
|
||||||
import android.provider.DocumentsContract;
|
import android.provider.DocumentsContract;
|
||||||
import android.provider.MediaStore;
|
|
||||||
import android.provider.OpenableColumns;
|
import android.provider.OpenableColumns;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.lang.reflect.Array;
|
import java.lang.reflect.Array;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.text.DecimalFormat;
|
import java.text.DecimalFormat;
|
||||||
import java.util.ArrayList;
|
|
||||||
|
|
||||||
public final class FilesUtils {
|
public class PathUtils {
|
||||||
|
|
||||||
/*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 static String get_parent_path(String path){
|
public static String get_parent_path(String path){
|
||||||
if (path.endsWith("/")){
|
if (path.endsWith("/")){
|
@ -2,6 +2,7 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:background="@color/fullScreenBackgroundColor"
|
android:background="@color/fullScreenBackgroundColor"
|
||||||
android:orientation="vertical">
|
android:orientation="vertical">
|
||||||
@ -14,33 +15,11 @@
|
|||||||
android:textSize="18sp"
|
android:textSize="18sp"
|
||||||
android:padding="10dp"/>
|
android:padding="10dp"/>
|
||||||
|
|
||||||
<sushi.hardcore.droidfs.widgets.ColoredSeekBar
|
<com.google.android.exoplayer2.ui.PlayerControlView
|
||||||
android:id="@+id/seekbar"
|
android:id="@+id/audio_controller"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"/>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="horizontal"
|
app:controller_layout_id="@layout/exo_custom_playback_control"
|
||||||
android:gravity="center_horizontal">
|
app:show_timeout="0"/>
|
||||||
|
|
||||||
<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>
|
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
@ -2,13 +2,15 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
android:background="@color/fullScreenBackgroundColor">
|
android:background="@color/fullScreenBackgroundColor">
|
||||||
|
|
||||||
<VideoView
|
<com.google.android.exoplayer2.ui.SimpleExoPlayerView
|
||||||
android:id="@+id/video_player"
|
android:id="@+id/video_player"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="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>
|
</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="video">Video</string>
|
||||||
<string name="audio">Audio</string>
|
<string name="audio">Audio</string>
|
||||||
<string name="media_player_prepare_failed">Failed to initialize the media player.</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="text">Text</string>
|
||||||
<string name="save_failed">Save failed</string>
|
<string name="save_failed">Save failed</string>
|
||||||
<string name="file_saved">File saved !</string>
|
<string name="file_saved">File saved !</string>
|
||||||
|
@ -2,11 +2,8 @@
|
|||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="AppTheme" parent="Theme.Cyanea.Dark.NoActionBar">
|
<style name="AppTheme" parent="Theme.Cyanea.Dark.NoActionBar">
|
||||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
<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:colorBackground">@color/backgroundColor</item>
|
||||||
<item name="android:textColor">@color/textColor</item>
|
<item name="android:textColor">@color/textColor</item>
|
||||||
<!--<item name="buttonStyle">@style/button</item>-->
|
|
||||||
<item name="checkboxStyle">@style/checkbox</item>
|
<item name="checkboxStyle">@style/checkbox</item>
|
||||||
<!--<item name="actionOverflowMenuStyle">@style/menu_overflow</item>-->
|
<!--<item name="actionOverflowMenuStyle">@style/menu_overflow</item>-->
|
||||||
</style>
|
</style>
|
||||||
|
Loading…
Reference in New Issue
Block a user