This commit is contained in:
Alex Akselrod 2023-10-13 18:21:24 +00:00 committed by GitHub
commit a97823b440
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 204 additions and 12 deletions

View file

@ -4,13 +4,13 @@ plugins {
} }
android { android {
compileSdkVersion 29 compileSdkVersion 31
buildToolsVersion "30.0.3" buildToolsVersion "30.0.3"
defaultConfig { defaultConfig {
applicationId "eu.neilalexander.yggdrasil" applicationId "eu.neilalexander.yggdrasil"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 31
versionCode 13 versionCode 13
versionName "0.1-013" versionName "0.1-013"
@ -37,6 +37,7 @@ android {
minifyEnabled false minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
signingConfig = signingConfigs.getByName("yggdrasil") signingConfig = signingConfigs.getByName("yggdrasil")
matchingFallbacks = ['release']
} }
} }
compileOptions { compileOptions {
@ -51,12 +52,19 @@ android {
dependencies { dependencies {
implementation fileTree(include: ['*.aar'], dir: 'libs') implementation fileTree(include: ['*.aar'], dir: 'libs')
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 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.core:core-ktx:1.5.0'
implementation 'androidx.appcompat:appcompat:1.3.0' implementation 'androidx.appcompat:appcompat:1.3.0'
implementation 'com.google.android.material:material:1.3.0' implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4' implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.preference:preference-ktx:1.1.0' implementation 'androidx.preference:preference-ktx:1.1.0'
implementation 'com.guolindev.permissionx:permissionx:1.6.4'
testImplementation 'junit:junit:4.+' testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation('org.akselrod.blemesh:lib:0.0.1') {
version {
branch = 'main'
}
}
} }

View file

@ -6,6 +6,10 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.BLUETOOTH_ADVERTISE" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<application <application
android:name=".GlobalApplication" android:name=".GlobalApplication"
@ -41,6 +45,7 @@
<service <service
android:name=".PacketTunnelProvider" android:name=".PacketTunnelProvider"
android:foregroundServiceType="location"
android:permission="android.permission.BIND_VPN_SERVICE" android:permission="android.permission.BIND_VPN_SERVICE"
android:exported="true"> android:exported="true">
<intent-filter> <intent-filter>
@ -67,4 +72,4 @@
</receiver> </receiver>
</application> </application>
</manifest> </manifest>

View file

@ -55,6 +55,7 @@ object ConfigurationProxy {
json.put("AdminListen", "none") json.put("AdminListen", "none")
json.put("IfName", "none") json.put("IfName", "none")
json.put("IfMTU", 65535) json.put("IfMTU", 65535)
json.put("Listen", JSONArray(arrayOf("tcp://127.0.0.1:9004")))
if (json.getJSONArray("MulticastInterfaces").get(0) is String) { if (json.getJSONArray("MulticastInterfaces").get(0) is String) {
var ar = JSONArray() var ar = JSONArray()
@ -94,4 +95,4 @@ object ConfigurationProxy {
(json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Beacon", value) (json.getJSONArray("MulticastInterfaces").get(0) as JSONObject).put("Beacon", value)
} }
} }
} }

View file

@ -10,6 +10,8 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat import androidx.core.app.NotificationCompat
const val PREF_KEY_ENABLED = "enabled" const val PREF_KEY_ENABLED = "enabled"
const val BLE_ENABLED = "ble"
const val CODED_PHY_ENABLED = "codedPhy"
const val MAIN_CHANNEL_ID = "Yggdrasil Service" const val MAIN_CHANNEL_ID = "Yggdrasil Service"
class GlobalApplication: Application(), YggStateReceiver.StateReceiver { class GlobalApplication: Application(), YggStateReceiver.StateReceiver {
@ -68,7 +70,7 @@ fun createServiceNotification(context: Context, state: State): Notification {
val intent = Intent(context, MainActivity::class.java).apply { val intent = Intent(context, MainActivity::class.java).apply {
this.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK 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) { val text = when (state) {
State.Disabled -> context.getText(R.string.tile_disabled) State.Disabled -> context.getText(R.string.tile_disabled)
@ -119,4 +121,4 @@ private fun createNotificationChannels(context: Context) {
context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
notificationManager.createNotificationChannel(channel) notificationManager.createNotificationChannel(channel)
} }
} }

View file

@ -1,9 +1,11 @@
package eu.neilalexander.yggdrasil package eu.neilalexander.yggdrasil
import android.Manifest
import android.app.Activity import android.app.Activity
import android.content.* import android.content.*
import android.graphics.Color import android.graphics.Color
import android.net.VpnService import android.net.VpnService
import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.widget.Switch import android.widget.Switch
import android.widget.TextView import android.widget.TextView
@ -14,6 +16,7 @@ import androidx.appcompat.widget.LinearLayoutCompat
import androidx.core.content.edit import androidx.core.content.edit
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import com.permissionx.guolindev.PermissionX
import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT import eu.neilalexander.yggdrasil.PacketTunnelProvider.Companion.STATE_INTENT
import mobile.Mobile import mobile.Mobile
import org.json.JSONArray 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?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main) setContentView(R.layout.activity_main)
@ -65,6 +108,7 @@ class MainActivity : AppCompatActivity() {
enabledSwitch.setOnCheckedChangeListener { _, isChecked -> enabledSwitch.setOnCheckedChangeListener { _, isChecked ->
when (isChecked) { when (isChecked) {
true -> { true -> {
checkBLEPermissions()
val vpnIntent = VpnService.prepare(this) val vpnIntent = VpnService.prepare(this)
if (vpnIntent != null) { if (vpnIntent != null) {
startVpnActivity.launch(vpnIntent) startVpnActivity.launch(vpnIntent)

View file

@ -9,11 +9,16 @@ import android.util.Log
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT import eu.neilalexander.yggdrasil.YggStateReceiver.Companion.YGG_STATE_INTENT
import org.akselrod.blemesh.BLEService
import mobile.Yggdrasil import mobile.Yggdrasil
import org.json.JSONArray import org.json.JSONArray
import java.io.FileInputStream import java.io.FileInputStream
import java.io.FileOutputStream import java.io.FileOutputStream
import java.io.InputStream
import java.io.OutputStream
import java.net.Socket
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.UUID
import kotlin.concurrent.thread import kotlin.concurrent.thread
@ -43,6 +48,8 @@ open class PacketTunnelProvider: VpnService() {
private var readerStream: FileInputStream? = null private var readerStream: FileInputStream? = null
private var writerStream: FileOutputStream? = null private var writerStream: FileOutputStream? = null
private var bleService: BLEService? = null
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
config = ConfigurationProxy(applicationContext) config = ConfigurationProxy(applicationContext)
@ -175,6 +182,20 @@ open class PacketTunnelProvider: VpnService() {
intent = Intent(YGG_STATE_INTENT) intent = Intent(YGG_STATE_INTENT)
intent.putExtra("state", STATE_ENABLED) intent.putExtra("state", STATE_ENABLED)
LocalBroadcastManager.getInstance(this).sendBroadcast(intent) 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,
UUID.fromString("4ec394b1-44c7-5f5b-9172-530cbf056f8e"),
UUID.fromString("9ed2717f-4dad-53fb-b682-52b2a4b077f8"),
publicKey,
codedPhy,
::peerConnect,
)
bleService?.start()
}
} }
private fun stop() { private fun stop() {
@ -182,6 +203,9 @@ open class PacketTunnelProvider: VpnService() {
return return
} }
bleService?.stop()
bleService = null
yggdrasil.stop() yggdrasil.stop()
readerStream?.let { readerStream?.let {
@ -336,4 +360,17 @@ open class PacketTunnelProvider: VpnService() {
readerStream = null readerStream = null
} }
} }
private fun peerConnect(): Pair<InputStream, OutputStream>? {
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)
}
} }

View file

@ -7,11 +7,13 @@ import android.content.Intent
import android.content.IntentFilter import android.content.IntentFilter
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle import android.os.Bundle
import android.os.Build
import android.view.ContextThemeWrapper import android.view.ContextThemeWrapper
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.widget.* import android.widget.*
import androidx.localbroadcastmanager.content.LocalBroadcastManager import androidx.localbroadcastmanager.content.LocalBroadcastManager
import androidx.preference.PreferenceManager
import com.google.android.material.textfield.TextInputEditText import com.google.android.material.textfield.TextInputEditText
import org.json.JSONArray import org.json.JSONArray
import org.json.JSONObject import org.json.JSONObject
@ -27,6 +29,8 @@ class PeersActivity : AppCompatActivity() {
private lateinit var configuredTableLabel: TextView private lateinit var configuredTableLabel: TextView
private lateinit var multicastListenSwitch: Switch private lateinit var multicastListenSwitch: Switch
private lateinit var multicastBeaconSwitch: Switch private lateinit var multicastBeaconSwitch: Switch
private lateinit var enableBLESwitch: Switch
private lateinit var enableCodedPHYSwitch: Switch
private lateinit var addPeerButton: ImageButton private lateinit var addPeerButton: ImageButton
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -51,9 +55,29 @@ class PeersActivity : AppCompatActivity() {
multicastBeaconSwitch.setOnCheckedChangeListener { button, _ -> multicastBeaconSwitch.setOnCheckedChangeListener { button, _ ->
config.multicastBeacon = button.isChecked 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 multicastListenSwitch.isChecked = config.multicastListen
multicastBeaconSwitch.isChecked = config.multicastBeacon 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<TableRow>(R.id.enableMulticastBeaconPanel) val multicastBeaconPanel = findViewById<TableRow>(R.id.enableMulticastBeaconPanel)
multicastBeaconPanel.setOnClickListener { multicastBeaconPanel.setOnClickListener {
multicastBeaconSwitch.toggle() multicastBeaconSwitch.toggle()
@ -63,6 +87,19 @@ class PeersActivity : AppCompatActivity() {
multicastListenSwitch.toggle() multicastListenSwitch.toggle()
} }
val enableBLEPanel = findViewById<TableRow>(R.id.enableBLEPanel)
val enableCodedPHYPanel = findViewById<TableRow>(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 = findViewById(R.id.addPeerButton)
addPeerButton.setOnClickListener { addPeerButton.setOnClickListener {
val view = inflater.inflate(R.layout.dialog_addpeer, null) val view = inflater.inflate(R.layout.dialog_addpeer, null)
@ -181,4 +218,4 @@ class PeersActivity : AppCompatActivity() {
} }
} }
} }
} }

View file

@ -217,6 +217,50 @@
</TableRow> </TableRow>
<TableRow
android:id="@+id/enableBLEPanel"
style="@style/SelectableSwitchItemStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable_ble"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<Switch
android:id="@+id/enableBLE"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</TableRow>
<TableRow
android:id="@+id/enableCodedPHYPanel"
style="@style/SelectableSwitchItemStyle">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/enable_coded_phy"
android:textColor="?attr/textDefault" />
<Space
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="2" />
<Switch
android:id="@+id/enableCodedPHY"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</TableRow>
</TableLayout> </TableLayout>
<TextView <TextView

View file

@ -57,8 +57,12 @@
<string name="peer_connectivity_title">Подключения пиров</string> <string name="peer_connectivity_title">Подключения пиров</string>
<string name="discoverable_over_multicast">Находимый через multicast</string> <string name="discoverable_over_multicast">Находимый через multicast</string>
<string name="search_for_multicast_peers">Искать пиров через multicast</string> <string name="search_for_multicast_peers">Искать пиров через multicast</string>
<string name="enable_ble">Искать пиров через Bluetooth LE</string>
<string name="enable_coded_phy">Использовать BLE Coded PHY</string>
<string name="explain_perms">Для поиска Bluetooth пиров, разрешите Nearby Devices и Location</string>
<string name="manual_perms">Для поиска Bluetooth пиров, разрешите Nearby Devices и Location в настройках</string>
<string name="configured_peers_hint">Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир.</string> <string name="configured_peers_hint">Yggdrasil будет пытаться подключаться к этим пирам автоматически. Если вы добавите несколько пиров, ваше устройство может быть использовано для переноса данных между другими узлами сети. Чтобы этого избежать настройте только один пир.</string>
<string name="peer_connectivity_hint">Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB. Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.</string> <string name="peer_connectivity_hint">Пиры могут быть найдены с помощью Multicast если они находятся в той же Wi-Fi сети, либо через USB или BLE (Android 12+). Трафик в мобильной сети может быть платным. Вы можете отключить мобильные данные в настройках устройства.</string>
<string name="node_info">Об узле</string> <string name="node_info">Об узле</string>
<string name="device_name">Название устройства</string> <string name="device_name">Название устройства</string>
<string name="tap_to_edit">Нажмите для изменения</string> <string name="tap_to_edit">Нажмите для изменения</string>
@ -83,4 +87,4 @@
<string name="private_key_label">Приватный ключ:</string> <string name="private_key_label">Приватный ключ:</string>
<string name="set_keys">Установить свои ключи</string> <string name="set_keys">Установить свои ключи</string>
<string name="save">Сохранить</string> <string name="save">Сохранить</string>
</resources> </resources>

View file

@ -57,8 +57,12 @@
<string name="peer_connectivity_title">Peer Connectivity</string> <string name="peer_connectivity_title">Peer Connectivity</string>
<string name="discoverable_over_multicast">Discoverable over multicast</string> <string name="discoverable_over_multicast">Discoverable over multicast</string>
<string name="search_for_multicast_peers">Search for multicast peers</string> <string name="search_for_multicast_peers">Search for multicast peers</string>
<string name="enable_ble">Search for peers over Bluetooth LE</string>
<string name="enable_coded_phy">Use BLE Coded PHY</string>
<string name="explain_perms">Bluetooth peering requires Nearby Devices and Location permissions</string>
<string name="manual_perms">To use Bluetooth peering, enable Nearby Devices and Location permissions manually</string>
<string name="configured_peers_hint">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.</string> <string name="configured_peers_hint">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.</string>
<string name="peer_connectivity_hint">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.</string> <string name="peer_connectivity_hint">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.</string>
<string name="node_info">Node Info</string> <string name="node_info">Node Info</string>
<string name="device_name">Device Name</string> <string name="device_name">Device Name</string>
<string name="tap_to_edit">Tap to edit</string> <string name="tap_to_edit">Tap to edit</string>
@ -83,4 +87,4 @@
<string name="private_key_label">Private key:</string> <string name="private_key_label">Private key:</string>
<string name="set_keys">Set your own keys</string> <string name="set_keys">Set your own keys</string>
<string name="save">Save</string> <string name="save">Save</string>
</resources> </resources>

View file

@ -1,2 +1,8 @@
rootProject.name = "Yggdrasil" rootProject.name = "Yggdrasil"
include ':app' include ':app'
sourceControl {
gitRepository("https://codeberg.org/aakselrod/blemesh-android.git") {
producesModule("org.akselrod.blemesh:lib")
}
}