org.gaeproxy.GAEProxyService.java Source code

Java tutorial

Introduction

Here is the source code for org.gaeproxy.GAEProxyService.java

Source

/* gaeproxy - GAppProxy / WallProxy client App for Android
 * Copyright (C) 2011 <max.c.lv@gmail.com>
 *
 * 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * 
 *                            ___====-_  _-====___
 *                      _--^^^#####//      \\#####^^^--_
 *                   _-^##########// (    ) \\##########^-_
 *                  -############//  |\^^/|  \\############-
 *                _/############//   (@::@)   \\############\_
 *               /#############((     \\//     ))#############\
 *              -###############\\    (oo)    //###############-
 *             -#################\\  / VV \  //#################-
 *            -###################\\/      \//###################-
 *           _#/|##########/\######(   /\   )######/\##########|\#_
 *           |/ |#/\#/\#/\/  \#/\##\  |  |  /##/\#/  \/\#/\#/\#| \|
 *           `  |/  V  V  `   V  \#\| |  | |/#/  V   '  V  V  \|  '
 *              `   `  `      `   / | |  | | \   '      '  '   '
 *                               (  | |  | |  )
 *                              __\ | |  | | /__
 *                             (vvv(VVV)(VVV)vvv)
 *
 *                              HERE BE DRAGONS
 *
 */

package org.gaeproxy;

import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.appwidget.AppWidgetManager;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.net.Uri;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;
import android.util.Pair;
import android.widget.RemoteViews;
import com.google.analytics.tracking.android.EasyTracker;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.ref.WeakReference;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.gaeproxy.db.App;
import org.xbill.DNS.ARecord;
import org.xbill.DNS.Lookup;
import org.xbill.DNS.Record;
import org.xbill.DNS.Resolver;
import org.xbill.DNS.SimpleResolver;
import org.xbill.DNS.Type;

public class GAEProxyService extends Service {

    public static final String BASE = "/data/data/org.gaeproxy/";
    final static String CMD_IPTABLES_RETURN = " -t nat -A OUTPUT -p tcp -d 0.0.0.0 -j RETURN\n";
    final static String CMD_IPTABLES_REDIRECT_ADD_HTTP = " -t nat -A OUTPUT -p tcp "
            + "--dport 80 -j REDIRECT --to 8123\n";
    final static String CMD_IPTABLES_REDIRECT_ADD_HTTPS = " -t nat -A OUTPUT -p tcp "
            + "--dport 443 -j REDIRECT --to 8124\n";
    final static String CMD_IPTABLES_DNAT_ADD_HTTP = " -t nat -A OUTPUT -p tcp "
            + "--dport 80 -j DNAT --to-destination 127.0.0.1:8123\n";
    final static String CMD_IPTABLES_DNAT_ADD_HTTPS = " -t nat -A OUTPUT -p tcp "
            + "--dport 443 -j DNAT --to-destination 127.0.0.1:8124\n";
    private static final int MSG_CONNECT_START = 0;
    private static final int MSG_CONNECT_FINISH = 1;
    private static final int MSG_CONNECT_SUCCESS = 2;
    private static final int MSG_CONNECT_FAIL = 3;
    private static final int MSG_HOST_CHANGE = 4;
    private static final int MSG_STOP_SELF = 5;
    private static final String[] BLACK_LIST = { "4.36.66.178", "8.7.198.45", "37.61.54.158", "46.82.174.68",
            "59.24.3.173", "64.33.88.161", "64.33.99.47", "64.66.163.251", "65.104.202.252", "65.160.219.113",
            "66.45.252.237", "72.14.205.104", "72.14.205.99", "78.16.49.15", "93.46.8.89", "128.121.126.139",
            "159.106.121.75", "169.132.13.103", "192.67.198.6", "202.106.1.2", "202.181.7.85", "203.161.230.171",
            "207.12.88.98", "208.56.31.43", "209.145.54.50", "209.220.30.174", "209.36.73.33", "211.94.66.147",
            "213.169.251.35", "216.221.188.182", "216.234.179.13", "59.24.3.173" };
    private static final String TAG = "GAEProxyService";
    private static final String DEFAULT_HOST = "74.125.128.106";
    private static final String VIDEO_HOST = "74.125.0.0|173.194.0.0";
    private static final String DEFAULT_DNS = "220.181.136.37";
    private static final Class<?>[] mStartForegroundSignature = new Class[] { int.class, Notification.class };
    private static final Class<?>[] mStopForegroundSignature = new Class[] { boolean.class };
    private static final Class<?>[] mSetForegroundSignature = new Class[] { boolean.class };
    public static volatile boolean statusLock = false;
    /*
      * This is a hack see
      * http://www.mail-archive.com/android-developers@googlegroups
      * .com/msg18298.html we are not really able to decide if the service was
      * started. So we remember a week reference to it. We set it if we are
      * running and clear it if we are stopped. If anything goes wrong, the
      * reference will hopefully vanish
      */
    private static WeakReference<GAEProxyService> sRunningInstance = null;
    final Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            Editor ed = settings.edit();
            switch (msg.what) {
            case MSG_CONNECT_START:
                ed.putBoolean("isConnecting", true);
                statusLock = true;

                PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
                mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
                        "GAEProxy");

                mWakeLock.acquire();

                break;
            case MSG_CONNECT_FINISH:
                ed.putBoolean("isConnecting", false);
                statusLock = false;

                if (mWakeLock != null && mWakeLock.isHeld())
                    mWakeLock.release();

                break;
            case MSG_CONNECT_SUCCESS:
                ed.putBoolean("isRunning", true);
                break;
            case MSG_CONNECT_FAIL:
                ed.putBoolean("isRunning", false);
                break;
            case MSG_HOST_CHANGE:
                ed.putString("videoHost", videoHost);
                ed.putString("appHost", appHost);
                break;
            case MSG_STOP_SELF:
                stopSelf();
                break;
            }
            ed.commit();
            super.handleMessage(msg);
        }
    };
    NotificationManager notificationManager;
    Notification notification;
    PendingIntent pendIntent;
    PowerManager.WakeLock mWakeLock;
    private String dnsHost = null;
    private DNSServer dnsServer = null;
    private int dnsPort = 8053;
    private String proxyType = "GAE";
    private String appId;
    private String appPath;
    private String appHost = DEFAULT_HOST;
    private String videoHost = VIDEO_HOST;
    private String[] appMask;
    private String[] videoMask;
    private int port;
    private String sitekey;
    private SharedPreferences settings = null;
    private boolean hasRedirectSupport = true;
    private boolean isGlobalProxy = false;
    private boolean isGFWList = false;
    private boolean isBypassApps = false;
    private Set<Integer> mProxiedApps;
    private Method mSetForeground;
    private Method mStartForeground;
    private Method mStopForeground;
    private Object[] mSetForegroundArgs = new Object[1];
    private Object[] mStartForegroundArgs = new Object[2];
    private Object[] mStopForegroundArgs = new Object[1];
    private BroadcastReceiver mShutdownReceiver = null;

    public static boolean isServiceStarted() {
        final boolean isServiceStarted;
        if (sRunningInstance == null) {
            isServiceStarted = false;
        } else if (sRunningInstance.get() == null) {
            isServiceStarted = false;
            sRunningInstance = null;
        } else {
            isServiceStarted = true;
        }
        return isServiceStarted;
    }

    private static boolean isInBlackList(String addr) {
        for (String a : BLACK_LIST) {
            if (a.endsWith(addr))
                return true;
        }
        return false;
    }

    private String loadStringFromRawResource(Resources resources, int resId) {
        InputStream rawResource = resources.openRawResource(resId);
        String content = streamToString(rawResource);
        try {
            rawResource.close();
        } catch (IOException ignored) {
        }
        return content;
    }

    private String streamToString(InputStream in) {
        String l;
        BufferedReader r = new BufferedReader(new InputStreamReader(in));
        StringBuilder s = new StringBuilder();
        try {
            while ((l = r.readLine()) != null) {
                s.append(l).append("\n");
            }
        } catch (IOException ignored) {
        }
        return s.toString();
    }

    private void writeConfig() throws FileNotFoundException, UnsupportedEncodingException {
        String config = loadStringFromRawResource(getResources(), R.raw.proxy);
        config = config.replace("APPID", appId);
        config = config.replace("PORT", String.valueOf(port));
        config = config.replace("PASSWORD", sitekey);
        config = config.replace("HOSTS", appHost);
        if (proxyType.equals("PaaS")) {
            config = config.replace("PAASPATH", appPath);
            config = config.replace("PAAS", "1");
        } else {
            config = config.replace("GAEPATH", appPath);
            config = config.replace("PAAS", "0");
            if (proxyType.equals("GAE-RC4")) {
                config = config.replace("ENCRYPT", "rc4");
            }
        }
        PrintWriter writer = new PrintWriter(BASE + "proxy.ini", "UTF-8");
        writer.print(config);
        writer.close();
    }

    private void flushDNS() {
        Utils.runRootCommand("ndc resolver flushdefaultif\n" + "ndc resolver flushif wlan0\n");
    }

    private boolean parseProxyURL(String url) {
        if (proxyType.equals("PaaS")) {
            Uri uri = Uri.parse(url);
            if (uri == null) {
                return false;
            }
            appId = uri.getHost();
            appPath = url;
            return true;
        }
        if (url == null)
            return false;
        String[] proxyString = url.split("\\/");
        if (proxyString.length < 4)
            return false;
        appPath = proxyString[3];
        String[] ids = proxyString[2].split("\\.");
        if (ids.length < 3)
            return false;
        appId = ids[0];
        return true;
    }

    public boolean connect() {

        try {

            writeConfig();

            final String cmd = BASE + "python-cl " + BASE + "goagent.py";

            if (Utils.isRoot()) {
                Utils.runRootCommand(cmd);
            } else {
                Utils.runCommand(cmd);
            }
        } catch (Exception e) {
            Log.e(TAG, "Cannot connect");
            return false;
        }

        return true;
    }

    private String getVersionName() {
        String version;
        try {
            PackageInfo pi = getPackageManager().getPackageInfo(getPackageName(), 0);
            version = pi.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            version = "Package name not found";
        }
        return version;
    }

    public void handleCommand(Intent intent) {

        if (intent == null) {
            stopSelf();
            return;
        }

        proxyType = settings.getString("proxyType", "GAE");
        sitekey = settings.getString("sitekey", "");
        try {
            port = Integer.valueOf(settings.getString("port", "1984"));
        } catch (NumberFormatException ex) {
            port = 1984;
        }

        isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
        isGFWList = settings.getBoolean("isGFWList", false);
        isBypassApps = settings.getBoolean("isBypassApps", false);

        if (!parseProxyURL(settings.getString("proxy", "proxyofmax.appspot.com"))) {
            stopSelf();
            return;
        }

        if (!"GAE".equals(proxyType) && !"PaaS".equals(proxyType)) {
            proxyType = "GAE";
        }

        if ("fetch.py".equals(appPath))
            appPath = "2";

        Log.e(TAG, "Proxy: " + appId + " " + appPath);
        Log.e(TAG, "Local Port: " + port);

        new Thread(new Runnable() {
            @Override
            public void run() {

                handler.sendEmptyMessage(MSG_CONNECT_START);

                Log.d(TAG, "IPTABLES: " + Utils.getIptables());

                // Test for Redirect Support
                hasRedirectSupport = Utils.getHasRedirectSupport();

                if (handleConnection()) {
                    // Connection and forward successful
                    notifyAlert(getString(R.string.forward_success), getString(R.string.service_running));

                    handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 500);

                    // for widget, maybe exception here
                    try {
                        RemoteViews views = new RemoteViews(getPackageName(), R.layout.gaeproxy_appwidget);
                        views.setImageViewResource(R.id.serviceToggle, R.drawable.on);
                        AppWidgetManager awm = AppWidgetManager.getInstance(GAEProxyService.this);
                        awm.updateAppWidget(
                                awm.getAppWidgetIds(
                                        new ComponentName(GAEProxyService.this, GAEProxyWidgetProvider.class)),
                                views);
                    } catch (Exception ignore) {
                        // Nothing
                    }
                } else {
                    // Connection or forward unsuccessful
                    notifyAlert(getString(R.string.forward_fail), getString(R.string.service_failed));

                    stopSelf();
                    handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 500);
                }

                handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 500);
            }
        }).start();
        markServiceStarted();
    }

    private String parseHost(String host, boolean isSafe) {
        String address = null;
        if (isSafe) {
            try {
                Lookup lookup = new Lookup(host, Type.A);
                Resolver resolver = new SimpleResolver("114.114.114.114");
                resolver.setTCP(false);
                resolver.setTimeout(10);
                lookup.setResolver(resolver);
                Record[] records = lookup.run();
                if (records.length > 0) {
                    boolean first = true;
                    StringBuilder sb = new StringBuilder();
                    for (Record record : records) {
                        ARecord r = (ARecord) record;
                        InetAddress addr = r.getAddress();
                        if (!first) {
                            sb.append("|");
                        } else {
                            first = false;
                        }
                        sb.append(addr.getHostAddress());
                    }
                    address = sb.toString();
                }
            } catch (Exception ignore) {
                address = null;
            }
        }

        if (address == null) {
            try {
                InetAddress addr = InetAddress.getByName(host);
                address = addr.getHostAddress();
            } catch (Exception ignore) {
                address = null;
            }
        }

        return address;
    }

    /** Called when the activity is first created. */
    public boolean handleConnection() {

        if (proxyType.equals("GAE")) {
            appHost = parseHost("g.maxcdn.info", true);
            if (appHost == null || appHost.equals("") || isInBlackList(appHost)) {
                appHost = settings.getString("appHost", DEFAULT_HOST);
            }
        } else if (proxyType.equals("PaaS")) {
            appHost = parseHost(appId, false);
            if (appHost == null || appHost.equals("") || isInBlackList(appHost)) {
                return false;
            }
        }

        videoHost = parseHost("v.maxcdn.info", true);
        if (videoHost == null || videoHost.equals("") || isInBlackList(videoHost)) {
            videoHost = settings.getString("videoHost", VIDEO_HOST);
        }

        handler.sendEmptyMessage(MSG_HOST_CHANGE);

        dnsHost = parseHost("myhosts.sinaapp.com", false);
        if (dnsHost == null || dnsHost.equals("") || isInBlackList(appHost)) {
            dnsHost = DEFAULT_DNS;
        }

        try {
            String[] hosts = dnsHost.split("\\|");
            dnsHost = hosts[hosts.length - 1];
            appMask = appHost.split("\\|");
            videoMask = videoHost.split("\\|");
        } catch (Exception ex) {
            return false;
        }

        if (!isGlobalProxy) {
            if (mProxiedApps == null) {
                mProxiedApps = App.getProxiedApps(this);
            }
        }

        // DNS Proxy Setup
        // with AsyncHttpClient
        if ("PaaS".equals(proxyType)) {
            Pair<String, String> orgHost = new Pair<String, String>(appId, appMask[0]);
            dnsServer = new DNSServer(this, dnsHost, orgHost);
        } else if ("GAE".equals(proxyType)) {
            dnsServer = new DNSServer(this, dnsHost, null);
        }
        dnsPort = dnsServer.getServPort();

        // Random mirror for load balance
        // only affect when appid equals proxyofmax
        if (appId.equals("proxyofmax")) {
            appId = new String(Base64.decodeBase64(getString(R.string.mirror_list).getBytes()));
            appPath = getString(R.string.mirror_path);
            sitekey = getString(R.string.mirror_sitekey);
        }

        if (!preConnection())
            return false;

        Thread dnsThread = new Thread(dnsServer);
        dnsThread.setDaemon(true);
        dnsThread.start();

        connect();
        flushDNS();

        return true;
    }

    private void initSoundVibrateLights(Notification notification) {
        notification.sound = null;
        notification.defaults |= Notification.DEFAULT_LIGHTS;
    }

    void invokeMethod(Method method, Object[] args) {
        try {
            method.invoke(this, mStartForegroundArgs);
        } catch (InvocationTargetException e) {
            // Should not happen.
            Log.w(TAG, "Unable to invoke method", e);
        } catch (IllegalAccessException e) {
            // Should not happen.
            Log.w(TAG, "Unable to invoke method", e);
        }
    }

    private void markServiceStarted() {
        sRunningInstance = new WeakReference<GAEProxyService>(this);
    }

    private void markServiceStopped() {
        sRunningInstance = null;
    }

    private void notifyAlert(String title, String info) {
        notification.icon = R.drawable.ic_stat_gaeproxy;
        notification.tickerText = title;
        notification.flags = Notification.FLAG_ONGOING_EVENT;
        initSoundVibrateLights(notification);
        // notification.defaults = Notification.DEFAULT_SOUND;
        notification.setLatestEventInfo(this, getString(R.string.app_name), info, pendIntent);
        startForegroundCompat(1, notification);
    }

    private void notifyAlert(String title, String info, int flags) {
        notification.icon = R.drawable.ic_stat_gaeproxy;
        notification.tickerText = title;
        notification.flags = flags;
        initSoundVibrateLights(notification);
        notification.setLatestEventInfo(this, getString(R.string.app_name), info, pendIntent);
        notificationManager.notify(0, notification);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        mShutdownReceiver = new BroadcastReceiver() {
            @Override
            public void onReceive(Context context, Intent intent) {
                stopSelf();
            }
        };
        IntentFilter filter = new IntentFilter(Intent.ACTION_SHUTDOWN);
        registerReceiver(mShutdownReceiver, filter);

        EasyTracker.getTracker().trackEvent("service", "start", getVersionName(), 0L);

        notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);

        settings = PreferenceManager.getDefaultSharedPreferences(this);

        Intent intent = new Intent(this, GAEProxyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        pendIntent = PendingIntent.getActivity(this, 0, intent, 0);
        notification = new Notification();

        try {
            mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature);
            mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature);
        } catch (NoSuchMethodException e) {
            // Running on an older platform.
            mStartForeground = mStopForeground = null;
        }

        try {
            mSetForeground = getClass().getMethod("setForeground", mSetForegroundSignature);
        } catch (NoSuchMethodException e) {
            throw new IllegalStateException("OS doesn't have Service.startForeground OR Service.setForeground!");
        }
    }

    /** Called when the activity is closed. */
    @Override
    public void onDestroy() {

        EasyTracker.getTracker().trackEvent("service", "stop", getVersionName(), 0L);

        if (mShutdownReceiver != null) {
            unregisterReceiver(mShutdownReceiver);
            mShutdownReceiver = null;
        }

        statusLock = true;

        stopForegroundCompat(1);

        notifyAlert(getString(R.string.forward_stop), getString(R.string.service_stopped),
                Notification.FLAG_AUTO_CANCEL);

        try {
            if (dnsServer != null)
                dnsServer.close();
        } catch (Exception e) {
            Log.e(TAG, "DNS Server close unexpected");
        }

        new Thread() {
            @Override
            public void run() {

                // Make sure the connection is closed, important here
                onDisconnect();
            }
        }.start();

        // for widget, maybe exception here
        try {
            RemoteViews views = new RemoteViews(getPackageName(), R.layout.gaeproxy_appwidget);
            views.setImageViewResource(R.id.serviceToggle, R.drawable.off);
            AppWidgetManager awm = AppWidgetManager.getInstance(this);
            awm.updateAppWidget(awm.getAppWidgetIds(new ComponentName(this, GAEProxyWidgetProvider.class)), views);
        } catch (Exception ignore) {
            // Nothing
        }

        Editor ed = settings.edit();
        ed.putBoolean("isRunning", false);
        ed.putBoolean("isConnecting", false);
        ed.commit();

        try {
            notificationManager.cancel(0);
        } catch (Exception ignore) {
            // Nothing
        }

        try {
            ProxySettings.resetProxy(this);
        } catch (Exception ignore) {
            // Nothing
        }

        super.onDestroy();

        statusLock = false;

        markServiceStopped();
    }

    private void onDisconnect() {
        Utils.runRootCommand(Utils.getIptables() + " -t nat -F OUTPUT");
        if (Utils.isRoot()) {
            Utils.runRootCommand(BASE + "proxy.sh stop");
        } else {
            Utils.runCommand(BASE + "proxy.sh stop");
        }
    }

    // This is the old onStart method that will be called on the pre-2.0
    // platform. On 2.0 or later we override onStartCommand() so this
    // method will not be called.
    @Override
    public void onStart(Intent intent, int startId) {

        handleCommand(intent);
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        handleCommand(intent);
        // We want this service to continue running until it is explicitly
        // stopped, so return sticky.
        return START_STICKY;
    }

    private boolean checkHTTPSProxy() {
        InputStream is = null;

        String socksIp = settings.getString("socksIp", null);
        String socksPort = settings.getString("socksPort", null);

        String sig = Utils.getSignature(this);

        if (sig == null)
            return false;

        for (int tries = 0; tries < 2; tries++) {
            try {
                BasicHttpParams httparams = new BasicHttpParams();
                HttpConnectionParams.setConnectionTimeout(httparams, 3000);
                HttpConnectionParams.setSoTimeout(httparams, 3000);
                DefaultHttpClient client = new DefaultHttpClient(httparams);
                HttpGet get = new HttpGet("http://myhosts.sinaapp.com/auth-4.php?sig=" + sig);
                HttpResponse getResponse = client.execute(get);
                is = getResponse.getEntity().getContent();

                BufferedReader reader = new BufferedReader(new InputStreamReader(is));

                String line = reader.readLine();

                if (line.startsWith("ERROR"))
                    return false;

                if (!line.startsWith("#ip"))
                    throw new Exception("Format error");
                line = reader.readLine();
                socksIp = line.trim().toLowerCase();

                line = reader.readLine();
                if (!line.startsWith("#port"))
                    throw new Exception("Format error");
                line = reader.readLine();
                socksPort = line.trim().toLowerCase();

                Editor ed = settings.edit();
                ed.putString("socksIp", socksIp);
                ed.putString("socksPort", socksPort);
                ed.commit();
            } catch (Exception e) {
                Log.e(TAG, "cannot get remote port info", e);
                continue;
            }
            break;
        }

        return !(socksIp == null || socksPort == null);
    }

    private boolean preConnection() {

        boolean isHTTPSProxy = checkHTTPSProxy();

        if (isHTTPSProxy) {
            String socksIp = settings.getString("socksIp", null);
            String socksPort = settings.getString("socksPort", null);

            if (Utils.isRoot()) {
                Utils.runRootCommand(BASE + "proxy.sh start " + port + " " + socksIp + " " + socksPort);
            } else {
                Utils.runCommand(BASE + "proxy.sh start " + port + " " + socksIp + " " + socksPort);
            }
        } else {
            if (Utils.isRoot()) {
                Utils.runRootCommand(BASE + "proxy.sh start " + port + " " + "127.0.0.1" + " " + port);
            } else {
                Utils.runCommand(BASE + "proxy.sh start " + port + " " + "127.0.0.1" + " " + port);
            }
        }

        StringBuilder init_sb = new StringBuilder();

        StringBuilder http_sb = new StringBuilder();

        StringBuilder https_sb = new StringBuilder();

        init_sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT\n");

        if (hasRedirectSupport) {
            init_sb.append(Utils.getIptables()).append(" -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to ")
                    .append(dnsPort).append("\n");
        } else {
            init_sb.append(Utils.getIptables())
                    .append(" -t nat -A OUTPUT -p udp --dport 53 -j DNAT --to-destination 127.0.0.1:")
                    .append(dnsPort).append("\n");
        }

        String cmd_bypass = Utils.getIptables() + CMD_IPTABLES_RETURN;

        init_sb.append(cmd_bypass.replace("0.0.0.0", dnsHost));

        for (String mask : appMask) {
            init_sb.append(cmd_bypass.replace("0.0.0.0", mask));
        }

        for (String mask : videoMask) {
            init_sb.append(cmd_bypass.replace("0.0.0.0", mask + "/16 --dport 443"));
        }

        init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + getApplicationInfo().uid));

        if (isGFWList) {
            String[] chn_list = getResources().getStringArray(R.array.chn_list);

            for (String item : chn_list) {
                init_sb.append(cmd_bypass.replace("0.0.0.0", item));
            }
        }
        if (isGlobalProxy || isBypassApps) {
            http_sb.append(hasRedirectSupport ? Utils.getIptables() + CMD_IPTABLES_REDIRECT_ADD_HTTP
                    : Utils.getIptables() + CMD_IPTABLES_DNAT_ADD_HTTP);
            https_sb.append(hasRedirectSupport ? Utils.getIptables() + CMD_IPTABLES_REDIRECT_ADD_HTTPS
                    : Utils.getIptables() + CMD_IPTABLES_DNAT_ADD_HTTPS);
        }
        if (!isGlobalProxy) {
            // for proxy specified apps

            if (mProxiedApps != null) {
                for (int uid : mProxiedApps) {
                    if (!isBypassApps) {
                        http_sb.append((hasRedirectSupport ? Utils.getIptables() + CMD_IPTABLES_REDIRECT_ADD_HTTP
                                : Utils.getIptables() + CMD_IPTABLES_DNAT_ADD_HTTP).replace("-t nat",
                                        "-t nat -m owner --uid-owner " + uid));
                        https_sb.append((hasRedirectSupport ? Utils.getIptables() + CMD_IPTABLES_REDIRECT_ADD_HTTPS
                                : Utils.getIptables() + CMD_IPTABLES_DNAT_ADD_HTTPS).replace("-t nat",
                                        "-t nat -m owner --uid-owner " + uid));
                    } else {
                        init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid));
                    }
                }
            }
        }

        String init_rules = init_sb.toString();
        Utils.runRootCommand(init_rules, 30 * 1000);

        String redt_rules = http_sb.toString();

        redt_rules += https_sb.toString();

        Utils.runRootCommand(redt_rules);

        return true;
    }

    /**
     * This is a wrapper around the new startForeground method, using the older
     * APIs if it is not available.
     */
    void startForegroundCompat(int id, Notification notification) {
        // If we have the new startForeground API, then use it.
        if (mStartForeground != null) {
            mStartForegroundArgs[0] = id;
            mStartForegroundArgs[1] = notification;
            invokeMethod(mStartForeground, mStartForegroundArgs);
            return;
        }

        // Fall back on the old API.
        mSetForegroundArgs[0] = Boolean.TRUE;
        invokeMethod(mSetForeground, mSetForegroundArgs);
        notificationManager.notify(id, notification);
    }

    /**
     * This is a wrapper around the new stopForeground method, using the older
     * APIs if it is not available.
     */
    void stopForegroundCompat(int id) {
        // If we have the new stopForeground API, then use it.
        if (mStopForeground != null) {
            mStopForegroundArgs[0] = Boolean.TRUE;
            try {
                mStopForeground.invoke(this, mStopForegroundArgs);
            } catch (InvocationTargetException e) {
                // Should not happen.
                Log.w(TAG, "Unable to invoke stopForeground", e);
            } catch (IllegalAccessException e) {
                // Should not happen.
                Log.w(TAG, "Unable to invoke stopForeground", e);
            }
            return;
        }

        // Fall back on the old API. Note to cancel BEFORE changing the
        // foreground state, since we could be killed at that point.
        notificationManager.cancel(id);
        mSetForegroundArgs[0] = Boolean.FALSE;
        invokeMethod(mSetForeground, mSetForegroundArgs);
    }
}