package fq.router2.life_cycle; import android.app.ActivityManager; import android.app.IntentService; import android.content.Context; import android.content.Intent; import android.content.pm.PackageInfo; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Build; import fq.router2.R; import fq.router2.feedback.HandleAlertIntent; import fq.router2.feedback.HandleFatalErrorIntent; import fq.router2.utils.*; import java.io.File; import java.io.OutputStreamWriter; import java.util.*; import fq.router2.SocksVpnService; public class LaunchService extends IntentService { public static Class SOCKS_VPN_SERVICE_CLASS; private static final Set<String> WAP_APN_LIST = new HashSet<String>(){{ add("cmwap"); add("uniwap"); add("3gwap"); add("ctwap"); }}; static { try { SOCKS_VPN_SERVICE_CLASS = LaunchService.class.forName("fq.router2.SocksVpnService"); } catch (ClassNotFoundException e) { LogUtils.e("failed to load SocksVpnService.class", e); } } public LaunchService() { super("Launch"); } public static boolean isVpnRunning(Context context) { return SOCKS_VPN_SERVICE_CLASS != null && SocksVpnService.isRunning(); } @Override protected void onHandleIntent(Intent intent) { LogUtils.i("ver: " + getMyVersion(this)); sendBroadcast(new LaunchingIntent(_(R.string.status_check_root), 5)); boolean rooted = ShellUtils.checkRooted(); LogUtils.i("rooted: " + rooted); sendBroadcast(new LaunchingIntent(_(R.string.status_check_existing_process), 10)); if (isVpnRunning(this)) { LogUtils.i("manager is already running in vpn mode"); sendBroadcast(new LaunchedIntent(true)); return; } String runningAs = isRunningAs(); if ("ROOT".equals(runningAs)) { LogUtils.i("manager is already running in root mode"); sendBroadcast(new LaunchedIntent(false)); return; } if ("VPN".equals(runningAs)) { sendBroadcast(new LaunchingIntent(_(R.string.status_restart_vpn), 0)); LogUtils.i("Restart manager"); try { ManagerProcess.kill(); Thread.sleep(1000); if (ManagerProcess.exists()) { handleFatalError(_(R.string.status_failed_to_restart_vpn)); LogUtils.e("failed to restart manager", null); } else { LaunchService.execute(this); } } catch (Exception e) { handleFatalError(LogUtils.e("failed to stop exiting process", e)); } return; } if (rooted) { sendBroadcast(new LaunchingIntent(_(R.string.status_about_to_launch_in_root_mode), 20)); } else { if (Build.VERSION.SDK_INT < 14) { handleFatalError(_(R.string.status_root_is_requried_below_4_0)); return; } sendBroadcast(new LaunchingIntent(_(R.string.status_about_to_launch_in_vpn_mode), 20)); File managerLogFile = new File("/data/data/fq.router2/log/fqsocks.log"); if (managerLogFile.exists() && !managerLogFile.canWrite()) { handleFatalError(LogUtils.e(_(R.string.status_root_permission_lost))); return; } } try { if (StartedAtFlag.read() > 0) { StartedAtFlag.delete(); } StartedAtFlag.create(); } catch (Exception e) { LogUtils.e("failed to check started at flag", e); } deployAndLaunch(); } private String isRunningAs() { if (!ManagerProcess.exists()) { return ""; } try { String content = HttpUtils.get("http://127.0.0.1:" + ConfigUtils.getHttpManagerPort() + "/ping"); String myVersion = getMyVersion(this); if (("VPN PONG/" + myVersion).equals(content)) { return "VPN"; } if (("PONG/" + myVersion).equals(content)) { return "ROOT"; } return ""; } catch (Exception e) { return ""; } } private String _(int id) { return getResources().getString(id); } private void deployAndLaunch() { sendBroadcast(new LaunchingIntent(_(R.string.status_clean_environment), 25)); try { LogUtils.i("Kill existing manager process"); LogUtils.i("try to kill manager process before launch"); ManagerProcess.kill(); } catch (Exception e) { LogUtils.e("failed to kill manager process before launch", e); } Deployer deployer = new Deployer(this); String fatalError = deployer.deploy(); if (fatalError.length() > 0) { handleFatalError(fatalError); return; } sendBroadcast(new LaunchingIntent(_(R.string.status_starting_manager), 45)); LogUtils.i("Launching..."); if (ShellUtils.checkRooted()) { fatalError = launch(false); if (fatalError.length() == 0) { sendBroadcast(new LaunchedIntent(false)); } else { handleFatalError(fatalError); } } else { fatalError = launch(true); if (fatalError.length() == 0) { sendBroadcast(new LaunchedIntent(true)); } else { handleFatalError(fatalError); } } } private String launch(boolean isVpnMode) { try { Process process = executeManager(isVpnMode); String apnName = getApnName(); LogUtils.i("apn name: " + apnName); if (apnName != null && WAP_APN_LIST.contains(apnName.trim().toLowerCase())) { sendBroadcast(new HandleAlertIntent(HandleAlertIntent.ALERT_TYPE_3G_APN)); Thread.sleep(3000); } for (int i = 0; i < 30; i++) { if (ping(this, isVpnMode)) { return ""; } if (hasProcessExited(process)) { return _(R.string.status_failed_to_launch); } sendBroadcast(new LaunchingIntent(_(R.string.status_starting_manager), 45 + i)); sleepOneSecond(); } if (apnName != null && WAP_APN_LIST.contains(apnName.trim().toLowerCase())) { return _(R.string.status_3g_apn_has_proxy); } return _(R.string.status_timed_out); } catch (Exception e) { return LogUtils.e("failed to launch", e); } } private String getApnName() { try { ConnectivityManager conManager = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE); NetworkInfo ni = conManager.getActiveNetworkInfo(); if (null == ni) { return ""; } return ni.getExtraInfo(); } catch (Exception e) { LogUtils.e("failed to get apn name", e); return ""; } } private boolean hasProcessExited(Process process) { try { process.exitValue(); return true; } catch (IllegalThreadStateException e) { return false; } } private Process executeManager(boolean isVpnMode) throws Exception { Map<String, String> env = new HashMap<String, String>() {{ put("FQROUTER_VERSION", getMyVersion(LaunchService.this)); }}; env.putAll(ShellUtils.pythonEnv()); if (isVpnMode) { Process process = ShellUtils.executeNoWait(env, ShellUtils.BUSYBOX_FILE.getAbsolutePath(), "sh"); OutputStreamWriter stdin = new OutputStreamWriter(process.getOutputStream()); try { String command = Deployer.PYTHON_LAUNCHER + " " + Deployer.MANAGER_VPN_PY.getAbsolutePath() + " > /data/data/fq.router2/log/current-python.log 2>&1"; LogUtils.i("write to stdin: " + command); stdin.write(command); stdin.write("\nexit\n"); } finally { stdin.close(); } return process; } else { String runMode = ManagerProcess.getRunMode(); if ("run-needs-su".equals(runMode)) { Process process = ShellUtils.executeNoWait(env, ShellUtils.BUSYBOX_FILE.getAbsolutePath(), "sh"); OutputStreamWriter stdin = new OutputStreamWriter(process.getOutputStream()); try { String command = Deployer.PYTHON_LAUNCHER + " " + Deployer.MANAGER_MAIN_PY.getAbsolutePath() + " run > /data/data/fq.router2/log/current-python.log 2>&1"; LogUtils.i("write to stdin: " + command); stdin.write(command); stdin.write("\nexit\n"); } finally { stdin.close(); } return process; } else { return ShellUtils.sudoNoWait(env, Deployer.PYTHON_LAUNCHER + " " + Deployer.MANAGER_MAIN_PY.getAbsolutePath() + " run > /data/data/fq.router2/log/current-python.log 2>&1"); } } } public static String getMyVersion(Context context) { try { PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); if (null == packageInfo.versionName) { return "Unknown"; } else { return packageInfo.versionName; } } catch (Exception e) { LogUtils.e("failed to get package info", e); return "Unknown"; } } private static void sleepOneSecond() { try { Thread.sleep(1000); } catch (InterruptedException e) { throw new RuntimeException(e); } } public static boolean ping(Context context, boolean isVpnMode) { return ping(context, isVpnMode, 3000); } public static boolean ping(Context context, boolean isVpnMode, int timeout) { try { String myVersion = getMyVersion(context); String content = HttpUtils.get("http://127.0.0.1:" + ConfigUtils.getHttpManagerPort() + "/ping", null, timeout); if (isVpnMode ? ("VPN PONG/" + myVersion).equals(content) : ("PONG/" + myVersion).equals(content)) { LogUtils.e("ping " + isVpnMode + " succeeded"); return true; } else { LogUtils.e("ping " + isVpnMode + " failed: " + content); return false; } } catch (HttpUtils.Error e) { LogUtils.e("ping " + isVpnMode + " failed: [" + e.responseCode + "] " + e.output); return false; } catch (Exception e) { LogUtils.e("ping " + isVpnMode + " failed: " + e, e); return false; } } private void handleFatalError(String message) { if (ShellUtils.isRooted() && "run-needs-su".equals(ManagerProcess.getRunMode())) { sendBroadcast(new HandleAlertIntent(HandleAlertIntent.ALERT_TYPE_RUN_NEEDS_SU)); } sendBroadcast(new HandleFatalErrorIntent(message)); } public static void execute(Context context) { context.startService(new Intent(context, LaunchService.class)); } }