Compare commits

...

6 Commits

Author SHA1 Message Date
majestrate 28ccc29512
Merge pull request #10 from frtget/fix-status-updates
Update status properly
2022-03-08 08:19:19 -05:00
frtget cd8d515038 Also check if VPN is enabled 2022-03-08 00:28:19 -05:00
frtget 5f45a9f743 Propagate status updates from daemon 2022-03-07 23:25:36 -05:00
frtget 5d50451c3b Finalize v2 android embedding migration 2022-03-07 22:46:16 -05:00
frtget cc7c93d179 Fix typos 2022-03-07 00:07:13 -05:00
frtget bfdb8b1316 Bump gradle plugins, kotlin, gradle and compileSdk 2022-03-06 23:42:35 -05:00
17 changed files with 191 additions and 99 deletions

View File

@ -26,7 +26,7 @@ apply plugin: 'kotlin-android'
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
android {
compileSdkVersion 29
compileSdkVersion 31
sourceSets {
main.java.srcDirs += 'src/main/kotlin'

View File

@ -14,7 +14,7 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:name="io.flutter.app.FlutterApplication"
android:name="${applicationName}"
android:label="Lokinet"
android:icon="@mipmap/ic_launcher">
<activity
@ -32,15 +32,6 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="@drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View File

@ -1,12 +1,12 @@
buildscript {
ext.kotlin_version = '1.3.50'
ext.kotlin_version = '1.6.10'
repositories {
google()
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.5.0'
classpath "com.android.tools.build:gradle:7.0.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View File

@ -4,7 +4,7 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:lokinet_lib/lokinet_lib.dart';
import 'package:lokinet_mobile/src/settings.dart';
import 'package:lokinet_mobile/src/utils/is_dakmode.dart';
import 'package:lokinet_mobile/src/utils/is_darkmode.dart';
import 'package:lokinet_mobile/src/widget/lokinet_divider.dart';
import 'package:lokinet_mobile/src/widget/lokinet_power_button.dart';
import 'package:lokinet_mobile/src/widget/themed_lokinet_logo.dart';
@ -87,14 +87,14 @@ class MyForm extends StatefulWidget {
class MyFormState extends State<MyForm> {
static final key = new GlobalKey<FormState>();
Timer _timer;
bool isConnected = false;
void _startTimer() {
const halfSec = Duration(milliseconds: 50);
_timer = Timer.periodic(halfSec, (Timer timer) async {
await _updateLokinetStatus();
});
@override
initState() {
super.initState();
LokinetLib.onStatusUpdate((bool status) => setState(() {
isConnected = status;
}));
}
Future _updateLokinetStatus() async {
@ -104,18 +104,12 @@ class MyFormState extends State<MyForm> {
});
}
Future _cancelTimer() async {
await _updateLokinetStatus();
if (_timer != null) _timer.cancel();
}
Future toogleLokinet() async {
Future toggleLokinet() async {
if (!key.currentState.validate()) {
return;
}
if (await LokinetLib.isRunning) {
await LokinetLib.disconnectFromLokinet();
await _cancelTimer();
} else {
//Save the exit node and upstream dns
final Settings settings = Settings.getInstance();
@ -126,7 +120,6 @@ class MyFormState extends State<MyForm> {
if (result)
LokinetLib.connectToLokinet(
exitNode: settings.exitNode, upstreamDNS: settings.upstreamDNS);
_startTimer();
}
}
@ -140,7 +133,7 @@ class MyFormState extends State<MyForm> {
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
LokinetPowerButton(toogleLokinet),
LokinetPowerButton(toggleLokinet),
LokinetDivider(),
Padding(
padding: EdgeInsets.only(left: 45, right: 45),

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lokinet_mobile/src/utils/is_dakmode.dart';
import 'package:lokinet_mobile/src/utils/is_darkmode.dart';
class LokinetDivider extends StatelessWidget {
final String _ending;

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lokinet_mobile/src/utils/is_dakmode.dart';
import 'package:lokinet_mobile/src/utils/is_darkmode.dart';
class LokinetPowerButton extends StatelessWidget {
final VoidCallback onPressed;

View File

@ -1,5 +1,5 @@
import 'package:flutter/material.dart';
import 'package:lokinet_mobile/src/utils/is_dakmode.dart';
import 'package:lokinet_mobile/src/utils/is_darkmode.dart';
class ThemedLokinetLogo extends StatelessWidget {
@override

View File

@ -2,7 +2,7 @@ group "io.oxen.lokinet_lib"
version "1.0-SNAPSHOT"
buildscript {
ext.kotlin_version = "1.3.50"
ext.kotlin_version = "1.6.10"
repositories {
google()
jcenter()
@ -12,7 +12,7 @@ buildscript {
}
dependencies {
classpath "com.android.tools.build:gradle:3.5.0"
classpath "com.android.tools.build:gradle:7.0.0"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.diffplug.spotless:spotless-plugin-gradle:5.14.0"
}
@ -49,7 +49,7 @@ spotless {
}
android {
compileSdkVersion 29
compileSdkVersion 31
ndkVersion '21.3.6528147'
sourceSets {

View File

@ -2,4 +2,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip

View File

@ -13,7 +13,7 @@ public class LokinetConfig {
/*** load config file from disk */
public native boolean Load();
/*** save chages to disk */
/*** save changes to disk */
public native boolean Save();
/** override default config value before loading from config file */

View File

@ -6,8 +6,11 @@ import android.os.Binder;
import android.os.IBinder;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import androidx.lifecycle.MutableLiveData;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Timer;
import java.util.TimerTask;
public class LokinetDaemon extends VpnService {
@ -50,13 +53,24 @@ public class LokinetDaemon extends VpnService {
int m_FD = -1;
int m_UDPSocket = -1;
private Timer mStatusCheckTimer;
private MutableLiveData<Boolean> mStatus = new MutableLiveData<Boolean>();
@Override
public void onCreate() {
mStatus.postValue(false);
mStatusCheckTimer = new Timer();
mStatusCheckTimer.schedule(new CheckStatus(), 0, 500);
super.onCreate();
}
@Override
public void onDestroy() {
if (mStatusCheckTimer != null) {
mStatusCheckTimer.cancel();
mStatusCheckTimer = null;
}
super.onDestroy();
disconnect();
}
@ -93,12 +107,20 @@ public class LokinetDaemon extends VpnService {
// set log leve to info
configVals.add(new ConfigValue("logging", "level", "info"));
boolean connectedSucessfully = connect(configVals);
if (connectedSucessfully) return START_STICKY;
else return START_NOT_STICKY;
boolean connectedSuccessfully = connect(configVals);
if (connectedSuccessfully)
return START_STICKY;
else
return START_NOT_STICKY;
}
}
@Override
public void onRevoke() {
mStatus.postValue(false);
super.onRevoke();
}
private class ConfigValue {
final String Section;
final String Key;
@ -111,8 +133,10 @@ public class LokinetDaemon extends VpnService {
}
public boolean Valid() {
if (Section == null || Key == null || Value == null) return false;
if (Section.isEmpty() || Key.isEmpty() || Value.isEmpty()) return false;
if (Section == null || Key == null || Value == null)
return false;
if (Section.isEmpty() || Key.isEmpty() || Value.isEmpty())
return false;
return true;
}
}
@ -153,7 +177,8 @@ public class LokinetDaemon extends VpnService {
for (ConfigValue conf : configVals) {
if (conf.Valid()) {
config.AddDefaultValue(conf.Section, conf.Key, conf.Value);
if (conf.Section.equals("dns") && conf.Key.equals("upstream")) upstreamDNS = conf.Value;
if (conf.Section.equals("dns") && conf.Key.equals("upstream"))
upstreamDNS = conf.Value;
}
}
}
@ -192,18 +217,19 @@ public class LokinetDaemon extends VpnService {
InjectVPNFD();
new Thread(
() -> {
Configure(config);
m_UDPSocket = GetUDPSocket();
protect(m_UDPSocket);
Mainloop();
})
() -> {
Configure(config);
m_UDPSocket = GetUDPSocket();
protect(m_UDPSocket);
Mainloop();
})
.start();
Log.d(LOG_TAG, "started successfully!");
} else {
Log.d(LOG_TAG, "already running");
}
updateStatus();
return true;
}
@ -211,15 +237,24 @@ public class LokinetDaemon extends VpnService {
if (IsRunning()) {
Stop();
}
// if (impl != null) {
// Free(impl);
// impl = null;
// }
// if (impl != null) {
// Free(impl);
// impl = null;
// }
updateStatus();
}
public MutableLiveData<Boolean> getStatus() {
return mStatus;
}
private void updateStatus() {
mStatus.postValue(IsRunning() && VpnService.prepare(LokinetDaemon.this) == null);
}
/**
* Class for clients to access. Because we know this service always runs in the same process as
* its clients, we don't need to deal with IPC.
* Class for clients to access. Because we know this service always runs in the
* same process as its clients, we don't need to deal with IPC.
*/
public class LocalBinder extends Binder {
public LokinetDaemon getService() {
@ -233,4 +268,10 @@ public class LokinetDaemon extends VpnService {
}
private final IBinder mBinder = new LocalBinder();
}
private class CheckStatus extends TimerTask {
public void run() {
updateStatus();
}
}
}

View File

@ -9,9 +9,11 @@ import android.net.VpnService
import android.os.IBinder
import android.util.Log
import androidx.annotation.NonNull
import androidx.lifecycle.Observer
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.embedding.engine.plugins.activity.ActivityAware
import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -29,17 +31,40 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
///
/// This local reference serves to register the plugin with the Flutter Engine and unregister it
/// when the Flutter Engine is detached from the Activity
private lateinit var channel: MethodChannel
private lateinit var mMethodChannel: MethodChannel
private lateinit var mStatusEventChannel: EventChannel
private var mEventSink: EventChannel.EventSink? = null
private var mStatusObserver =
Observer<Boolean> { newStatus ->
// Propagate to the dart package.
mEventSink?.success(newStatus)
}
override fun onAttachedToEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
System.loadLibrary("lokinet-android")
channel = MethodChannel(binding.binaryMessenger, "lokinet_lib")
channel.setMethodCallHandler(this)
mMethodChannel = MethodChannel(binding.binaryMessenger, "lokinet_lib_method_channel")
mMethodChannel.setMethodCallHandler(this)
mStatusEventChannel =
EventChannel(binding.binaryMessenger, "lokinet_lib_status_event_channel")
mStatusEventChannel.setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
mEventSink = events
}
override fun onCancel(arguments: Any?) {
mEventSink?.endOfStream()
mEventSink = null
}
}
)
}
override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
channel.setMethodCallHandler(null)
mMethodChannel.setMethodCallHandler(null)
doUnbindService()
}
@ -49,15 +74,16 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
val intent = VpnService.prepare(activityBinding.activity.applicationContext)
if (intent != null) {
var listener: PluginRegistry.ActivityResultListener? = null
listener = PluginRegistry.ActivityResultListener { req, res, _ ->
if (req == 0 && res == RESULT_OK) {
result.success(true)
} else {
result.success(false)
}
listener?.let { activityBinding.removeActivityResultListener(it) }
true
}
listener =
PluginRegistry.ActivityResultListener { req, res, _ ->
if (req == 0 && res == RESULT_OK) {
result.success(true)
} else {
result.success(false)
}
listener?.let { activityBinding.removeActivityResultListener(it) }
true
}
activityBinding.addActivityResultListener(listener)
activityBinding.activity.startActivityForResult(intent, 0)
} else {
@ -80,7 +106,11 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
val exitNode = call.argument<String>("exit_node")
val upstreamDNS = call.argument<String>("upstream_dns")
val lokinetIntent = Intent(activityBinding.activity.applicationContext, LokinetDaemon::class.java)
val lokinetIntent =
Intent(
activityBinding.activity.applicationContext,
LokinetDaemon::class.java
)
lokinetIntent.action = LokinetDaemon.ACTION_CONNECT
lokinetIntent.putExtra(LokinetDaemon.EXIT_NODE, exitNode)
lokinetIntent.putExtra(LokinetDaemon.UPSTREAM_DNS, upstreamDNS)
@ -91,7 +121,11 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
result.success(true)
}
"disconnect" -> {
val lokinetIntent = Intent(activityBinding.activity.applicationContext, LokinetDaemon::class.java)
val lokinetIntent =
Intent(
activityBinding.activity.applicationContext,
LokinetDaemon::class.java
)
lokinetIntent.action = LokinetDaemon.ACTION_DISCONNECT
activityBinding.activity.applicationContext.startService(lokinetIntent)
@ -129,26 +163,36 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
override fun onDetachedFromActivityForConfigChanges() {}
private val mConnection: ServiceConnection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
mBoundService = (service as LokinetDaemon.LocalBinder).getService()
}
private val mConnection: ServiceConnection =
object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
mBoundService = (service as LokinetDaemon.LocalBinder).getService()
override fun onServiceDisconnected(className: ComponentName) {
mBoundService = null
}
}
mBoundService?.getStatus()?.observeForever(mStatusObserver)
}
override fun onServiceDisconnected(className: ComponentName) {
mBoundService?.getStatus()?.removeObserver(mStatusObserver)
mBoundService = null
}
}
fun doBindService() {
if (activityBinding.activity.applicationContext.bindService(
Intent(activityBinding.activity.applicationContext, LokinetDaemon::class.java),
mConnection, Context.BIND_AUTO_CREATE
)
Intent(
activityBinding.activity.applicationContext,
LokinetDaemon::class.java
),
mConnection,
Context.BIND_AUTO_CREATE
)
) {
mShouldUnbind = true
} else {
Log.e(
LokinetDaemon.LOG_TAG, "Error: The requested service doesn't exist, or this client isn't allowed access to it."
LokinetDaemon.LOG_TAG,
"Error: The requested service doesn't exist, or this client isn't allowed access to it."
)
}
}
@ -160,4 +204,3 @@ class LokinetLibPlugin : FlutterPlugin, MethodCallHandler, ActivityAware {
}
}
}

View File

@ -6,7 +6,11 @@ import 'package:flutter/services.dart';
import 'package:path_provider/path_provider.dart';
class LokinetLib {
static const MethodChannel _channel = const MethodChannel('lokinet_lib');
static const MethodChannel _methodChannel =
const MethodChannel('lokinet_lib_method_channel');
static const EventChannel _statusEventChannel =
const EventChannel('lokinet_lib_status_event_channel');
static Future bootstrapLokinet() async {
final request = await HttpClient()
@ -25,29 +29,29 @@ class LokinetLib {
static Future<bool> prepareConnection() async {
if (!(await isBootstrapped)) await bootstrapLokinet();
final bool prepare = await _channel.invokeMethod('prepare');
final bool prepare = await _methodChannel.invokeMethod('prepare');
return prepare;
}
static Future<bool> connectToLokinet(
{String exitNode = "exit.loki", String upstreamDNS = "9.9.9.9"}) async {
final bool connect = await _channel.invokeMethod(
final bool connect = await _methodChannel.invokeMethod(
'connect', {"exit_node": exitNode, "upstream_dns": upstreamDNS});
return connect;
}
static Future<bool> disconnectFromLokinet() async {
final bool disconnect = await _channel.invokeMethod('disconnect');
final bool disconnect = await _methodChannel.invokeMethod('disconnect');
return disconnect;
}
static Future<bool> get isPrepared async {
final bool prepared = await _channel.invokeMethod('isPrepared');
final bool prepared = await _methodChannel.invokeMethod('isPrepared');
return prepared;
}
static Future<bool> get isRunning async {
final bool isRunning = await _channel.invokeMethod('isRunning');
final bool isRunning = await _methodChannel.invokeMethod('isRunning');
return isRunning;
}
@ -57,8 +61,14 @@ class LokinetLib {
}
static Future<dynamic> get status async {
var status = await _channel.invokeMethod('getStatus') as String;
var status = await _methodChannel.invokeMethod('getStatus') as String;
if (status.isNotEmpty) return jsonDecode(status);
return null;
}
static void onStatusUpdate(void Function(bool) onStatusUpdateCallback) {
_statusEventChannel
.receiveBroadcastStream()
.listen((dynamic status) => onStatusUpdateCallback(status));
}
}

View File

@ -88,6 +88,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
meta:
dependency: transitive
description:
@ -204,7 +211,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
version: "0.4.8"
typed_data:
dependency: transitive
description:

View File

@ -77,7 +77,7 @@ packages:
name: cupertino_icons
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
version: "1.0.4"
fake_async:
dependency: transitive
description:
@ -149,6 +149,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.12.11"
material_color_utilities:
dependency: transitive
description:
name: material_color_utilities
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.3"
meta:
dependency: transitive
description:
@ -211,7 +218,7 @@ packages:
name: platform
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.0"
version: "3.1.0"
plugin_platform_interface:
dependency: transitive
description:
@ -225,7 +232,7 @@ packages:
name: process
url: "https://pub.dartlang.org"
source: hosted
version: "4.2.1"
version: "4.2.4"
shared_preferences:
dependency: "direct main"
description:
@ -314,7 +321,7 @@ packages:
name: test_api
url: "https://pub.dartlang.org"
source: hosted
version: "0.4.3"
version: "0.4.8"
typed_data:
dependency: transitive
description:
@ -335,7 +342,7 @@ packages:
name: win32
url: "https://pub.dartlang.org"
source: hosted
version: "2.2.2"
version: "2.4.1"
xdg_directories:
dependency: transitive
description:
@ -358,5 +365,5 @@ packages:
source: hosted
version: "2.2.1"
sdks:
dart: ">=2.14.0 <3.0.0"
dart: ">=2.15.0 <3.0.0"
flutter: ">=1.20.0"