vpn client: use device hostname
diff --git a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
index b7647db..14a8875 100644
--- a/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
+++ b/core/client/android/app/src/main/java/me/lekva/pcloud/PCloudApp.java
@@ -4,6 +4,7 @@
 import android.app.NotificationChannel;
 import android.content.SharedPreferences;
 import android.os.Build;
+import android.provider.Settings;
 
 import androidx.core.app.NotificationManagerCompat;
 import androidx.security.crypto.EncryptedSharedPreferences;
@@ -39,6 +40,39 @@
         nm.createNotificationChannel(channel);
     }
 
+    String getHostname() {
+        String userConfiguredDeviceName = getUserConfiguredDeviceName();
+        if (!isEmpty(userConfiguredDeviceName)) return userConfiguredDeviceName;
+        return getModelName();
+    }
+
+    private String getUserConfiguredDeviceName() {
+        String nameFromSystemBluetooth = Settings.System.getString(getContentResolver(), "bluetooth_name");
+        String nameFromSecureBluetooth = Settings.Secure.getString(getContentResolver(), "bluetooth_name");
+        String nameFromSystemDevice = Settings.Secure.getString(getContentResolver(), "device_name");
+        if (!isEmpty(nameFromSystemBluetooth)) return nameFromSystemBluetooth;
+        if (!isEmpty(nameFromSecureBluetooth)) return nameFromSecureBluetooth;
+        if (!isEmpty(nameFromSystemDevice)) return nameFromSystemDevice;
+        return null;
+    }
+
+    String getModelName() {
+        String manu = Build.MANUFACTURER;
+        String model = Build.MODEL;
+        // Strip manufacturer from model.
+        int idx = model.toLowerCase().indexOf(manu.toLowerCase());
+        if (idx != -1) {
+            model = model.substring(idx + manu.length());
+            model = model.trim();
+        }
+        return manu + " " + model;
+    }
+
+    private static boolean isEmpty(String str) {
+        return str == null || str.length() == 0;
+    }
+
+
     // encryptToPref a byte array of data using the Jetpack Security
     // library and writes it to a global encrypted preference store.
     public void encryptToPref(String prefKey, String plaintext) throws IOException, GeneralSecurityException {
diff --git a/core/client/cmd/pcloud/app.go b/core/client/cmd/pcloud/app.go
index e54710b..a0cab6c 100644
--- a/core/client/cmd/pcloud/app.go
+++ b/core/client/cmd/pcloud/app.go
@@ -9,4 +9,5 @@
 	TriggerService() error
 	Connect(config Config) error
 	CreateStorage() Storage
+	GetHostname() (string, error)
 }
diff --git a/core/client/cmd/pcloud/app_android.go b/core/client/cmd/pcloud/app_android.go
index ce90588..3d18663 100644
--- a/core/client/cmd/pcloud/app_android.go
+++ b/core/client/cmd/pcloud/app_android.go
@@ -213,3 +213,18 @@
 	})
 
 }
+
+func (a *androidApp) GetHostname() (string, error) {
+	var hostname string
+	err := jni.Do(a.jvm, func(env *jni.Env) error {
+		cls := jni.GetObjectClass(env, a.appCtx)
+		m := jni.GetMethodID(env, cls, "getHostname", "()Ljava/lang/String;")
+		jHostname, err := jni.CallObjectMethod(env, a.appCtx, m)
+		if err != nil {
+			return err
+		}
+		hostname = jni.GoString(env, jni.String(jHostname))
+		return nil
+	})
+	return hostname, err
+}
diff --git a/core/client/cmd/pcloud/app_darwin.go b/core/client/cmd/pcloud/app_darwin.go
index 28532d0..3e4bd04 100644
--- a/core/client/cmd/pcloud/app_darwin.go
+++ b/core/client/cmd/pcloud/app_darwin.go
@@ -36,3 +36,7 @@
 func (a *darwinApp) CreateStorage() Storage {
 	return nil
 }
+
+func (a *darwinApp) GetHostname() (string, error) {
+	return "", nil
+}
diff --git a/core/client/cmd/pcloud/client.go b/core/client/cmd/pcloud/client.go
index 6763f17..78a31f1 100644
--- a/core/client/cmd/pcloud/client.go
+++ b/core/client/cmd/pcloud/client.go
@@ -16,7 +16,7 @@
 
 type VPNClient interface {
 	Sign(apiAddr string, message []byte) ([]byte, error)
-	Join(apiAddr string, message, signature []byte) ([]byte, error)
+	Join(apiAddr, hostname string, message, signature []byte) ([]byte, error)
 }
 
 type directVPNClient struct {
@@ -69,7 +69,7 @@
 	cfgYamlB64 string
 }
 
-func (c *directVPNClient) Join(apiAddr string, message, signature []byte) ([]byte, error) {
+func (c *directVPNClient) Join(apiAddr, hostname string, message, signature []byte) ([]byte, error) {
 	pubKey, privKey, err := x25519Keypair()
 	if err != nil {
 		return nil, err
@@ -77,7 +77,7 @@
 	req := joinReq{
 		message,
 		signature,
-		"test",
+		hostname,
 		cert.MarshalX25519PublicKey(pubKey),
 		"111.0.0.13/24",
 	}
diff --git a/core/client/cmd/pcloud/main.go b/core/client/cmd/pcloud/main.go
index 1440af6..287407b 100644
--- a/core/client/cmd/pcloud/main.go
+++ b/core/client/cmd/pcloud/main.go
@@ -7,6 +7,7 @@
 	"fmt"
 	"image"
 	"image/png"
+	"strings"
 	"time"
 
 	"gioui.org/app"
@@ -194,9 +195,15 @@
 func (p *processor) JoinAndGetNetworkConfig(code []byte) error {
 	var invite qrCodeData
 	if err := json.NewDecoder(bytes.NewReader(code)).Decode(&invite); err != nil {
-		panic(err)
+		return err
 	}
-	network, err := p.vc.Join(invite.VPNApiAddr, invite.Message, invite.Signature)
+	hostname, err := p.app.GetHostname()
+	if err != nil {
+		return err
+	}
+	hostname = strings.ToLower(strings.ReplaceAll(hostname, " ", "-"))
+	fmt.Printf("------ %s\n", hostname)
+	network, err := p.vc.Join(invite.VPNApiAddr, hostname, invite.Message, invite.Signature)
 	if err != nil {
 		return err
 	}