Skip to content

Commit

Permalink
android: switch to using gomobile
Browse files Browse the repository at this point in the history
gomobile replaces our custom JNI bindings

Updates tailscale/corp#18202

Signed-off-by: Percy Wegmann <[email protected]>
  • Loading branch information
oxtoacart committed Mar 19, 2024
1 parent 8a72a49 commit 99b2db4
Show file tree
Hide file tree
Showing 33 changed files with 1,031 additions and 1,762 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,6 @@ tailscale.jks
#IDE
.vscode
.idea

libtailscale.aar
libtailscale-sources.jar
41 changes: 30 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ DEBUG_APK=tailscale-debug.apk
RELEASE_AAB=tailscale-release.aab
APPID=com.tailscale.ipn
AAR=android_legacy/libs/ipn.aar
AAR_NEXTGEN=android/libs/ipn.aar
KEYSTORE=tailscale.jks
KEYSTORE_ALIAS=tailscale
TAILSCALE_VERSION=$(shell ./version/tailscale-version.sh 200)
Expand Down Expand Up @@ -136,12 +135,6 @@ $(AAR): toolchain checkandroidsdk
-ldflags "-X tailscale.com/version.longStamp=$(VERSIONNAME) -X tailscale.com/version.shortStamp=$(VERSIONNAME_SHORT) -X tailscale.com/version.gitCommitStamp=$(TAILSCALE_COMMIT) -X tailscale.com/version.extraGitCommitStamp=$(OUR_VERSION)" \
-buildmode archive -target android -appid $(APPID) -tags novulkan,tailscale_go -o $@ github.com/tailscale/tailscale-android/cmd/tailscale

$(AAR_NEXTGEN): $(AAR)
@mkdir -p android/libs && \
cp $(AAR) $(AAR_NEXTGEN)

lib: $(AAR_NEXTGEN)

# tailscale-debug.apk builds a debuggable APK with the Google Play SDK.
$(DEBUG_APK): $(AAR)
(cd android_legacy && ./gradlew test assemblePlayDebug)
Expand All @@ -152,19 +145,45 @@ apk: $(DEBUG_APK)
run: install
adb shell am start -n com.tailscale.ipn/com.tailscale.ipn.IPNActivity

GOMOBILE=/tmp/gopath/bin/gomobile
GOBIND=/tmp/gopath/bin/gobind
LIBTAILSCALE=android/libs/libtailscale.aar
LIBTAILSCALE_SOURCES=$(shell find libtailscale -name *.go) go.mod go.sum

$(GOMOBILE):
@echo "building gomobile" && \
export GOPATH=/tmp/gopath && \
mkdir -p $$GOPATH && \
export PATH=$$PATH:$$GOPATH/bin && \
go install golang.org/x/mobile/cmd/gomobile@latest

$(GOBIND): $(GOMOBILE)
@export GOPATH=/tmp/gopath && \
$(GOMOBILE) init

gomobile: $(GOBIND)

$(LIBTAILSCALE): $(LIBTAILSCALE_SOURCES) $(GOBIND)
@export GOPATH=/tmp/gopath && \
export PATH=$$PATH:$$GOPATH/bin && \
$(GOMOBILE) bind -target android -androidapi 26 ./libtailscale && \
cp libtailscale.aar $(LIBTAILSCALE)

libtailscale: $(LIBTAILSCALE)

# tailscale-fdroid.apk builds a non-Google Play SDK, without the Google bits.
# This is effectively what the F-Droid build definition produces.
# This is useful for testing on e.g. Amazon Fire Stick devices.
tailscale-fdroid.apk: $(AAR)
(cd android_legacy && ./gradlew test assembleFdroidDebug)
mv android_legacy/build/outputs/apk/fdroid/debug/android_legacy-fdroid-debug.apk $@

tailscale-new-fdroid.apk: $(AAR_NEXTGEN)
tailscale-new-fdroid.apk: $(LIBTAILSCALE)
(cd android && ./gradlew test assembleFdroidDebug)
mv android/build/outputs/apk/fdroid/debug/android-fdroid-debug.apk $@

tailscale-new-debug.apk:
(cd android && ./gradlew test buildAllGoLibs assemblePlayDebug)
tailscale-new-debug.apk: $(LIBTAILSCALE)
(cd android && ./gradlew test assemblePlayDebug)
mv android/build/outputs/apk/play/debug/android-play-debug.apk $@

tailscale-new-debug: tailscale-new-debug.apk
Expand All @@ -190,4 +209,4 @@ clean:
-rm -rf android_legacy/build $(DEBUG_APK) $(RELEASE_AAB) $(AAR) tailscale-fdroid.apk
-pkill -f gradle

.PHONY: all clean install android_legacy/lib $(DEBUG_APK) $(RELEASE_AAB) $(AAR) release bump_version dockershell lib
.PHONY: all clean install android_legacy/lib $(DEBUG_APK) $(RELEASE_AAB) $(AAR) release bump_version dockershell lib tailscale-new-debug
61 changes: 1 addition & 60 deletions android/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ dependencies {
implementation("io.coil-kt:coil-compose:2.6.0")

// Tailscale dependencies.
implementation ':ipn@aar'
implementation ':libtailscale@aar'

// Tests
testImplementation "junit:junit:4.12"
Expand All @@ -105,62 +105,3 @@ dependencies {
// Non-free dependencies.
playImplementation 'com.google.android.gms:play-services-auth:20.7.0'
}

def ndkPath = project.hasProperty('ndkPath') ? project.property('ndkPath') : System.getenv('ANDROID_SDK_ROOT')

task checkNDK {
doFirst {
if (ndkPath == null) {
throw new GradleException('NDK path not found. Please define ndkPath in local.properties or ANDROID_SDK_HOME environment variable.')
}
}
}

task buildGoLibArm64(type: Exec) {
inputs.dir '../pkg/tailscale'
outputs.file 'src/main/jniLibs/arm64-v8a/libtailscale.so'
environment "CC", "$ndkPath/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/aarch64-linux-android30-clang"
commandLine 'sh', '-c', "GOOS=android GOARCH=arm64 CGO_ENABLED=1 go build -buildmode=c-shared -ldflags=-w -o src/main/jniLibs/arm64-v8a/libtailscale.so ../pkg/tailscale"
}

task buildGoLibArmeabi(type: Exec) {
inputs.dir '../pkg/tailscale'
outputs.file 'src/main/jniLibs/armeabi-v7a/libtailscale.so'
environment "CC", "$ndkPath/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/armv7a-linux-androideabi30-clang"
commandLine 'sh', '-c', "GOOS=android GOARCH=arm CGO_ENABLED=1 go build -buildmode=c-shared -ldflags=-w -o src/main/jniLibs/armeabi-v7a/libtailscale.so ../pkg/tailscale"
}

task buildGoLibX86(type: Exec) {
inputs.dir '../pkg/tailscale'
outputs.file 'src/main/jniLibs/x86/libtailscale.so'
environment "CC", "$ndkPath/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/i686-linux-android30-clang"
commandLine 'sh', '-c', "GOOS=android GOARCH=386 CGO_ENABLED=1 go build -buildmode=c-shared -ldflags=-w -o src/main/jniLibs/x86/libtailscale.so ../pkg/tailscale"
}

task buildGoLibX86_64(type: Exec) {
inputs.dir '../pkg/tailscale'
outputs.file 'src/main/jniLibs/x86_64/libtailscale.so'
environment "CC", "$ndkPath/ndk-bundle/toolchains/llvm/prebuilt/darwin-x86_64/bin/x86_64-linux-android30-clang"
commandLine 'sh', '-c', "GOOS=android GOARCH=amd64 CGO_ENABLED=1 go build -buildmode=c-shared -ldflags=-w -o src/main/jniLibs/x86_64/libtailscale.so ../pkg/tailscale"
}

task buildAllGoLibs {
dependsOn checkNDK, buildGoLibArm64, buildGoLibArmeabi, buildGoLibX86, buildGoLibX86_64
}

assemble.dependsOn buildAllGoLibs

task cleanGoLibs(type: Delete) {
delete 'src/main/jniLibs/arm64-v8a/libtailscale.so',
'src/main/jniLibs/armeabi-v7a/libtailscale.so',
'src/main/jniLibs/x86/libtailscale.so',
'src/main/jniLibs/x86_64/libtailscale.so'
}

clean.dependsOn cleanGoLibs

tasks.whenTaskAdded { task ->
if (task.name.startsWith('merge') && task.name.endsWith('JniLibFolders')) {
task.mustRunAfter buildAllGoLibs
}
}
1 change: 0 additions & 1 deletion android/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
android:exported="true"
android:label="@string/app_name"
android:launchMode="singleTask"
android:theme="@style/Theme.GioApp"
android:windowSoftInputMode="adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
64 changes: 38 additions & 26 deletions android/src/main/java/com/tailscale/ipn/App.java
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import android.os.Looper;
import android.provider.MediaStore;
import android.provider.Settings;
import android.util.Log;

import androidx.browser.customtabs.CustomTabsIntent;
import androidx.core.app.NotificationCompat;
Expand All @@ -46,6 +47,8 @@
import com.tailscale.ipn.mdm.MDMSettings;
import com.tailscale.ipn.mdm.ShowHideSetting;
import com.tailscale.ipn.mdm.StringSetting;
import com.tailscale.ipn.ui.localapi.Request;
import com.tailscale.ipn.ui.notifier.Notifier;

import java.io.File;
import java.io.IOException;
Expand All @@ -57,7 +60,9 @@
import java.util.List;
import java.util.Objects;

public class App extends Application {
import libtailscale.Libtailscale;

public class App extends Application implements libtailscale.AppContext {
static final String STATUS_CHANNEL_ID = "tailscale-status";
static final int STATUS_NOTIFICATION_ID = 1;
static final String NOTIFY_CHANNEL_ID = "tailscale-notify";
Expand All @@ -74,9 +79,17 @@ public class App extends Application {
private ConnectivityManager connectivityManager;

public static App getApplication() {
// TODO: this should be injected to MDMSettings by grabbing it from the activity's context
// rather than being a static singleton.
return _application;
}

private libtailscale.Application app;

public libtailscale.Application getTailscaleApp() {
return app;
}

private static boolean isEmpty(String str) {
return str == null || str.length() == 0;
}
Expand All @@ -86,34 +99,33 @@ static void startActivityForResult(Activity act, Intent intent, int request) {
f.startActivityForResult(intent, request);
}

static native void initBackend(byte[] dataDir, Context context);

static native void onVPNPrepared();

private static native void onDnsConfigChanged();
public DnsConfig getDnsConfigObj() {
return this.dns;
}

static native void onShareIntent(int nfiles, int[] types, String[] mimes, String[] items, String[] names, long[] sizes);
@Override
public String getPlatformDNSConfig() {
return dns.getDnsConfigAsString();
}

static native void onWriteStorageGranted();
@Override
public boolean isPlayVersion() {
return MaybeGoogle.isGoogle();
}

public DnsConfig getDnsConfigObj() {
return this.dns;
@Override
public void log(String s, String s1) {
Log.d(s, s1);
}

@Override
public void onCreate() {
super.onCreate();

System.loadLibrary("tailscale");

String dataDir = this.getFilesDir().getAbsolutePath();
byte[] dataDirUTF8;
try {
dataDirUTF8 = dataDir.getBytes("UTF-8");
initBackend(dataDirUTF8, this);
} catch (Exception e) {
android.util.Log.d("tailscale", "Error getting directory");
}
app = Libtailscale.start(dataDir, this);
Request.setApp(app);
Notifier.setApp(app);

this.connectivityManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
setAndRegisterNetworkCallbacks();
Expand Down Expand Up @@ -145,13 +157,13 @@ public void onAvailable(Network network) {
}

dns.updateDNSFromNetwork(sb.toString());
onDnsConfigChanged();
Libtailscale.onDnsConfigChanged();
}

@Override
public void onLost(Network network) {
super.onLost(network);
onDnsConfigChanged();
Libtailscale.onDnsConfigChanged();
}
});
}
Expand Down Expand Up @@ -221,7 +233,7 @@ String getHostname() {
return getModelName();
}

String getModelName() {
public String getModelName() {
String manu = Build.MANUFACTURER;
String model = Build.MODEL;
// Strip manufacturer from model.
Expand All @@ -233,7 +245,7 @@ String getModelName() {
return manu + " " + model;
}

String getOSVersion() {
public String getOSVersion() {
return Build.VERSION.RELEASE;
}

Expand All @@ -259,7 +271,7 @@ public void run() {
});
}

boolean isChromeOS() {
public boolean isChromeOS() {
return getPackageManager().hasSystemFeature("android.hardware.type.pc");
}

Expand All @@ -269,7 +281,7 @@ void prepareVPN(Activity act, int reqCode) {
public void run() {
Intent intent = VpnService.prepare(act);
if (intent == null) {
onVPNPrepared();
Libtailscale.onVPNPrepared();
} else {
startActivityForResult(act, intent, reqCode);
}
Expand Down Expand Up @@ -386,7 +398,7 @@ public void createNotificationChannel(String id, String name, int importance) {
//
// Where the fields are:
// name ifindex mtu isUp hasBroadcast isLoopback isPointToPoint hasMulticast | ip1/N ip2/N ip3/N;
String getInterfacesAsString() {
public String getInterfacesAsString() {
List<NetworkInterface> interfaces;
try {
interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
Expand Down
7 changes: 5 additions & 2 deletions android/src/main/java/com/tailscale/ipn/IPNActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

import java.util.List;

import libtailscale.Libtailscale;

public final class IPNActivity extends Activity {
final static int WRITE_STORAGE_RESULT = 1000;

Expand Down Expand Up @@ -82,14 +84,15 @@ private void handleIntent() {
nfiles++;
}
}
App.onShareIntent(nfiles, types, mimes, items, names, sizes);
// TODO(oxtoacart): actually implement this
// App.onShareIntent(nfiles, types, mimes, items, names, sizes);
}

@Override
public void onRequestPermissionsResult(int reqCode, String[] perms, int[] grants) {
if (reqCode == WRITE_STORAGE_RESULT) {
if (grants.length > 0 && grants[0] == PackageManager.PERMISSION_GRANTED) {
App.onWriteStorageGranted();
Libtailscale.onWriteStorageGranted();
}
}
}
Expand Down
Loading

0 comments on commit 99b2db4

Please sign in to comment.