Skip to content

Commit

Permalink
[Fix] Properly work around Uri parceling changes.
Browse files Browse the repository at this point in the history
Fixes: #1038
  • Loading branch information
zhanghai committed Oct 8, 2023
1 parent e8a7887 commit d9c2ed6
Show file tree
Hide file tree
Showing 5 changed files with 164 additions and 7 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* Copyright (c) 2023 Hai Zhang <[email protected]>
* All Rights Reserved.
*/

package me.zhanghai.android.files.compat

import android.net.Uri
import android.os.Build
import android.os.Parcel
import androidx.annotation.RequiresApi
import kotlinx.parcelize.Parceler
import me.zhanghai.android.files.hiddenapi.RestrictedHiddenApi
import me.zhanghai.android.files.util.lazyReflectedMethod

// Work around Uri parceling changes in
// https://android.googlesource.com/platform/frameworks/base/+/97f621d81fc51de240ba73bc008d997e0eea7939
// (and the String8 change in API 30).
object UriParcelerCompat : Parceler<Uri?> {
private const val NULL_TYPE_ID = 0
private const val STRING_URI_TYPE_ID = 1
private const val OPAQUE_URI_TYPE_ID = 2
private const val HIERARCHICAL_URI_TYPE_ID = 3

private const val REPRESENTATION_BOTH = 0
private const val REPRESENTATION_ENCODED = 1
private const val REPRESENTATION_DECODED = 2

@get:RequiresApi(Build.VERSION_CODES.R)
@RestrictedHiddenApi
private val parcelReadString8Method by lazyReflectedMethod(Parcel::class.java, "readString8")

override fun create(parcel: Parcel): Uri? {
// Parcel.readParcelableCreator()
parcel.readString() ?: return null
// Uri.CREATOR.createFromParcel()
val uriString = when (val typeId = parcel.readInt()) {
NULL_TYPE_ID -> return null
// Uri.StringUri.readFrom()
STRING_URI_TYPE_ID -> parcel.readUriString()
OPAQUE_URI_TYPE_ID -> {
// Uri.OpaqueUri.readFrom()
val scheme = parcel.readUriString()!!
// Assume that we never persist an Uri with only a scheme.
if (scheme.contains(':')) {
scheme
} else {
val encodedSsp = readEncodedPart(parcel)
val encodedFragment = readEncodedPart(parcel)
// Uri.OpaqueUri.toString()
buildString {
append(scheme)
append(':')
append(encodedSsp)
if (!encodedFragment.isNullOrEmpty()) {
append('#')
append(encodedFragment)
}
}
}
}
HIERARCHICAL_URI_TYPE_ID -> {
// Uri.HierarchicalUri.readFrom()
// Scheme can be null for HierarchicalUri.
val scheme = parcel.readUriString() as String?
// Assume that we never persist an Uri with only a scheme.
if (scheme != null && scheme.contains(':')) {
scheme
} else {
val encodedAuthority = readEncodedPart(parcel)
val hasSchemeOrAuthority = !scheme.isNullOrEmpty() ||
!encodedAuthority.isNullOrEmpty()
val encodedPath = readEncodedPathPart(hasSchemeOrAuthority, parcel)
val encodedQuery = readEncodedPart(parcel)
val encodedFragment = readEncodedPart(parcel)
// Uri.HierarchicalUri.toString()
buildString {
if (scheme != null) {
append(scheme)
append(':')
}
if (encodedAuthority != null) {
append("//")
append(encodedAuthority)
}
if (encodedPath != null) {
append(encodedPath)
}
if (!encodedQuery.isNullOrEmpty()) {
append('?')
append(encodedQuery)
}
if (!encodedFragment.isNullOrEmpty()) {
append('#')
append(encodedFragment)
}
}
}
}
else -> error("Unknown type ID $typeId")
}
return Uri.parse(uriString)
}

private fun Parcel.readUriString(): String? =
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
parcelReadString8Method.invoke(this) as String?
} else {
readString()
}

// Uri.Part.readFrom()
private fun readEncodedPart(parcel: Parcel): String? =
when (val representation = parcel.readInt()) {
REPRESENTATION_BOTH -> parcel.readUriString().also { parcel.readUriString() }
REPRESENTATION_ENCODED -> parcel.readUriString()
REPRESENTATION_DECODED -> Uri.encode(parcel.readUriString())
else -> error("Unknown representation $representation")
}

// Uri.PathPart.readFrom()
private fun readEncodedPathPart(hasSchemeOrAuthority: Boolean, parcel: Parcel): String? {
val encodedPathPart = when (val representation = parcel.readInt()) {
REPRESENTATION_BOTH -> parcel.readUriString().also { parcel.readUriString() }
REPRESENTATION_ENCODED -> parcel.readUriString()
REPRESENTATION_DECODED -> Uri.encode(parcel.readUriString(), "/")
else -> error("Unknown representation $representation")
}
return if (hasSchemeOrAuthority) {
makeEncodedPathPartAbsolute(encodedPathPart)
} else {
encodedPathPart
}
}

// Uri.PathPart.makeAbsolute()
private fun makeEncodedPathPartAbsolute(encodedPathPart: String?): String? =
if (encodedPathPart.isNullOrEmpty() || encodedPathPart.startsWith("/")) {
encodedPathPart
} else {
"/${encodedPathPart}"
}

override fun Uri?.write(parcel: Parcel, flags: Int) {
parcel.writeParcelable(this, flags)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@ import android.os.Parcelable
import android.os.storage.StorageVolume
import android.provider.DocumentsContract
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.WriteWith
import me.zhanghai.android.files.app.contentResolver
import me.zhanghai.android.files.compat.DocumentsContractCompat
import me.zhanghai.android.files.compat.UriParcelerCompat
import me.zhanghai.android.files.compat.createOpenDocumentTreeIntentCompat
import me.zhanghai.android.files.storage.StorageVolumeListLiveData
import me.zhanghai.android.files.util.getParcelableExtraSafe
Expand All @@ -22,7 +24,7 @@ import me.zhanghai.android.files.util.valueCompat

@Parcelize
@JvmInline
value class DocumentTreeUri(val value: Uri) : Parcelable {
value class DocumentTreeUri(val value: @WriteWith<UriParcelerCompat> Uri) : Parcelable {
val documentId: String
get() = DocumentsContract.getTreeDocumentId(value)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,14 @@ 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.app.contentResolver
import me.zhanghai.android.files.compat.DocumentsContractCompat
import me.zhanghai.android.files.compat.UriParcelerCompat

@Parcelize
@JvmInline
value class DocumentUri(val value: Uri) : Parcelable {
value class DocumentUri(val value: @WriteWith<UriParcelerCompat> Uri) : Parcelable {
val documentId: String
get() = DocumentsContract.getDocumentId(value)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@ import java8.nio.file.Path
import java8.nio.file.WatchEvent
import java8.nio.file.WatchKey
import java8.nio.file.WatchService
import me.zhanghai.android.files.compat.UriParcelerCompat
import me.zhanghai.android.files.provider.common.ByteString
import me.zhanghai.android.files.provider.common.ByteStringListPath
import me.zhanghai.android.files.provider.common.UriAuthority
import me.zhanghai.android.files.provider.common.toByteString
import me.zhanghai.android.files.provider.content.resolver.Resolver
import me.zhanghai.android.files.provider.content.resolver.ResolverException
import me.zhanghai.android.files.util.ValueParceler.write
import me.zhanghai.android.files.util.readParcelable
import java.io.File
import java.net.URI
Expand Down Expand Up @@ -135,14 +137,16 @@ internal class ContentPath : ByteStringListPath<ContentPath> {

private constructor(source: Parcel) : super(source) {
fileSystem = source.readParcelable()!!
uri = source.readParcelable()
//uri = source.readParcelable()
uri = UriParcelerCompat.create(source)
}

override fun writeToParcel(dest: Parcel, flags: Int) {
super.writeToParcel(dest, flags)

dest.writeParcelable(fileSystem, flags)
dest.writeParcelable(uri, flags)
//dest.writeParcelable(uri, flags)
with(UriParcelerCompat) { uri.write(dest, flags) }
}

companion object {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,11 @@ import java8.nio.file.PathMatcher
import java8.nio.file.WatchService
import java8.nio.file.attribute.UserPrincipalLookupService
import java8.nio.file.spi.FileSystemProvider
import me.zhanghai.android.files.compat.UriParcelerCompat
import me.zhanghai.android.files.provider.common.ByteString
import me.zhanghai.android.files.provider.common.ByteStringBuilder
import me.zhanghai.android.files.provider.common.ByteStringListPathCreator
import me.zhanghai.android.files.provider.common.toByteString
import me.zhanghai.android.files.util.readParcelable
import java.io.IOException

internal class DocumentFileSystem(
Expand Down Expand Up @@ -116,7 +116,8 @@ internal class DocumentFileSystem(
override fun describeContents(): Int = 0

override fun writeToParcel(dest: Parcel, flags: Int) {
dest.writeParcelable(treeUri, flags)
//dest.writeParcelable(treeUri, flags)
with(UriParcelerCompat) { treeUri.write(dest, flags) }
}

companion object {
Expand All @@ -127,7 +128,8 @@ internal class DocumentFileSystem(
@JvmField
val CREATOR = object : Parcelable.Creator<DocumentFileSystem> {
override fun createFromParcel(source: Parcel): DocumentFileSystem {
val treeUri = source.readParcelable<Uri>()!!
//val treeUri = source.readParcelable<Uri>()!!
val treeUri = UriParcelerCompat.create(source)!!
return DocumentFileSystemProvider.getOrNewFileSystem(treeUri)
}

Expand Down

1 comment on commit d9c2ed6

@zhanghai
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.