|
|
|
|
@ -3,6 +3,7 @@
|
|
|
|
|
|
|
|
|
|
package com.tailscale.ipn
|
|
|
|
|
|
|
|
|
|
import android.content.Context
|
|
|
|
|
import android.content.Intent
|
|
|
|
|
import android.net.Uri
|
|
|
|
|
import android.os.Build
|
|
|
|
|
@ -14,6 +15,7 @@ import androidx.activity.compose.setContent
|
|
|
|
|
import androidx.compose.material3.MaterialTheme
|
|
|
|
|
import androidx.compose.material3.Surface
|
|
|
|
|
import androidx.compose.ui.Modifier
|
|
|
|
|
import androidx.core.content.FileProvider
|
|
|
|
|
import androidx.lifecycle.lifecycleScope
|
|
|
|
|
import com.tailscale.ipn.ui.model.Ipn
|
|
|
|
|
import com.tailscale.ipn.ui.theme.AppTheme
|
|
|
|
|
@ -27,6 +29,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
|
|
|
|
import kotlinx.coroutines.flow.StateFlow
|
|
|
|
|
import kotlinx.coroutines.launch
|
|
|
|
|
import kotlinx.coroutines.withContext
|
|
|
|
|
import java.io.File
|
|
|
|
|
|
|
|
|
|
// ShareActivity is the entry point for Taildrop share intents
|
|
|
|
|
class ShareActivity : ComponentActivity() {
|
|
|
|
|
@ -57,11 +60,13 @@ class ShareActivity : ComponentActivity() {
|
|
|
|
|
override fun onNewIntent(intent: Intent) {
|
|
|
|
|
super.onNewIntent(intent)
|
|
|
|
|
setIntent(intent)
|
|
|
|
|
loadFiles()
|
|
|
|
|
lifecycleScope.launch {
|
|
|
|
|
loadFiles()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Loads the files from the intent.
|
|
|
|
|
fun loadFiles() {
|
|
|
|
|
suspend fun loadFiles() {
|
|
|
|
|
if (intent == null) {
|
|
|
|
|
TSLog.e(TAG, "Share failure - No intent found")
|
|
|
|
|
return
|
|
|
|
|
@ -69,31 +74,37 @@ class ShareActivity : ComponentActivity() {
|
|
|
|
|
|
|
|
|
|
val act = intent.action
|
|
|
|
|
|
|
|
|
|
val uris: List<Uri?>? =
|
|
|
|
|
val uris: List<Uri> =
|
|
|
|
|
when (act) {
|
|
|
|
|
Intent.ACTION_SEND -> {
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
|
listOf(intent.getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java))
|
|
|
|
|
} else {
|
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
|
listOf(intent.getParcelableExtra(Intent.EXTRA_STREAM) as? Uri)
|
|
|
|
|
if (intent.extras?.containsKey(Intent.EXTRA_STREAM) == true) {
|
|
|
|
|
// If EXTRA_STREAM is present, get the single URI for that stream
|
|
|
|
|
listOfNotNull(intent.versionSafeGetStreamUri())
|
|
|
|
|
}
|
|
|
|
|
else if (intent.extras?.containsKey(Intent.EXTRA_TEXT) == true) {
|
|
|
|
|
// If EXTRA_TEXT is present, create a temporary file with the text content.
|
|
|
|
|
// This could be any shared text, like a URL or plain text from the clipboard.
|
|
|
|
|
val text = intent.getStringExtra(Intent.EXTRA_TEXT) ?: ""
|
|
|
|
|
val uri = createTemporaryFile(text)
|
|
|
|
|
listOf(uri)
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
TSLog.e(TAG, "No extras found in intent - nothing to share")
|
|
|
|
|
emptyList()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
Intent.ACTION_SEND_MULTIPLE -> {
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
|
intent.getParcelableArrayListExtra<Uri?>(Intent.EXTRA_STREAM, Uri::class.java)
|
|
|
|
|
} else {
|
|
|
|
|
@Suppress("DEPRECATION") intent.getParcelableArrayListExtra<Uri?>(Intent.EXTRA_STREAM)
|
|
|
|
|
}
|
|
|
|
|
// If ACTION_SEND_MULTIPLE, assume this is a list of files to share
|
|
|
|
|
intent.versionSafeGetStreamUris()
|
|
|
|
|
}
|
|
|
|
|
else -> {
|
|
|
|
|
TSLog.e(TAG, "No extras found in intent - nothing to share")
|
|
|
|
|
null
|
|
|
|
|
TSLog.e(TAG, "Unexpected intent action: $act. Expected ACTION_SEND or ACTION_SEND_MULTIPLE")
|
|
|
|
|
emptyList()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
val pendingFiles: List<Ipn.OutgoingFile> =
|
|
|
|
|
uris?.filterNotNull()?.mapNotNull { uri ->
|
|
|
|
|
uris.mapNotNull { uri ->
|
|
|
|
|
contentResolver?.query(uri, null, null, null, null)?.use { cursor ->
|
|
|
|
|
val nameCol = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
|
|
|
|
|
val sizeCol = cursor.getColumnIndex(OpenableColumns.SIZE)
|
|
|
|
|
@ -107,7 +118,7 @@ class ShareActivity : ComponentActivity() {
|
|
|
|
|
null
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} ?: emptyList()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (pendingFiles.isEmpty()) {
|
|
|
|
|
TSLog.e(TAG, "Share failure - no files extracted from intent")
|
|
|
|
|
@ -123,3 +134,54 @@ class ShareActivity : ComponentActivity() {
|
|
|
|
|
return if (extension != null) "$randomId.$extension" else randomId.toString()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Intent.versionSafeGetStreamUri(): Uri? =
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
|
getParcelableExtra(Intent.EXTRA_STREAM, Uri::class.java)
|
|
|
|
|
}
|
|
|
|
|
else {
|
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
|
getParcelableExtra(Intent.EXTRA_STREAM) as? Uri
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private fun Intent.versionSafeGetStreamUris(): List<Uri> =
|
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
|
|
|
|
|
getParcelableArrayListExtra<Uri?>(Intent.EXTRA_STREAM, Uri::class.java)
|
|
|
|
|
} else {
|
|
|
|
|
@Suppress("DEPRECATION")
|
|
|
|
|
getParcelableArrayListExtra<Uri?>(Intent.EXTRA_STREAM)
|
|
|
|
|
}
|
|
|
|
|
?.filterNotNull()
|
|
|
|
|
?: emptyList()
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Creates a temporary txt file in the app's cache directory with the given content.
|
|
|
|
|
* Then grants temporary read permission to the file using FileProvider and returns its URI.
|
|
|
|
|
*/
|
|
|
|
|
private suspend fun Context.createTemporaryFile(
|
|
|
|
|
content: String,
|
|
|
|
|
dir: File = cacheDir,
|
|
|
|
|
fileName: String = "shared_text_${System.currentTimeMillis()}.txt",
|
|
|
|
|
): Uri {
|
|
|
|
|
// Create temporary file in cache directory
|
|
|
|
|
val tempFile = File(dir, fileName)
|
|
|
|
|
withContext(Dispatchers.IO) {
|
|
|
|
|
tempFile.writeText(content)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get content URI using FileProvider
|
|
|
|
|
val uri = FileProvider.getUriForFile(
|
|
|
|
|
this,
|
|
|
|
|
"${applicationContext.packageName}.fileprovider",
|
|
|
|
|
tempFile
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Grant temporary read permission
|
|
|
|
|
grantUriPermission(
|
|
|
|
|
packageName,
|
|
|
|
|
uri,
|
|
|
|
|
Intent.FLAG_GRANT_READ_URI_PERMISSION
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
return uri
|
|
|
|
|
}
|