android: add quick setting tile support

Fixes tailscale/tailscale#516

Signed-off-by: Elias Naur <mail@eliasnaur.com>
pull/3/head
Elias Naur 4 years ago
parent df1d8b338b
commit bae9b8394a

@ -10,7 +10,7 @@
<application android:label="Tailscale" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round"
android:name=".App">
<activity android:name="org.gioui.GioActivity"
android:label="Tailscale"
android:label="@string/app_name"
android:theme="@style/Theme.GioApp"
android:configChanges="orientation|screenSize|screenLayout|smallestScreenSize|keyboardHidden"
android:windowSoftInputMode="adjustResize"
@ -19,6 +19,9 @@
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE_PREFERENCES" />
</intent-filter>
</activity>
<service android:name=".IPNService"
android:permission="android.permission.BIND_VPN_SERVICE">
@ -26,6 +29,18 @@
<action android:name="android.net.VpnService"/>
</intent-filter>
</service>
<service
android:name=".QuickToggleService"
android:icon="@drawable/ic_tile"
android:label="@string/tile_name"
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
<intent-filter>
<action android:name="android.service.quicksettings.action.QS_TILE"/>
</intent-filter>
<meta-data
android:name="android.service.quicksettings.ACTIVE_TILE"
android:value="true"/>
</service>
</application>
</manifest>

@ -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;

@ -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();
}

@ -0,0 +1,35 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="60"
android:viewportHeight="60">
<group>
<path
android:pathData="M15,30.0002m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#FFFDFA"/>
<path
android:pathData="M30,30.0002m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#FFFDFA"/>
<path
android:pathData="M15,45.0002m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#54514D"/>
<path
android:pathData="M45,45.0002m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#54514D"/>
<path
android:pathData="M30,45.0002m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#FFFDFA"/>
<path
android:pathData="M45,30.0002m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#FFFDFA"/>
<path
android:pathData="M15,15.0002m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#54514D"/>
<path
android:pathData="M30,14.9999m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#54514D"/>
<path
android:pathData="M45,14.9999m-5,0a5,5 0,1 1,10 0a5,5 0,1 1,-10 0"
android:fillColor="#54514D"/>
</group>
</vector>

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Tailscale</string>
<string name="tile_name">Tailscale</string>
</resources>

@ -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{})
}

@ -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:

@ -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)),

Loading…
Cancel
Save