From 61fb6bbf8e4613305492548178569e9acdae14c5 Mon Sep 17 00:00:00 2001 From: Jonathan Nobels Date: Wed, 27 Mar 2024 14:56:04 -0400 Subject: [PATCH] android/taildrop: support direct mode for incoming taildrop (#251) Updates tailscale/corp#18202 Implements direct mode support for incoming taildrop files. None of the localAPI endpoints are implemented here but this will get taildrop files to the right places. Signed-off-by: Jonathan Nobels --- .../src/main/java/com/tailscale/ipn/App.kt | 32 ++++++++++++- libtailscale/backend.go | 14 ++++-- libtailscale/callbacks.go | 48 ------------------- libtailscale/interfaces.go | 4 +- libtailscale/tailscale.go | 7 +-- 5 files changed, 47 insertions(+), 58 deletions(-) diff --git a/android/src/main/java/com/tailscale/ipn/App.kt b/android/src/main/java/com/tailscale/ipn/App.kt index 1a7c799..93cc6df 100644 --- a/android/src/main/java/com/tailscale/ipn/App.kt +++ b/android/src/main/java/com/tailscale/ipn/App.kt @@ -109,7 +109,14 @@ class App : Application(), libtailscale.AppContext { override fun onCreate() { super.onCreate() val dataDir = this.filesDir.absolutePath - app = Libtailscale.start(dataDir, this) + + // Set this to enable direct mode for taildrop whereby downloads will be saved directly + // to the given folder. We will preferentially use /Downloads and fallback to + // an app local directory "Taildrop" if we cannot create that. This mode does not support + // user notifications for incoming files. + val directFileDir = this.prepareDownloadsFolder() + + app = Libtailscale.start(dataDir, directFileDir.absolutePath, this) Request.setApp(app) Notifier.setApp(app) connectivityManager = this.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager @@ -464,4 +471,27 @@ class App : Application(), libtailscale.AppContext { } } } + + fun prepareDownloadsFolder(): File { + var downloads = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + + try { + if (!downloads.exists()) { + downloads.mkdirs() + } + } catch (e: Exception) { + Log.e(TAG, "Failed to create downloads folder: $e") + downloads = File(this.filesDir, "Taildrop") + try { + if (!downloads.exists()) { + downloads.mkdirs() + } + } catch (e: Exception) { + Log.e(TAG, "Failed to create Taildrop folder: $e") + downloads = File("") + } + } + + return downloads + } } diff --git a/libtailscale/backend.go b/libtailscale/backend.go index 58053af..5932ee0 100644 --- a/libtailscale/backend.go +++ b/libtailscale/backend.go @@ -35,6 +35,10 @@ import ( type App struct { dataDir string + + // enables direct file mode for the taildrop manager + directFileRoot string + // appCtx is a global reference to the com.tailscale.ipn.App instance. appCtx AppContext @@ -46,7 +50,7 @@ type App struct { ready sync.WaitGroup } -func start(dataDir string, appCtx AppContext) Application { +func start(dataDir, directFileRoot string, appCtx AppContext) Application { defer func() { if p := recover(); p != nil { log.Printf("panic in Start %s: %s", p, debug.Stack()) @@ -70,7 +74,7 @@ func start(dataDir string, appCtx AppContext) Application { os.Setenv("HOME", dataDir) } - return newApp(dataDir, appCtx) + return newApp(dataDir, directFileRoot, appCtx) } type backend struct { @@ -113,7 +117,7 @@ func (a *App) runBackend(ctx context.Context) error { } configs := make(chan configPair) configErrs := make(chan error) - b, err := newBackend(a.dataDir, a.appCtx, a.store, func(rcfg *router.Config, dcfg *dns.OSConfig) error { + b, err := newBackend(a.dataDir, a.directFileRoot, a.appCtx, a.store, func(rcfg *router.Config, dcfg *dns.OSConfig) error { if rcfg == nil { return nil } @@ -228,7 +232,7 @@ func (a *App) runBackend(ctx context.Context) error { } } -func newBackend(dataDir string, appCtx AppContext, store *stateStore, +func newBackend(dataDir, directFileRoot string, appCtx AppContext, store *stateStore, settings settingsFunc) (*backend, error) { sys := new(tsd.System) @@ -299,6 +303,8 @@ func newBackend(dataDir string, appCtx AppContext, store *stateStore, engine.Close() return nil, fmt.Errorf("runBackend: NewLocalBackend: %v", err) } + lb.SetDirectFileRoot(directFileRoot) + if err := ns.Start(lb); err != nil { return nil, fmt.Errorf("startNetstack: %w", err) } diff --git a/libtailscale/callbacks.go b/libtailscale/callbacks.go index a0728b2..30daff1 100644 --- a/libtailscale/callbacks.go +++ b/libtailscale/callbacks.go @@ -26,50 +26,10 @@ var ( // onGoogleToken receives google ID tokens. onGoogleToken = make(chan string) - // onWriteStorageGranted is notified when we are granted WRITE_STORAGE_PERMISSION. - onWriteStorageGranted = make(chan struct{}, 1) - // onDNSConfigChanged is notified when the network changes and the DNS config needs to be updated. onDNSConfigChanged = make(chan struct{}, 1) ) -func OnShareIntent(nfiles int32, types []int32, mimes []string, items []string, names []string, sizes []int) { - // TODO(oxtoacart): actually implement this - // const ( - // typeNone = 0 - // typeInline = 1 - // typeURI = 2 - // ) - // jenv := (*jni.Env)(unsafe.Pointer(env)) - // var files []File - // for i := 0; i < int(nfiles); i++ { - // f := File{ - // Type: FileType(types[i]), - // MIMEType: mimes[i], - // Name: names[i], - // } - // if f.Name == "" { - // f.Name = "file.bin" - // } - // switch f.Type { - // case FileTypeText: - // f.Text = items[i] - // f.Size = int64(len(f.Text)) - // case FileTypeURI: - // f.URI = items[i] - // f.Size = sizes[i] - // default: - // panic("unknown file type") - // } - // files = append(files, f) - // } - // select { - // case <-onFileShare: - // default: - // } - // onFileShare <- files -} - func OnDnsConfigChanged() { select { case onDNSConfigChanged <- struct{}{}: @@ -77,14 +37,6 @@ func OnDnsConfigChanged() { } } -//export Java_com_tailscale_ipn_App_onWriteStorageGranted -func OnWriteStorageGranted() { - select { - case onWriteStorageGranted <- struct{}{}: - default: - } -} - func notifyVPNPrepared() { select { case onVPNPrepared <- struct{}{}: diff --git a/libtailscale/interfaces.go b/libtailscale/interfaces.go index 3a6185e..9cd84ec 100644 --- a/libtailscale/interfaces.go +++ b/libtailscale/interfaces.go @@ -7,8 +7,8 @@ import _ "golang.org/x/mobile/bind" // Start starts the application, storing state in the given dataDir and using // the given appCtx. -func Start(dataDir string, appCtx AppContext) Application { - return start(dataDir, appCtx) +func Start(dataDir, directFileRoot string, appCtx AppContext) Application { + return start(dataDir, directFileRoot, appCtx) } // AppContext provides a context within which the Application is running. This diff --git a/libtailscale/tailscale.go b/libtailscale/tailscale.go index 37b33a4..d0e4bac 100644 --- a/libtailscale/tailscale.go +++ b/libtailscale/tailscale.go @@ -30,10 +30,11 @@ const ( customLoginServerPrefKey = "customloginserver" ) -func newApp(dataDir string, appCtx AppContext) Application { +func newApp(dataDir, directFileRoot string, appCtx AppContext) Application { a := &App{ - dataDir: dataDir, - appCtx: appCtx, + directFileRoot: directFileRoot, + dataDir: dataDir, + appCtx: appCtx, } a.ready.Add(1)