diff --git a/app/build.gradle b/app/build.gradle
index 854ae6a..f21a625 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,13 +4,13 @@ plugins {
}
android {
- compileSdkVersion 29
+ compileSdkVersion 31
buildToolsVersion "30.0.3"
defaultConfig {
applicationId "eu.neilalexander.yggdrasil"
minSdkVersion 21
- targetSdkVersion 29
+ targetSdkVersion 31
versionCode 13
versionName "0.1-013"
@@ -37,6 +37,7 @@ android {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig = signingConfigs.getByName("yggdrasil")
+ matchingFallbacks = ['release']
}
}
compileOptions {
@@ -51,12 +52,19 @@ android {
dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
+ implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3"
implementation 'androidx.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference-ktx:1.1.0'
+ implementation 'com.guolindev.permissionx:permissionx:1.6.4'
testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
+ implementation('org.akselrod.blemesh:lib:0.0.1') {
+ version {
+ branch = 'main'
+ }
+ }
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index e5231bb..23eb924 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,6 +6,10 @@
+
+
+
+
@@ -67,4 +72,4 @@
-
\ No newline at end of file
+
diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt b/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt
index e5dd0ba..514bd49 100644
--- a/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt
+++ b/app/src/main/java/eu/neilalexander/yggdrasil/ConfigurationProxy.kt
@@ -55,6 +55,7 @@ object ConfigurationProxy {
json.put("AdminListen", "none")
json.put("IfName", "none")
json.put("IfMTU", 65535)
+ json.put("Listen", JSONArray(arrayOf("tcp://127.0.0.1:9004")))
if (json.getJSONArray("MulticastInterfaces").get(0) is String) {
var ar = JSONArray()
@@ -94,4 +95,4 @@ object ConfigurationProxy {
(json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Beacon", value)
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt
index b2f02b4..ffc4cec 100644
--- a/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt
+++ b/app/src/main/java/eu/neilalexander/yggdrasil/GlobalApplication.kt
@@ -10,6 +10,8 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
const val PREF_KEY_ENABLED = "enabled"
+const val BLE_ENABLED = "ble"
+const val CODED_PHY_ENABLED = "codedPhy"
const val MAIN_CHANNEL_ID = "Yggdrasil Service"
class GlobalApplication: Application(), YggStateReceiver.StateReceiver {
@@ -68,7 +70,7 @@ fun createServiceNotification(context: Context, state: State): Notification {
val intent = Intent(context, MainActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
}
- val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
+ val pendingIntent: PendingIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE)
val text = when (state) {
State.Disabled -> context.getText(R.string.tile_disabled)
@@ -119,4 +121,4 @@ private fun createNotificationChannels(context: Context) {
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel)
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt
index af70595..d2ceca3 100644
--- a/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt
+++ b/app/src/main/java/eu/neilalexander/yggdrasil/MainActivity.kt
@@ -1,9 +1,11 @@
package eu.neilalexander.yggdrasil
+import android.Manifest
import android.app.Activity
import android.content.*
import android.graphics.Color
import android.net.VpnService
+import android.os.Build
import android.os.Bundle
import android.widget.Switch
import android.widget.TextView
@@ -14,6 +16,7 @@ import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.edit
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
+import com.permissionx.guolindev.PermissionX
import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT
import mobile.Mobile
import org.json.JSONArray
@@ -43,6 +46,46 @@ class MainActivity : AppCompatActivity() {
}
}
+ private fun checkBLEPermissions() {
+ val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
+ val bleEnabled = preferences.getBoolean(BLE_ENABLED, (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S))
+ if (!bleEnabled) {
+ return
+ }
+
+ PermissionX.init(this)
+ .permissions(
+ Manifest.permission.ACCESS_FINE_LOCATION,
+ Manifest.permission.BLUETOOTH_ADVERTISE,
+ Manifest.permission.BLUETOOTH_CONNECT,
+ Manifest.permission.BLUETOOTH_SCAN,
+ )
+ .onExplainRequestReason { scope, deniedList ->
+ scope.showRequestReasonDialog(
+ deniedList,
+ getString(R.string.explain_perms),
+ getString(R.string.ok),
+ getString(R.string.cancel),
+ )
+ }
+ .onForwardToSettings { scope, deniedList ->
+ scope.showForwardToSettingsDialog(
+ deniedList,
+ getString(R.string.manual_perms),
+ getString(R.string.ok),
+ getString(R.string.cancel),
+ )
+ }
+ .request { allGranted, _, _ ->
+ if(!allGranted) {
+ preferences.edit().apply {
+ putBoolean(BLE_ENABLED, false)
+ commit()
+ }
+ }
+ }
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
@@ -65,6 +108,7 @@ class MainActivity : AppCompatActivity() {
enabledSwitch.setOnCheckedChangeListener { _, isChecked ->
when (isChecked) {
true -> {
+ checkBLEPermissions()
val vpnIntent = VpnService.prepare(this)
if (vpnIntent != null) {
startVpnActivity.launch(vpnIntent)
diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt
index 36d3b56..9cbe14b 100644
--- a/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt
+++ b/app/src/main/java/eu/neilalexander/yggdrasil/PacketTunnelProvider.kt
@@ -9,10 +9,14 @@ import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT
+import org.akselrod.blemesh.BLEService
import mobile.Yggdrasil
import org.json.JSONArray
import java.io.FileInputStream
import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.net.Socket
import java.util.concurrent.atomic.AtomicBoolean
import kotlin.concurrent.thread
@@ -43,6 +47,8 @@ open class PacketTunnelProvider: VpnService() {
private var readerStream: FileInputStream? = null
private var writerStream: FileOutputStream? = null
+ private var bleService: BLEService? = null
+
override fun onCreate() {
super.onCreate()
config = ConfigurationProxy(applicationContext)
@@ -175,6 +181,13 @@ open class PacketTunnelProvider: VpnService() {
intent = Intent(YGG_STATE_INTENT)
intent.putExtra("state", STATE_ENABLED)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent)
+
+ if (preferences.getBoolean(BLE_ENABLED, (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S))) {
+ val publicKey = config.getJSON().getString("PublicKey")
+ val codedPhy = preferences.getBoolean(CODED_PHY_ENABLED, false)
+ bleService = BLEService(this.baseContext, publicKey, codedPhy, ::peerConnect)
+ bleService?.start()
+ }
}
private fun stop() {
@@ -182,6 +195,9 @@ open class PacketTunnelProvider: VpnService() {
return
}
+ bleService?.stop()
+ bleService = null
+
yggdrasil.stop()
readerStream?.let {
@@ -336,4 +352,17 @@ open class PacketTunnelProvider: VpnService() {
readerStream = null
}
}
+
+ private fun peerConnect(): Pair? {
+ var socket: Socket?
+ try {
+ socket = Socket("127.0.0.1", 9004)
+ } catch (e: Exception) {
+ Log.e(TAG, "Couldn't open peer socket: $e")
+ return null
+ }
+ return Pair(socket.inputStream, socket.outputStream)
+ }
+
+
}
diff --git a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt
index a3f4e08..8e5ff83 100644
--- a/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt
+++ b/app/src/main/java/eu/neilalexander/yggdrasil/PeersActivity.kt
@@ -7,11 +7,13 @@ import android.content.Intent
import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
+import android.os.Build
import android.view.ContextThemeWrapper
import android.view.LayoutInflater
import android.view.View
import android.widget.*
import androidx.localbroadcastmanager.content.LocalBroadcastManager
+import androidx.preference.PreferenceManager
import com.google.android.material.textfield.TextInputEditText
import org.json.JSONArray
import org.json.JSONObject
@@ -27,6 +29,8 @@ class PeersActivity : AppCompatActivity() {
private lateinit var configuredTableLabel: TextView
private lateinit var multicastListenSwitch: Switch
private lateinit var multicastBeaconSwitch: Switch
+ private lateinit var enableBLESwitch: Switch
+ private lateinit var enableCodedPHYSwitch: Switch
private lateinit var addPeerButton: ImageButton
override fun onCreate(savedInstanceState: Bundle?) {
@@ -51,9 +55,29 @@ class PeersActivity : AppCompatActivity() {
multicastBeaconSwitch.setOnCheckedChangeListener { button, _ ->
config.multicastBeacon = button.isChecked
}
+
+ val preferences = PreferenceManager.getDefaultSharedPreferences(this.baseContext)
+
+ enableBLESwitch = findViewById(R.id.enableBLE)
+ enableBLESwitch.setOnCheckedChangeListener { button, _ ->
+ preferences.edit().apply {
+ putBoolean(BLE_ENABLED, button.isChecked)
+ commit()
+ }
+ }
+ enableCodedPHYSwitch = findViewById(R.id.enableCodedPHY)
+ enableCodedPHYSwitch.setOnCheckedChangeListener { button, _ ->
+ preferences.edit().apply {
+ putBoolean(CODED_PHY_ENABLED, button.isChecked)
+ commit()
+ }
+ }
+
multicastListenSwitch.isChecked = config.multicastListen
multicastBeaconSwitch.isChecked = config.multicastBeacon
-
+ enableBLESwitch.isChecked = preferences.getBoolean(BLE_ENABLED, (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S))
+ enableCodedPHYSwitch.isChecked = preferences.getBoolean(CODED_PHY_ENABLED, false)
+
val multicastBeaconPanel = findViewById(R.id.enableMulticastBeaconPanel)
multicastBeaconPanel.setOnClickListener {
multicastBeaconSwitch.toggle()
@@ -63,6 +87,19 @@ class PeersActivity : AppCompatActivity() {
multicastListenSwitch.toggle()
}
+ val enableBLEPanel = findViewById(R.id.enableBLEPanel)
+ val enableCodedPHYPanel = findViewById(R.id.enableCodedPHYPanel)
+
+ enableBLEPanel.isEnabled = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+ enableCodedPHYPanel.isEnabled = (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
+
+ enableBLEPanel.setOnClickListener {
+ enableBLESwitch.toggle()
+ }
+ enableCodedPHYPanel.setOnClickListener {
+ enableCodedPHYSwitch.toggle()
+ }
+
addPeerButton = findViewById(R.id.addPeerButton)
addPeerButton.setOnClickListener {
val view = inflater.inflate(R.layout.dialog_addpeer, null)
@@ -181,4 +218,4 @@ class PeersActivity : AppCompatActivity() {
}
}
}
-}
\ No newline at end of file
+}
diff --git a/app/src/main/res/layout/activity_peers.xml b/app/src/main/res/layout/activity_peers.xml
index 88724c2..d6e144c 100644
--- a/app/src/main/res/layout/activity_peers.xml
+++ b/app/src/main/res/layout/activity_peers.xml
@@ -217,6 +217,50 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Подключения пиров
Находимый через multicast
Искать пиров через multicast
+ Искать пиров через Bluetooth LE
+ Использовать BLE Coded PHY
+ Для поиска Bluetooth пиров, разрешите Nearby Devices и Location
+ Для поиска Bluetooth пиров, разрешите Nearby Devices и Location в настройках
Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир.
- Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.
+ Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB или BLE (Android 12+). Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.
Об узле
Название устройства
Нажмите для изменения
@@ -83,4 +87,4 @@
Приватный ключ:
Установить свои ключи
Сохранить
-
\ No newline at end of file
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 96e6545..22ca57e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -57,8 +57,12 @@
Peer Connectivity
Discoverable over multicast
Search for multicast peers
+ Search for peers over Bluetooth LE
+ Use BLE Coded PHY
+ Bluetooth peering requires Nearby Devices and Location permissions
+ To use Bluetooth peering, enable Nearby Devices and Location permissions manually
Yggdrasil will automatically attempt to connect to configured peers when started. If you configure more than one peer, your device may carry traffic on behalf of other network nodes. Avoid this by configuring only a single peer.
- Multicast peers will be discovered on the same Wi-Fi network or via USB. Data charges may apply when using mobile data. You can prevent data usage in the device settings.
+ Multicast peers will be discovered on the same Wi-Fi network or via USB or BLE (Android 12+). Data charges may apply when using mobile data. You can prevent data usage in the device settings.
Node Info
Device Name
Tap to edit
@@ -83,4 +87,4 @@
Private key:
Set your own keys
Save
-
\ No newline at end of file
+
diff --git a/settings.gradle b/settings.gradle
index 8413a98..7726caa 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,8 @@
rootProject.name = "Yggdrasil"
include ':app'
+
+sourceControl {
+ gitRepository("https://codeberg.org/aakselrod/blemesh-android.git") {
+ producesModule("org.akselrod.blemesh:lib")
+ }
+}