/*
* Copyright 2011 yingxinwu.g@gmail.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xink.vpn;
import static xink.vpn.Constants.*;
import java.util.List;
import xink.vpn.wrapper.VpnManager;
import xink.vpn.wrapper.VpnProfile;
import xink.vpn.wrapper.VpnService;
import xink.vpn.wrapper.VpnState;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.ConditionVariable;
import android.os.IBinder;
import android.util.Log;
public class VpnActor {
private static final int ONE_SEC = 1000;
private static final String TAG = "xink";
private VpnProfileRepository repository;
private VpnManager vpnMgr;
private VpnService vpnSrv;
private Context context;
public VpnActor(final Context ctx) {
super();
context = ctx;
}
public void connect() {
final VpnProfile p = getRepository().getActiveProfile();
if (p == null)
throw new NoActiveVpnException("connect failed, no active vpn");
connect(p);
}
public void connect(final VpnProfile p) {
Log.i(TAG, "connect to: " + p);
p.preConnect();
final VpnProfile cp = p.dulicateToConnect(); // connect using a clone, so the secret key can be replace
getVpnMgr().startVpnService();
ServiceConnection c = new ServiceConnection() {
@Override
public void onServiceConnected(final ComponentName className, final IBinder service) {
try {
boolean success = getVpnSrv().connect(service, cp);
if (!success) {
Log.d(TAG, "~~~~~~ connect() failed!");
broadcastConnectivity(cp.getName(), VpnState.IDLE, VPN_ERROR_CONNECTION_FAILED);
} else {
Log.d(TAG, "~~~~~~ connect() succeeded!");
}
} catch (Throwable e) {
Log.e(TAG, "connect()", e);
broadcastConnectivity(cp.getName(), VpnState.IDLE, VPN_ERROR_CONNECTION_FAILED);
} finally {
context.unbindService(this);
}
}
@Override
public void onServiceDisconnected(final ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
checkStatus();
}
};
if (!getVpnMgr().bindVpnService(c)) {
Log.e(TAG, "bind service failed");
broadcastConnectivity(cp.getName(), VpnState.IDLE, VPN_ERROR_CONNECTION_FAILED);
}
}
public void disconnect() {
Log.i(TAG, "disconnect active vpn");
getVpnMgr().startVpnService();
ServiceConnection c = new ServiceConnection() {
@Override
public void onServiceConnected(final ComponentName className, final IBinder service) {
try {
getVpnSrv().disconnect(service);
} catch (Exception e) {
Log.e(TAG, "disconnect()", e);
checkStatus();
} finally {
context.unbindService(this);
}
}
@Override
public void onServiceDisconnected(final ComponentName className) {
Log.e(TAG, "onServiceDisconnected");
checkStatus();
}
};
if (!getVpnMgr().bindVpnService(c)) {
Log.e(TAG, "bind service failed");
checkStatus();
}
}
public void checkStatus() {
final VpnProfile p = getRepository().getActiveProfile();
if (p == null)
return;
checkStatus(p);
}
private void checkStatus(final VpnProfile p) {
Log.i(TAG, "check status of vpn: " + p);
final ConditionVariable cv = new ConditionVariable();
cv.close();
getVpnMgr().startVpnService();
ServiceConnection c = new ServiceConnection() {
@Override
public synchronized void onServiceConnected(final ComponentName className, final IBinder service) {
cv.open();
try {
getVpnSrv().checkStatus(service, p);
} catch (Exception e) {
Log.e(TAG, "checkStatus()", e);
broadcastConnectivity(p.getName(), VpnState.IDLE, VPN_ERROR_NO_ERROR);
} finally {
context.unbindService(this);
}
}
@Override
public void onServiceDisconnected(final ComponentName className) {
cv.open();
broadcastConnectivity(p.getName(), VpnState.IDLE, VPN_ERROR_NO_ERROR);
context.unbindService(this);
}
};
boolean ret = getVpnMgr().bindVpnService(c);
if (ret && !cv.block(ONE_SEC)) { // if binding failed, wait for a second, let status propagate
broadcastConnectivity(p.getName(), VpnState.IDLE, VPN_ERROR_NO_ERROR);
}
}
public void checkAllStatus() {
VpnProfileRepository repo = getRepository();
synchronized (repo) {
List<VpnProfile> profiles = repo.getAllVpnProfiles();
for (VpnProfile p : profiles) {
checkStatus(p);
}
}
}
private VpnProfileRepository getRepository() {
if (repository == null) {
repository = VpnProfileRepository.getInstance(context);
}
return repository;
}
private VpnManager getVpnMgr() {
if (vpnMgr == null) {
vpnMgr = new VpnManager(context);
}
return vpnMgr;
}
private VpnService getVpnSrv() {
if (vpnSrv == null) {
vpnSrv = new VpnService(context);
}
return vpnSrv;
}
public void activate(final VpnProfile p) {
getRepository().setActiveProfile(p);
broadcastConnectivity(p.getName(), p.getState(), VPN_ERROR_NO_ERROR);
}
public void broadcastConnectivity(final String profileName, final VpnState s, final int error) {
Intent intent = new Intent(ACTION_VPN_CONNECTIVITY);
intent.putExtra(BROADCAST_PROFILE_NAME, profileName);
intent.putExtra(BROADCAST_CONNECTION_STATE, s);
if (error != VPN_ERROR_NO_ERROR) {
intent.putExtra(BROADCAST_ERROR_CODE, error);
}
context.sendBroadcast(intent);
}
}