From 24cfc1093e007279616f448a40142150375bd2b2 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sun, 26 Jul 2020 21:16:06 +0200 Subject: [PATCH] RestrictedFileProvider & Playing media without disk writes --- app/build.gradle | 12 +- app/libgocryptfs/main.go | 1 - app/src/main/AndroidManifest.xml | 13 +- .../sushi/hardcore/droidfs/BaseActivity.kt | 1 - .../droidfs/ChangePasswordActivity.kt | 5 +- .../sushi/hardcore/droidfs/ConstValues.kt | 2 + .../sushi/hardcore/droidfs/CreateActivity.kt | 5 +- .../sushi/hardcore/droidfs/MainActivity.kt | 6 +- .../sushi/hardcore/droidfs/OpenActivity.kt | 7 +- .../adapters/ExplorerElementAdapter.kt | 37 +- .../droidfs/explorers/BaseExplorerActivity.kt | 174 +++++----- .../droidfs/explorers/ExplorerActivity.kt | 196 +++++------ .../droidfs/explorers/ExplorerActivityDrop.kt | 8 +- .../droidfs/explorers/ExplorerActivityPick.kt | 36 +- .../droidfs/file_viewers/AudioPlayer.kt | 73 +--- .../file_viewers/FileViewerActivity.kt | 58 +--- .../file_viewers/GocryptfsDataSource.kt | 53 +++ .../droidfs/file_viewers/MediaPlayer.kt | 64 ++++ .../droidfs/file_viewers/TextEditor.kt | 8 +- .../droidfs/file_viewers/VideoPlayer.kt | 25 +- .../provider/RestrictedFileProvider.kt | 114 ++++++ .../provider/TemporaryFileProvider.java | 326 ------------------ .../hardcore/droidfs/util/DatabaseUtil.java | 84 ----- .../hardcore/droidfs/util/ExternalProvider.kt | 61 ++-- .../hardcore/droidfs/util/GocryptfsVolume.kt | 1 - .../util/{FilesUtils.java => PathUtils.java} | 26 +- .../main/res/layout/activity_audio_player.xml | 33 +- .../main/res/layout/activity_video_player.xml | 6 +- .../layout/exo_custom_playback_control.xml | 74 ++++ app/src/main/res/values/strings.xml | 2 +- app/src/main/res/values/styles.xml | 3 - 31 files changed, 630 insertions(+), 884 deletions(-) create mode 100644 app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt create mode 100644 app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt create mode 100644 app/src/main/java/sushi/hardcore/droidfs/provider/RestrictedFileProvider.kt delete mode 100644 app/src/main/java/sushi/hardcore/droidfs/provider/TemporaryFileProvider.java delete mode 100644 app/src/main/java/sushi/hardcore/droidfs/util/DatabaseUtil.java rename app/src/main/java/sushi/hardcore/droidfs/util/{FilesUtils.java => PathUtils.java} (87%) create mode 100644 app/src/main/res/layout/exo_custom_playback_control.xml diff --git a/app/build.gradle b/app/build.gradle index afca7ae..f857036 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,12 +6,16 @@ android { compileSdkVersion 29 buildToolsVersion "30.0.0" + compileOptions { + targetCompatibility JavaVersion.VERSION_1_8 + } + defaultConfig { applicationId "sushi.hardcore.droidfs" minSdkVersion 21 - targetSdkVersion 28 + targetSdkVersion 29 versionCode 1 - versionName "1.0.1" + versionName "1.1.0" ndk { abiFilters 'x86_64', 'armeabi-v7a', 'arm64-v8a' @@ -35,7 +39,7 @@ android { dependencies { implementation fileTree(dir: "libs", include: ["*.jar"]) implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" - implementation 'androidx.core:core-ktx:1.3.0' + implementation 'androidx.core:core-ktx:1.3.1' implementation 'androidx.appcompat:appcompat:1.1.0' testImplementation 'junit:junit:4.12' @@ -44,4 +48,6 @@ dependencies { implementation 'com.github.clans:fab:1.6.4' implementation 'com.jaredrummler:cyanea:1.0.2' implementation 'com.github.bumptech.glide:glide:4.11.0' + implementation 'com.google.android.exoplayer:exoplayer-core:2.11.7' + implementation 'com.google.android.exoplayer:exoplayer-ui:2.11.7' } diff --git a/app/libgocryptfs/main.go b/app/libgocryptfs/main.go index f1f7fa5..bbc0002 100644 --- a/app/libgocryptfs/main.go +++ b/app/libgocryptfs/main.go @@ -771,7 +771,6 @@ func gcf_open_read_mode(sessionID int, path string) int { } defer syscall.Close(dirfd) fd, err := syscallcompat.Openat(dirfd, cName, newFlags, 0) - // Handle a few specific errors if err != nil { return -1 } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index a7ad0da..49ae32d 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -2,6 +2,10 @@ + + @@ -13,7 +17,8 @@ android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:supportsRtl="true" - android:theme="@style/AppTheme"> + android:theme="@style/AppTheme" + android:requestLegacyExternalStorage="true"> + android:writePermission="${applicationId}.WRITE_TEMPORARY_STORAGE"/> diff --git a/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt index 8ddfc2d..7e3e069 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/BaseActivity.kt @@ -3,7 +3,6 @@ package sushi.hardcore.droidfs import android.content.SharedPreferences import android.os.Bundle import android.view.WindowManager -import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import com.jaredrummler.cyanea.app.CyaneaAppCompatActivity diff --git a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt index ac77acc..1127668 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ChangePasswordActivity.kt @@ -7,7 +7,6 @@ import android.os.Bundle import android.text.Editable import android.text.TextWatcher import android.view.View -import android.view.WindowManager import android.widget.AdapterView.OnItemClickListener import android.widget.Toast import kotlinx.android.synthetic.main.activity_change_password.* @@ -18,7 +17,7 @@ import kotlinx.android.synthetic.main.activity_change_password.saved_path_listvi import kotlinx.android.synthetic.main.toolbar.* import sushi.hardcore.droidfs.adapters.SavedVolumesAdapter import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.GocryptfsVolume import sushi.hardcore.droidfs.util.WidgetUtil import sushi.hardcore.droidfs.util.Wiper @@ -80,7 +79,7 @@ class ChangePasswordActivity : BaseActivity() { if (resultCode == Activity.RESULT_OK) { if (requestCode == PICK_DIRECTORY_REQUEST_CODE) { if (data != null) { - val path = FilesUtils.getFullPathFromTreeUri(data.data, this) + val path = PathUtils.getFullPathFromTreeUri(data.data, this) edit_volume_path.setText(path) } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt b/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt index 2e09257..9fc4aae 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt @@ -1,5 +1,6 @@ package sushi.hardcore.droidfs +import android.net.Uri import java.io.File class ConstValues { @@ -7,6 +8,7 @@ class ConstValues { const val creator = "DroidFS" const val saved_volumes_key = "saved_volumes" const val sort_order_key = "sort_order" + val fakeUri = Uri.parse("fakeuri://droidfs") const val wipe_passes = 2 const val seek_bar_inc = 200 private val fileExtensions = mapOf( diff --git a/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt index 7facd16..250b7fb 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/CreateActivity.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.view.View -import android.view.WindowManager import android.widget.Toast import kotlinx.android.synthetic.main.activity_create.* import kotlinx.android.synthetic.main.activity_create.checkbox_remember_path @@ -15,7 +14,7 @@ import kotlinx.android.synthetic.main.activity_create.edit_volume_path import kotlinx.android.synthetic.main.toolbar.* import sushi.hardcore.droidfs.explorers.ExplorerActivity import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.GocryptfsVolume import sushi.hardcore.droidfs.util.WidgetUtil import sushi.hardcore.droidfs.util.Wiper @@ -57,7 +56,7 @@ class CreateActivity : BaseActivity() { if (resultCode == Activity.RESULT_OK) { if (requestCode == PICK_DIRECTORY_REQUEST_CODE) { if (data != null) { - val path = FilesUtils.getFullPathFromTreeUri(data.data, this) + val path = PathUtils.getFullPathFromTreeUri(data.data, this) edit_volume_path.setText(path) } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt index bf1a8e5..975c52d 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/MainActivity.kt @@ -35,14 +35,14 @@ class MainActivity : BaseActivity() { if (!storageAvailable) { ColoredAlertDialog(this) .setTitle(R.string.storage_unavailable) - .setMessage(getString(R.string.storage_unavailable_msg)) + .setMessage(R.string.storage_unavailable_msg) .setPositiveButton(R.string.ok ) { _, _ -> finish() }.show() } if (!sharedPrefs.getBoolean("alreadyLaunched", false)){ ColoredAlertDialog(this) .setTitle(R.string.warning) - .setMessage(getString(R.string.usf_home_warning_msg)) + .setMessage(R.string.usf_home_warning_msg) .setCancelable(false) .setPositiveButton(getString(R.string.see_unsafe_features)){ _, _ -> val intent = Intent(this, SettingsActivity::class.java) @@ -65,7 +65,7 @@ class MainActivity : BaseActivity() { if (grantResults[0] != PackageManager.PERMISSION_GRANTED || grantResults[1] != PackageManager.PERMISSION_GRANTED) { ColoredAlertDialog(this) .setTitle(R.string.storage_perm_denied) - .setMessage(getString(R.string.storage_perm_denied_msg)) + .setMessage(R.string.storage_perm_denied_msg) .setPositiveButton(R.string.ok ) { _, _ -> finish() }.show() } diff --git a/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt index 51c2bff..65f978b 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/OpenActivity.kt @@ -5,7 +5,6 @@ import android.content.Intent import android.os.Build import android.os.Bundle import android.view.View -import android.view.WindowManager import android.widget.AdapterView.OnItemClickListener import android.widget.Toast import kotlinx.android.synthetic.main.activity_open.checkbox_remember_path @@ -19,7 +18,7 @@ import sushi.hardcore.droidfs.explorers.ExplorerActivity import sushi.hardcore.droidfs.explorers.ExplorerActivityDrop import sushi.hardcore.droidfs.explorers.ExplorerActivityPick import sushi.hardcore.droidfs.fingerprint_stuff.FingerprintPasswordHashSaver -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.GocryptfsVolume import sushi.hardcore.droidfs.util.WidgetUtil import sushi.hardcore.droidfs.util.Wiper @@ -76,7 +75,7 @@ class OpenActivity : BaseActivity() { if (resultCode == Activity.RESULT_OK) { if (requestCode == PICK_DIRECTORY_REQUEST_CODE) { if (data != null) { - val path = FilesUtils.getFullPathFromTreeUri(data.data, this) + val path = PathUtils.getFullPathFromTreeUri(data.data, this) edit_volume_path.setText(path) } } @@ -128,7 +127,7 @@ class OpenActivity : BaseActivity() { } else { ColoredAlertDialog(this) .setTitle(R.string.open_volume_failed) - .setMessage(getString(R.string.open_failed_hash_msg)) + .setMessage(R.string.open_failed_hash_msg) .setPositiveButton(R.string.ok, null) .show() } diff --git a/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt b/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt index 7c9f208..9bfcb6b 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt @@ -14,23 +14,23 @@ import androidx.core.content.ContextCompat import sushi.hardcore.droidfs.ConstValues.Companion.getAssociatedDrawable import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.widgets.ThemeColor import java.text.DateFormat import java.util.* class ExplorerElementAdapter(private val context: Context) : BaseAdapter() { private val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, context.resources.configuration.locale) - private lateinit var explorer_elements: List + private lateinit var explorerElements: List private val inflater: LayoutInflater = LayoutInflater.from(context) val selectedItems: MutableList = ArrayList() private val themeColor = ThemeColor.getThemeColor(context) override fun getCount(): Int { - return explorer_elements.size + return explorerElements.size } override fun getItem(position: Int): ExplorerElement { - return explorer_elements[position] + return explorerElements[position] } override fun getItemId(position: Int): Long { @@ -55,7 +55,7 @@ class ExplorerElementAdapter(private val context: Context) : BaseAdapter() { } else -> { textElementMtime.text = dateFormat.format(currentElement.mTime) - textElementSize.text = FilesUtils.formatSize(currentElement.size) + textElementSize.text = PathUtils.formatSize(currentElement.size) drawableId = getAssociatedDrawable(currentElement.name) } } @@ -73,56 +73,55 @@ class ExplorerElementAdapter(private val context: Context) : BaseAdapter() { fun onItemClick(position: Int) { if (selectedItems.isNotEmpty()) { - if (!explorer_elements[position].isParentFolder) { + if (!explorerElements[position].isParentFolder) { if (selectedItems.contains(position)) { selectedItems.remove(position) } else { selectedItems.add(position) } - notifyDataSetInvalidated() + notifyDataSetChanged() } } } fun onItemLongClick(position: Int) { - if (!explorer_elements[position].isParentFolder) { + if (!explorerElements[position].isParentFolder) { if (!selectedItems.contains(position)) { selectedItems.add(position) } else { selectedItems.remove(position) } - notifyDataSetInvalidated() + notifyDataSetChanged() } } fun selectAll() { - for (i in explorer_elements.indices) { - if (!selectedItems.contains(i) && !explorer_elements[i].isParentFolder) { + for (i in explorerElements.indices) { + if (!selectedItems.contains(i) && !explorerElements[i].isParentFolder) { selectedItems.add(i) } } - notifyDataSetInvalidated() + notifyDataSetChanged() } fun unSelectAll() { selectedItems.clear() - notifyDataSetInvalidated() + notifyDataSetChanged() } fun setExplorerElements(explorer_elements: List) { unSelectAll() - this.explorer_elements = explorer_elements + this.explorerElements = explorer_elements } val currentDirectoryTotalSize: Long get() { - var total_size: Long = 0 - for (e in explorer_elements) { + var totalSize: Long = 0 + for (e in explorerElements) { if (e.isRegularFile) { - total_size += e.size + totalSize += e.size } } - return total_size + return totalSize } - } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt index 0cfe35e..03f8845 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -1,7 +1,6 @@ package sushi.hardcore.droidfs.explorers import android.content.Intent -import android.content.SharedPreferences import android.os.Bundle import android.view.Menu import android.view.MenuItem @@ -29,50 +28,48 @@ import sushi.hardcore.droidfs.file_viewers.AudioPlayer import sushi.hardcore.droidfs.file_viewers.ImageViewer import sushi.hardcore.droidfs.file_viewers.TextEditor import sushi.hardcore.droidfs.file_viewers.VideoPlayer -import sushi.hardcore.droidfs.provider.TemporaryFileProvider +import sushi.hardcore.droidfs.provider.RestrictedFileProvider import sushi.hardcore.droidfs.util.ExternalProvider -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.GocryptfsVolume import sushi.hardcore.droidfs.widgets.ColoredAlertDialog import java.util.* open class BaseExplorerActivity : BaseActivity() { - private lateinit var shared_prefs_editor: SharedPreferences.Editor - private lateinit var sort_modes_entries: Array - private lateinit var sort_modes_values: Array - private var current_sort_mode_index = 0 + private lateinit var sortModesEntries: Array + private lateinit var sortModesValues: Array + private var currentSortModeIndex = 0 protected lateinit var gocryptfsVolume: GocryptfsVolume - private lateinit var volume_name: String - protected var current_path = "" - protected lateinit var explorer_elements: MutableList - protected lateinit var explorer_adapter: ExplorerElementAdapter + private lateinit var volumeName: String + protected var currentDirectoryPath = "" + protected lateinit var explorerElements: MutableList + protected lateinit var explorerAdapter: ExplorerElementAdapter private var usf_open = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) usf_open = sharedPrefs.getBoolean("usf_open", false) val intent = intent - volume_name = intent.getStringExtra("volume_name") ?: "" + volumeName = intent.getStringExtra("volume_name") ?: "" val sessionID = intent.getIntExtra("sessionID", -1) gocryptfsVolume = GocryptfsVolume(sessionID) - sort_modes_entries = resources.getStringArray(R.array.sort_orders_entries) - sort_modes_values = resources.getStringArray(R.array.sort_orders_values) - current_sort_mode_index = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.sort_order_key, "name")) - shared_prefs_editor = sharedPrefs.edit() + sortModesEntries = resources.getStringArray(R.array.sort_orders_entries) + sortModesValues = resources.getStringArray(R.array.sort_orders_values) + currentSortModeIndex = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.sort_order_key, "name")) init() setSupportActionBar(toolbar) title = "" - title_text.text = getString(R.string.volume, volume_name) - explorer_adapter = ExplorerElementAdapter(this) - setCurrentPath(current_path) - list_explorer.adapter = explorer_adapter + title_text.text = getString(R.string.volume, volumeName) + explorerAdapter = ExplorerElementAdapter(this) + setCurrentPath(currentDirectoryPath) + list_explorer.adapter = explorerAdapter list_explorer.onItemClickListener = OnItemClickListener { _, _, position, _ -> onExplorerItemClick(position) } list_explorer.onItemLongClickListener = OnItemLongClickListener { _, _, position, _ -> - explorer_adapter.onItemLongClick(position) + explorerAdapter.onItemLongClick(position) invalidateOptionsMenu() true } refresher.setOnRefreshListener { - setCurrentPath(current_path) + setCurrentPath(currentDirectoryPath) refresher.isRefreshing = false } } @@ -89,29 +86,29 @@ open class BaseExplorerActivity : BaseActivity() { } protected open fun onExplorerItemClick(position: Int) { - val wasSelecting = explorer_adapter.selectedItems.isNotEmpty() - explorer_adapter.onItemClick(position) - if (explorer_adapter.selectedItems.isEmpty()) { + val wasSelecting = explorerAdapter.selectedItems.isNotEmpty() + explorerAdapter.onItemClick(position) + if (explorerAdapter.selectedItems.isEmpty()) { if (!wasSelecting) { - val full_path = FilesUtils.path_join(current_path, explorer_elements[position].name) + val fullPath = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name) when { - explorer_elements[position].isDirectory -> { - setCurrentPath(full_path) + explorerElements[position].isDirectory -> { + setCurrentPath(fullPath) } - explorer_elements[position].isParentFolder -> { - setCurrentPath(FilesUtils.get_parent_path(current_path)) + explorerElements[position].isParentFolder -> { + setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath)) } - isImage(full_path) -> { - startFileViewer(ImageViewer::class.java, full_path) + isImage(fullPath) -> { + startFileViewer(ImageViewer::class.java, fullPath) } - isVideo(full_path) -> { - startFileViewer(VideoPlayer::class.java, full_path) + isVideo(fullPath) -> { + startFileViewer(VideoPlayer::class.java, fullPath) } - isText(full_path) -> { - startFileViewer(TextEditor::class.java, full_path) + isText(fullPath) -> { + startFileViewer(TextEditor::class.java, fullPath) } - isAudio(full_path) -> { - startFileViewer(AudioPlayer::class.java, full_path) + isAudio(fullPath) -> { + startFileViewer(AudioPlayer::class.java, fullPath) } else -> { val dialogListView = layoutInflater.inflate(R.layout.dialog_listview, null) @@ -125,10 +122,10 @@ open class BaseExplorerActivity : BaseActivity() { .create() listView.setOnItemClickListener{_, _, fileTypePosition, _ -> when (adapter.getItem(fileTypePosition)){ - "image" -> startFileViewer(ImageViewer::class.java, full_path) - "video" -> startFileViewer(VideoPlayer::class.java, full_path) - "audio" -> startFileViewer(AudioPlayer::class.java, full_path) - "text" -> startFileViewer(TextEditor::class.java, full_path) + "image" -> startFileViewer(ImageViewer::class.java, fullPath) + "video" -> startFileViewer(VideoPlayer::class.java, fullPath) + "audio" -> startFileViewer(AudioPlayer::class.java, fullPath) + "text" -> startFileViewer(TextEditor::class.java, fullPath) } dialog.dismiss() } @@ -141,41 +138,42 @@ open class BaseExplorerActivity : BaseActivity() { } private fun sortExplorerElements() { - when (sort_modes_values[current_sort_mode_index]) { + when (sortModesValues[currentSortModeIndex]) { "name" -> { - explorer_elements.sortWith(Comparator { o1, o2 -> o1.name.compareTo(o2.name) }) + explorerElements.sortWith(Comparator { o1, o2 -> o1.name.compareTo(o2.name) }) } "size" -> { - explorer_elements.sortWith(Comparator { o1, o2 -> (o1.size - o2.size).toInt() }) + explorerElements.sortWith(Comparator { o1, o2 -> (o1.size - o2.size).toInt() }) } "date" -> { - explorer_elements.sortWith(Comparator { o1, o2 -> o1.mTime.compareTo(o2.mTime) }) + explorerElements.sortWith(Comparator { o1, o2 -> o1.mTime.compareTo(o2.mTime) }) } "name_desc" -> { - explorer_elements.sortWith(Comparator { o1, o2 -> o2.name.compareTo(o1.name) }) + explorerElements.sortWith(Comparator { o1, o2 -> o2.name.compareTo(o1.name) }) } "size_desc" -> { - explorer_elements.sortWith(Comparator { o1, o2 -> (o2.size - o1.size).toInt() }) + explorerElements.sortWith(Comparator { o1, o2 -> (o2.size - o1.size).toInt() }) } "date_desc" -> { - explorer_elements.sortWith(Comparator { o1, o2 -> o2.mTime.compareTo(o1.mTime) }) + explorerElements.sortWith(Comparator { o1, o2 -> o2.mTime.compareTo(o1.mTime) }) } } - shared_prefs_editor.putString(ConstValues.sort_order_key, sort_modes_values[current_sort_mode_index]) - shared_prefs_editor.apply() + val sharedPrefsEditor = sharedPrefs.edit() + sharedPrefsEditor.putString(ConstValues.sort_order_key, sortModesValues[currentSortModeIndex]) + sharedPrefsEditor.apply() } protected fun setCurrentPath(path: String) { - explorer_elements = gocryptfsVolume.list_dir(path) - text_dir_empty.visibility = if (explorer_elements.size == 0) View.VISIBLE else View.INVISIBLE + explorerElements = gocryptfsVolume.list_dir(path) + text_dir_empty.visibility = if (explorerElements.size == 0) View.VISIBLE else View.INVISIBLE sortExplorerElements() if (path.isNotEmpty()) { //not root - explorer_elements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1)) + explorerElements.add(0, ExplorerElement("..", (-1).toShort(), -1, -1)) } - explorer_adapter.setExplorerElements(explorer_elements) - current_path = path - current_path_text.text = getString(R.string.location, current_path) - total_size_text.text = getString(R.string.total_size, FilesUtils.formatSize(explorer_adapter.currentDirectoryTotalSize)) + explorerAdapter.setExplorerElements(explorerElements) + currentDirectoryPath = path + current_path_text.text = getString(R.string.location, currentDirectoryPath) + total_size_text.text = getString(R.string.total_size, PathUtils.formatSize(explorerAdapter.currentDirectoryTotalSize)) } private fun askCloseVolume() { @@ -193,19 +191,19 @@ open class BaseExplorerActivity : BaseActivity() { protected open fun closeVolumeOnDestroy() { gocryptfsVolume.close() - TemporaryFileProvider.wipeAll() //additional security + RestrictedFileProvider.wipeAll() //additional security } override fun onBackPressed() { - if (explorer_adapter.selectedItems.isEmpty()) { - val parent_path = FilesUtils.get_parent_path(current_path) - if (parent_path == current_path) { + if (explorerAdapter.selectedItems.isEmpty()) { + val parentPath = PathUtils.get_parent_path(currentDirectoryPath) + if (parentPath == currentDirectoryPath) { askCloseVolume() } else { - setCurrentPath(FilesUtils.get_parent_path(current_path)) + setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath)) } } else { - explorer_adapter.unSelectAll() + explorerAdapter.unSelectAll() invalidateOptionsMenu() } } @@ -214,14 +212,14 @@ open class BaseExplorerActivity : BaseActivity() { if (folder_name.isEmpty()) { Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() } else { - if (!gocryptfsVolume.mkdir(FilesUtils.path_join(current_path, folder_name))) { + if (!gocryptfsVolume.mkdir(PathUtils.path_join(currentDirectoryPath, folder_name))) { ColoredAlertDialog(this) .setTitle(R.string.error) .setMessage(R.string.error_mkdir) .setPositiveButton(R.string.ok, null) .show() } else { - setCurrentPath(current_path) + setCurrentPath(currentDirectoryPath) invalidateOptionsMenu() } } @@ -254,14 +252,14 @@ open class BaseExplorerActivity : BaseActivity() { if (new_name.isEmpty()) { Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() } else { - if (!gocryptfsVolume.rename(FilesUtils.path_join(current_path, old_name), FilesUtils.path_join(current_path, new_name))) { + if (!gocryptfsVolume.rename(PathUtils.path_join(currentDirectoryPath, old_name), PathUtils.path_join(currentDirectoryPath, new_name))) { ColoredAlertDialog(this) .setTitle(R.string.error) .setMessage(getString(R.string.rename_failed, old_name)) .setPositiveButton(R.string.ok, null) .show() } else { - setCurrentPath(current_path) + setCurrentPath(currentDirectoryPath) invalidateOptionsMenu() } } @@ -272,7 +270,7 @@ open class BaseExplorerActivity : BaseActivity() { if (usf_open){ menu.findItem(R.id.explorer_menu_external_open)?.isVisible = false } - val selectedItems = explorer_adapter.selectedItems + val selectedItems = explorerAdapter.selectedItems if (selectedItems.isEmpty()){ toolbar.navigationIcon = null menu.findItem(R.id.explorer_menu_close).isVisible = true @@ -283,7 +281,7 @@ open class BaseExplorerActivity : BaseActivity() { menu.findItem(R.id.explorer_menu_sort).isVisible = false if (selectedItems.size == 1) { menu.findItem(R.id.explorer_menu_rename).isVisible = true - if (usf_open && explorer_elements[selectedItems[0]].isRegularFile) { + if (usf_open && explorerElements[selectedItems[0]].isRegularFile) { menu.findItem(R.id.explorer_menu_external_open)?.isVisible = true } } @@ -293,39 +291,39 @@ open class BaseExplorerActivity : BaseActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { - explorer_adapter.unSelectAll() + explorerAdapter.unSelectAll() invalidateOptionsMenu() true } R.id.explorer_menu_sort -> { ColoredAlertDialog(this) .setTitle(R.string.sort_order) - .setSingleChoiceItems(sort_modes_entries, current_sort_mode_index) { dialog, which -> - current_sort_mode_index = which - setCurrentPath(current_path) + .setSingleChoiceItems(sortModesEntries, currentSortModeIndex) { dialog, which -> + currentSortModeIndex = which + setCurrentPath(currentDirectoryPath) dialog.dismiss() }.show() true } R.id.explorer_menu_rename -> { - val dialog_edit_text_view = layoutInflater.inflate(R.layout.dialog_edit_text, null) - val old_name = explorer_elements[explorer_adapter.selectedItems[0]].name - val dialog_edit_text = dialog_edit_text_view.findViewById(R.id.dialog_edit_text) - dialog_edit_text.setText(old_name) - dialog_edit_text.selectAll() + val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null) + val oldName = explorerElements[explorerAdapter.selectedItems[0]].name + val dialogEditText = dialogEditTextView.findViewById(R.id.dialog_edit_text) + dialogEditText.setText(oldName) + dialogEditText.selectAll() val dialog = ColoredAlertDialog(this) - .setView(dialog_edit_text_view) + .setView(dialogEditTextView) .setTitle(R.string.rename_title) .setPositiveButton(R.string.ok) { _, _ -> - val new_name = dialog_edit_text.text.toString() - rename(old_name, new_name) + val newName = dialogEditText.text.toString() + rename(oldName, newName) } .setNegativeButton(R.string.cancel, null) .create() - dialog_edit_text.setOnEditorActionListener { _, _, _ -> - val new_name = dialog_edit_text.text.toString() + dialogEditText.setOnEditorActionListener { _, _, _ -> + val newName = dialogEditText.text.toString() dialog.dismiss() - rename(old_name, new_name) + rename(oldName, newName) true } dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) @@ -334,8 +332,8 @@ open class BaseExplorerActivity : BaseActivity() { } R.id.explorer_menu_external_open -> { if (usf_open){ - ExternalProvider.open(this, gocryptfsVolume, FilesUtils.path_join(current_path, explorer_elements[explorer_adapter.selectedItems[0]].name)) - explorer_adapter.unSelectAll() + ExternalProvider.open(this, gocryptfsVolume, PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name)) + explorerAdapter.unSelectAll() invalidateOptionsMenu() } true @@ -357,6 +355,6 @@ open class BaseExplorerActivity : BaseActivity() { override fun onResume() { super.onResume() - ExternalProvider.clear_cache(this) + ExternalProvider.removeFiles(this) } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt index cad7990..2d098a2 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -14,7 +14,7 @@ import kotlinx.android.synthetic.main.activity_explorer.* import sushi.hardcore.droidfs.OpenActivity import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.util.ExternalProvider -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.GocryptfsVolume import sushi.hardcore.droidfs.util.Wiper import sushi.hardcore.droidfs.widgets.ColoredAlertDialog @@ -37,16 +37,16 @@ class ExplorerActivity : BaseExplorerActivity() { if (fileName.isEmpty()) { Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() } else { - val handleID = gocryptfsVolume.open_write_mode(FilesUtils.path_join(current_path, fileName)) + val handleID = gocryptfsVolume.open_write_mode(PathUtils.path_join(currentDirectoryPath, fileName)) if (handleID == -1) { ColoredAlertDialog(this) .setTitle(R.string.error) - .setMessage(getString(R.string.file_creation_failed)) + .setMessage(R.string.file_creation_failed) .setPositiveButton(R.string.ok, null) .show() } else { gocryptfsVolume.close_file(handleID) - setCurrentPath(current_path) + setCurrentPath(currentDirectoryPath) invalidateOptionsMenu() } } @@ -110,7 +110,7 @@ class ExplorerActivity : BaseExplorerActivity() { if (uris.isNotEmpty()){ var success = true for (uri in uris) { - val dstPath = FilesUtils.path_join(current_path, FilesUtils.getFilenameFromURI(this, uri)) + val dstPath = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri)) contentResolver.openInputStream(uri)?.let { success = gocryptfsVolume.import_file(it, dstPath) } @@ -154,32 +154,32 @@ class ExplorerActivity : BaseExplorerActivity() { .setNegativeButton(getString(R.string.no), null) .show() } - setCurrentPath(current_path) + setCurrentPath(currentDirectoryPath) } } } else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { val uri = data.data - val output_dir = FilesUtils.getFullPathFromTreeUri(uri, this) - var failed_item: String? = null - for (i in explorer_adapter.selectedItems) { - val element = explorer_adapter.getItem(i) - val full_path = FilesUtils.path_join(current_path, element.name) - failed_item = if (element.isDirectory) { - recursive_export_directory(full_path, output_dir) + val outputDir = PathUtils.getFullPathFromTreeUri(uri, this) + var failedItem: String? = null + for (i in explorerAdapter.selectedItems) { + val element = explorerAdapter.getItem(i) + val fullPath = PathUtils.path_join(currentDirectoryPath, element.name) + failedItem = if (element.isDirectory) { + recursiveExportDirectory(fullPath, outputDir) } else { - if (gocryptfsVolume.export_file(full_path, FilesUtils.path_join(output_dir, element.name))) null else full_path + if (gocryptfsVolume.export_file(fullPath, PathUtils.path_join(outputDir, element.name))) null else fullPath } - if (failed_item != null) { + if (failedItem != null) { ColoredAlertDialog(this) .setTitle(R.string.error) - .setMessage(getString(R.string.export_failed, failed_item)) + .setMessage(getString(R.string.export_failed, failedItem)) .setPositiveButton(R.string.ok, null) .show() break } } - if (failed_item == null) { + if (failedItem == null) { ColoredAlertDialog(this) .setTitle(R.string.success_export) .setMessage(R.string.success_export_msg) @@ -187,33 +187,33 @@ class ExplorerActivity : BaseExplorerActivity() { .show() } } - explorer_adapter.unSelectAll() + explorerAdapter.unSelectAll() invalidateOptionsMenu() } else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) { if (resultCode == Activity.RESULT_OK && data != null) { - val remote_sessionID = data.getIntExtra("sessionID", -1) - val remote_gocryptfsVolume = GocryptfsVolume(remote_sessionID) + val remoteSessionID = data.getIntExtra("sessionID", -1) + val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID) val path = data.getStringExtra("path") - var failed_item: String? = null + var failedItem: String? = null if (path == null) { val paths = data.getStringArrayListExtra("paths") val types = data.getIntegerArrayListExtra("types") if (types != null && paths != null){ for (i in paths.indices) { - failed_item = if (types[i] == 0) { //directory - recursive_import_directory_from_other_volume(remote_gocryptfsVolume, paths[i], current_path) + failedItem = if (types[i] == 0) { //directory + recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath) } else { - if (import_file_from_other_volume(remote_gocryptfsVolume, paths[i], current_path)) null else paths[i] + if (importFileFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)) null else paths[i] } - if (failed_item != null) { + if (failedItem != null) { break } } } } else { - failed_item = if (import_file_from_other_volume(remote_gocryptfsVolume, path, current_path)) null else path + failedItem = if (importFileFromOtherVolume(remoteGocryptfsVolume, path, currentDirectoryPath)) null else path } - if (failed_item == null) { + if (failedItem == null) { ColoredAlertDialog(this) .setTitle(R.string.success_import) .setMessage(R.string.success_import_msg) @@ -222,12 +222,12 @@ class ExplorerActivity : BaseExplorerActivity() { } else { ColoredAlertDialog(this) .setTitle(R.string.error) - .setMessage(getString(R.string.import_failed, failed_item)) + .setMessage(getString(R.string.import_failed, failedItem)) .setPositiveButton(R.string.ok, null) .show() } - remote_gocryptfsVolume.close() - setCurrentPath(current_path) + remoteGocryptfsVolume.close() + setCurrentPath(currentDirectoryPath) } } } @@ -238,14 +238,14 @@ class ExplorerActivity : BaseExplorerActivity() { if (usf_share){ menu.findItem(R.id.explorer_menu_share).isVisible = false } - val any_item_selected = explorer_adapter.selectedItems.isNotEmpty() - menu.findItem(R.id.explorer_menu_select_all).isVisible = any_item_selected - menu.findItem(R.id.explorer_menu_delete).isVisible = any_item_selected - menu.findItem(R.id.explorer_menu_decrypt).isVisible = any_item_selected && usf_decrypt - if (any_item_selected && usf_share){ + val anyItemSelected = explorerAdapter.selectedItems.isNotEmpty() + menu.findItem(R.id.explorer_menu_select_all).isVisible = anyItemSelected + menu.findItem(R.id.explorer_menu_delete).isVisible = anyItemSelected + menu.findItem(R.id.explorer_menu_decrypt).isVisible = anyItemSelected && usf_decrypt + if (anyItemSelected && usf_share){ var containsDir = false - for (i in explorer_adapter.selectedItems) { - if (explorer_elements[i].isDirectory) { + for (i in explorerAdapter.selectedItems) { + if (explorerElements[i].isDirectory) { containsDir = true break } @@ -260,32 +260,32 @@ class ExplorerActivity : BaseExplorerActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.explorer_menu_select_all -> { - explorer_adapter.selectAll() + explorerAdapter.selectAll() invalidateOptionsMenu() true } R.id.explorer_menu_delete -> { - val size = explorer_adapter.selectedItems.size + val size = explorerAdapter.selectedItems.size val dialog = ColoredAlertDialog(this) dialog.setTitle(R.string.warning) - dialog.setPositiveButton(R.string.ok) { _, _ -> remove_selected_items() } + dialog.setPositiveButton(R.string.ok) { _, _ -> removeSelectedItems() } dialog.setNegativeButton(R.string.cancel, null) if (size > 1) { - dialog.setMessage(getString(R.string.multiple_delete_confirm, explorer_adapter.selectedItems.size.toString())) + dialog.setMessage(getString(R.string.multiple_delete_confirm, explorerAdapter.selectedItems.size.toString())) } else { - dialog.setMessage(getString(R.string.single_delete_confirm, explorer_adapter.getItem(explorer_adapter.selectedItems[0]).name)) + dialog.setMessage(getString(R.string.single_delete_confirm, explorerAdapter.getItem(explorerAdapter.selectedItems[0]).name)) } dialog.show() true } R.id.explorer_menu_share -> { val paths: MutableList = ArrayList() - for (i in explorer_adapter.selectedItems) { - val e = explorer_elements[i] - paths.add(FilesUtils.path_join(current_path, e.name)) + for (i in explorerAdapter.selectedItems) { + val e = explorerElements[i] + paths.add(PathUtils.path_join(currentDirectoryPath, e.name)) } ExternalProvider.share(this, gocryptfsVolume, paths) - explorer_adapter.unSelectAll() + explorerAdapter.unSelectAll() invalidateOptionsMenu() true } @@ -298,18 +298,18 @@ class ExplorerActivity : BaseExplorerActivity() { } } - private fun import_file_from_other_volume(remote_gocryptfsVolume: GocryptfsVolume, full_path: String, output_dir: String): Boolean { - val output_path = FilesUtils.path_join(output_dir, File(full_path).name) + private fun importFileFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, full_path: String, output_dir: String): Boolean { + val outputPath = PathUtils.path_join(output_dir, File(full_path).name) var success = true - val src_handleID = remote_gocryptfsVolume.open_read_mode(full_path) - if (src_handleID != -1) { - val dst_handleID = gocryptfsVolume.open_write_mode(output_path) - if (dst_handleID != -1) { + val srcHandleID = remote_gocryptfsVolume.open_read_mode(full_path) + if (srcHandleID != -1) { + val dstHandleID = gocryptfsVolume.open_write_mode(outputPath) + if (dstHandleID != -1) { var length: Int - val io_buffer = ByteArray(GocryptfsVolume.DefaultBS) + val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS) var offset: Long = 0 - while (remote_gocryptfsVolume.read_file(src_handleID, offset, io_buffer).also { length = it } > 0){ - val written = gocryptfsVolume.write_file(dst_handleID, offset, io_buffer, length).toLong() + while (remote_gocryptfsVolume.read_file(srcHandleID, offset, ioBuffer).also { length = it } > 0){ + val written = gocryptfsVolume.write_file(dstHandleID, offset, ioBuffer, length).toLong() if (written == length.toLong()) { offset += length.toLong() } else { @@ -317,46 +317,46 @@ class ExplorerActivity : BaseExplorerActivity() { break } } - gocryptfsVolume.close_file(dst_handleID) + gocryptfsVolume.close_file(dstHandleID) } - remote_gocryptfsVolume.close_file(src_handleID) + remote_gocryptfsVolume.close_file(srcHandleID) } return success } - private fun recursive_import_directory_from_other_volume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, output_dir: String): String? { - val directory_path = FilesUtils.path_join(output_dir, File(remote_directory_path).name) - if (!gocryptfsVolume.path_exists(directory_path)) { - if (!gocryptfsVolume.mkdir(directory_path)) { - return directory_path + private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, output_dir: String): String? { + val directoryPath = PathUtils.path_join(output_dir, File(remote_directory_path).name) + if (!gocryptfsVolume.path_exists(directoryPath)) { + if (!gocryptfsVolume.mkdir(directoryPath)) { + return directoryPath } } - val explorer_elements = remote_gocryptfsVolume.list_dir(remote_directory_path) - for (e in explorer_elements) { - val full_path = FilesUtils.path_join(remote_directory_path, e.name) + val explorerElements = remote_gocryptfsVolume.list_dir(remote_directory_path) + for (e in explorerElements) { + val fullPath = PathUtils.path_join(remote_directory_path, e.name) if (e.isDirectory) { - val failed_item = recursive_import_directory_from_other_volume(remote_gocryptfsVolume, full_path, directory_path) - failed_item?.let { return it } + val failedItem = recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath) + failedItem?.let { return it } } else { - if (!import_file_from_other_volume(remote_gocryptfsVolume, full_path, directory_path)) { - return full_path + if (!importFileFromOtherVolume(remote_gocryptfsVolume, fullPath, directoryPath)) { + return fullPath } } } return null } - private fun recursive_export_directory(plain_directory_path: String, output_dir: String?): String? { - if (File(FilesUtils.path_join(output_dir, plain_directory_path)).mkdir()) { - val explorer_elements = gocryptfsVolume.list_dir(plain_directory_path) - for (e in explorer_elements) { - val full_path = FilesUtils.path_join(plain_directory_path, e.name) + private fun recursiveExportDirectory(plain_directory_path: String, output_dir: String?): String? { + if (File(PathUtils.path_join(output_dir, plain_directory_path)).mkdir()) { + val explorerElements = gocryptfsVolume.list_dir(plain_directory_path) + for (e in explorerElements) { + val fullPath = PathUtils.path_join(plain_directory_path, e.name) if (e.isDirectory) { - val failed_item = recursive_export_directory(full_path, output_dir) - failed_item?.let { return it } + val failedItem = recursiveExportDirectory(fullPath, output_dir) + failedItem?.let { return it } } else { - if (!gocryptfsVolume.export_file(full_path, FilesUtils.path_join(output_dir, full_path))) { - return full_path + if (!gocryptfsVolume.export_file(fullPath, PathUtils.path_join(output_dir, fullPath))) { + return fullPath } } } @@ -365,16 +365,16 @@ class ExplorerActivity : BaseExplorerActivity() { return output_dir } - private fun recursive_remove_directory(plain_directory_path: String): String? { - val explorer_elements = gocryptfsVolume.list_dir(plain_directory_path) - for (e in explorer_elements) { - val full_path = FilesUtils.path_join(plain_directory_path, e.name) + private fun recursiveRemoveDirectory(plain_directory_path: String): String? { + val explorerElements = gocryptfsVolume.list_dir(plain_directory_path) + for (e in explorerElements) { + val fullPath = PathUtils.path_join(plain_directory_path, e.name) if (e.isDirectory) { - val result = recursive_remove_directory(full_path) + val result = recursiveRemoveDirectory(fullPath) result?.let { return it } } else { - if (!gocryptfsVolume.remove_file(full_path)) { - return full_path + if (!gocryptfsVolume.remove_file(fullPath)) { + return fullPath } } } @@ -385,30 +385,30 @@ class ExplorerActivity : BaseExplorerActivity() { } } - private fun remove_selected_items() { - var failed_item: String? = null - for (i in explorer_adapter.selectedItems) { - val element = explorer_adapter.getItem(i) - val full_path = FilesUtils.path_join(current_path, element.name) + private fun removeSelectedItems() { + var failedItem: String? = null + for (i in explorerAdapter.selectedItems) { + val element = explorerAdapter.getItem(i) + val fullPath = PathUtils.path_join(currentDirectoryPath, element.name) if (element.isDirectory) { - val result = recursive_remove_directory(full_path) - result?.let{ failed_item = it } + val result = recursiveRemoveDirectory(fullPath) + result?.let{ failedItem = it } } else { - if (!gocryptfsVolume.remove_file(full_path)) { - failed_item = full_path + if (!gocryptfsVolume.remove_file(fullPath)) { + failedItem = fullPath } } - if (failed_item != null) { + if (failedItem != null) { ColoredAlertDialog(this) .setTitle(R.string.error) - .setMessage(getString(R.string.remove_failed, failed_item)) + .setMessage(getString(R.string.remove_failed, failedItem)) .setPositiveButton(R.string.ok, null) .show() break } } - explorer_adapter.unSelectAll() + explorerAdapter.unSelectAll() invalidateOptionsMenu() - setCurrentPath(current_path) //refresh + setCurrentPath(currentDirectoryPath) //refresh } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt index 0888073..5a0d73e 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt @@ -5,7 +5,7 @@ import android.net.Uri import android.view.Menu import android.view.MenuItem import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.widgets.ColoredAlertDialog class ExplorerActivityDrop : BaseExplorerActivity() { @@ -16,7 +16,7 @@ class ExplorerActivityDrop : BaseExplorerActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.explorer_drop, menu) handleMenuItems(menu) - menu.findItem(R.id.explorer_menu_validate).isVisible = explorer_adapter.selectedItems.isEmpty() + menu.findItem(R.id.explorer_menu_validate).isVisible = explorerAdapter.selectedItems.isEmpty() return true } @@ -31,13 +31,13 @@ class ExplorerActivityDrop : BaseExplorerActivity() { if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){ if (intent.action == Intent.ACTION_SEND) { val uri = intent.getParcelableExtra(Intent.EXTRA_STREAM) - val output_path = FilesUtils.path_join(current_path, FilesUtils.getFilenameFromURI(this, uri)) + val output_path = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri)) error_msg = if (gocryptfsVolume.import_file(this, uri, output_path)) null else getString(R.string.import_failed, output_path) } else if (intent.action == Intent.ACTION_SEND_MULTIPLE) { val uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) if (uris != null){ for (uri in uris) { - val output_path = FilesUtils.path_join(current_path, FilesUtils.getFilenameFromURI(this, uri)) + val output_path = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(this, uri)) if (!gocryptfsVolume.import_file(this, uri, output_path)) { error_msg = getString(R.string.import_failed, output_path) break diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt index ff6120a..52f99c7 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityPick.kt @@ -5,8 +5,8 @@ import android.content.Intent import android.view.Menu import android.view.MenuItem import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.provider.TemporaryFileProvider -import sushi.hardcore.droidfs.util.FilesUtils +import sushi.hardcore.droidfs.provider.RestrictedFileProvider +import sushi.hardcore.droidfs.util.PathUtils import java.util.* class ExplorerActivityPick : BaseExplorerActivity() { @@ -17,21 +17,21 @@ class ExplorerActivityPick : BaseExplorerActivity() { } override fun onExplorerItemClick(position: Int) { - val wasSelecting = explorer_adapter.selectedItems.isNotEmpty() - explorer_adapter.onItemClick(position) - if (explorer_adapter.selectedItems.isEmpty()) { + val wasSelecting = explorerAdapter.selectedItems.isNotEmpty() + explorerAdapter.onItemClick(position) + if (explorerAdapter.selectedItems.isEmpty()) { if (!wasSelecting) { - val full_path = FilesUtils.path_join(current_path, explorer_elements[position].name) + val full_path = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name) when { - explorer_elements[position].isDirectory -> { + explorerElements[position].isDirectory -> { setCurrentPath(full_path) } - explorer_elements[position].isParentFolder -> { - setCurrentPath(FilesUtils.get_parent_path(current_path)) + explorerElements[position].isParentFolder -> { + setCurrentPath(PathUtils.get_parent_path(currentDirectoryPath)) } else -> { result_intent.putExtra("path", full_path) - return_activity_result() + returnActivityResult() } } } @@ -42,7 +42,7 @@ class ExplorerActivityPick : BaseExplorerActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.explorer_pick, menu) handleMenuItems(menu) - val any_item_selected = explorer_adapter.selectedItems.isNotEmpty() + val any_item_selected = explorerAdapter.selectedItems.isNotEmpty() menu.findItem(R.id.explorer_menu_select_all).isVisible = any_item_selected menu.findItem(R.id.explorer_menu_validate).isVisible = any_item_selected return true @@ -51,35 +51,35 @@ class ExplorerActivityPick : BaseExplorerActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { R.id.explorer_menu_select_all -> { - explorer_adapter.selectAll() + explorerAdapter.selectAll() invalidateOptionsMenu() true } R.id.explorer_menu_validate -> { val paths = ArrayList() val types = ArrayList() - for (i in explorer_adapter.selectedItems) { - val e = explorer_elements[i] - paths.add(FilesUtils.path_join(current_path, e.name)) + for (i in explorerAdapter.selectedItems) { + val e = explorerElements[i] + paths.add(PathUtils.path_join(currentDirectoryPath, e.name)) types.add(e.elementType.toInt()) } result_intent.putStringArrayListExtra("paths", paths) result_intent.putIntegerArrayListExtra("types", types) - return_activity_result() + returnActivityResult() true } else -> super.onOptionsItemSelected(item) } } - private fun return_activity_result() { + private fun returnActivityResult() { setResult(Activity.RESULT_OK, result_intent) finish() } override fun closeVolumeOnDestroy() { //don't close volume - TemporaryFileProvider.wipeAll() + RestrictedFileProvider.wipeAll() } override fun closeVolumeOnUserExit() { diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/AudioPlayer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/AudioPlayer.kt index a58a339..d96d535 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/AudioPlayer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/AudioPlayer.kt @@ -1,20 +1,13 @@ package sushi.hardcore.droidfs.file_viewers -import android.media.MediaPlayer -import android.os.Handler -import android.widget.SeekBar -import androidx.core.content.ContextCompat +import com.google.android.exoplayer2.SimpleExoPlayer import kotlinx.android.synthetic.main.activity_audio_player.* -import sushi.hardcore.droidfs.ConstValues import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.widgets.ColoredAlertDialog import java.io.File -import java.io.IOException -class AudioPlayer: FileViewerActivity(){ - private lateinit var player: MediaPlayer - private var isPrepared = false +class AudioPlayer: MediaPlayer(){ override fun viewFile() { + super.viewFile() setContentView(R.layout.activity_audio_player) val filename = File(filePath).name val pos = filename.lastIndexOf('.') @@ -23,65 +16,9 @@ class AudioPlayer: FileViewerActivity(){ } else { filename } - val tmpFileUri = exportFile(filePath) - tmpFileUri?.let { - player = MediaPlayer() - player.setDataSource(this, tmpFileUri) - try { - player.prepare() - isPrepared = true - } catch (e: IOException){ - ColoredAlertDialog(this) - .setTitle(R.string.error) - .setMessage(getString(R.string.media_player_prepare_failed)) - .setCancelable(false) - .setPositiveButton(R.string.ok) { _, _ -> finish() } - .show() - } - if (isPrepared){ - player.isLooping = true - button_pause.setOnClickListener { - if (player.isPlaying) { - player.pause() - button_pause.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.icon_play)) - } else { - player.start() - button_pause.setImageDrawable(ContextCompat.getDrawable(this, R.drawable.icon_pause)) - } - } - button_stop.setOnClickListener { finish() } - seekbar.max = player.duration / ConstValues.seek_bar_inc - val handler = Handler() - runOnUiThread(object : Runnable { - override fun run() { - if (isPrepared) { - seekbar.progress = player.currentPosition / ConstValues.seek_bar_inc - } - handler.postDelayed(this, ConstValues.seek_bar_inc.toLong()) - } - }) - seekbar.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { - override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { - if (::player.isInitialized && fromUser) { - player.seekTo(progress * ConstValues.seek_bar_inc) - } - } - override fun onStartTrackingTouch(seekBar: SeekBar) {} - override fun onStopTrackingTouch(seekBar: SeekBar) {} - }) - player.start() - } - } } - override fun onDestroy() { - super.onDestroy() - if (::player.isInitialized) { - if (player.isPlaying) { - player.stop() - } - isPrepared = false - player.release() - } + override fun bindPlayer(player: SimpleExoPlayer) { + audio_controller.player = player } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt index c5876d8..80f0b68 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/FileViewerActivity.kt @@ -1,21 +1,13 @@ package sushi.hardcore.droidfs.file_viewers -import android.net.Uri import android.os.Bundle import android.view.View -import android.view.WindowManager -import androidx.preference.PreferenceManager import sushi.hardcore.droidfs.BaseActivity import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.provider.TemporaryFileProvider import sushi.hardcore.droidfs.util.GocryptfsVolume -import sushi.hardcore.droidfs.util.Wiper import sushi.hardcore.droidfs.widgets.ColoredAlertDialog -import java.io.File -import java.util.ArrayList abstract class FileViewerActivity: BaseActivity() { - private var cachedFiles: MutableList = ArrayList() lateinit var gocryptfsVolume: GocryptfsVolume lateinit var filePath: String override fun onCreate(savedInstanceState: Bundle?) { @@ -23,15 +15,17 @@ abstract class FileViewerActivity: BaseActivity() { filePath = intent.getStringExtra("path")!! val sessionID = intent.getIntExtra("sessionID", -1) gocryptfsVolume = GocryptfsVolume(sessionID) - toggleFullscreen() + hideSystemUi() viewFile() } - open fun toggleFullscreen(){ - var uiOptions = window.decorView.systemUiVisibility - //uiOptions ^= View.SYSTEM_UI_FLAG_HIDE_NAVIGATION; - uiOptions = uiOptions xor View.SYSTEM_UI_FLAG_FULLSCREEN - uiOptions = uiOptions xor View.SYSTEM_UI_FLAG_IMMERSIVE - window.decorView.systemUiVisibility = uiOptions + open fun hideSystemUi(){ + window.decorView.systemUiVisibility = + View.SYSTEM_UI_FLAG_LOW_PROFILE or + View.SYSTEM_UI_FLAG_FULLSCREEN/* or + View.SYSTEM_UI_FLAG_LAYOUT_STABLE or + View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or + View.SYSTEM_UI_FLAG_HIDE_NAVIGATION or + View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION*/ } abstract fun viewFile() fun loadWholeFile(path: String): ByteArray? { @@ -43,10 +37,10 @@ abstract class FileViewerActivity: BaseActivity() { val handleID = gocryptfsVolume.open_read_mode(path) if (handleID != -1) { var offset: Long = 0 - val io_buffer = ByteArray(GocryptfsVolume.DefaultBS) + val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS) var length: Int - while (gocryptfsVolume.read_file(handleID, offset, io_buffer).also { length = it } > 0){ - System.arraycopy(io_buffer, 0, fileBuff, offset.toInt(), length) + while (gocryptfsVolume.read_file(handleID, offset, ioBuffer).also { length = it } > 0){ + System.arraycopy(ioBuffer, 0, fileBuff, offset.toInt(), length) offset += length.toLong() } gocryptfsVolume.close_file(handleID) @@ -65,7 +59,7 @@ abstract class FileViewerActivity: BaseActivity() { } catch (e: OutOfMemoryError){ ColoredAlertDialog(this) .setTitle(R.string.error) - .setMessage(getString(R.string.outofmemoryerror_msg)) + .setMessage(R.string.outofmemoryerror_msg) .setCancelable(false) .setPositiveButton(getString(R.string.ok)) { _, _ -> finish() } .show() @@ -81,30 +75,4 @@ abstract class FileViewerActivity: BaseActivity() { } return null } - fun exportFile(path: String): Uri? { - val tmpFileUri = TemporaryFileProvider.createFile(this, File(path).name) - cachedFiles.add(tmpFileUri) - return if (gocryptfsVolume.export_file(this, path, tmpFileUri)) { - tmpFileUri - } else { - ColoredAlertDialog(this) - .setTitle(R.string.error) - .setMessage(getString(R.string.export_failed, path)) - .setCancelable(false) - .setPositiveButton(R.string.ok) { _, _ -> finish() } - .show() - null - } - } - - override fun onDestroy() { - super.onDestroy() - Thread{ - for (uri in cachedFiles) { - if (Wiper.wipe(this, uri)){ - cachedFiles.remove(uri) - } - } - }.start() - } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt new file mode 100644 index 0000000..ff2cd61 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt @@ -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) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt new file mode 100644 index 0000000..54a6047 --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt @@ -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() + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt index 5f45c6f..191617a 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/TextEditor.kt @@ -21,8 +21,8 @@ class TextEditor: FileViewerActivity() { private lateinit var titleText: TextView private var changedSinceLastSave = false private var wordWrap = true - override fun toggleFullscreen() { - //don't toggle fullscreen + override fun hideSystemUi() { + //don't hide system ui } override fun viewFile() { loadWholeFile(filePath)?.let { @@ -32,7 +32,7 @@ class TextEditor: FileViewerActivity() { } catch (e: OutOfMemoryError){ ColoredAlertDialog(this) .setTitle(R.string.error) - .setMessage(getString(R.string.outofmemoryerror_msg)) + .setMessage(R.string.outofmemoryerror_msg) .setCancelable(false) .setPositiveButton(getString(R.string.ok)) { _, _ -> finish() } .show() @@ -100,7 +100,7 @@ class TextEditor: FileViewerActivity() { if (changedSinceLastSave){ ColoredAlertDialog(this) .setTitle(R.string.warning) - .setMessage(getString(R.string.ask_save)) + .setMessage(R.string.ask_save) .setPositiveButton(getString(R.string.save)) { _, _ -> if (save()){ finish() diff --git a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/VideoPlayer.kt b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/VideoPlayer.kt index 7afa081..69f9240 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/file_viewers/VideoPlayer.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/file_viewers/VideoPlayer.kt @@ -1,27 +1,16 @@ package sushi.hardcore.droidfs.file_viewers -import android.widget.MediaController +import com.google.android.exoplayer2.SimpleExoPlayer import kotlinx.android.synthetic.main.activity_video_player.* import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.widgets.ColoredAlertDialog -class VideoPlayer: FileViewerActivity() { +class VideoPlayer: MediaPlayer() { override fun viewFile() { - val mc = MediaController(this) + super.viewFile() setContentView(R.layout.activity_video_player) - mc.setAnchorView(video_player) - video_player.setOnErrorListener { _, _, _ -> - ColoredAlertDialog(this) - .setTitle(R.string.error) - .setMessage(getString(R.string.video_play_failed)) - .setCancelable(false) - .setPositiveButton(R.string.ok) { _, _ -> finish() } - .show() - true - } - val tmpFileUri = exportFile(filePath) - video_player.setVideoURI(tmpFileUri) - video_player.setMediaController(mc) - video_player.start() + } + + override fun bindPlayer(player: SimpleExoPlayer) { + video_player.player = player } } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/provider/RestrictedFileProvider.kt b/app/src/main/java/sushi/hardcore/droidfs/provider/RestrictedFileProvider.kt new file mode 100644 index 0000000..7cbe02d --- /dev/null +++ b/app/src/main/java/sushi/hardcore/droidfs/provider/RestrictedFileProvider.kt @@ -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() + + 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?): Int { + throw RuntimeException("Operation not supported") + } + + override fun query(uri: Uri, projection: Array?, selection: String?, selectionArgs: Array?, 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?): 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/provider/TemporaryFileProvider.java b/app/src/main/java/sushi/hardcore/droidfs/provider/TemporaryFileProvider.java deleted file mode 100644 index 94a173d..0000000 --- a/app/src/main/java/sushi/hardcore/droidfs/provider/TemporaryFileProvider.java +++ /dev/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 . - */ - -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. - *

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

- * 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); - } -} diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/DatabaseUtil.java b/app/src/main/java/sushi/hardcore/droidfs/util/DatabaseUtil.java deleted file mode 100644 index 73cc501..0000000 --- a/app/src/main/java/sushi/hardcore/droidfs/util/DatabaseUtil.java +++ /dev/null @@ -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 . - */ - -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(); - } -} diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt b/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt index e1da99c..fea972b 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/util/ExternalProvider.kt @@ -3,9 +3,8 @@ package sushi.hardcore.droidfs.util import android.content.Context import android.content.Intent import android.net.Uri -import androidx.appcompat.app.AlertDialog import sushi.hardcore.droidfs.R -import sushi.hardcore.droidfs.provider.TemporaryFileProvider +import sushi.hardcore.droidfs.provider.RestrictedFileProvider import sushi.hardcore.droidfs.widgets.ColoredAlertDialog import java.io.File import java.net.URLConnection @@ -13,51 +12,53 @@ import java.util.* object ExternalProvider { private const val content_type_all = "*/*" - private var cached_files: MutableList = ArrayList() - private fun get_content_type(filename: String, previous_content_type: String?): String? { + private var storedFiles: MutableList = ArrayList() + private fun getContentType(filename: String, previous_content_type: String?): String? { if (content_type_all != previous_content_type) { - var content_type = URLConnection.guessContentTypeFromName(filename) - if (content_type == null) { - content_type = content_type_all + var contentType = URLConnection.guessContentTypeFromName(filename) + if (contentType == null) { + contentType = content_type_all } if (previous_content_type == null) { - return content_type - } else if (previous_content_type != content_type) { + return contentType + } else if (previous_content_type != contentType) { return content_type_all } } return previous_content_type } - private fun export_file(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Export_file_result { - val filename = File(file_path).name - val tmp_file_uri = TemporaryFileProvider.createFile(context, filename) - cached_files.add(tmp_file_uri) - if (gocryptfsVolume.export_file(context, file_path, tmp_file_uri)) { - return Export_file_result(tmp_file_uri, get_content_type(filename, previous_content_type)) + private fun exportFile(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String, previous_content_type: String?): Pair { + val fileName = File(file_path).name + val tmpFileUri = RestrictedFileProvider.newFile(fileName) + if (tmpFileUri != null){ + storedFiles.add(tmpFileUri) + if (gocryptfsVolume.export_file(context, file_path, tmpFileUri)) { + return Pair(tmpFileUri, getContentType(fileName, previous_content_type)) + } } ColoredAlertDialog(context) .setTitle(R.string.error) .setMessage(context.getString(R.string.export_failed, file_path)) .setPositiveButton(R.string.ok, null) .show() - return Export_file_result(null, null) + return Pair(null, null) } fun share(context: Context, gocryptfsVolume: GocryptfsVolume, file_paths: List) { - var content_type: String? = null + var contentType: String? = null val uris = ArrayList() for (path in file_paths) { - val result = export_file(context, gocryptfsVolume, path, content_type) - content_type = if (result.uri == null) { - return + val result = exportFile(context, gocryptfsVolume, path, contentType) + contentType = if (result.first != null) { + uris.add(result.first!!) + result.second } else { - uris.add(result.uri!!) - result.content_type + return } } val shareIntent = Intent() - shareIntent.type = content_type + shareIntent.type = contentType if (uris.size == 1) { shareIntent.action = Intent.ACTION_SEND shareIntent.putExtra(Intent.EXTRA_STREAM, uris[0]) @@ -69,24 +70,22 @@ object ExternalProvider { } fun open(context: Context, gocryptfsVolume: GocryptfsVolume, file_path: String) { - val result = export_file(context, gocryptfsVolume, file_path, null) - result.uri?.let { + val result = exportFile(context, gocryptfsVolume, file_path, null) + result.first?.let { val openIntent = Intent() openIntent.action = Intent.ACTION_VIEW - openIntent.setDataAndType(result.uri, result.content_type) + openIntent.setDataAndType(result.first, result.second) context.startActivity(openIntent) } } - fun clear_cache(context: Context) { + fun removeFiles(context: Context) { Thread{ - for (uri in cached_files) { + for (uri in storedFiles) { if (Wiper.wipe(context, uri)){ - cached_files.remove(uri) + storedFiles.remove(uri) } } }.start() } - - private class Export_file_result(var uri: Uri?, var content_type: String?) } \ No newline at end of file diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/GocryptfsVolume.kt b/app/src/main/java/sushi/hardcore/droidfs/util/GocryptfsVolume.kt index d8e6271..13f8a7a 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/GocryptfsVolume.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/util/GocryptfsVolume.kt @@ -26,7 +26,6 @@ class GocryptfsVolume(var sessionID: Int) { const val KeyLen = 32 const val ScryptDefaultLogN = 16 const val DefaultBS = 4096 - //external fun scrypt_hash(data: CharArray, logN: Int): ByteArray external fun create_volume(root_cipher_dir: String, password: CharArray, logN: Int, creator: String): Boolean external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int external fun change_password(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean diff --git a/app/src/main/java/sushi/hardcore/droidfs/util/FilesUtils.java b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.java similarity index 87% rename from app/src/main/java/sushi/hardcore/droidfs/util/FilesUtils.java rename to app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.java index ffaa6c1..8647394 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/util/FilesUtils.java +++ b/app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.java @@ -1,42 +1,18 @@ package sushi.hardcore.droidfs.util; -import android.annotation.SuppressLint; -import android.annotation.TargetApi; -import android.content.ContentUris; import android.content.Context; import android.database.Cursor; import android.net.Uri; -import android.os.Build; -import android.os.Environment; import android.os.storage.StorageManager; import android.provider.DocumentsContract; -import android.provider.MediaStore; import android.provider.OpenableColumns; - import androidx.annotation.Nullable; - import java.io.File; import java.lang.reflect.Array; import java.lang.reflect.Method; import java.text.DecimalFormat; -import java.util.ArrayList; -public final class FilesUtils { - - /*public static ArrayList recursive_get_files(File root_path){ - ArrayList results_files = new ArrayList<>(); - final File[] elements = root_path.listFiles(); - if (elements != null){ - for (File item : elements){ - if (item.isDirectory()){ - results_files.addAll(recursive_get_files(item)); - } else if (item.isFile()){ - results_files.add(item); - } - } - } - return results_files; - }*/ +public class PathUtils { public static String get_parent_path(String path){ if (path.endsWith("/")){ diff --git a/app/src/main/res/layout/activity_audio_player.xml b/app/src/main/res/layout/activity_audio_player.xml index ccee3f8..6bd7538 100644 --- a/app/src/main/res/layout/activity_audio_player.xml +++ b/app/src/main/res/layout/activity_audio_player.xml @@ -2,6 +2,7 @@ @@ -14,33 +15,11 @@ android:textSize="18sp" android:padding="10dp"/> - - - - - - - - - + app:controller_layout_id="@layout/exo_custom_playback_control" + app:show_timeout="0"/> \ No newline at end of file diff --git a/app/src/main/res/layout/activity_video_player.xml b/app/src/main/res/layout/activity_video_player.xml index 60a3e52..43c399b 100644 --- a/app/src/main/res/layout/activity_video_player.xml +++ b/app/src/main/res/layout/activity_video_player.xml @@ -2,13 +2,15 @@ - + android:layout_gravity="center" + app:controller_layout_id="@layout/exo_custom_playback_control"/> \ No newline at end of file diff --git a/app/src/main/res/layout/exo_custom_playback_control.xml b/app/src/main/res/layout/exo_custom_playback_control.xml new file mode 100644 index 0000000..cb50f5a --- /dev/null +++ b/app/src/main/res/layout/exo_custom_playback_control.xml @@ -0,0 +1,74 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d242e2b..c1990f6 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -109,7 +109,7 @@ Video Audio Failed to initialize the media player. - Failed to play this video. + Failed to play this file. Text Save failed File saved ! diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 4230a74..72d4401 100644 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -2,11 +2,8 @@