From bae9b8394a8b651bbd637ade1c36622caa453293 Mon Sep 17 00:00:00 2001 From: Elias Naur Date: Mon, 13 Jul 2020 22:46:10 +0200 Subject: [PATCH] android: add quick setting tile support Fixes tailscale/tailscale#516 Signed-off-by: Elias Naur --- android/src/main/AndroidManifest.xml | 17 ++++++- .../src/main/java/com/tailscale/ipn/App.java | 4 ++ .../com/tailscale/ipn/QuickToggleService.java | 31 ++++++++++++ android/src/main/res/drawable/ic_tile.xml | 35 ++++++++++++++ android/src/main/res/values/strings.xml | 5 ++ cmd/tailscale/callbacks.go | 5 ++ cmd/tailscale/main.go | 47 ++++++++++++------- jni/jni.go | 6 +++ 8 files changed, 132 insertions(+), 18 deletions(-) create mode 100644 android/src/main/java/com/tailscale/ipn/QuickToggleService.java create mode 100644 android/src/main/res/drawable/ic_tile.xml create mode 100644 android/src/main/res/values/strings.xml diff --git a/android/src/main/AndroidManifest.xml b/android/src/main/AndroidManifest.xml index ab1b2a6..fec93a6 100644 --- a/android/src/main/AndroidManifest.xml +++ b/android/src/main/AndroidManifest.xml @@ -10,7 +10,7 @@ + + + @@ -26,6 +29,18 @@ + + + + + + diff --git a/android/src/main/java/com/tailscale/ipn/App.java b/android/src/main/java/com/tailscale/ipn/App.java index 97e042f..41c00f1 100644 --- a/android/src/main/java/com/tailscale/ipn/App.java +++ b/android/src/main/java/com/tailscale/ipn/App.java @@ -83,6 +83,10 @@ public class App extends Application { ); } + void setTileStatus(boolean wantRunning) { + QuickToggleService.setStatus(this, wantRunning); + } + String getHostname() { String userConfiguredDeviceName = getUserConfiguredDeviceName(); if (!isEmpty(userConfiguredDeviceName)) return userConfiguredDeviceName; diff --git a/android/src/main/java/com/tailscale/ipn/QuickToggleService.java b/android/src/main/java/com/tailscale/ipn/QuickToggleService.java new file mode 100644 index 0000000..f866866 --- /dev/null +++ b/android/src/main/java/com/tailscale/ipn/QuickToggleService.java @@ -0,0 +1,31 @@ +// Copyright (c) 2020 Tailscale Inc & AUTHORS All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package com.tailscale.ipn; + +import android.content.Context; +import android.content.ComponentName; +import android.service.quicksettings.Tile; +import android.service.quicksettings.TileService; + +public class QuickToggleService extends TileService { + private static boolean active; + + @Override public void onStartListening() { + Tile t = getQsTile(); + t.setState(active ? Tile.STATE_ACTIVE : Tile.STATE_INACTIVE); + t.updateTile(); + } + + @Override public void onClick() { + onTileClick(); + } + + static void setStatus(Context ctx, boolean wantRunning) { + active = wantRunning; + requestListeningState(ctx, new ComponentName(ctx, QuickToggleService.class)); + } + + private static native void onTileClick(); +} diff --git a/android/src/main/res/drawable/ic_tile.xml b/android/src/main/res/drawable/ic_tile.xml new file mode 100644 index 0000000..3cd5907 --- /dev/null +++ b/android/src/main/res/drawable/ic_tile.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + diff --git a/android/src/main/res/values/strings.xml b/android/src/main/res/values/strings.xml new file mode 100644 index 0000000..07b3455 --- /dev/null +++ b/android/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + + Tailscale + Tailscale + diff --git a/cmd/tailscale/callbacks.go b/cmd/tailscale/callbacks.go index f784bbb..d021329 100644 --- a/cmd/tailscale/callbacks.go +++ b/cmd/tailscale/callbacks.go @@ -94,3 +94,8 @@ func Java_com_tailscale_ipn_App_onConnectivityChanged(env *C.JNIEnv, cls C.jclas default: } } + +//export Java_com_tailscale_ipn_QuickToggleService_onTileClick +func Java_com_tailscale_ipn_QuickToggleService_onTileClick(env *C.JNIEnv, cls C.jclass) { + requestBackend(ToggleEvent{}) +} diff --git a/cmd/tailscale/main.go b/cmd/tailscale/main.go index aa795ad..8e672f6 100644 --- a/cmd/tailscale/main.go +++ b/cmd/tailscale/main.go @@ -40,7 +40,7 @@ type App struct { // backend is the channel for events from the frontend to the // backend. - backend chan UIEvent + backendEvents chan UIEvent // mu protects the following fields. mu sync.Mutex @@ -83,13 +83,14 @@ type SearchEvent struct { Query string } -type ReauthEvent struct{} - -type WebAuthEvent struct{} - -type GoogleAuthEvent struct{} - -type LogoutEvent struct{} +// UIEvent types. +type ( + ToggleEvent struct{} + ReauthEvent struct{} + WebAuthEvent struct{} + GoogleAuthEvent struct{} + LogoutEvent struct{} +) // serverOAuthID is the OAuth ID of the tailscale-android server, used // by GoogleSignInOptions.Builder.requestIdToken. @@ -97,13 +98,15 @@ const serverOAuthID = "744055068597-hv4opg0h7vskq1hv37nq3u26t8c15qk0.apps.google const enabledKey = "ipn_enabled" +// backendEvents receives events from the UI (Activity, Tile etc.) to the backend. +var backendEvents = make(chan UIEvent) + func main() { a := &App{ jvm: jni.JVMFor(app.JavaVM()), appCtx: jni.Object(app.AppContext()), updates: make(chan struct{}, 1), vpnClosed: make(chan struct{}, 1), - backend: make(chan UIEvent), } appDir, err := app.DataDir() if err != nil { @@ -232,8 +235,11 @@ func (a *App) runBackend() error { if m := state.NetworkMap; m != nil && service != 0 { alarm(a.notifyExpiry(service, m.Expiry)) } - case e := <-a.backend: + case e := <-backendEvents: switch e := e.(type) { + case ToggleEvent: + prefs.WantRunning = !prefs.WantRunning + go b.backend.SetPrefs(prefs) case WebAuthEvent: if !signingIn { go b.backend.StartLoginInteractive() @@ -388,7 +394,14 @@ func (a *App) notify(state BackendState) { func (a *App) setPrefs(prefs *ipn.Prefs) { a.mu.Lock() a.prefs = prefs + wantRunning := jni.True + if !prefs.WantRunning { + wantRunning = jni.False + } a.mu.Unlock() + if err := a.callVoidMethod(a.appCtx, "setTileStatus", "(Z)V", jni.Value(wantRunning)); err != nil { + fatalErr(err) + } select { case a.updates <- struct{}{}: default: @@ -418,7 +431,7 @@ func (a *App) runUI() error { for { select { case <-a.vpnClosed: - a.request(ConnectEvent{Enable: false}) + requestBackend(ConnectEvent{Enable: false}) case <-a.updates: a.mu.Lock() oldState := state.backend.State @@ -563,9 +576,9 @@ func (a *App) updateState(javaPeer jni.Object, state *clientState) { state.Peers = peers } -func (a *App) request(e UIEvent) { +func requestBackend(e UIEvent) { go func() { - a.backend <- e + backendEvents <- e }() } @@ -578,15 +591,15 @@ func (a *App) processUIEvents(w *app.Window, events []UIEvent, peer jni.Object, case loginMethodGoogle: a.googleSignIn(peer) default: - a.request(WebAuthEvent{}) + requestBackend(WebAuthEvent{}) } case WebAuthEvent: a.store.WriteString(loginMethodPrefKey, loginMethodWeb) - a.request(e) + requestBackend(e) case LogoutEvent: - a.request(e) + requestBackend(e) case ConnectEvent: - a.request(e) + requestBackend(e) case CopyEvent: w.WriteClipboard(e.Text) case GoogleAuthEvent: diff --git a/jni/jni.go b/jni/jni.go index b3485d7..b772555 100644 --- a/jni/jni.go +++ b/jni/jni.go @@ -40,9 +40,15 @@ type ( MethodID C.jmethodID String C.jstring ByteArray C.jbyteArray + Boolean C.jboolean Value uint64 // All JNI types fit into 64-bits. ) +const ( + True Boolean = C.JNI_TRUE + False Boolean = C.JNI_FALSE +) + func JVMFor(jvmPtr uintptr) JVM { return JVM{ jvm: (*C.JavaVM)(unsafe.Pointer(jvmPtr)),