/* * 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); } }