Skip to content

Commit

Permalink
Feat: Add root ID and path editing for external storage shortcut
Browse files Browse the repository at this point in the history
Fixes: #1206
  • Loading branch information
zhanghai committed Apr 19, 2024
1 parent 94a077e commit 1ac781c
Show file tree
Hide file tree
Showing 40 changed files with 265 additions and 143 deletions.
8 changes: 4 additions & 4 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -179,13 +179,13 @@
android:theme="@style/Theme.MaterialFiles.Translucent" />

<activity
android:name="me.zhanghai.android.files.storage.AddDocumentManagerShortcutActivity"
android:label="@string/storage_add_document_manager_shortcut_title"
android:name="me.zhanghai.android.files.storage.AddExternalStorageShortcutActivity"
android:label="@string/storage_add_external_storage_shortcut_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />

<activity
android:name="me.zhanghai.android.files.storage.EditDocumentManagerShortcutDialogActivity"
android:label="@string/storage_edit_document_manager_shortcut_title"
android:name="me.zhanghai.android.files.storage.EditExternalStorageShortcutDialogActivity"
android:label="@string/storage_edit_external_storage_shortcut_title"
android:theme="@style/Theme.MaterialFiles.Translucent" />

<activity
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ private const val VERSION_CODE_1_3_0 = 24
private const val VERSION_CODE_1_4_0 = 26
private const val VERSION_CODE_1_5_0 = 29
private const val VERSION_CODE_1_6_0 = 32
private const val VERSION_CODE_1_7_2 = 37
private const val VERSION_CODE_LATEST = BuildConfig.VERSION_CODE

private var lastVersionCode: Int
Expand Down Expand Up @@ -56,5 +57,8 @@ private fun upgradeAppFrom(lastVersionCode: Int) {
if (lastVersionCode < VERSION_CODE_1_6_0) {
upgradeAppTo1_6_0()
}
if (lastVersionCode < VERSION_CODE_1_7_2) {
upgradeAppTo1_7_2()
}
// Continue with new `if`s on lastVersionCode instead of `else if`.
}
89 changes: 89 additions & 0 deletions app/src/main/java/me/zhanghai/android/files/app/AppUpgraders.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ package me.zhanghai.android.files.app

import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Parcel
import android.os.Parcelable
import androidx.annotation.StringRes
Expand All @@ -18,6 +19,7 @@ import me.zhanghai.android.files.compat.readBooleanCompat
import me.zhanghai.android.files.compat.writeBooleanCompat
import me.zhanghai.android.files.compat.writeParcelableListCompat
import me.zhanghai.android.files.file.DocumentTreeUri
import me.zhanghai.android.files.file.asExternalStorageUriOrNull
import me.zhanghai.android.files.file.displayName
import me.zhanghai.android.files.file.storageVolume
import me.zhanghai.android.files.filelist.FileSortOptions
Expand All @@ -28,13 +30,15 @@ import me.zhanghai.android.files.provider.common.ByteString
import me.zhanghai.android.files.provider.common.moveToByteString
import me.zhanghai.android.files.provider.content.ContentFileSystem
import me.zhanghai.android.files.provider.document.DocumentFileSystem
import me.zhanghai.android.files.provider.document.resolver.ExternalStorageProviderHacks
import me.zhanghai.android.files.provider.linux.LinuxFileSystem
import me.zhanghai.android.files.provider.root.RootStrategy
import me.zhanghai.android.files.provider.sftp.SftpFileSystem
import me.zhanghai.android.files.provider.smb.SmbFileSystem
import me.zhanghai.android.files.storage.DocumentTree
import me.zhanghai.android.files.storage.FileSystemRoot
import me.zhanghai.android.files.storage.PrimaryStorageVolume
import me.zhanghai.android.files.util.StableUriParceler
import me.zhanghai.android.files.util.asBase64
import me.zhanghai.android.files.util.readParcelable
import me.zhanghai.android.files.util.readParcelableListCompat
Expand Down Expand Up @@ -541,3 +545,88 @@ private fun addViewTypePathSetting1_6_0() {
pathSharedPreferences.edit { putString(newKey, defaultViewType) }
}
}

internal fun upgradeAppTo1_7_2() {
migrateDocumentManagerShortcutSetting1_7_2()
}

private fun migrateDocumentManagerShortcutSetting1_7_2() {
val key = application.getString(R.string.pref_key_storages)
val oldBytes =
defaultSharedPreferences.getString(key, null)?.asBase64()?.toByteArray() ?: return
val newBytes =
try {
Parcel.obtain().use { newParcel ->
Parcel.obtain().use { oldParcel ->
oldParcel.unmarshall(oldBytes, 0, oldBytes.size)
oldParcel.setDataPosition(0)
newParcel.writeInt(oldParcel.readInt())
readWriteLengthPrefixedValue(oldParcel, newParcel) {
val size = oldParcel.readInt()
newParcel.writeInt(size)
repeat(size) {
val oldPosition = oldParcel.dataPosition()
oldParcel.readInt()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// Skip prefix length.
oldParcel.readInt()
}
val className = oldParcel.readString()
oldParcel.setDataPosition(oldPosition)
when (className) {
"me.zhanghai.android.files.storage.DocumentManagerShortcut" -> {
newParcel.writeInt(oldParcel.readInt())
readWriteLengthPrefixedValue(oldParcel, newParcel) {
oldParcel.readString()
newParcel.writeString(
"me.zhanghai.android.files.storage" +
".ExternalStorageShortcut"
)
val id = oldParcel.readLong()
newParcel.writeLong(id)
val customName = oldParcel.readString()
newParcel.writeString(customName)
var uri = StableUriParceler.create(oldParcel)!!
if (uri.asExternalStorageUriOrNull() == null) {
// Reset to a valid external storage URI.
uri =
ExternalStorageProviderHacks
.DOCUMENT_URI_ANDROID_DATA
}
with(StableUriParceler) { uri.write(newParcel, 0) }
}
}
else -> {
val storage = oldParcel.readValue(appClassLoader)
newParcel.writeValue(storage)
}
}
}
}
}
newParcel.marshall()
}
} catch (e: Exception) {
e.printStackTrace()
null
}
defaultSharedPreferences.edit { putString(key, newBytes?.toBase64()?.value) }
}

private fun readWriteLengthPrefixedValue(oldParcel: Parcel, newParcel: Parcel, block: () -> Unit) {
var lengthPosition = 0
var startPosition = 0
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
oldParcel.readInt()
lengthPosition = newParcel.dataPosition()
newParcel.writeInt(-1)
startPosition = newParcel.dataPosition()
}
block()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val endPosition = newParcel.dataPosition()
newParcel.setDataPosition(lengthPosition)
newParcel.writeInt(endPosition - startPosition)
newParcel.setDataPosition(endPosition)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ import me.zhanghai.android.files.util.StableUriParceler
@Parcelize
@JvmInline
value class DocumentUri(val value: @WriteWith<StableUriParceler> Uri) : Parcelable {
val treeDocumentId: String
get() = DocumentsContract.getTreeDocumentId(value)

val documentId: String
get() = DocumentsContract.getDocumentId(value)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
/*
* Copyright (c) 2024 Hai Zhang <[email protected]>
* All Rights Reserved.
*/

package me.zhanghai.android.files.file

import android.net.Uri
import android.os.Parcelable
import android.provider.DocumentsContract
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.WriteWith
import me.zhanghai.android.files.compat.DocumentsContractCompat
import me.zhanghai.android.files.util.StableUriParceler

@Parcelize
@JvmInline
value class ExternalStorageUri(val value: @WriteWith<StableUriParceler> Uri) : Parcelable {
constructor(rootId: String, path: String) : this(
DocumentsContract.buildDocumentUriUsingTree(
DocumentsContract.buildTreeDocumentUri(
DocumentsContractCompat.EXTERNAL_STORAGE_PROVIDER_AUTHORITY, rootId
), "$rootId:$path"
)
)

val rootId: String
get() = DocumentsContract.getTreeDocumentId(value)

val path: String
get() = DocumentsContract.getDocumentId(value).removePrefix("$rootId:")
}

fun Uri.asExternalStorageUriOrNull(): ExternalStorageUri? =
if (isExternalStorageUri) ExternalStorageUri(this) else null

fun Uri.asExternalStorageUri(): ExternalStorageUri {
require(isExternalStorageUri)
return ExternalStorageUri(this)
}

/** @see DocumentsContractCompat.isDocumentUri */
private val Uri.isExternalStorageUri: Boolean
get() =
DocumentsContractCompat.isDocumentUri(this) &&
authority == DocumentsContractCompat.EXTERNAL_STORAGE_PROVIDER_AUTHORITY &&
pathSegments.size == 4

val ExternalStorageUri.displayName: String
get() =
if (rootId == DocumentsContractCompat.EXTERNAL_STORAGE_PRIMARY_EMULATED_ROOT_ID) {
path
} else {
DocumentsContract.getDocumentId(value)
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import me.zhanghai.android.files.app.AppActivity
import me.zhanghai.android.files.util.args
import me.zhanghai.android.files.util.putArgs

class AddDocumentManagerShortcutActivity : AppActivity() {
private val args by args<AddDocumentManagerShortcutFragment.Args>()
class AddExternalStorageShortcutActivity : AppActivity() {
private val args by args<AddExternalStorageShortcutFragment.Args>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Calls ensureSubDecor().
findViewById<View>(android.R.id.content)
if (savedInstanceState == null) {
val fragment = AddDocumentManagerShortcutFragment().putArgs(args)
val fragment = AddExternalStorageShortcutFragment().putArgs(args)
supportFragmentManager.commit {
add(fragment, AddDocumentManagerShortcutFragment::class.java.name)
add(fragment, AddExternalStorageShortcutFragment::class.java.name)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,41 +6,35 @@
package me.zhanghai.android.files.storage

import android.os.Bundle
import androidx.annotation.StringRes
import androidx.fragment.app.Fragment
import kotlinx.parcelize.Parcelize
import me.zhanghai.android.files.R
import me.zhanghai.android.files.app.packageManager
import me.zhanghai.android.files.file.DocumentUri
import me.zhanghai.android.files.file.ExternalStorageUri
import me.zhanghai.android.files.util.ParcelableArgs
import me.zhanghai.android.files.util.args
import me.zhanghai.android.files.util.createDocumentManagerViewDirectoryIntent
import me.zhanghai.android.files.util.createDocumentsUiViewDirectoryIntent
import me.zhanghai.android.files.util.finish
import me.zhanghai.android.files.util.showToast

class AddDocumentManagerShortcutFragment : Fragment() {
class AddExternalStorageShortcutFragment : Fragment() {
private val args by args<Args>()

override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)

val uri = args.uri
val hasDocumentManager = uri.value.createDocumentManagerViewDirectoryIntent()
val hasDocumentsUi = uri.value.createDocumentsUiViewDirectoryIntent()
.resolveActivity(packageManager) != null
if (hasDocumentManager) {
val documentManagerShortcut = DocumentManagerShortcut(
null, args.customNameRes?.let { getString(it) }, uri
)
Storages.addOrReplace(documentManagerShortcut)
if (hasDocumentsUi) {
val externalStorageShortcut = ExternalStorageShortcut(null, null, uri)
Storages.addOrReplace(externalStorageShortcut)
} else {
showToast(R.string.activity_not_found)
}
finish()
}

@Parcelize
class Args(
@StringRes val customNameRes: Int?,
val uri: DocumentUri
) : ParcelableArgs
class Args(val uri: ExternalStorageUri) : ParcelableArgs
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import android.os.Bundle
import androidx.appcompat.app.AppCompatDialogFragment
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import me.zhanghai.android.files.R
import me.zhanghai.android.files.file.asDocumentUri
import me.zhanghai.android.files.file.asExternalStorageUri
import me.zhanghai.android.files.provider.document.resolver.ExternalStorageProviderHacks
import me.zhanghai.android.files.util.createIntent
import me.zhanghai.android.files.util.finish
Expand Down Expand Up @@ -42,19 +42,19 @@ class AddStorageDialogFragment : AppCompatDialogFragment() {
private val STORAGE_TYPES = listOfNotNull(
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
R.string.storage_add_storage_android_data to
AddDocumentManagerShortcutActivity ::class.createIntent().putArgs(
AddDocumentManagerShortcutFragment.Args(
R.string.storage_add_storage_android_data,
ExternalStorageProviderHacks.DOCUMENT_URI_ANDROID_DATA.asDocumentUri()
AddExternalStorageShortcutActivity ::class.createIntent().putArgs(
AddExternalStorageShortcutFragment.Args(
ExternalStorageProviderHacks.DOCUMENT_URI_ANDROID_DATA
.asExternalStorageUri()
)
)
} else null,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
R.string.storage_add_storage_android_obb to
AddDocumentManagerShortcutActivity ::class.createIntent().putArgs(
AddDocumentManagerShortcutFragment.Args(
R.string.storage_add_storage_android_obb,
ExternalStorageProviderHacks.DOCUMENT_URI_ANDROID_OBB.asDocumentUri()
AddExternalStorageShortcutActivity ::class.createIntent().putArgs(
AddExternalStorageShortcutFragment.Args(
ExternalStorageProviderHacks.DOCUMENT_URI_ANDROID_OBB
.asExternalStorageUri()
)
)
} else null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,18 @@ import me.zhanghai.android.files.app.AppActivity
import me.zhanghai.android.files.util.args
import me.zhanghai.android.files.util.putArgs

class EditDocumentManagerShortcutDialogActivity : AppActivity() {
private val args by args<EditDocumentManagerShortcutDialogFragment.Args>()
class EditExternalStorageShortcutDialogActivity : AppActivity() {
private val args by args<EditExternalStorageShortcutDialogFragment.Args>()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

// Calls ensureSubDecor().
findViewById<View>(android.R.id.content)
if (savedInstanceState == null) {
val fragment = EditDocumentManagerShortcutDialogFragment().putArgs(args)
val fragment = EditExternalStorageShortcutDialogFragment().putArgs(args)
supportFragmentManager.commit {
add(fragment, EditDocumentManagerShortcutDialogFragment::class.java.name)
add(fragment, EditExternalStorageShortcutDialogFragment::class.java.name)
}
}
}
Expand Down
Loading

0 comments on commit 1ac781c

Please sign in to comment.