/* * 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.io.BufferedReader; import java.io.DataOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.util.Timer; import java.util.TimerTask; import xink.vpn.wrapper.VpnProfile; import xink.vpn.wrapper.VpnState; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.preference.PreferenceManager; import android.util.Log; /** * Keep VPN connection alive, by accessing internet resource periodically. */ public final class KeepAlive extends BroadcastReceiver { public static final String PREF_HEARTBEAT_PERIOD = "xink.vpn.pref.keepAlive.period"; public static final String PREF_ENABLED = "xink.vpn.pref.keepAlive"; private static final String TAG = KeepAlive.class.getName(); private static Timer timer = new Timer("xink.vpn.HeartbeatTimer", true); private static Heartbeat heartbeat; /** * Get informed about VPN connectivity, to start/stop the heartbeat session. */ @Override public void onReceive(final Context context, final Intent intent) { String action = intent.getAction(); if (!ACTION_VPN_CONNECTIVITY.equals(action)) return; VpnProfileRepository repo = VpnProfileRepository.getInstance(context); VpnProfile p = repo.getActiveProfile(); String profileName = intent.getStringExtra(BROADCAST_PROFILE_NAME); if (p == null || profileName == null || !profileName.equals(p.getName())) { //Log.d(TAG, "ignores non-active profile event: " + profileName); return; } SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); VpnState newState = Utils.extractVpnState(intent); stateChanged(p, newState, prefs); } private void stateChanged(final VpnProfile p, final VpnState newState, final SharedPreferences prefs) { //Log.d(TAG, p + " state ==> " + newState); switch (newState) { case CONNECTED: startHeartbeat(p, prefs); break; case IDLE: stopHeartbeat(); break; default: break; } } private static void startHeartbeat(final VpnProfile p, final SharedPreferences prefs) { boolean enabled = prefs.getBoolean(PREF_ENABLED, false); if (!enabled || heartbeat != null) return; int period = getPeriodFromPrefs(prefs); Log.d(TAG, "start heartbeat every (ms)" + period); heartbeat = new Heartbeat(p); timer.schedule(heartbeat, period, period); } private static int getPeriodFromPrefs(final SharedPreferences prefs) { String periodStr = prefs.getString(PREF_HEARTBEAT_PERIOD, Period.TEN_MIN.toString()); Period p = Period.valueOf(periodStr); return p.value; } private static synchronized void stopHeartbeat() { if (heartbeat == null) return; heartbeat.cancel(); int removed = timer.purge(); heartbeat = null; Log.d(TAG, "removed heartbeat timerTask: " + removed); } private static class Heartbeat extends TimerTask { private VpnProfile profile; // current hearbeat vpn profile protected Heartbeat(final VpnProfile p) { this.profile = p; } @Override public void run() { try { execPing(); } catch (IOException e) { Log.e(TAG, "heartdbeat error", e); } } // ping the vpn server to keep connection alive private void execPing() throws IOException { Process process = null; try { process = new ProcessBuilder("sh").redirectErrorStream(true).start(); DataOutputStream os = new DataOutputStream(process.getOutputStream()); os.writeBytes("ping -c 10 " + profile.getServerName() + "\n"); os.writeBytes("exit\n"); os.flush(); dumpPingResults(process); } finally { if (process != null) { process.destroy(); } } } private void dumpPingResults(final Process process) throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); String line; while ((line = reader.readLine()) != null) { Log.d(TAG, line); } } } private static enum Period { FIVE_MIN(300000), TEN_MIN(600000), FIFTEEN_MIN(900000), THIRTY_MIN(1800000), TEST_5_SEC(5000); private int value; // in miliseconds private Period(final int v) { value = v; } } }