Skip to content

Commit

Permalink
Add event channel to handle state change
Browse files Browse the repository at this point in the history
  • Loading branch information
ctrysbita committed Dec 30, 2018
1 parent 2ccd015 commit cfd98ce
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 20 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@
import java.util.SortedSet;
import java.util.UUID;

import io.flutter.plugin.common.EventChannel;
import io.xdea.fluttervpn.VPNStateHandler;


public class CharonVpnService extends VpnService implements Runnable {
private static final String NOTIFICATION_CHANNEL = "org.strongswan.android.CharonVpnService.VPN_STATE_NOTIFICATION";
Expand Down Expand Up @@ -110,12 +113,12 @@ public int onStartCommand(Intent intent, int flags, int startId) {
Bundle bundle = intent.getExtras();
VpnProfile profile = new VpnProfile();
profile.setId(1);
profile.setUUID(UUID.fromString(bundle.getString("_uuid")));
profile.setName(bundle.getString("PROFILE_NAME"));
profile.setGateway(bundle.getString("PROFILE_NAME"));
profile.setVpnType(VpnType.fromIdentifier("ikev2-eap"));
profile.setUUID(UUID.randomUUID());
profile.setName(bundle.getString("address"));
profile.setGateway(bundle.getString("address"));
profile.setUsername(bundle.getString("username"));
profile.setPassword(bundle.getString("password"));
profile.setVpnType(VpnType.fromIdentifier("ikev2-eap"));
profile.setSelectedAppsHandling(0);
profile.setFlags(0);
setNextProfile(profile);
Expand Down Expand Up @@ -226,6 +229,11 @@ public void run() {
public void updateStatus(int status) {
NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
manager.notify(1, buildNotification());

// Update state through event channel.
EventChannel.EventSink sink = VPNStateHandler.Companion.getEventHandler();
if(sink != null)
sink.success(status);
}

/**
Expand Down
42 changes: 28 additions & 14 deletions android/src/main/kotlin/io/xdea/fluttervpn/FlutterVpnPlugin.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,25 @@
/**
* Copyright (C) 2018 Jason C.H
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

package io.xdea.fluttervpn

import android.app.Activity.RESULT_OK
import android.content.Intent
import android.net.VpnService
import android.os.Bundle
import android.support.v4.content.ContextCompat
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
Expand All @@ -22,16 +37,20 @@ class FlutterVpnPlugin(private val registrar: Registrar) : MethodCallHandler {
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
// Register method channel
// Register method channel.
val channel = MethodChannel(registrar.messenger(), "flutter_vpn")
channel.setMethodCallHandler(FlutterVpnPlugin(registrar))

// Register event channel to handle state change.
val eventChannel = EventChannel(registrar.messenger(), "flutter_vpn_states")
eventChannel.setStreamHandler(VPNStateHandler())
}

fun onPrepareResult(requestCode: Int, resultCode: Int, result: Result): Boolean {
if (requestCode == 0 && resultCode == RESULT_OK)
result.success(true)
else
result.error("error", "Failed to prepare", false)
result.error("PrepareError", "Failed to prepare", false)
return true
}
}
Expand All @@ -43,15 +62,15 @@ class FlutterVpnPlugin(private val registrar: Registrar) : MethodCallHandler {
val intent = VpnService.prepare(registrar.activeContext())
if (intent != null) {
registrar.addActivityResultListener { req, res, _ -> onPrepareResult(req, res, result) }

registrar.activity().startActivityForResult(intent, 0)

}
}
"connect" -> {
val intent = VpnService.prepare(registrar.activeContext())
if (intent != null)
result.error("error", "Not prepared", false)
if (intent != null) {
result.error("PrepareError", "Not prepared", false)
return
}
val map = call.arguments as HashMap<String, String>
val address = map["address"]
val username = map["username"]
Expand All @@ -69,15 +88,12 @@ class FlutterVpnPlugin(private val registrar: Registrar) : MethodCallHandler {

private fun connect(address: String?, username: String?, password: String?) {
val profileInfo = Bundle()
profileInfo.putString("_uuid", "be869700-4ad4-4215-8453-619a1472b384")
profileInfo.putString("address", address)
profileInfo.putString("username", username)
profileInfo.putString("password", password)
profileInfo.putBoolean("REQUIRES_PASSWORD", true)
profileInfo.putString("PROFILE_NAME", address)
/* we assume we have the necessary permission */
val intent = Intent(registrar.activeContext(), CharonVpnService::class.java)

intent.putExtras(profileInfo)
val intent = Intent(registrar.activeContext(), CharonVpnService::class.java)
.putExtras(profileInfo)
ContextCompat.startForegroundService(registrar.activeContext(), intent)
}

Expand All @@ -92,6 +108,4 @@ class FlutterVpnPlugin(private val registrar: Registrar) : MethodCallHandler {
intent.action = CharonVpnService.DISCONNECT_ACTION
registrar.activeContext().startService(intent)
}


}
46 changes: 46 additions & 0 deletions android/src/main/kotlin/io/xdea/fluttervpn/VPNStateHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/**
* Copyright (C) 2018 Jason C.H
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/

package io.xdea.fluttervpn

import io.flutter.plugin.common.EventChannel

/*
STATE_CHILD_SA_UP = 1;
STATE_CHILD_SA_DOWN = 2;
STATE_AUTH_ERROR = 3;
STATE_PEER_AUTH_ERROR = 4;
STATE_LOOKUP_ERROR = 5;
STATE_UNREACHABLE_ERROR = 6;
STATE_CERTIFICATE_UNAVAILABLE = 7;
STATE_GENERIC_ERROR = 8;
*/

class VPNStateHandler : EventChannel.StreamHandler {

companion object {
/**
* The charon VPN service will update state through the sink if not `null`.
*/
var eventHandler: EventChannel.EventSink? = null
}

override fun onListen(p0: Any?, sink: EventChannel.EventSink?) {
eventHandler = sink
}

override fun onCancel(p0: Any?) {
eventHandler = null
}
}
5 changes: 5 additions & 0 deletions example/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -27,9 +27,12 @@ class _MyAppState extends State<MyApp> {
final _usernameController = TextEditingController();
final _passwordController = TextEditingController();

var state = FlutterVpnState.down;

@override
void initState() {
FlutterVpn.prepare();
FlutterVpn.onStateChanged.listen((s) => setState(() => state = s));
super.initState();
}

Expand All @@ -43,6 +46,7 @@ class _MyAppState extends State<MyApp> {
body: ListView(
padding: const EdgeInsets.all(15.0),
children: <Widget>[
Text('Current State: $state'),
TextFormField(
controller: _addressController,
decoration: InputDecoration(icon: Icon(Icons.map)),
Expand All @@ -53,6 +57,7 @@ class _MyAppState extends State<MyApp> {
),
TextFormField(
controller: _passwordController,
obscureText: true,
decoration: InputDecoration(icon: Icon(Icons.lock_outline)),
),
RaisedButton(
Expand Down
42 changes: 40 additions & 2 deletions lib/flutter_vpn.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,52 @@ import 'dart:async';

import 'package:flutter/services.dart';

const _channel = const MethodChannel('flutter_vpn');
const _eventChannel = const EventChannel('flutter_vpn_states');

enum FlutterVpnState {
up,
down,
authError,
peerAuthError,
lookUpError,
unreachableError,
certificateUnavailable,
genericError
}

class FlutterVpn {
static const MethodChannel _channel = const MethodChannel('flutter_vpn');
/// Receive state change from charon VPN service.
///
/// Can only be listened once.
/// If more than one, only the last subscription will receive events.
static Stream<FlutterVpnState> get onStateChanged =>
_eventChannel.receiveBroadcastStream().map((event) {
switch (event) {
case 1:
return FlutterVpnState.up;
case 2:
return FlutterVpnState.down;
case 3:
return FlutterVpnState.authError;
case 4:
return FlutterVpnState.peerAuthError;
case 5:
return FlutterVpnState.lookUpError;
case 6:
return FlutterVpnState.unreachableError;
case 7:
return FlutterVpnState.certificateUnavailable;
case 8:
return FlutterVpnState.genericError;
}
});

/// Prepare for vpn connection.
///
/// For first connection it will show a dialog to ask for permission.
/// When your connection was interrupted by another VPN connection,
/// you should prepare again before reconnection.
/// you should prepare again before reconnect.
static Future<bool> prepare() async {
return await _channel.invokeMethod('prepare');
}
Expand Down

0 comments on commit cfd98ce

Please sign in to comment.