Skip to content

Commit 3bf7335

Browse files
committed
android: bump OSS
OSS and Version updated to 1.87.25-t0f15e4419-gde3b6dbfd Signed-off-by: kari-ts <[email protected]>
1 parent de3b6db commit 3bf7335

File tree

6 files changed

+169
-188
lines changed

6 files changed

+169
-188
lines changed

android/src/main/java/com/tailscale/ipn/util/ShareFileHelper.kt

Lines changed: 96 additions & 100 deletions
Original file line numberDiff line numberDiff line change
@@ -49,91 +49,89 @@ object ShareFileHelper : libtailscale.ShareFileHelper {
4949
return file.uri.toString() to os
5050
}
5151

52-
private fun openWriterFD(fileName: String, offset: Long): Pair<String, SeekableOutputStream?> {
53-
54-
val ctx = appContext ?: return "" to null
55-
val dirUri = savedUri ?: return "" to null
56-
val dir = DocumentFile.fromTreeUri(ctx, Uri.parse(dirUri)) ?: return "" to null
57-
58-
// Reuse existing doc if it exists
52+
@Throws(IOException::class)
53+
private fun openWriterFD(fileName: String, offset: Long): Pair<String, SeekableOutputStream> {
54+
val ctx = appContext ?: throw IOException("App context not initialized")
55+
val dirUri = savedUri ?: throw IOException("No directory URI")
56+
val dir =
57+
DocumentFile.fromTreeUri(ctx, Uri.parse(dirUri))
58+
?: throw IOException("Invalid tree URI: $dirUri")
5959
val file =
6060
dir.findFile(fileName)
6161
?: dir.createFile("application/octet-stream", fileName)
62-
?: return "" to null
62+
?: throw IOException("Failed to create file: $fileName")
6363

64-
// Always get a ParcelFileDescriptor so we can sync
65-
val pfd = ctx.contentResolver.openFileDescriptor(file.uri, "rw") ?: return "" to null
64+
val pfd =
65+
ctx.contentResolver.openFileDescriptor(file.uri, "rw")
66+
?: throw IOException("Failed to open file descriptor for ${file.uri}")
6667
val fos = FileOutputStream(pfd.fileDescriptor)
6768

6869
if (offset != 0L) fos.channel.position(offset) else fos.channel.truncate(0)
69-
7070
return file.uri.toString() to SeekableOutputStream(fos, pfd)
7171
}
7272

7373
private val currentUri = ConcurrentHashMap<String, String>()
7474

75-
override fun openFileWriter(fileName: String): libtailscale.OutputStream {
76-
val (uri, stream) = openWriterFD(fileName, 0)
77-
currentUri[fileName] = uri // 🠚 cache the exact doc we opened
78-
return OutputStreamAdapter(stream ?: OutputStream.nullOutputStream())
79-
}
80-
81-
override fun openFileWriterAt(fileName: String, offset: Long): libtailscale.OutputStream {
75+
@Throws(IOException::class)
76+
override fun openFileWriter(fileName: String, offset: Long): libtailscale.OutputStream {
8277
val (uri, stream) = openWriterFD(fileName, offset)
78+
if (stream == null) {
79+
throw IOException("Failed to open file writer for $fileName")
80+
}
8381
currentUri[fileName] = uri
84-
return OutputStreamAdapter(stream ?: OutputStream.nullOutputStream())
82+
return OutputStreamAdapter(stream)
8583
}
8684

87-
override fun openFileURI(fileName: String): String {
85+
@Throws(IOException::class)
86+
override fun getFileURI(fileName: String): String {
8887
currentUri[fileName]?.let {
8988
return it
9089
}
91-
val ctx = appContext ?: return ""
92-
val dirStr = savedUri ?: return ""
93-
val dir = DocumentFile.fromTreeUri(ctx, Uri.parse(dirStr)) ?: return ""
9490

95-
val file = dir.findFile(fileName) ?: return ""
96-
val uri = file.uri.toString()
91+
val ctx = appContext ?: throw IOException("App context not initialized")
92+
val dirStr = savedUri ?: throw IOException("No saved directory URI")
93+
val dir =
94+
DocumentFile.fromTreeUri(ctx, Uri.parse(dirStr))
95+
?: throw IOException("Invalid tree URI: $dirStr")
9796

97+
val file = dir.findFile(fileName) ?: throw IOException("File not found: $fileName")
98+
val uri = file.uri.toString()
9899
currentUri[fileName] = uri
99100
return uri
100101
}
101102

102-
override fun renamePartialFile(
103-
partialUri: String,
104-
targetDirUri: String,
105-
targetName: String
106-
): String {
103+
@Throws(IOException::class)
104+
override fun renameFile(oldPath: String, targetName: String): String {
107105
val ctx = appContext ?: throw IOException("not initialized")
108-
val srcUri = Uri.parse(partialUri)
106+
val dirUri = savedUri ?: throw IOException("directory not set")
107+
val srcUri = Uri.parse(oldPath)
109108
val dir =
110-
DocumentFile.fromTreeUri(ctx, Uri.parse(targetDirUri))
111-
?: throw IOException("cannot open dir $targetDirUri")
112-
109+
DocumentFile.fromTreeUri(ctx, Uri.parse(dirUri))
110+
?: throw IOException("cannot open dir $dirUri")
111+
113112
var finalName = targetName
114113
dir.findFile(finalName)?.let { existing ->
115114
if (lengthOfUri(ctx, existing.uri) == 0L) {
116-
existing.delete() // remove stale 0‑byte file
115+
existing.delete()
117116
} else {
118117
finalName = generateNewFilename(finalName)
119118
}
120119
}
121-
120+
122121
try {
123122
DocumentsContract.renameDocument(ctx.contentResolver, srcUri, finalName)?.also { newUri ->
124123
runCatching { ctx.contentResolver.delete(srcUri, null, null) }
125124
cleanupPartials(dir, targetName)
126125
return newUri.toString()
127126
}
128127
} catch (_: Exception) {
129-
// rename not supported; fall through to copy‑delete
128+
// fallback
130129
}
131-
132-
// fallback - copy contents then delete source
130+
133131
val dest =
134132
dir.createFile("application/octet-stream", finalName)
135133
?: throw IOException("createFile failed for $finalName")
136-
134+
137135
ctx.contentResolver.openInputStream(srcUri).use { inp ->
138136
ctx.contentResolver.openOutputStream(dest.uri, "w").use { out ->
139137
if (inp == null || out == null) {
@@ -143,7 +141,7 @@ object ShareFileHelper : libtailscale.ShareFileHelper {
143141
inp.copyTo(out)
144142
}
145143
}
146-
// delete the original .partial
144+
147145
ctx.contentResolver.delete(srcUri, null, null)
148146
cleanupPartials(dir, targetName)
149147
return dest.uri.toString()
@@ -163,33 +161,35 @@ object ShareFileHelper : libtailscale.ShareFileHelper {
163161
}
164162

165163
@Throws(IOException::class)
166-
override fun deleteFile(uriString: String) {
164+
override fun deleteFile(uri: String) {
167165
val ctx = appContext ?: throw IOException("DeleteFile: not initialized")
168166

169-
val uri = Uri.parse(uriString)
167+
val uri = Uri.parse(uri)
170168
val doc =
171169
DocumentFile.fromSingleUri(ctx, uri)
172-
?: throw IOException("DeleteFile: cannot resolve URI $uriString")
170+
?: throw IOException("DeleteFile: cannot resolve URI $uri")
173171

174172
if (!doc.delete()) {
175-
throw IOException("DeleteFile: delete() returned false for $uriString")
173+
throw IOException("DeleteFile: delete() returned false for $uri")
176174
}
177175
}
178176

179-
override fun treeURI(): String = savedUri ?: throw IllegalStateException("not initialized")
180-
177+
@Throws(IOException::class)
181178
override fun getFileInfo(fileName: String): String {
182-
val context = appContext ?: return ""
183-
val dirUri = savedUri ?: return ""
184-
val dir = DocumentFile.fromTreeUri(context, Uri.parse(dirUri)) ?: return ""
179+
val context = appContext ?: throw IOException("not initialized")
180+
val dirUri = savedUri ?: throw IOException("not initialized")
181+
val dir =
182+
DocumentFile.fromTreeUri(context, Uri.parse(dirUri))
183+
?: throw IOException("could not resolve SAF root")
185184

186-
val file = dir.findFile(fileName) ?: return ""
185+
val file =
186+
dir.findFile(fileName) ?: throw IOException("file \"$fileName\" not found in SAF directory")
187187

188-
val name = file.name ?: return ""
188+
val name = file.name ?: throw IOException("file name missing for $fileName")
189189
val size = file.length()
190-
val modTime = file.lastModified() // milliseconds since epoch
190+
val modTime = file.lastModified()
191191

192-
return """{"name":${jsonEscape(name)},"size":$size,"modTime":$modTime}"""
192+
return """{"name":${JSONObject.quote(name)},"size":$size,"modTime":$modTime}"""
193193
}
194194

195195
private fun jsonEscape(s: String): String {
@@ -216,71 +216,67 @@ object ShareFileHelper : libtailscale.ShareFileHelper {
216216
.toTypedArray()
217217
}
218218

219-
override fun listPartialFilesJSON(suffix: String): String {
220-
return listPartialFiles(suffix)
221-
.joinToString(prefix = "[\"", separator = "\",\"", postfix = "\"]")
219+
@Throws(IOException::class)
220+
override fun listFilesJSON(suffix: String): String {
221+
val list = listPartialFiles(suffix)
222+
if (list.isEmpty()) {
223+
throw IOException("no files found matching suffix \"$suffix\"")
224+
}
225+
return list.joinToString(prefix = "[\"", separator = "\",\"", postfix = "\"]")
222226
}
223227

224-
override fun openPartialFileReader(name: String): libtailscale.InputStream? {
225-
val context = appContext ?: return null
226-
val rootUri = savedUri ?: return null
227-
val dir = DocumentFile.fromTreeUri(context, Uri.parse(rootUri)) ?: return null
228+
@Throws(IOException::class)
229+
override fun openFileReader(name: String): libtailscale.InputStream {
230+
val context = appContext ?: throw IOException("not initialized")
231+
val rootUri = savedUri ?: throw IOException("not initialized")
232+
val dir =
233+
DocumentFile.fromTreeUri(context, Uri.parse(rootUri))
234+
?: throw IOException("could not open SAF root")
228235

229-
// We know `name` includes the suffix (e.g. ".<id>.partial"), but the actual
230-
// file in SAF might include extra bits, so let's just match by that suffix.
231-
// You could also match exactly `endsWith(name)` if the filenames line up
232-
val suffix = name.substringAfterLast('.', ".$name") // or hard-code ".partial"
236+
val suffix = name.substringAfterLast('.', ".$name")
233237

234238
val file =
235239
dir.listFiles().firstOrNull {
236240
val fname = it.name ?: return@firstOrNull false
237-
// call the String overload explicitly:
238-
fname.endsWith(suffix, /*ignoreCase=*/ false)
239-
}
240-
?: run {
241-
TSLog.d("ShareFileHelper", "no file ending with $suffix in SAF directory")
242-
return null
243-
}
241+
fname.endsWith(suffix, ignoreCase = false)
242+
} ?: throw IOException("no file ending with \"$suffix\" in SAF directory")
244243

245-
TSLog.d("ShareFileHelper", "found SAF file ${file.name}, opening")
246244
val inStream =
247245
context.contentResolver.openInputStream(file.uri)
248-
?: run {
249-
TSLog.d("ShareFileHelper", "openInputStream returned null for ${file.uri}")
250-
return null
251-
}
246+
?: throw IOException("openInputStream returned null for ${file.uri}")
247+
252248
return InputStreamAdapter(inStream)
253249
}
254-
}
255250

256-
private class SeekableOutputStream(
257-
private val fos: FileOutputStream,
258-
private val pfd: ParcelFileDescriptor
259-
) : OutputStream() {
251+
private class SeekableOutputStream(
252+
private val fos: FileOutputStream,
253+
private val pfd: ParcelFileDescriptor
254+
) : OutputStream() {
260255

261-
private var closed = false
256+
private var closed = false
262257

263-
override fun write(b: Int) = fos.write(b)
258+
override fun write(b: Int) = fos.write(b)
264259

265-
override fun write(b: ByteArray) = fos.write(b)
260+
override fun write(b: ByteArray) = fos.write(b)
266261

267-
override fun write(b: ByteArray, off: Int, len: Int) {
268-
fos.write(b, off, len)
269-
}
262+
override fun write(b: ByteArray, off: Int, len: Int) {
263+
fos.write(b, off, len)
264+
}
270265

271-
override fun close() {
272-
if (!closed) {
273-
closed = true
274-
try {
275-
fos.flush()
276-
fos.fd.sync() // blocks until data + metadata are durable
277-
val size = fos.channel.size()
278-
} finally {
279-
fos.close()
280-
pfd.close()
266+
override fun close() {
267+
if (!closed) {
268+
closed = true
269+
try {
270+
fos.flush()
271+
fos.fd.sync() // blocks until data + metadata are durable
272+
val size = fos.channel.size()
273+
} finally {
274+
fos.close()
275+
pfd.close()
276+
}
281277
}
282278
}
283-
}
284279

285-
override fun flush() = fos.flush()
280+
override fun flush() = fos.flush()
281+
}
286282
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@ module github.com/tailscale/tailscale-android
33
go 1.24.4
44

55
require (
6-
github.com/tailscale/wireguard-go v0.0.0-20250530210235-65cd6eed7d7f
6+
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da
77
golang.org/x/mobile v0.0.0-20240806205939-81131f6468ab
8-
tailscale.com v1.85.0-pre.0.20250627205655-0a64e86a0df8
8+
tailscale.com v1.87.0-pre.0.20250801224156-0f15e4419683
99
)
1010

1111
require (

go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -163,8 +163,8 @@ github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976 h1:U
163163
github.com/tailscale/web-client-prebuilt v0.0.0-20250124233751-d4cd19a26976/go.mod h1:agQPE6y6ldqCOui2gkIh7ZMztTkIQKH049tv8siLuNQ=
164164
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6 h1:l10Gi6w9jxvinoiq15g8OToDdASBni4CyJOdHY1Hr8M=
165165
github.com/tailscale/wf v0.0.0-20240214030419-6fbb0a674ee6/go.mod h1:ZXRML051h7o4OcI0d3AaILDIad/Xw0IkXaHM17dic1Y=
166-
github.com/tailscale/wireguard-go v0.0.0-20250530210235-65cd6eed7d7f h1:vg3PmQdq1BbB2V81iC1VBICQtfwbVGZ/4A/p7QKXTK0=
167-
github.com/tailscale/wireguard-go v0.0.0-20250530210235-65cd6eed7d7f/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
166+
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da h1:jVRUZPRs9sqyKlYHHzHjAqKN+6e/Vog6NpHYeNPJqOw=
167+
github.com/tailscale/wireguard-go v0.0.0-20250716170648-1d0488a3d7da/go.mod h1:BOm5fXUBFM+m9woLNBoxI9TaBXXhGNP50LX/TGIvGb4=
168168
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e h1:zOGKqN5D5hHhiYUp091JqK7DPCqSARyUfduhGUY8Bek=
169169
github.com/tailscale/xnet v0.0.0-20240729143630-8497ac4dab2e/go.mod h1:orPd6JZXXRyuDusYilywte7k094d7dycXXU5YnWsrwg=
170170
github.com/tc-hib/winres v0.2.1 h1:YDE0FiP0VmtRaDn7+aaChp1KiF4owBiJa5l964l5ujA=
@@ -235,5 +235,5 @@ howett.net/plist v1.0.0 h1:7CrbWYbPPO/PyNy38b2EB/+gYbjCe2DXBxgtOOZbSQM=
235235
howett.net/plist v1.0.0/go.mod h1:lqaXoTrLY4hg8tnEzNru53gicrbv7rrk+2xJA/7hw9g=
236236
software.sslmate.com/src/go-pkcs12 v0.4.0 h1:H2g08FrTvSFKUj+D309j1DPfk5APnIdAQAB8aEykJ5k=
237237
software.sslmate.com/src/go-pkcs12 v0.4.0/go.mod h1:Qiz0EyvDRJjjxGyUQa2cCNZn/wMyzrRJ/qcDXOQazLI=
238-
tailscale.com v1.85.0-pre.0.20250627205655-0a64e86a0df8 h1:gR3XF35IWpV4WhON27gR2vd8ypXbnjnrj5WreLWFxWk=
239-
tailscale.com v1.85.0-pre.0.20250627205655-0a64e86a0df8/go.mod h1:zrtwlwmFfEWbUz77UN58gaLADx4rXSecFhGO+XW0JbU=
238+
tailscale.com v1.87.0-pre.0.20250801224156-0f15e4419683 h1:meEUX1Nsr5SaXiaeivOGG4c7gsQm/P3Jr3dzbtE0j6k=
239+
tailscale.com v1.87.0-pre.0.20250801224156-0f15e4419683/go.mod h1:Lm8dnzU2i/Emw15r6sl3FRNp/liSQ/nYw6ZSQvIdZ1M=

libtailscale/backend.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ func (a *App) newBackend(dataDir string, appCtx AppContext, store *stateStore,
337337
}
338338
lb, err := ipnlocal.NewLocalBackend(logf, logID.Public(), sys, 0)
339339
if ext, ok := ipnlocal.GetExt[*taildrop.Extension](lb); ok {
340-
ext.SetFileOps(NewAndroidFileOps(a.shareFileHelper))
340+
ext.SetFileOps(newAndroidFileOps(a.shareFileHelper))
341341
ext.SetDirectFileRoot(a.directFileRoot)
342342
}
343343

0 commit comments

Comments
 (0)