diff --git a/res/drawable/btn_cab_done_shadow.xml b/res/drawable/btn_cab_done_shadow.xml
index 4dd67cf6..4e30aefc 100755
--- a/res/drawable/btn_cab_done_shadow.xml
+++ b/res/drawable/btn_cab_done_shadow.xml
@@ -18,10 +18,10 @@
-->
-
-
-
+
+
+
diff --git a/res/drawable/pressed_background_shadow.xml b/res/drawable/pressed_background_shadow.xml
index e660d9ac..a33972f0 100755
--- a/res/drawable/pressed_background_shadow.xml
+++ b/res/drawable/pressed_background_shadow.xml
@@ -18,5 +18,5 @@
-->
-
+
diff --git a/res/drawable/progress_horizontal_shadow.xml b/res/drawable/progress_horizontal_shadow.xml
index 0e2ecfa1..2a45e52a 100755
--- a/res/drawable/progress_horizontal_shadow.xml
+++ b/res/drawable/progress_horizontal_shadow.xml
@@ -19,17 +19,17 @@
-
+
- -
-
-
+ -
+
+
- -
-
-
+ -
+
+
diff --git a/res/drawable/selectable_background_shadow.xml b/res/drawable/selectable_background_shadow.xml
index e0a3f0da..7b8221ee 100755
--- a/res/drawable/selectable_background_shadow.xml
+++ b/res/drawable/selectable_background_shadow.xml
@@ -18,8 +18,9 @@
-->
-
-
-
+ android:exitFadeDuration="@android:integer/config_mediumAnimTime">
+
+
+
\ No newline at end of file
diff --git a/res/drawable/spinner_background_ab_shadow.xml b/res/drawable/spinner_background_ab_shadow.xml
index 5ddd9ec0..21532f3b 100755
--- a/res/drawable/spinner_background_ab_shadow.xml
+++ b/res/drawable/spinner_background_ab_shadow.xml
@@ -18,11 +18,11 @@
-->
-
-
-
-
+
+
+
+
diff --git a/res/drawable/tab_indicator_ab_shadow.xml b/res/drawable/tab_indicator_ab_shadow.xml
index e58ed862..0586cc7b 100755
--- a/res/drawable/tab_indicator_ab_shadow.xml
+++ b/res/drawable/tab_indicator_ab_shadow.xml
@@ -18,20 +18,28 @@
-->
-
-
-
+
+
+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
-
-
-
+
+
+
diff --git a/res/layout/layout_apps.xml b/res/layout/layout_apps.xml
index a35c5974..8f2a1396 100644
--- a/res/layout/layout_apps.xml
+++ b/res/layout/layout_apps.xml
@@ -1,10 +1,10 @@
-
-
+ android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical" android:duplicateParentState="false">
+
+
\ No newline at end of file
diff --git a/res/layout/layout_apps_item.xml b/res/layout/layout_apps_item.xml
index 98f00cef..b2aa20a8 100644
--- a/res/layout/layout_apps_item.xml
+++ b/res/layout/layout_apps_item.xml
@@ -1,34 +1,34 @@
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:stretchColumns="1">
-
+
-
+
-
+
-
+
-
+
diff --git a/res/layout/layout_switch.xml b/res/layout/layout_switch.xml
index c18c2ffb..b9e1c86f 100644
--- a/res/layout/layout_switch.xml
+++ b/res/layout/layout_switch.xml
@@ -1,26 +1,26 @@
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
-
+
-
+
diff --git a/res/layout/overlay.xml b/res/layout/overlay.xml
index 49c1b45f..7fa7b8a6 100644
--- a/res/layout/overlay.xml
+++ b/res/layout/overlay.xml
@@ -1,6 +1,6 @@
\ No newline at end of file
+ android:minWidth="80dp" android:maxWidth="80dp" android:gravity="center"
+ android:textSize="70sp" android:textColor="#ffffffff"
+ android:background="#99000088" android:padding="10dp"
+ android:visibility="invisible"/>
\ No newline at end of file
diff --git a/res/values-v14/styles_shadow.xml b/res/values-v14/styles_shadow.xml
index a8d2b0d5..231d161d 100755
--- a/res/values-v14/styles_shadow.xml
+++ b/res/values-v14/styles_shadow.xml
@@ -19,38 +19,39 @@
-
+
+ - @style/Theme.Shadow.Widget
-
+
-
-
-
-
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index f3cb82f5..bc48ae9a 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -16,11 +16,11 @@
- #ff5e5e5e
- #17170443
- #000000
- #646464
- #c8c8c8
- #323232
+ #ff5e5e5e
+ #17170443
+ #000000
+ #646464
+ #c8c8c8
+ #323232
\ No newline at end of file
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 54cd22f6..cc7ca5fc 100755
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1,127 +1,127 @@
- shadowsocks
+ shadowsocks
-
- Service Controller
- Proxy Switch
- Enable / Disable Proxy
+
+ Service Controller
+ Proxy Switch
+ Enable / Disable Proxy
-
- Shadowsocks Settings
- Server
- Hostname of your Shadowsocks server
- Remote Port
- Remote port of shadowsocks server.
- Local Port
- Local port listening on
- Password
- Password of your Shadowsocks server
- Encrypt Method
-
- - Table
- - RC4
-
-
- - table
- - rc4
-
+
+ Shadowsocks Settings
+ Server
+ Hostname of your Shadowsocks server
+ Remote Port
+ Remote port of shadowsocks server.
+ Local Port
+ Local port listening on
+ Password
+ Password of your Shadowsocks server
+ Encrypt Method
+
+ - Table
+ - RC4
+
+
+ - table
+ - rc4
+
-
- Feature Settings
- HTTP Proxy
- Local HTTP Proxy
- DNS Proxy
- Redirect DNS queries to remote server.
- CHN Route
- Bypass all sites located in China
-
- Global Proxy
- Set up system wide proxy
-
- Individual Proxy
- Set individual proxy for apps
-
- Select apps to use with Shadowsocks:
- Bypass Mode
- Enable this option to bypass selected apps
- Auto Connect
- Start Shadowsocks at the startup
+
+ Feature Settings
+ HTTP Proxy
+ Local HTTP Proxy
+ DNS Proxy
+ Redirect DNS queries to remote server.
+ CHN Route
+ Bypass all sites located in China
+
+ Global Proxy
+ Set up system wide proxy
+
+ Individual Proxy
+ Set individual proxy for apps
+
+ Select apps to use with Shadowsocks:
+ Bypass Mode
+ Enable this option to bypass selected apps
+ Auto Connect
+ Start Shadowsocks at the startup
-
- Shadowsocks started
- Running in the background
- Shadowsocks failed to start
- Shadowsocks stopped
- Shadowsocks stopped
- Please check your network status and try again.
+
+ Shadowsocks started
+ Running in the background
+ Shadowsocks failed to start
+ Shadowsocks stopped
+ Shadowsocks stopped
+ Please check your network status and try again.
-
- OK
- The port number should be greater than 1024
- Port should not be empty
- Proxy should not be empty
- An unexpected exit detected, context has been recovered
-
- Connecting…
- Initializing…
- Reseting…
- Loading app list…
+
+ OK
+ The port number should be greater than 1024
+ Port should not be empty
+ Proxy should not be empty
+ An unexpected exit detected, context has been recovered
+
+ Connecting…
+ Initializing…
+ Reseting…
+ Loading app list…
-
- Reset
- About
- Shadowsocks is an open source socks proxy.\n\nYou can find more details
- here:\ngithub.com/shadowsocks
-
+
+ Reset
+ About
+ Shadowsocks is an open source socks proxy.\n\nYou can find more details
+ here:\ngithub.com/shadowsocks
+
-
-
- - 0.0.0.0/7
- - 10.0.0.0/8
- - 14.0.0.0/8
- - 27.0.0.0/8
- - 36.0.0.0/8
- - 39.0.0.0/8
- - 42.0.0.0/7
- - 49.0.0.0/8
- - 58.0.0.0/7
- - 60.0.0.0/7
- - 101.0.0.0/8
- - 103.0.0.0/8
- - 106.0.0.0/8
- - 110.0.0.0/7
- - 112.0.0.0/4
- - 134.128.0.0/9
- - 139.0.0.0/9
- - 139.128.0.0/9
- - 140.0.0.0/9
- - 140.128.0.0/9
- - 144.0.0.0/9
- - 144.128.0.0/9
- - 150.0.0.0/8
- - 153.0.0.0/8
- - 157.0.0.0/9
- - 157.128.0.0/9
- - 159.128.0.0/9
- - 161.128.0.0/9
- - 162.0.0.0/9
- - 163.0.0.0/8
- - 166.0.0.0/9
- - 167.128.0.0/9
- - 168.128.0.0/9
- - 171.0.0.0/8
- - 172.0.0.0/12
- - 175.0.0.0/8
- - 180.0.0.0/8
- - 182.0.0.0/7
- - 192.112.0.0/12
- - 192.128.0.0/9
- - 202.0.0.0/7
- - 210.0.0.0/7
- - 218.0.0.0/7
- - 220.0.0.0/6
-
+
+
+ - 0.0.0.0/7
+ - 10.0.0.0/8
+ - 14.0.0.0/8
+ - 27.0.0.0/8
+ - 36.0.0.0/8
+ - 39.0.0.0/8
+ - 42.0.0.0/7
+ - 49.0.0.0/8
+ - 58.0.0.0/7
+ - 60.0.0.0/7
+ - 101.0.0.0/8
+ - 103.0.0.0/8
+ - 106.0.0.0/8
+ - 110.0.0.0/7
+ - 112.0.0.0/4
+ - 134.128.0.0/9
+ - 139.0.0.0/9
+ - 139.128.0.0/9
+ - 140.0.0.0/9
+ - 140.128.0.0/9
+ - 144.0.0.0/9
+ - 144.128.0.0/9
+ - 150.0.0.0/8
+ - 153.0.0.0/8
+ - 157.0.0.0/9
+ - 157.128.0.0/9
+ - 159.128.0.0/9
+ - 161.128.0.0/9
+ - 162.0.0.0/9
+ - 163.0.0.0/8
+ - 166.0.0.0/9
+ - 167.128.0.0/9
+ - 168.128.0.0/9
+ - 171.0.0.0/8
+ - 172.0.0.0/12
+ - 175.0.0.0/8
+ - 180.0.0.0/8
+ - 182.0.0.0/7
+ - 192.112.0.0/12
+ - 192.128.0.0/9
+ - 202.0.0.0/7
+ - 210.0.0.0/7
+ - 218.0.0.0/7
+ - 220.0.0.0/6
+
diff --git a/res/values/styles_shadow.xml b/res/values/styles_shadow.xml
index bff14b4a..33f90f36 100755
--- a/res/values/styles_shadow.xml
+++ b/res/values/styles_shadow.xml
@@ -19,64 +19,66 @@
-
+
-
+
-
-
-
-
-
+
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/xml/pref_feature.xml b/res/xml/pref_feature.xml
index f6312ba7..a630256a 100755
--- a/res/xml/pref_feature.xml
+++ b/res/xml/pref_feature.xml
@@ -1,50 +1,50 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/pref_proxy.xml b/res/xml/pref_proxy.xml
index 67d3ef1b..1957a5b2 100755
--- a/res/xml/pref_proxy.xml
+++ b/res/xml/pref_proxy.xml
@@ -1,36 +1,36 @@
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
diff --git a/res/xml/shadowsocks_headers.xml b/res/xml/shadowsocks_headers.xml
index dca94452..0cfe6052 100644
--- a/res/xml/shadowsocks_headers.xml
+++ b/res/xml/shadowsocks_headers.xml
@@ -1,16 +1,16 @@
-
+
-
-
+
+
\ No newline at end of file
diff --git a/src/com/github/shadowsocks/AppManager.java b/src/com/github/shadowsocks/AppManager.java
index 43d16122..c0ada974 100755
--- a/src/com/github/shadowsocks/AppManager.java
+++ b/src/com/github/shadowsocks/AppManager.java
@@ -21,388 +21,362 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
-import android.widget.*;
+import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
import com.actionbarsherlock.app.SherlockActivity;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+import java.util.StringTokenizer;
+import java.util.Vector;
-import java.util.*;
+public class AppManager extends SherlockActivity
+ implements OnCheckedChangeListener, OnClickListener {
-public class AppManager extends SherlockActivity implements OnCheckedChangeListener,
- OnClickListener {
+ public final static String PREFS_KEY_PROXYED = "Proxyed";
+ private static final int MSG_LOAD_START = 1;
+ private static final int MSG_LOAD_FINISH = 2;
+ final Handler handler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_LOAD_START:
+ pd = ProgressDialog.show(AppManager.this, "", getString(R.string.loading), true, true);
+ break;
+ case MSG_LOAD_FINISH:
- public final static String PREFS_KEY_PROXYED = "Proxyed";
- private static final int MSG_LOAD_START = 1;
- private static final int MSG_LOAD_FINISH = 2;
- final Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case MSG_LOAD_START:
- pd = ProgressDialog.show(AppManager.this, "",
- getString(R.string.loading), true, true);
- break;
- case MSG_LOAD_FINISH:
+ listApps.setAdapter(adapter);
- listApps.setAdapter(adapter);
+ listApps.setOnScrollListener(new OnScrollListener() {
- listApps.setOnScrollListener(new OnScrollListener() {
+ boolean visible;
- boolean visible;
-
- @Override
- public void onScroll(AbsListView view,
- int firstVisibleItem, int visibleItemCount,
- int totalItemCount) {
- if (visible) {
- String name = apps[firstVisibleItem].getName();
- if (name != null && name.length() > 1)
- overlay.setText(apps[firstVisibleItem]
- .getName().substring(0, 1));
- else
- overlay.setText("*");
- overlay.setVisibility(View.VISIBLE);
- }
- }
-
- @Override
- public void onScrollStateChanged(AbsListView view,
- int scrollState) {
- visible = true;
- if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
- overlay.setVisibility(View.INVISIBLE);
- }
- }
- });
-
- if (pd != null) {
- pd.dismiss();
- pd = null;
- }
- break;
- }
- super.handleMessage(msg);
- }
- };
- private ProxyedApp[] apps = null;
- private ListView listApps;
- private AppManager mAppManager;
- private TextView overlay;
- private ProgressDialog pd = null;
- private ListAdapter adapter;
- private ImageLoader dm;
- private boolean appsLoaded = false;
-
- public static ProxyedApp[] getProxyedApps(Context context) {
-
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context);
-
- String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
- String[] tordApps;
-
- StringTokenizer st = new StringTokenizer(tordAppString, "|");
- tordApps = new String[st.countTokens()];
- int tordIdx = 0;
- while (st.hasMoreTokens()) {
- tordApps[tordIdx++] = st.nextToken();
- }
-
- Arrays.sort(tordApps);
-
- // else load the apps up
- PackageManager pMgr = context.getPackageManager();
-
- List lAppInfo = pMgr.getInstalledApplications(0);
-
- Iterator itAppInfo = lAppInfo.iterator();
-
- Vector vectorApps = new Vector();
-
- ApplicationInfo aInfo = null;
-
- while (itAppInfo.hasNext()) {
- aInfo = itAppInfo.next();
-
- // ignore system apps
- if (aInfo.uid < 10000)
- continue;
-
- ProxyedApp app = new ProxyedApp();
-
- app.setUid(aInfo.uid);
-
- app.setUsername(pMgr.getNameForUid(app.getUid()));
-
- // check if this application is allowed
- if (aInfo.packageName != null
- && aInfo.packageName.equals("com.github.shadowsocks")) {
- app.setProxyed(true);
- } else if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) {
- app.setProxyed(true);
- } else {
- app.setProxyed(false);
- }
-
- vectorApps.add(app);
-
- }
-
- ProxyedApp[] apps = new ProxyedApp[vectorApps.size()];
- vectorApps.toArray(apps);
- return apps;
- }
-
- public void getApps(Context context) {
-
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(context);
-
- String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
- String[] tordApps;
-
- StringTokenizer st = new StringTokenizer(tordAppString, "|");
- tordApps = new String[st.countTokens()];
- int tordIdx = 0;
- while (st.hasMoreTokens()) {
- tordApps[tordIdx++] = st.nextToken();
- }
-
- Arrays.sort(tordApps);
-
- Vector vectorApps = new Vector();
-
- // else load the apps up
- PackageManager pMgr = context.getPackageManager();
-
- List lAppInfo = pMgr.getInstalledApplications(0);
-
- Iterator itAppInfo = lAppInfo.iterator();
-
- ApplicationInfo aInfo = null;
-
- while (itAppInfo.hasNext()) {
- aInfo = itAppInfo.next();
-
- // ignore system apps
- if (aInfo.uid < 10000)
- continue;
-
- if (aInfo.processName == null)
- continue;
- if (pMgr.getApplicationLabel(aInfo) == null
- || pMgr.getApplicationLabel(aInfo).toString().equals(""))
- continue;
- if (pMgr.getApplicationIcon(aInfo) == null)
- continue;
-
- ProxyedApp tApp = new ProxyedApp();
-
- tApp.setEnabled(aInfo.enabled);
- tApp.setUid(aInfo.uid);
- tApp.setUsername(pMgr.getNameForUid(tApp.getUid()));
- tApp.setProcname(aInfo.processName);
- tApp.setName(pMgr.getApplicationLabel(aInfo).toString());
-
- // check if this application is allowed
- if (Arrays.binarySearch(tordApps, tApp.getUsername()) >= 0) {
- tApp.setProxyed(true);
- } else {
- tApp.setProxyed(false);
- }
-
- vectorApps.add(tApp);
- }
-
- apps = new ProxyedApp[vectorApps.size()];
- vectorApps.toArray(apps);
-
- }
-
- private void loadApps() {
- getApps(this);
-
- Arrays.sort(apps, new Comparator() {
@Override
- public int compare(ProxyedApp o1, ProxyedApp o2) {
- if (o1 == null || o2 == null || o1.getName() == null
- || o2.getName() == null)
- return 1;
- if (o1.isProxyed() == o2.isProxyed())
- return o1.getName().compareTo(o2.getName());
- if (o1.isProxyed())
- return -1;
- return 1;
- }
- });
-
- final LayoutInflater inflater = getLayoutInflater();
-
- adapter = new ArrayAdapter(this, R.layout.layout_apps_item,
- R.id.itemtext, apps) {
- @Override
- public View getView(int position, View convertView, ViewGroup parent) {
- ListEntry entry;
- if (convertView == null) {
- // Inflate a new view
- convertView = inflater.inflate(R.layout.layout_apps_item,
- parent, false);
- entry = new ListEntry();
- entry.icon = (ImageView) convertView
- .findViewById(R.id.itemicon);
- entry.box = (CheckBox) convertView
- .findViewById(R.id.itemcheck);
- entry.text = (TextView) convertView
- .findViewById(R.id.itemtext);
-
- entry.text.setOnClickListener(mAppManager);
- entry.text.setOnClickListener(mAppManager);
-
- convertView.setTag(entry);
-
- entry.box.setOnCheckedChangeListener(mAppManager);
+ public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
+ int totalItemCount) {
+ if (visible) {
+ String name = apps[firstVisibleItem].getName();
+ if (name != null && name.length() > 1) {
+ overlay.setText(apps[firstVisibleItem].getName().substring(0, 1));
} else {
- // Convert an existing view
- entry = (ListEntry) convertView.getTag();
+ overlay.setText("*");
}
-
- final ProxyedApp app = apps[position];
-
- entry.icon.setTag(app.getUid());
-
- dm.DisplayImage(app.getUid(),
- (Activity) convertView.getContext(), entry.icon);
-
- entry.text.setText(app.getName());
-
- final CheckBox box = entry.box;
- box.setTag(app);
- box.setChecked(app.isProxyed());
-
- entry.text.setTag(box);
-
- return convertView;
+ overlay.setVisibility(View.VISIBLE);
+ }
}
- };
-
- appsLoaded = true;
-
- }
-
- /**
- * Called an application is check/unchecked
- */
- @Override
- public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
- final ProxyedApp app = (ProxyedApp) buttonView.getTag();
- if (app != null) {
- app.setProxyed(isChecked);
- }
-
- saveAppSettings(this);
-
- }
-
- @Override
- public void onClick(View v) {
-
- CheckBox cbox = (CheckBox) v.getTag();
-
- final ProxyedApp app = (ProxyedApp) cbox.getTag();
- if (app != null) {
- app.setProxyed(!app.isProxyed());
- cbox.setChecked(app.isProxyed());
- }
-
- saveAppSettings(this);
-
- }
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
-
- this.setContentView(R.layout.layout_apps);
-
- this.dm = ImageLoaderFactory.getImageLoader(this);
-
- this.overlay = (TextView) View.inflate(this, R.layout.overlay, null);
- getWindowManager()
- .addView(
- overlay,
- new WindowManager.LayoutParams(
- LayoutParams.WRAP_CONTENT,
- LayoutParams.WRAP_CONTENT,
- WindowManager.LayoutParams.TYPE_APPLICATION,
- WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
- | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
- PixelFormat.TRANSLUCENT));
-
- mAppManager = this;
-
- }
-
- @Override
- protected void onResume() {
- super.onResume();
-
- new Thread() {
@Override
- public void run() {
- handler.sendEmptyMessage(MSG_LOAD_START);
-
- listApps = (ListView) findViewById(R.id.applistview);
-
- if (!appsLoaded)
- loadApps();
- handler.sendEmptyMessage(MSG_LOAD_FINISH);
+ public void onScrollStateChanged(AbsListView view, int scrollState) {
+ visible = true;
+ if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
+ overlay.setVisibility(View.INVISIBLE);
+ }
}
- }.start();
+ });
+ if (pd != null) {
+ pd.dismiss();
+ pd = null;
+ }
+ break;
+ }
+ super.handleMessage(msg);
+ }
+ };
+ private ProxyedApp[] apps = null;
+ private ListView listApps;
+ private AppManager mAppManager;
+ private TextView overlay;
+ private ProgressDialog pd = null;
+ private ListAdapter adapter;
+ private ImageLoader dm;
+ private boolean appsLoaded = false;
+
+ public static ProxyedApp[] getProxyedApps(Context context) {
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
+
+ String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
+ String[] tordApps;
+
+ StringTokenizer st = new StringTokenizer(tordAppString, "|");
+ tordApps = new String[st.countTokens()];
+ int tordIdx = 0;
+ while (st.hasMoreTokens()) {
+ tordApps[tordIdx++] = st.nextToken();
}
- /*
- * (non-Javadoc)
- *
- * @see android.app.Activity#onStop()
- */
- @Override
- protected void onStop() {
- super.onStop();
+ Arrays.sort(tordApps);
- // Log.d(getClass().getName(),"Exiting Preferences");
+ // else load the apps up
+ PackageManager pMgr = context.getPackageManager();
+
+ List lAppInfo = pMgr.getInstalledApplications(0);
+
+ Iterator itAppInfo = lAppInfo.iterator();
+
+ Vector vectorApps = new Vector();
+
+ ApplicationInfo aInfo = null;
+
+ while (itAppInfo.hasNext()) {
+ aInfo = itAppInfo.next();
+
+ // ignore system apps
+ if (aInfo.uid < 10000) continue;
+
+ ProxyedApp app = new ProxyedApp();
+
+ app.setUid(aInfo.uid);
+
+ app.setUsername(pMgr.getNameForUid(app.getUid()));
+
+ // check if this application is allowed
+ if (aInfo.packageName != null && aInfo.packageName.equals("com.github.shadowsocks")) {
+ app.setProxyed(true);
+ } else if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) {
+ app.setProxyed(true);
+ } else {
+ app.setProxyed(false);
+ }
+
+ vectorApps.add(app);
}
- public void saveAppSettings(Context context) {
- if (apps == null)
- return;
+ ProxyedApp[] apps = new ProxyedApp[vectorApps.size()];
+ vectorApps.toArray(apps);
+ return apps;
+ }
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(this);
+ public void getApps(Context context) {
- // final SharedPreferences prefs =
- // context.getSharedPreferences(PREFS_KEY, 0);
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- StringBuilder tordApps = new StringBuilder();
+ String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
+ String[] tordApps;
- for (int i = 0; i < apps.length; i++) {
- if (apps[i].isProxyed()) {
- tordApps.append(apps[i].getUsername());
- tordApps.append("|");
- }
+ StringTokenizer st = new StringTokenizer(tordAppString, "|");
+ tordApps = new String[st.countTokens()];
+ int tordIdx = 0;
+ while (st.hasMoreTokens()) {
+ tordApps[tordIdx++] = st.nextToken();
+ }
+
+ Arrays.sort(tordApps);
+
+ Vector vectorApps = new Vector();
+
+ // else load the apps up
+ PackageManager pMgr = context.getPackageManager();
+
+ List lAppInfo = pMgr.getInstalledApplications(0);
+
+ Iterator itAppInfo = lAppInfo.iterator();
+
+ ApplicationInfo aInfo = null;
+
+ while (itAppInfo.hasNext()) {
+ aInfo = itAppInfo.next();
+
+ // ignore system apps
+ if (aInfo.uid < 10000) continue;
+
+ if (aInfo.processName == null) continue;
+ if (pMgr.getApplicationLabel(aInfo) == null || pMgr.getApplicationLabel(aInfo)
+ .toString()
+ .equals("")) {
+ continue;
+ }
+ if (pMgr.getApplicationIcon(aInfo) == null) continue;
+
+ ProxyedApp tApp = new ProxyedApp();
+
+ tApp.setEnabled(aInfo.enabled);
+ tApp.setUid(aInfo.uid);
+ tApp.setUsername(pMgr.getNameForUid(tApp.getUid()));
+ tApp.setProcname(aInfo.processName);
+ tApp.setName(pMgr.getApplicationLabel(aInfo).toString());
+
+ // check if this application is allowed
+ if (Arrays.binarySearch(tordApps, tApp.getUsername()) >= 0) {
+ tApp.setProxyed(true);
+ } else {
+ tApp.setProxyed(false);
+ }
+
+ vectorApps.add(tApp);
+ }
+
+ apps = new ProxyedApp[vectorApps.size()];
+ vectorApps.toArray(apps);
+ }
+
+ private void loadApps() {
+ getApps(this);
+
+ Arrays.sort(apps, new Comparator() {
+ @Override
+ public int compare(ProxyedApp o1, ProxyedApp o2) {
+ if (o1 == null || o2 == null || o1.getName() == null || o2.getName() == null) return 1;
+ if (o1.isProxyed() == o2.isProxyed()) return o1.getName().compareTo(o2.getName());
+ if (o1.isProxyed()) return -1;
+ return 1;
+ }
+ });
+
+ final LayoutInflater inflater = getLayoutInflater();
+
+ adapter = new ArrayAdapter(this, R.layout.layout_apps_item, R.id.itemtext, apps) {
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ ListEntry entry;
+ if (convertView == null) {
+ // Inflate a new view
+ convertView = inflater.inflate(R.layout.layout_apps_item, parent, false);
+ entry = new ListEntry();
+ entry.icon = (ImageView) convertView.findViewById(R.id.itemicon);
+ entry.box = (CheckBox) convertView.findViewById(R.id.itemcheck);
+ entry.text = (TextView) convertView.findViewById(R.id.itemtext);
+
+ entry.text.setOnClickListener(mAppManager);
+ entry.text.setOnClickListener(mAppManager);
+
+ convertView.setTag(entry);
+
+ entry.box.setOnCheckedChangeListener(mAppManager);
+ } else {
+ // Convert an existing view
+ entry = (ListEntry) convertView.getTag();
}
- Editor edit = prefs.edit();
- edit.putString(PREFS_KEY_PROXYED, tordApps.toString());
- edit.commit();
+ final ProxyedApp app = apps[position];
+ entry.icon.setTag(app.getUid());
+
+ dm.DisplayImage(app.getUid(), (Activity) convertView.getContext(), entry.icon);
+
+ entry.text.setText(app.getName());
+
+ final CheckBox box = entry.box;
+ box.setTag(app);
+ box.setChecked(app.isProxyed());
+
+ entry.text.setTag(box);
+
+ return convertView;
+ }
+ };
+
+ appsLoaded = true;
+ }
+
+ /** Called an application is check/unchecked */
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ final ProxyedApp app = (ProxyedApp) buttonView.getTag();
+ if (app != null) {
+ app.setProxyed(isChecked);
}
- private static class ListEntry {
- private CheckBox box;
- private TextView text;
- private ImageView icon;
+ saveAppSettings(this);
+ }
+
+ @Override
+ public void onClick(View v) {
+
+ CheckBox cbox = (CheckBox) v.getTag();
+
+ final ProxyedApp app = (ProxyedApp) cbox.getTag();
+ if (app != null) {
+ app.setProxyed(!app.isProxyed());
+ cbox.setChecked(app.isProxyed());
}
+ saveAppSettings(this);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ this.setContentView(R.layout.layout_apps);
+
+ this.dm = ImageLoaderFactory.getImageLoader(this);
+
+ this.overlay = (TextView) View.inflate(this, R.layout.overlay, null);
+ getWindowManager().addView(overlay,
+ new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+ | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT));
+
+ mAppManager = this;
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+
+ new Thread() {
+
+ @Override
+ public void run() {
+ handler.sendEmptyMessage(MSG_LOAD_START);
+
+ listApps = (ListView) findViewById(R.id.applistview);
+
+ if (!appsLoaded) loadApps();
+ handler.sendEmptyMessage(MSG_LOAD_FINISH);
+ }
+ }.start();
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see android.app.Activity#onStop()
+ */
+ @Override
+ protected void onStop() {
+ super.onStop();
+
+ // Log.d(getClass().getName(),"Exiting Preferences");
+ }
+
+ public void saveAppSettings(Context context) {
+ if (apps == null) return;
+
+ SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+
+ // final SharedPreferences prefs =
+ // context.getSharedPreferences(PREFS_KEY, 0);
+
+ StringBuilder tordApps = new StringBuilder();
+
+ for (int i = 0; i < apps.length; i++) {
+ if (apps[i].isProxyed()) {
+ tordApps.append(apps[i].getUsername());
+ tordApps.append("|");
+ }
+ }
+
+ Editor edit = prefs.edit();
+ edit.putString(PREFS_KEY_PROXYED, tordApps.toString());
+ edit.commit();
+ }
+
+ private static class ListEntry {
+ private CheckBox box;
+ private TextView text;
+ private ImageView icon;
+ }
}
diff --git a/src/com/github/shadowsocks/Exec.java b/src/com/github/shadowsocks/Exec.java
index 66d2d7f5..d3a32625 100755
--- a/src/com/github/shadowsocks/Exec.java
+++ b/src/com/github/shadowsocks/Exec.java
@@ -27,46 +27,41 @@ import java.io.FileDescriptor;
*/
public class Exec {
- static {
- java.lang.System.loadLibrary("exec");
- }
+ static {
+ java.lang.System.loadLibrary("exec");
+ }
- /**
- * Close a given file descriptor.
- */
- public static native void close(FileDescriptor fd);
+ /** Close a given file descriptor. */
+ public static native void close(FileDescriptor fd);
- /**
- * Create a subprocess. Differs from java.lang.ProcessBuilder in that a pty
- * is used to communicate with the subprocess.
- *
- * Callers are responsible for calling Exec.close() on the returned file
- * descriptor.
- *
- * @param rdt Whether redirect stdout and stderr
- * @param cmd The command to execute.
- * @param args An array of arguments to the command.
- * @param envVars An array of strings of the form "VAR=value" to be added to the
- * environment of the process.
- * @param scripts The scripts to execute.
- * @param processId A one-element array to which the process ID of the started
- * process will be written.
- * @return File descriptor
- */
- public static native FileDescriptor createSubprocess(int rdt, String cmd,
- String[] args, String[] envVars,
- String scripts, int[] processId);
+ /**
+ * Create a subprocess. Differs from java.lang.ProcessBuilder in that a pty
+ * is used to communicate with the subprocess.
+ *
+ * Callers are responsible for calling Exec.close() on the returned file
+ * descriptor.
+ *
+ * @param rdt Whether redirect stdout and stderr
+ * @param cmd The command to execute.
+ * @param args An array of arguments to the command.
+ * @param envVars An array of strings of the form "VAR=value" to be added to the
+ * environment of the process.
+ * @param scripts The scripts to execute.
+ * @param processId A one-element array to which the process ID of the started
+ * process will be written.
+ * @return File descriptor
+ */
+ public static native FileDescriptor createSubprocess(int rdt, String cmd, String[] args,
+ String[] envVars, String scripts, int[] processId);
- /**
- * Send SIGHUP to a process group.
- */
- public static native void hangupProcessGroup(int processId);
+ /** Send SIGHUP to a process group. */
+ public static native void hangupProcessGroup(int processId);
- /**
- * Causes the calling thread to wait for the process associated with the
- * receiver to finish executing.
- *
- * @return The exit value of the Process being waited on
- */
- public static native int waitFor(int processId);
+ /**
+ * Causes the calling thread to wait for the process associated with the
+ * receiver to finish executing.
+ *
+ * @return The exit value of the Process being waited on
+ */
+ public static native int waitFor(int processId);
}
diff --git a/src/com/github/shadowsocks/ImageLoader.java b/src/com/github/shadowsocks/ImageLoader.java
index b416f57b..d57feefa 100755
--- a/src/com/github/shadowsocks/ImageLoader.java
+++ b/src/com/github/shadowsocks/ImageLoader.java
@@ -45,7 +45,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.widget.ImageView;
-
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -54,195 +53,190 @@ import java.util.Stack;
public class ImageLoader {
- final int stub_id = R.drawable.sym_def_app_icon;
- PhotosQueue photosQueue = new PhotosQueue();
- PhotosLoader photoLoaderThread = new PhotosLoader();
- // the simplest in-memory cache implementation. This should be replaced with
- // something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
- private HashMap cache = new HashMap();
- private File cacheDir;
- private Context context;
+ final int stub_id = R.drawable.sym_def_app_icon;
+ PhotosQueue photosQueue = new PhotosQueue();
+ PhotosLoader photoLoaderThread = new PhotosLoader();
+ // the simplest in-memory cache implementation. This should be replaced with
+ // something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
+ private HashMap cache = new HashMap();
+ private File cacheDir;
+ private Context context;
- public ImageLoader(Context c) {
- // Make the background thead low priority. This way it will not affect
- // the UI performance
- photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
+ public ImageLoader(Context c) {
+ // Make the background thead low priority. This way it will not affect
+ // the UI performance
+ photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
- context = c;
+ context = c;
- // Find the dir to save cached images
- cacheDir = context.getCacheDir();
+ // Find the dir to save cached images
+ cacheDir = context.getCacheDir();
+ }
+ public void clearCache() {
+ // clear memory cache
+ cache.clear();
+
+ // clear SD cache
+ File[] files = cacheDir.listFiles();
+ for (File f : files)
+ f.delete();
+ }
+
+ // decodes image and scales it to reduce memory consumption
+ private Bitmap decodeFile(File f) {
+ try {
+ // decode image size
+ BitmapFactory.Options o = new BitmapFactory.Options();
+ o.inJustDecodeBounds = true;
+ BitmapFactory.decodeStream(new FileInputStream(f), null, o);
+
+ // Find the correct scale value. It should be the power of 2.
+ final int REQUIRED_SIZE = 70;
+ int width_tmp = o.outWidth, height_tmp = o.outHeight;
+ int scale = 1;
+ while (true) {
+ if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE) break;
+ width_tmp /= 2;
+ height_tmp /= 2;
+ scale *= 2;
+ }
+
+ // decode with inSampleSize
+ BitmapFactory.Options o2 = new BitmapFactory.Options();
+ o2.inSampleSize = scale;
+ return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
+ } catch (FileNotFoundException ignored) {
+ }
+ return null;
+ }
+
+ public void DisplayImage(int uid, Activity activity, ImageView imageView) {
+ if (cache.containsKey(uid)) {
+ imageView.setImageBitmap(cache.get(uid));
+ } else {
+ queuePhoto(uid, activity, imageView);
+ imageView.setImageResource(stub_id);
+ }
+ }
+
+ private Bitmap getBitmap(int uid) {
+ // I identify images by hashcode. Not a perfect solution, good for the
+ // demo.
+ String filename = String.valueOf(uid);
+ File f = new File(cacheDir, filename);
+
+ // from SD cache
+ Bitmap b = decodeFile(f);
+ if (b != null) return b;
+
+ // from web
+ try {
+ BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context, uid);
+ return icon.getBitmap();
+ } catch (Exception ex) {
+ return null;
+ }
+ }
+
+ private void queuePhoto(int uid, Activity activity, ImageView imageView) {
+ // This ImageView may be used for other images before. So there may be
+ // some old tasks in the queue. We need to discard them.
+ photosQueue.Clean(imageView);
+ PhotoToLoad p = new PhotoToLoad(uid, imageView);
+ synchronized (photosQueue.photosToLoad) {
+ photosQueue.photosToLoad.push(p);
+ photosQueue.photosToLoad.notifyAll();
}
- public void clearCache() {
- // clear memory cache
- cache.clear();
+ // start thread if it's not started yet
+ if (photoLoaderThread.getState() == Thread.State.NEW) photoLoaderThread.start();
+ }
- // clear SD cache
- File[] files = cacheDir.listFiles();
- for (File f : files)
- f.delete();
+ public void stopThread() {
+ photoLoaderThread.interrupt();
+ }
+
+ // Used to display bitmap in the UI thread
+ class BitmapDisplayer implements Runnable {
+ Bitmap bitmap;
+ ImageView imageView;
+
+ public BitmapDisplayer(Bitmap b, ImageView i) {
+ bitmap = b;
+ imageView = i;
}
- // decodes image and scales it to reduce memory consumption
- private Bitmap decodeFile(File f) {
- try {
- // decode image size
- BitmapFactory.Options o = new BitmapFactory.Options();
- o.inJustDecodeBounds = true;
- BitmapFactory.decodeStream(new FileInputStream(f), null, o);
+ @Override
+ public void run() {
+ if (bitmap != null) {
+ imageView.setImageBitmap(bitmap);
+ } else {
+ imageView.setImageResource(stub_id);
+ }
+ }
+ }
- // Find the correct scale value. It should be the power of 2.
- final int REQUIRED_SIZE = 70;
- int width_tmp = o.outWidth, height_tmp = o.outHeight;
- int scale = 1;
- while (true) {
- if (width_tmp / 2 < REQUIRED_SIZE
- || height_tmp / 2 < REQUIRED_SIZE)
- break;
- width_tmp /= 2;
- height_tmp /= 2;
- scale *= 2;
+ class PhotosLoader extends Thread {
+ @Override
+ public void run() {
+ try {
+ while (true) {
+ // thread waits until there are any images to load in the
+ // queue
+ if (photosQueue.photosToLoad.size() == 0) {
+ synchronized (photosQueue.photosToLoad) {
+ photosQueue.photosToLoad.wait();
}
-
- // decode with inSampleSize
- BitmapFactory.Options o2 = new BitmapFactory.Options();
- o2.inSampleSize = scale;
- return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
- } catch (FileNotFoundException ignored) {
- }
- return null;
- }
-
- public void DisplayImage(int uid, Activity activity, ImageView imageView) {
- if (cache.containsKey(uid))
- imageView.setImageBitmap(cache.get(uid));
- else {
- queuePhoto(uid, activity, imageView);
- imageView.setImageResource(stub_id);
- }
- }
-
- private Bitmap getBitmap(int uid) {
- // I identify images by hashcode. Not a perfect solution, good for the
- // demo.
- String filename = String.valueOf(uid);
- File f = new File(cacheDir, filename);
-
- // from SD cache
- Bitmap b = decodeFile(f);
- if (b != null)
- return b;
-
- // from web
- try {
- BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context,
- uid);
- return icon.getBitmap();
- } catch (Exception ex) {
- return null;
- }
- }
-
- private void queuePhoto(int uid, Activity activity, ImageView imageView) {
- // This ImageView may be used for other images before. So there may be
- // some old tasks in the queue. We need to discard them.
- photosQueue.Clean(imageView);
- PhotoToLoad p = new PhotoToLoad(uid, imageView);
- synchronized (photosQueue.photosToLoad) {
- photosQueue.photosToLoad.push(p);
- photosQueue.photosToLoad.notifyAll();
- }
-
- // start thread if it's not started yet
- if (photoLoaderThread.getState() == Thread.State.NEW)
- photoLoaderThread.start();
- }
-
- public void stopThread() {
- photoLoaderThread.interrupt();
- }
-
- // Used to display bitmap in the UI thread
- class BitmapDisplayer implements Runnable {
- Bitmap bitmap;
- ImageView imageView;
-
- public BitmapDisplayer(Bitmap b, ImageView i) {
- bitmap = b;
- imageView = i;
- }
-
- @Override
- public void run() {
- if (bitmap != null)
- imageView.setImageBitmap(bitmap);
- else
- imageView.setImageResource(stub_id);
- }
- }
-
- class PhotosLoader extends Thread {
- @Override
- public void run() {
- try {
- while (true) {
- // thread waits until there are any images to load in the
- // queue
- if (photosQueue.photosToLoad.size() == 0)
- synchronized (photosQueue.photosToLoad) {
- photosQueue.photosToLoad.wait();
- }
- if (photosQueue.photosToLoad.size() != 0) {
- PhotoToLoad photoToLoad;
- synchronized (photosQueue.photosToLoad) {
- photoToLoad = photosQueue.photosToLoad.pop();
- }
- Bitmap bmp = getBitmap(photoToLoad.uid);
- cache.put(photoToLoad.uid, bmp);
- Object tag = photoToLoad.imageView.getTag();
- if (tag != null && ((Integer) tag) == photoToLoad.uid) {
- BitmapDisplayer bd = new BitmapDisplayer(bmp,
- photoToLoad.imageView);
- Activity a = (Activity) photoToLoad.imageView
- .getContext();
- a.runOnUiThread(bd);
- }
- }
- if (Thread.interrupted())
- break;
- }
- } catch (InterruptedException e) {
- // allow thread to exit
+ }
+ if (photosQueue.photosToLoad.size() != 0) {
+ PhotoToLoad photoToLoad;
+ synchronized (photosQueue.photosToLoad) {
+ photoToLoad = photosQueue.photosToLoad.pop();
}
- }
- }
-
- // stores list of photos to download
- class PhotosQueue {
- private final Stack photosToLoad = new Stack();
-
- // removes all instances of this ImageView
- public void Clean(ImageView image) {
- synchronized (photosToLoad) {
- for (int j = 0; j < photosToLoad.size(); ) {
- if (photosToLoad.get(j).imageView == image) photosToLoad.remove(j);
- else ++j;
- }
+ Bitmap bmp = getBitmap(photoToLoad.uid);
+ cache.put(photoToLoad.uid, bmp);
+ Object tag = photoToLoad.imageView.getTag();
+ if (tag != null && ((Integer) tag) == photoToLoad.uid) {
+ BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad.imageView);
+ Activity a = (Activity) photoToLoad.imageView.getContext();
+ a.runOnUiThread(bd);
}
+ }
+ if (Thread.interrupted()) break;
}
+ } catch (InterruptedException e) {
+ // allow thread to exit
+ }
}
+ }
- // Task for the queue
- private class PhotoToLoad {
- public int uid;
- public ImageView imageView;
+ // stores list of photos to download
+ class PhotosQueue {
+ private final Stack photosToLoad = new Stack();
- public PhotoToLoad(int u, ImageView i) {
- uid = u;
- imageView = i;
+ // removes all instances of this ImageView
+ public void Clean(ImageView image) {
+ synchronized (photosToLoad) {
+ for (int j = 0; j < photosToLoad.size(); ) {
+ if (photosToLoad.get(j).imageView == image) {
+ photosToLoad.remove(j);
+ } else {
+ ++j;
+ }
}
+ }
}
+ }
+ // Task for the queue
+ private class PhotoToLoad {
+ public int uid;
+ public ImageView imageView;
+
+ public PhotoToLoad(int u, ImageView i) {
+ uid = u;
+ imageView = i;
+ }
+ }
}
diff --git a/src/com/github/shadowsocks/ImageLoaderFactory.java b/src/com/github/shadowsocks/ImageLoaderFactory.java
index 3c48b4ef..d9de221b 100755
--- a/src/com/github/shadowsocks/ImageLoaderFactory.java
+++ b/src/com/github/shadowsocks/ImageLoaderFactory.java
@@ -42,12 +42,12 @@ import android.content.Context;
public class ImageLoaderFactory {
- private static ImageLoader il = null;
+ private static ImageLoader il = null;
- public static ImageLoader getImageLoader(Context context) {
- if (il == null) {
- il = new ImageLoader(context);
- }
- return il;
+ public static ImageLoader getImageLoader(Context context) {
+ if (il == null) {
+ il = new ImageLoader(context);
}
+ return il;
+ }
}
diff --git a/src/com/github/shadowsocks/ProxyedApp.java b/src/com/github/shadowsocks/ProxyedApp.java
index 277be9f6..988c34f8 100755
--- a/src/com/github/shadowsocks/ProxyedApp.java
+++ b/src/com/github/shadowsocks/ProxyedApp.java
@@ -40,94 +40,70 @@ package com.github.shadowsocks;
public class ProxyedApp {
- private boolean enabled;
- private int uid;
- private String username;
- private String procname;
- private String name;
- private boolean proxyed = false;
+ private boolean enabled;
+ private int uid;
+ private String username;
+ private String procname;
+ private String name;
+ private boolean proxyed = false;
- /**
- * @return the name
- */
- public String getName() {
- return name;
- }
+ /** @return the name */
+ public String getName() {
+ return name;
+ }
- /**
- * @param name the name to set
- */
- public void setName(String name) {
- this.name = name;
- }
+ /** @param name the name to set */
+ public void setName(String name) {
+ this.name = name;
+ }
- /**
- * @return the procname
- */
- public String getProcname() {
- return procname;
- }
+ /** @return the procname */
+ public String getProcname() {
+ return procname;
+ }
- /**
- * @param procname the procname to set
- */
- public void setProcname(String procname) {
- this.procname = procname;
- }
+ /** @param procname the procname to set */
+ public void setProcname(String procname) {
+ this.procname = procname;
+ }
- /**
- * @return the uid
- */
- public int getUid() {
- return uid;
- }
+ /** @return the uid */
+ public int getUid() {
+ return uid;
+ }
- /**
- * @param uid the uid to set
- */
- public void setUid(int uid) {
- this.uid = uid;
- }
+ /** @param uid the uid to set */
+ public void setUid(int uid) {
+ this.uid = uid;
+ }
- /**
- * @return the username
- */
- public String getUsername() {
- return username;
- }
+ /** @return the username */
+ public String getUsername() {
+ return username;
+ }
- /**
- * @param username the username to set
- */
- public void setUsername(String username) {
- this.username = username;
- }
+ /** @param username the username to set */
+ public void setUsername(String username) {
+ this.username = username;
+ }
- /**
- * @return the enabled
- */
- public boolean isEnabled() {
- return enabled;
- }
+ /** @return the enabled */
+ public boolean isEnabled() {
+ return enabled;
+ }
- /**
- * @param enabled the enabled to set
- */
- public void setEnabled(boolean enabled) {
- this.enabled = enabled;
- }
+ /** @param enabled the enabled to set */
+ public void setEnabled(boolean enabled) {
+ this.enabled = enabled;
+ }
- /**
- * @return the proxyed
- */
- public boolean isProxyed() {
- return proxyed;
- }
+ /** @return the proxyed */
+ public boolean isProxyed() {
+ return proxyed;
+ }
- /**
- * @param proxyed the proxyed to set
- */
- public void setProxyed(boolean proxyed) {
- this.proxyed = proxyed;
- }
+ /** @param proxyed the proxyed to set */
+ public void setProxyed(boolean proxyed) {
+ this.proxyed = proxyed;
+ }
}
diff --git a/src/com/github/shadowsocks/Shadowsocks.java b/src/com/github/shadowsocks/Shadowsocks.java
index d80a51eb..c5c374a6 100755
--- a/src/com/github/shadowsocks/Shadowsocks.java
+++ b/src/com/github/shadowsocks/Shadowsocks.java
@@ -52,7 +52,8 @@ import android.graphics.Typeface;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
-import android.preference.*;
+import android.preference.Preference;
+import android.preference.PreferenceManager;
import android.util.Log;
import android.view.KeyEvent;
import android.widget.CompoundButton;
@@ -61,588 +62,554 @@ import android.widget.TextView;
import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem;
import com.google.analytics.tracking.android.EasyTracker;
-import net.saik0.android.unifiedpreference.UnifiedPreferenceFragment;
-import net.saik0.android.unifiedpreference.UnifiedSherlockPreferenceActivity;
-import org.jraf.android.backport.switchwidget.Switch;
import de.keyboardsurfer.android.widget.crouton.Crouton;
import de.keyboardsurfer.android.widget.crouton.Style;
-
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;
+import net.saik0.android.unifiedpreference.UnifiedPreferenceFragment;
+import net.saik0.android.unifiedpreference.UnifiedSherlockPreferenceActivity;
+import org.jraf.android.backport.switchwidget.Switch;
public class Shadowsocks extends UnifiedSherlockPreferenceActivity
- implements CompoundButton.OnCheckedChangeListener, OnSharedPreferenceChangeListener {
-
- public static final String PREFS_NAME = "Shadowsocks";
- public static final String[] PROXY_PREFS = {"proxy", "remotePort", "port"
- , "sitekey", "encMethod"};
- public static final String[] FEATRUE_PREFS = {"isHTTPProxy", "isDNSProxy", "isGFWList", "isGlobalProxy", "isBypassApps"
- , "proxyedApps", "isAutoConnect"};
- private static final String TAG = "Shadowsocks";
- private static final int MSG_CRASH_RECOVER = 1;
- private static final int MSG_INITIAL_FINISH = 2;
- private static ProgressDialog mProgressDialog = null;
- final Handler handler = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(Shadowsocks.this);
- Editor ed = settings.edit();
- switch (msg.what) {
- case MSG_CRASH_RECOVER:
- Crouton.makeText(Shadowsocks.this, R.string.crash_alert, Style.ALERT).show();
- ed.putBoolean("isRunning", false);
- break;
- case MSG_INITIAL_FINISH:
- if (mProgressDialog != null) {
- mProgressDialog.dismiss();
- mProgressDialog = null;
- }
- break;
- }
- ed.commit();
- super.handleMessage(msg);
- }
- };
- private Switch switchButton;
-
- private void copyAssets(String path) {
-
- AssetManager assetManager = getAssets();
- String[] files = null;
- try {
- files = assetManager.list(path);
- } catch (IOException e) {
- Log.e(TAG, e.getMessage());
- }
- if (files != null) {
- for (String file : files) {
- InputStream in = null;
- OutputStream out = null;
- try {
- if (path.length() > 0) {
- in = assetManager.open(path + "/" + file);
- } else {
- in = assetManager.open(file);
- }
- out = new FileOutputStream("/data/data/com.github.shadowsocks/"
- + file);
- copyFile(in, out);
- in.close();
- in = null;
- out.flush();
- out.close();
- out = null;
- } catch (Exception e) {
- Log.e(TAG, e.getMessage());
- }
- }
- }
- }
-
- private void copyFile(InputStream in, OutputStream out) throws IOException {
- byte[] buffer = new byte[1024];
- int read;
- while ((read = in.read(buffer)) != -1) {
- out.write(buffer, 0, read);
- }
- }
-
- private void crash_recovery() {
-
- StringBuilder sb = new StringBuilder();
- sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT").append("\n");
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`").append("\n");
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n");
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/polipo.pid`").append("\n");
- sb.append("killall -9 pdnsd").append("\n");
- sb.append("killall -9 redsocks").append("\n");
- sb.append("killall -9 shadowsocks").append("\n");
- sb.append("killall -9 polipo").append("\n");
- Utils.runRootCommand(sb.toString());
-
- }
-
- private boolean isTextEmpty(String s, String msg) {
- if (s == null || s.length() <= 0) {
- showAToast(msg);
- return true;
- }
- return false;
- }
+ implements CompoundButton.OnCheckedChangeListener, OnSharedPreferenceChangeListener {
+ public static final String PREFS_NAME = "Shadowsocks";
+ public static final String[] PROXY_PREFS = {
+ "proxy", "remotePort", "port", "sitekey", "encMethod"
+ };
+ public static final String[] FEATRUE_PREFS = {
+ "isHTTPProxy", "isDNSProxy", "isGFWList", "isGlobalProxy", "isBypassApps", "proxyedApps",
+ "isAutoConnect"
+ };
+ private static final String TAG = "Shadowsocks";
+ private static final int MSG_CRASH_RECOVER = 1;
+ private static final int MSG_INITIAL_FINISH = 2;
+ private static ProgressDialog mProgressDialog = null;
+ final Handler handler = new Handler() {
@Override
- public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
- if (compoundButton == switchButton) {
- if (!serviceStart()) {
- switchButton.setChecked(false);
- }
- }
- }
-
- /**
- * Called when the activity is first created.
- */
- @Override
- public void onCreate(Bundle savedInstanceState) {
- setHeaderRes(R.xml.shadowsocks_headers);
- super.onCreate(savedInstanceState);
-
- RelativeLayout switchLayout = (RelativeLayout)
- getLayoutInflater().inflate(R.layout.layout_switch, null);
-
- getSupportActionBar().setCustomView(switchLayout);
- getSupportActionBar().setDisplayShowTitleEnabled(false);
- getSupportActionBar().setDisplayShowCustomEnabled(true);
- getSupportActionBar().setDisplayShowHomeEnabled(false);
-
-
- switchButton = (Switch) switchLayout.findViewById(R.id.switchButton);
-
- TextView title = (TextView) switchLayout.findViewById(R.id.title);
- Typeface tf = Typefaces.get(this, "fonts/Iceland.ttf");
- if (tf != null) title.setTypeface(tf);
- title.setText(R.string.app_name);
-
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- menu.add(0, 0, 0, R.string.recovery)
- .setIcon(android.R.drawable.ic_menu_revert)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- menu.add(0, 1, 1, R.string.about)
- .setIcon(android.R.drawable.ic_menu_info_details)
- .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
- return true;
- }
-
- /**
- * Called when the activity is closed.
- */
- @Override
- public void onDestroy() {
- SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
-
- SharedPreferences.Editor editor = settings.edit();
- editor.putBoolean("isConnected", ShadowsocksService.isServiceStarted());
- editor.commit();
-
- super.onDestroy();
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
- try {
- finish();
- } catch (Exception ignore) {
- // Nothing
- }
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case 0:
- recovery();
- break;
- case 1:
- String versionName = "";
- try {
- versionName = getPackageManager().getPackageInfo(
- getPackageName(), 0).versionName;
- } catch (NameNotFoundException e) {
- versionName = "";
- }
- showAToast(getString(R.string.about) + " (" + versionName + ")\n\n"
- + getString(R.string.copy_rights));
- break;
- }
-
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- protected void onPause() {
- super.onPause();
- PreferenceManager.getDefaultSharedPreferences(this)
- .unregisterOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- protected void onResume() {
- super.onResume();
- SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(this);
-
- Editor edit = settings.edit();
-
- if (ShadowsocksService.isServiceStarted()) {
- edit.putBoolean("isRunning", true);
- switchButton.setChecked(true);
- } else {
- if (settings.getBoolean("isRunning", false)) {
- new Thread() {
- @Override
- public void run() {
- crash_recovery();
- handler.sendEmptyMessage(MSG_CRASH_RECOVER);
- }
- }.start();
- }
- edit.putBoolean("isRunning", false);
- switchButton.setChecked(false);
- }
-
- edit.commit();
-
- setPreferenceEnabled();
-
- switchButton.setOnCheckedChangeListener(this);
- PreferenceManager.getDefaultSharedPreferences(this)
- .registerOnSharedPreferenceChangeListener(this);
-
- }
-
- private void setPreferenceEnabled() {
- SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(Shadowsocks.this);
-
- boolean enabled = !settings.getBoolean("isRunning", false)
- && !settings.getBoolean("isConnecting", false);
-
- for (String name : PROXY_PREFS) {
- Preference pref = findPreference(name);
- if (pref != null) {
- pref.setEnabled(enabled);
- }
- }
- for (String name : FEATRUE_PREFS) {
- Preference pref = findPreference(name);
- if (pref != null) {
- if (name.equals("isBypassApps")
- || name.equals("proxyedApps")) {
- boolean isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
- pref.setEnabled(enabled && !isGlobalProxy);
- } else {
- pref.setEnabled(enabled);
- }
- }
- }
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
- String key) {
- // Let's do something a preference value changes
- SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(this);
-
- if (key.equals("isRunning")) {
- if (settings.getBoolean("isRunning", false)) {
- switchButton.setChecked(true);
- } else {
- switchButton.setChecked(false);
- }
- }
-
- if (key.equals("isConnecting")) {
- if (settings.getBoolean("isConnecting", false)) {
- Log.d(TAG, "Connecting start");
- if (mProgressDialog == null)
- mProgressDialog = ProgressDialog.show(this, "",
- getString(R.string.connecting), true, true);
- } else {
- Log.d(TAG, "Connecting finish");
- if (mProgressDialog != null) {
- mProgressDialog.dismiss();
- mProgressDialog = null;
- }
- }
- }
-
- if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
- setPreferenceEnabled();
- }
- }
-
- @Override
- public void onStart() {
- super.onStart();
- EasyTracker.getInstance().activityStart(this);
-
- final SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(Shadowsocks.this);
-
- boolean init = !settings.getBoolean("isRunning", false)
- && !settings.getBoolean("isConnecting", false);
-
- if (init) {
- if (mProgressDialog == null) {
- mProgressDialog = ProgressDialog.show(this, "",
- getString(R.string.initializing), true, true);
- }
-
- new Thread() {
- @Override
- public void run() {
-
- Utils.isRoot();
-
- String versionName;
- try {
- versionName = getPackageManager().getPackageInfo(
- getPackageName(), 0).versionName;
- } catch (NameNotFoundException e) {
- versionName = "NONE";
- }
-
-
- if (!settings.getBoolean(versionName, false)) {
- Editor edit = settings.edit();
- edit.putBoolean(versionName, true);
- edit.commit();
- reset();
- }
-
- handler.sendEmptyMessage(MSG_INITIAL_FINISH);
- }
- }.start();
- }
- }
-
- @Override
- public void onStop() {
- super.onStop();
- EasyTracker.getInstance().activityStop(this);
-
- if (mProgressDialog != null) {
+ public void handleMessage(Message msg) {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(Shadowsocks.this);
+ Editor ed = settings.edit();
+ switch (msg.what) {
+ case MSG_CRASH_RECOVER:
+ Crouton.makeText(Shadowsocks.this, R.string.crash_alert, Style.ALERT).show();
+ ed.putBoolean("isRunning", false);
+ break;
+ case MSG_INITIAL_FINISH:
+ if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
- }
+ }
+ break;
+ }
+ ed.commit();
+ super.handleMessage(msg);
}
+ };
+ private Switch switchButton;
- public void reset() {
+ private void copyAssets(String path) {
- crash_recovery();
-
- copyAssets("");
- copyAssets(Utils.getABI());
-
- Utils.runCommand("chmod 755 /data/data/com.github.shadowsocks/iptables\n"
- + "chmod 755 /data/data/com.github.shadowsocks/redsocks\n"
- + "chmod 755 /data/data/com.github.shadowsocks/pdnsd\n"
- + "chmod 755 /data/data/com.github.shadowsocks/shadowsocks\n"
- + "chmod 755 /data/data/com.github.shadowsocks/polipo\n"
- );
+ AssetManager assetManager = getAssets();
+ String[] files = null;
+ try {
+ files = assetManager.list(path);
+ } catch (IOException e) {
+ Log.e(TAG, e.getMessage());
}
-
- private void recovery() {
-
- if (mProgressDialog == null)
- mProgressDialog = ProgressDialog.show(this, "", getString(R.string.recovering),
- true, true);
-
- final Handler h = new Handler() {
- @Override
- public void handleMessage(Message msg) {
- if (mProgressDialog != null) {
- mProgressDialog.dismiss();
- mProgressDialog = null;
- }
- }
- };
-
- stopService(new Intent(this, ShadowsocksService.class));
-
- new Thread() {
- @Override
- public void run() {
-
- reset();
-
- h.sendEmptyMessage(0);
- }
- }.start();
-
- }
-
- /**
- * Called when connect button is clicked.
- */
- public boolean serviceStart() {
-
- if (ShadowsocksService.isServiceStarted()) {
- stopService(new Intent(this, ShadowsocksService.class));
- return false;
- }
-
- SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(this);
-
- final String proxy = settings.getString("proxy", "");
- if (isTextEmpty(proxy, getString(R.string.proxy_empty)))
- return false;
-
- String portText = settings.getString("port", "");
- if (isTextEmpty(portText, getString(R.string.port_empty)))
- return false;
+ if (files != null) {
+ for (String file : files) {
+ InputStream in = null;
+ OutputStream out = null;
try {
- int port = Integer.valueOf(portText);
- if (port <= 1024) {
- this.showAToast(getString(R.string.port_alert));
- return false;
- }
+ if (path.length() > 0) {
+ in = assetManager.open(path + "/" + file);
+ } else {
+ in = assetManager.open(file);
+ }
+ out = new FileOutputStream("/data/data/com.github.shadowsocks/" + file);
+ copyFile(in, out);
+ in.close();
+ in = null;
+ out.flush();
+ out.close();
+ out = null;
} catch (Exception e) {
- this.showAToast(getString(R.string.port_alert));
- return false;
+ Log.e(TAG, e.getMessage());
}
+ }
+ }
+ }
- Intent it = new Intent(this, ShadowsocksService.class);
- startService(it);
+ private void copyFile(InputStream in, OutputStream out) throws IOException {
+ byte[] buffer = new byte[1024];
+ int read;
+ while ((read = in.read(buffer)) != -1) {
+ out.write(buffer, 0, read);
+ }
+ }
- return true;
+ private void crash_recovery() {
+
+ StringBuilder sb = new StringBuilder();
+ sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/polipo.pid`").append("\n");
+ sb.append("killall -9 pdnsd").append("\n");
+ sb.append("killall -9 redsocks").append("\n");
+ sb.append("killall -9 shadowsocks").append("\n");
+ sb.append("killall -9 polipo").append("\n");
+ Utils.runRootCommand(sb.toString());
+ }
+
+ private boolean isTextEmpty(String s, String msg) {
+ if (s == null || s.length() <= 0) {
+ showAToast(msg);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
+ if (compoundButton == switchButton) {
+ if (!serviceStart()) {
+ switchButton.setChecked(false);
+ }
+ }
+ }
+
+ /** Called when the activity is first created. */
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ setHeaderRes(R.xml.shadowsocks_headers);
+ super.onCreate(savedInstanceState);
+
+ RelativeLayout switchLayout =
+ (RelativeLayout) getLayoutInflater().inflate(R.layout.layout_switch, null);
+
+ getSupportActionBar().setCustomView(switchLayout);
+ getSupportActionBar().setDisplayShowTitleEnabled(false);
+ getSupportActionBar().setDisplayShowCustomEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(false);
+
+ switchButton = (Switch) switchLayout.findViewById(R.id.switchButton);
+
+ TextView title = (TextView) switchLayout.findViewById(R.id.title);
+ Typeface tf = Typefaces.get(this, "fonts/Iceland.ttf");
+ if (tf != null) title.setTypeface(tf);
+ title.setText(R.string.app_name);
+
+ final SharedPreferences settings =
+ PreferenceManager.getDefaultSharedPreferences(Shadowsocks.this);
+
+ boolean init =
+ !settings.getBoolean("isRunning", false) && !settings.getBoolean("isConnecting", false);
+
+ if (init) {
+ if (mProgressDialog == null) {
+ mProgressDialog =
+ ProgressDialog.show(this, "", getString(R.string.initializing), true, true);
+ }
+
+ new Thread() {
+ @Override
+ public void run() {
+
+ Utils.isRoot();
+
+ String versionName;
+ try {
+ versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
+ } catch (NameNotFoundException e) {
+ versionName = "NONE";
+ }
+
+ if (!settings.getBoolean(versionName, false)) {
+ Editor edit = settings.edit();
+ edit.putBoolean(versionName, true);
+ edit.commit();
+ reset();
+ }
+
+ handler.sendEmptyMessage(MSG_INITIAL_FINISH);
+ }
+ }.start();
+ }
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, 0, 0, R.string.recovery)
+ .setIcon(android.R.drawable.ic_menu_revert)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ menu.add(0, 1, 1, R.string.about)
+ .setIcon(android.R.drawable.ic_menu_info_details)
+ .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+ return true;
+ }
+
+ /** Called when the activity is closed. */
+ @Override
+ public void onDestroy() {
+ SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
+
+ SharedPreferences.Editor editor = settings.edit();
+ editor.putBoolean("isConnected", ShadowsocksService.isServiceStarted());
+ editor.commit();
+
+ super.onDestroy();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) {
+ try {
+ finish();
+ } catch (Exception ignore) {
+ // Nothing
+ }
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ switch (item.getItemId()) {
+ case 0:
+ recovery();
+ break;
+ case 1:
+ String versionName = "";
+ try {
+ versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
+ } catch (NameNotFoundException e) {
+ versionName = "";
+ }
+ showAToast(getString(R.string.about) + " (" + versionName + ")\n\n" + getString(
+ R.string.copy_rights));
+ break;
}
- private void showAToast(String msg) {
- AlertDialog.Builder builder = new AlertDialog.Builder(this);
- builder.setMessage(msg)
- .setCancelable(false)
- .setNegativeButton(getString(R.string.ok_iknow),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int id) {
- dialog.cancel();
- }
- });
- AlertDialog alert = builder.create();
- alert.show();
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
+
+ Editor edit = settings.edit();
+
+ if (ShadowsocksService.isServiceStarted()) {
+ edit.putBoolean("isRunning", true);
+ switchButton.setChecked(true);
+ } else {
+ if (settings.getBoolean("isRunning", false)) {
+ new Thread() {
+ @Override
+ public void run() {
+ crash_recovery();
+ handler.sendEmptyMessage(MSG_CRASH_RECOVER);
+ }
+ }.start();
+ }
+ edit.putBoolean("isRunning", false);
+ switchButton.setChecked(false);
}
- public static class ProxyFragment extends UnifiedPreferenceFragment
- implements OnSharedPreferenceChangeListener {
+ edit.commit();
- private void setPreferenceEnabled() {
- SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(getActivity());
+ setPreferenceEnabled();
- boolean enabled = !settings.getBoolean("isRunning", false)
- && !settings.getBoolean("isConnecting", false);
+ switchButton.setOnCheckedChangeListener(this);
+ PreferenceManager.getDefaultSharedPreferences(this)
+ .registerOnSharedPreferenceChangeListener(this);
+ }
- for (String name : PROXY_PREFS) {
- Preference pref = findPreference(name);
- if (pref != null) {
- pref.setEnabled(enabled);
- }
- }
+ private void setPreferenceEnabled() {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(Shadowsocks.this);
+
+ boolean enabled =
+ !settings.getBoolean("isRunning", false) && !settings.getBoolean("isConnecting", false);
+
+ for (String name : PROXY_PREFS) {
+ Preference pref = findPreference(name);
+ if (pref != null) {
+ pref.setEnabled(enabled);
+ }
+ }
+ for (String name : FEATRUE_PREFS) {
+ Preference pref = findPreference(name);
+ if (pref != null) {
+ if (name.equals("isBypassApps") || name.equals("proxyedApps")) {
+ boolean isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
+ pref.setEnabled(enabled && !isGlobalProxy);
+ } else {
+ pref.setEnabled(enabled);
}
+ }
+ }
+ }
- @Override
- public void onResume() {
- super.onResume();
- setPreferenceEnabled();
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ // Let's do something a preference value changes
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
- // Set up a listener whenever a key changes
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
-
- }
-
- @Override
- public void onPause() {
- super.onPause();
- // Unregister the listener whenever a key changes
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
- setPreferenceEnabled();
- }
- }
+ if (key.equals("isRunning")) {
+ if (settings.getBoolean("isRunning", false)) {
+ switchButton.setChecked(true);
+ } else {
+ switchButton.setChecked(false);
+ }
}
- public static class FeatureFragment extends UnifiedPreferenceFragment
- implements OnSharedPreferenceChangeListener {
-
- private void setPreferenceEnabled() {
- SharedPreferences settings = PreferenceManager
- .getDefaultSharedPreferences(getActivity());
-
- boolean enabled = !settings.getBoolean("isRunning", false)
- && !settings.getBoolean("isConnecting", false);
-
- for (String name : FEATRUE_PREFS) {
- Preference pref = findPreference(name);
- if (pref != null) {
- if (name.equals("isBypassApps")
- || name.equals("proxyedApps")) {
- boolean isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
- pref.setEnabled(enabled && !isGlobalProxy);
- } else {
- pref.setEnabled(enabled);
- }
- }
- }
+ if (key.equals("isConnecting")) {
+ if (settings.getBoolean("isConnecting", false)) {
+ Log.d(TAG, "Connecting start");
+ if (mProgressDialog == null) {
+ mProgressDialog =
+ ProgressDialog.show(this, "", getString(R.string.connecting), true, true);
}
-
- @Override
- public void onResume() {
- super.onResume();
- setPreferenceEnabled();
-
- // Set up a listener whenever a key changes
- getPreferenceScreen().getSharedPreferences()
- .registerOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onPause() {
- super.onPause();
- // Unregister the listener whenever a key changes
- getPreferenceScreen().getSharedPreferences()
- .unregisterOnSharedPreferenceChangeListener(this);
- }
-
- @Override
- public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
- if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
- setPreferenceEnabled();
- }
+ } else {
+ Log.d(TAG, "Connecting finish");
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
}
+ }
}
- public static class Typefaces {
- private static final String TAG = "Typefaces";
+ if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
+ setPreferenceEnabled();
+ }
+ }
- private static final Hashtable cache = new Hashtable();
+ @Override
+ public void onStart() {
+ super.onStart();
+ EasyTracker.getInstance().activityStart(this);
+ }
- public static Typeface get(Context c, String assetPath) {
- synchronized (cache) {
- if (!cache.containsKey(assetPath)) {
- try {
- Typeface t = Typeface.createFromAsset(c.getAssets(),
- assetPath);
- cache.put(assetPath, t);
- } catch (Exception e) {
- Log.e(TAG, "Could not get typeface '" + assetPath
- + "' because " + e.getMessage());
- return null;
- }
- }
- return cache.get(assetPath);
- }
- }
+ @Override
+ public void onStop() {
+ super.onStop();
+ EasyTracker.getInstance().activityStop(this);
+
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+
+ public void reset() {
+
+ crash_recovery();
+
+ copyAssets("");
+ copyAssets(Utils.getABI());
+
+ Utils.runCommand("chmod 755 /data/data/com.github.shadowsocks/iptables\n"
+ + "chmod 755 /data/data/com.github.shadowsocks/redsocks\n"
+ + "chmod 755 /data/data/com.github.shadowsocks/pdnsd\n"
+ + "chmod 755 /data/data/com.github.shadowsocks/shadowsocks\n"
+ + "chmod 755 /data/data/com.github.shadowsocks/polipo\n");
+ }
+
+ private void recovery() {
+
+ if (mProgressDialog == null) {
+ mProgressDialog = ProgressDialog.show(this, "", getString(R.string.recovering), true, true);
}
+ final Handler h = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ if (mProgressDialog != null) {
+ mProgressDialog.dismiss();
+ mProgressDialog = null;
+ }
+ }
+ };
+
+ stopService(new Intent(this, ShadowsocksService.class));
+
+ new Thread() {
+ @Override
+ public void run() {
+
+ reset();
+
+ h.sendEmptyMessage(0);
+ }
+ }.start();
+ }
+
+ /** Called when connect button is clicked. */
+ public boolean serviceStart() {
+
+ if (ShadowsocksService.isServiceStarted()) {
+ stopService(new Intent(this, ShadowsocksService.class));
+ return false;
+ }
+
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
+
+ final String proxy = settings.getString("proxy", "");
+ if (isTextEmpty(proxy, getString(R.string.proxy_empty))) return false;
+
+ String portText = settings.getString("port", "");
+ if (isTextEmpty(portText, getString(R.string.port_empty))) return false;
+ try {
+ int port = Integer.valueOf(portText);
+ if (port <= 1024) {
+ this.showAToast(getString(R.string.port_alert));
+ return false;
+ }
+ } catch (Exception e) {
+ this.showAToast(getString(R.string.port_alert));
+ return false;
+ }
+
+ Intent it = new Intent(this, ShadowsocksService.class);
+ startService(it);
+
+ return true;
+ }
+
+ private void showAToast(String msg) {
+ AlertDialog.Builder builder = new AlertDialog.Builder(this);
+ builder.setMessage(msg)
+ .setCancelable(false)
+ .setNegativeButton(getString(R.string.ok_iknow), new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int id) {
+ dialog.cancel();
+ }
+ });
+ AlertDialog alert = builder.create();
+ alert.show();
+ }
+
+ public static class ProxyFragment extends UnifiedPreferenceFragment
+ implements OnSharedPreferenceChangeListener {
+
+ private void setPreferenceEnabled() {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
+
+ boolean enabled =
+ !settings.getBoolean("isRunning", false) && !settings.getBoolean("isConnecting", false);
+
+ for (String name : PROXY_PREFS) {
+ Preference pref = findPreference(name);
+ if (pref != null) {
+ pref.setEnabled(enabled);
+ }
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ setPreferenceEnabled();
+
+ // Set up a listener whenever a key changes
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // Unregister the listener whenever a key changes
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
+ setPreferenceEnabled();
+ }
+ }
+ }
+
+ public static class FeatureFragment extends UnifiedPreferenceFragment
+ implements OnSharedPreferenceChangeListener {
+
+ private void setPreferenceEnabled() {
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
+
+ boolean enabled =
+ !settings.getBoolean("isRunning", false) && !settings.getBoolean("isConnecting", false);
+
+ for (String name : FEATRUE_PREFS) {
+ Preference pref = findPreference(name);
+ if (pref != null) {
+ if (name.equals("isBypassApps") || name.equals("proxyedApps")) {
+ boolean isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
+ pref.setEnabled(enabled && !isGlobalProxy);
+ } else {
+ pref.setEnabled(enabled);
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ setPreferenceEnabled();
+
+ // Set up a listener whenever a key changes
+ getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ // Unregister the listener whenever a key changes
+ getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
+ if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
+ setPreferenceEnabled();
+ }
+ }
+ }
+
+ public static class Typefaces {
+ private static final String TAG = "Typefaces";
+
+ private static final Hashtable cache = new Hashtable();
+
+ public static Typeface get(Context c, String assetPath) {
+ synchronized (cache) {
+ if (!cache.containsKey(assetPath)) {
+ try {
+ Typeface t = Typeface.createFromAsset(c.getAssets(), assetPath);
+ cache.put(assetPath, t);
+ } catch (Exception e) {
+ Log.e(TAG, "Could not get typeface '" + assetPath + "' because " + e.getMessage());
+ return null;
+ }
+ }
+ return cache.get(assetPath);
+ }
+ }
+ }
}
diff --git a/src/com/github/shadowsocks/ShadowsocksApplication.java b/src/com/github/shadowsocks/ShadowsocksApplication.java
index c1d67704..4a004043 100755
--- a/src/com/github/shadowsocks/ShadowsocksApplication.java
+++ b/src/com/github/shadowsocks/ShadowsocksApplication.java
@@ -43,16 +43,15 @@ import com.google.analytics.tracking.android.EasyTracker;
public class ShadowsocksApplication extends Application {
- private static String sTmpDir;
+ private static String sTmpDir;
- public static String getTmpDir() {
- return sTmpDir;
- }
-
- @Override
- public void onCreate() {
- EasyTracker.getInstance().setContext(this);
- sTmpDir = getCacheDir().getAbsolutePath();
- }
+ public static String getTmpDir() {
+ return sTmpDir;
+ }
+ @Override
+ public void onCreate() {
+ EasyTracker.getInstance().setContext(this);
+ sTmpDir = getCacheDir().getAbsolutePath();
+ }
}
diff --git a/src/com/github/shadowsocks/ShadowsocksReceiver.java b/src/com/github/shadowsocks/ShadowsocksReceiver.java
index 72577deb..393dfbec 100644
--- a/src/com/github/shadowsocks/ShadowsocksReceiver.java
+++ b/src/com/github/shadowsocks/ShadowsocksReceiver.java
@@ -47,47 +47,47 @@ import android.preference.PreferenceManager;
public class ShadowsocksReceiver extends BroadcastReceiver {
- private static final String TAG = "Shadowsocks";
+ private static final String TAG = "Shadowsocks";
- @Override
- public void onReceive(Context context, Intent intent) {
+ @Override
+ public void onReceive(Context context, Intent intent) {
- if (intent != null) {
- final String action = intent.getAction();
- if (action.equals(Intent.ACTION_SHUTDOWN)) {
- context.stopService(new Intent(context, ShadowsocksService.class));
- return;
- }
- }
-
- SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
- String versionName;
- try {
- versionName = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
- } catch (PackageManager.NameNotFoundException e) {
- versionName = "NONE";
- }
-
- final boolean isAutoConnect = settings.getBoolean("isAutoConnect", false);
- final boolean isInstalled = settings.getBoolean(versionName, false);
-
- if (isAutoConnect && isInstalled) {
- final String portText = settings.getString("port", "");
- if (portText == null || portText.length() <= 0) {
- return;
- }
- try {
- int port = Integer.valueOf(portText);
- if (port <= 1024) {
- return;
- }
- } catch (Exception e) {
- return;
- }
-
- Intent it = new Intent(context, ShadowsocksService.class);
- context.startService(it);
- }
+ if (intent != null) {
+ final String action = intent.getAction();
+ if (action.equals(Intent.ACTION_SHUTDOWN)) {
+ context.stopService(new Intent(context, ShadowsocksService.class));
+ return;
+ }
}
+ SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
+ String versionName;
+ try {
+ versionName =
+ context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
+ } catch (PackageManager.NameNotFoundException e) {
+ versionName = "NONE";
+ }
+
+ final boolean isAutoConnect = settings.getBoolean("isAutoConnect", false);
+ final boolean isInstalled = settings.getBoolean(versionName, false);
+
+ if (isAutoConnect && isInstalled) {
+ final String portText = settings.getString("port", "");
+ if (portText == null || portText.length() <= 0) {
+ return;
+ }
+ try {
+ int port = Integer.valueOf(portText);
+ if (port <= 1024) {
+ return;
+ }
+ } catch (Exception e) {
+ return;
+ }
+
+ Intent it = new Intent(context, ShadowsocksService.class);
+ context.startService(it);
+ }
+ }
}
diff --git a/src/com/github/shadowsocks/ShadowsocksService.java b/src/com/github/shadowsocks/ShadowsocksService.java
index 1ecad407..1b964147 100755
--- a/src/com/github/shadowsocks/ShadowsocksService.java
+++ b/src/com/github/shadowsocks/ShadowsocksService.java
@@ -55,8 +55,6 @@ import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.util.Log;
import com.google.analytics.tracking.android.EasyTracker;
-import org.apache.http.conn.util.InetAddressUtils;
-
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
@@ -68,601 +66,583 @@ import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.HashSet;
+import org.apache.http.conn.util.InetAddressUtils;
public class ShadowsocksService extends Service {
- public static final String BASE = "/data/data/com.github.shadowsocks/";
- final static String REDSOCKS_CONF = "base {" +
- " log_debug = off;" +
- " log_info = off;" +
- " log = stderr;" +
- " daemon = on;" +
- " redirector = iptables;" +
- "}" +
- "redsocks {" +
- " local_ip = 127.0.0.1;" +
- " local_port = 8123;" +
- " ip = 127.0.0.1;" +
- " port = %d;" +
- " type = socks5;" +
- "}";
- final static String SHADOWSOCKS_CONF =
- "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}";
- 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_SOCKS = " -t nat -A OUTPUT -p tcp "
- + "-j REDIRECT --to 8123\n";
- final static String CMD_IPTABLES_DNAT_ADD_SOCKS = " -t nat -A OUTPUT -p tcp "
- + "-j DNAT --to-destination 127.0.0.1:8123\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 TAG = "ShadowsocksService";
- private final static int DNS_PORT = 8153;
- 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};
- /*
- * 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 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);
-
- 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);
-
- 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("appHost", appHost);
- break;
- case MSG_STOP_SELF:
- stopSelf();
- break;
- }
- ed.commit();
- super.handleMessage(msg);
- }
- };
- private Notification notification;
- private NotificationManager notificationManager;
- private PendingIntent pendIntent;
- private PowerManager.WakeLock mWakeLock;
- private String appHost;
- private int remotePort;
- private int localPort;
- private String sitekey;
- private SharedPreferences settings = null;
- private boolean hasRedirectSupport = true;
- private boolean isGlobalProxy = false;
- private boolean isGFWList = false;
- private boolean isBypassApps = false;
- private boolean isDNSProxy = false;
- private boolean isHTTPProxy = false;
- private String encMethod;
- private ProxyedApp apps[];
- 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];
-
- 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 int getPid(String name) {
- try {
- BufferedReader reader = new BufferedReader(new FileReader(BASE + name + ".pid"));
- final String line = reader.readLine();
- return Integer.valueOf(line);
- } catch (FileNotFoundException e) {
- Log.e(TAG, "Cannot open pid file: " + name);
- } catch (IOException e) {
- Log.e(TAG, "Cannot read pid file: " + name);
- } catch (NumberFormatException e) {
- Log.e(TAG, "Invalid pid", e);
- }
- return -1;
- }
-
- private void startShadowsocksDaemon() {
- new Thread() {
- @Override
- public void run() {
- final String cmd = String.format(BASE +
- "shadowsocks -s \"%s\" -p \"%d\" -l \"%d\" -k \"%s\" -m \"%s\" -f "
- + BASE + "shadowsocks.pid",
- appHost, remotePort, localPort, sitekey, encMethod);
- System.exec(cmd);
- }
- }.start();
- }
-
- private void startPolipoDaemon() {
- new Thread() {
- @Override
- public void run() {
- final String cmd = String.format(BASE +
- "polipo proxyPort=%d socksParentProxy=127.0.0.1:%d daemonise=true pidFile=\"%s\" logLevel=1 logFile=\"%s\"",
- localPort + 1, localPort, BASE + "polipo.pid", BASE + "polipo.log");
- System.exec(cmd);
- }
- }.start();
- }
-
- private void startDnsDaemon() {
- final String cmd = BASE + "pdnsd -c " + BASE + "pdnsd.conf";
- Utils.runCommand(cmd);
- }
-
- 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;
- }
-
- appHost = settings.getString("proxy", "127.0.0.1");
- sitekey = settings.getString("sitekey", "default");
- encMethod = settings.getString("encMethod", "table");
- try {
- remotePort = Integer.valueOf(settings.getString("remotePort", "1984"));
- } catch (NumberFormatException ex) {
- remotePort = 1984;
- }
- try {
- localPort = Integer.valueOf(settings.getString("port", "1984"));
- } catch (NumberFormatException ex) {
- localPort = 1984;
- }
-
- isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
- isGFWList = settings.getBoolean("isGFWList", false);
- isBypassApps = settings.getBoolean("isBypassApps", false);
- isDNSProxy = settings.getBoolean("isDNSProxy", false);
- isHTTPProxy = settings.getBoolean("isHTTPProxy", false);
-
- if (isHTTPProxy) {
- localPort--;
- }
-
- new Thread(new Runnable() {
- @Override
- public void run() {
-
- handler.sendEmptyMessage(MSG_CONNECT_START);
-
- boolean resolved = false;
-
- if (appHost != null) {
-
- boolean isIPv6Support = Utils.isIPv6Support();
- InetAddress addr = null;
-
- if (isIPv6Support) {
- try {
- addr = Inet6Address.getByName(appHost);
- } catch (UnknownHostException ignored) {
- addr = null;
- }
- }
-
- if (addr == null) {
- try {
- addr = InetAddress.getByName(appHost);
- } catch (UnknownHostException ignored) {
- addr = null;
- }
- }
-
- if (addr != null) {
- appHost = addr.getHostAddress();
- resolved = true;
- }
- }
-
- Log.d(TAG, "IPTABLES: " + Utils.getIptables());
-
- // Test for Redirect Support
- hasRedirectSupport = Utils.getHasRedirectSupport();
-
- if (resolved && handleConnection()) {
- // Connection and forward successful
- notifyAlert(getString(R.string.forward_success),
- getString(R.string.service_running));
-
- handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 500);
-
-
- } 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 void startRedsocksDaemon() {
- String conf = String.format(REDSOCKS_CONF, localPort);
- String cmd = String.format("%sredsocks -p %sredsocks.pid -c %sredsocks.conf",
- BASE, BASE, BASE);
- Utils.runRootCommand("echo \"" + conf + "\" > " + BASE + "redsocks.conf\n"
- + cmd);
- }
-
- private boolean waitForProcess(final String name) {
- final int pid = getPid(name);
- if (pid == -1) return false;
-
- Exec.hangupProcessGroup(-pid);
- Thread t = new Thread() {
- @Override
- public void run() {
- Exec.waitFor(-pid);
- Log.d(TAG, "Successfully exit pid: " + pid);
- }
- };
- t.start();
- try {
- t.join(300);
- } catch (InterruptedException ignored) {
- }
- return !t.isAlive();
- }
-
- /**
- * Called when the activity is first created.
- */
- public boolean handleConnection() {
-
- if (isHTTPProxy) {
- startPolipoDaemon();
- }
- startShadowsocksDaemon();
- startDnsDaemon();
- startRedsocksDaemon();
- setupIptables();
-
- 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(this);
- }
-
- private void markServiceStopped() {
- sRunningInstance = null;
- }
-
- private void notifyAlert(String title, String info) {
- notification.icon = R.drawable.ic_stat_shadowsocks;
- 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_shadowsocks;
- notification.tickerText = title;
- notification.flags = flags;
- initSoundVibrateLights(notification);
- notification.setLatestEventInfo(this, getString(R.string.app_name),
- info, pendIntent);
- notificationManager.notify(0, notification);
- }
-
+ public static final String BASE = "/data/data/com.github.shadowsocks/";
+ final static String REDSOCKS_CONF = "base {" +
+ " log_debug = off;" +
+ " log_info = off;" +
+ " log = stderr;" +
+ " daemon = on;" +
+ " redirector = iptables;" +
+ "}" +
+ "redsocks {" +
+ " local_ip = 127.0.0.1;" +
+ " local_port = 8123;" +
+ " ip = 127.0.0.1;" +
+ " port = %d;" +
+ " type = socks5;" +
+ "}";
+ final static String SHADOWSOCKS_CONF =
+ "{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}";
+ 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_SOCKS =
+ " -t nat -A OUTPUT -p tcp " + "-j REDIRECT --to 8123\n";
+ final static String CMD_IPTABLES_DNAT_ADD_SOCKS =
+ " -t nat -A OUTPUT -p tcp " + "-j DNAT --to-destination 127.0.0.1:8123\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 TAG = "ShadowsocksService";
+ private final static int DNS_PORT = 8153;
+ 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 };
+ /*
+ * 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 sRunningInstance = null;
+ final Handler handler = new Handler() {
@Override
- public IBinder onBind(Intent intent) {
- return null;
+ public void handleMessage(Message msg) {
+ Editor ed = settings.edit();
+ switch (msg.what) {
+ case MSG_CONNECT_START:
+ ed.putBoolean("isConnecting", 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);
+
+ 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("appHost", appHost);
+ break;
+ case MSG_STOP_SELF:
+ stopSelf();
+ break;
+ }
+ ed.commit();
+ super.handleMessage(msg);
+ }
+ };
+ private Notification notification;
+ private NotificationManager notificationManager;
+ private PendingIntent pendIntent;
+ private PowerManager.WakeLock mWakeLock;
+ private String appHost;
+ private int remotePort;
+ private int localPort;
+ private String sitekey;
+ private SharedPreferences settings = null;
+ private boolean hasRedirectSupport = true;
+ private boolean isGlobalProxy = false;
+ private boolean isGFWList = false;
+ private boolean isBypassApps = false;
+ private boolean isDNSProxy = false;
+ private boolean isHTTPProxy = false;
+ private String encMethod;
+ private ProxyedApp apps[];
+ 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];
+
+ 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 int getPid(String name) {
+ try {
+ BufferedReader reader = new BufferedReader(new FileReader(BASE + name + ".pid"));
+ final String line = reader.readLine();
+ return Integer.valueOf(line);
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Cannot open pid file: " + name);
+ } catch (IOException e) {
+ Log.e(TAG, "Cannot read pid file: " + name);
+ } catch (NumberFormatException e) {
+ Log.e(TAG, "Invalid pid", e);
+ }
+ return -1;
+ }
+
+ private void startShadowsocksDaemon() {
+ new Thread() {
+ @Override
+ public void run() {
+ final String cmd = String.format(BASE
+ +
+ "shadowsocks -s \"%s\" -p \"%d\" -l \"%d\" -k \"%s\" -m \"%s\" -f "
+ + BASE
+ + "shadowsocks.pid", appHost, remotePort, localPort, sitekey, encMethod);
+ System.exec(cmd);
+ }
+ }.start();
+ }
+
+ private void startPolipoDaemon() {
+ new Thread() {
+ @Override
+ public void run() {
+ final String cmd = String.format(BASE
+ + "polipo proxyPort=%d socksParentProxy=127.0.0.1:%d daemonise=true pidFile=\"%s\" logLevel=1 logFile=\"%s\"",
+ localPort + 1, localPort, BASE + "polipo.pid", BASE + "polipo.log");
+ System.exec(cmd);
+ }
+ }.start();
+ }
+
+ private void startDnsDaemon() {
+ final String cmd = BASE + "pdnsd -c " + BASE + "pdnsd.conf";
+ Utils.runCommand(cmd);
+ }
+
+ 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;
}
- @Override
- public void onCreate() {
- super.onCreate();
-
- EasyTracker.getTracker().trackEvent("service", "start",
- getVersionName(), 0L);
-
- settings = PreferenceManager.getDefaultSharedPreferences(this);
- notificationManager = (NotificationManager) this
- .getSystemService(NOTIFICATION_SERVICE);
-
- Intent intent = new Intent(this, Shadowsocks.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!");
- }
+ appHost = settings.getString("proxy", "127.0.0.1");
+ sitekey = settings.getString("sitekey", "default");
+ encMethod = settings.getString("encMethod", "table");
+ try {
+ remotePort = Integer.valueOf(settings.getString("remotePort", "1984"));
+ } catch (NumberFormatException ex) {
+ remotePort = 1984;
+ }
+ try {
+ localPort = Integer.valueOf(settings.getString("port", "1984"));
+ } catch (NumberFormatException ex) {
+ localPort = 1984;
}
- /**
- * Called when the activity is closed.
- */
- @Override
- public void onDestroy() {
+ isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
+ isGFWList = settings.getBoolean("isGFWList", false);
+ isBypassApps = settings.getBoolean("isBypassApps", false);
+ isDNSProxy = settings.getBoolean("isDNSProxy", false);
+ isHTTPProxy = settings.getBoolean("isHTTPProxy", false);
- EasyTracker.getTracker().trackEvent("service", "stop",
- getVersionName(), 0L);
-
- stopForegroundCompat(1);
-
- notifyAlert(getString(R.string.forward_stop),
- getString(R.string.service_stopped),
- Notification.FLAG_AUTO_CANCEL);
-
- new Thread() {
- @Override
- public void run() {
- // Make sure the connection is closed, important here
- onDisconnect();
- }
- }.start();
-
- Editor ed = settings.edit();
- ed.putBoolean("isRunning", false);
- ed.putBoolean("isConnecting", false);
- ed.commit();
-
- try {
- notificationManager.cancel(0);
- } catch (Exception ignore) {
- // Nothing
- }
-
- super.onDestroy();
-
- markServiceStopped();
+ if (isHTTPProxy) {
+ localPort--;
}
- private void onDisconnect() {
- Utils.runRootCommand(Utils.getIptables() + " -t nat -F OUTPUT");
- StringBuilder sb = new StringBuilder();
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n");
- if (!waitForProcess("pdnsd")) {
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`").append("\n");
- }
- if (!waitForProcess("shadowsocks")) {
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
- }
- if (isHTTPProxy) {
- if (!waitForProcess("polipo")) {
- sb.append("kill -9 `cat /data/data/com.github.shadowsocks/polipo.pid`").append("\n");
- }
- }
- Utils.runRootCommand(sb.toString());
- }
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
- // 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) {
+ handler.sendEmptyMessage(MSG_CONNECT_START);
- handleCommand(intent);
+ boolean resolved = false;
- }
+ if (appHost != null) {
- @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;
- }
+ boolean isIPv6Support = Utils.isIPv6Support();
+ InetAddress addr = null;
- private boolean setupIptables() {
-
- StringBuilder init_sb = new StringBuilder();
-
- StringBuilder http_sb = new StringBuilder();
-
- init_sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT\n");
-
- String cmd_bypass = Utils.getIptables() + CMD_IPTABLES_RETURN;
-
- if (!InetAddressUtils.isIPv6Address(appHost.toUpperCase())) {
- init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-d " + appHost));
- }
- init_sb.append(cmd_bypass.replace("0.0.0.0", "127.0.0.1"));
- if (!isDNSProxy) {
- init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport " + 53));
- init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner "
- + getApplicationInfo().uid));
- }
-
- if (hasRedirectSupport) {
- init_sb.append(Utils.getIptables()).append(" -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to ").append(DNS_PORT).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(DNS_PORT).append("\n");
- }
-
- 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_SOCKS : Utils.getIptables()
- + CMD_IPTABLES_DNAT_ADD_SOCKS);
- }
- if (!isGlobalProxy) {
- // for proxy specified apps
- if (apps == null || apps.length <= 0)
- apps = AppManager.getProxyedApps(this);
-
- HashSet uidSet = new HashSet();
- for (ProxyedApp app : apps) {
- if (app.isProxyed()) {
- uidSet.add(app.getUid());
- }
- }
- for (int uid : uidSet) {
- if (!isBypassApps) {
- http_sb.append((hasRedirectSupport ? Utils.getIptables()
- + CMD_IPTABLES_REDIRECT_ADD_SOCKS : Utils.getIptables()
- + CMD_IPTABLES_DNAT_ADD_SOCKS).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();
-
- 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;
+ if (isIPv6Support) {
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);
+ addr = Inet6Address.getByName(appHost);
+ } catch (UnknownHostException ignored) {
+ addr = null;
}
- return;
+ }
+
+ if (addr == null) {
+ try {
+ addr = InetAddress.getByName(appHost);
+ } catch (UnknownHostException ignored) {
+ addr = null;
+ }
+ }
+
+ if (addr != null) {
+ appHost = addr.getHostAddress();
+ resolved = true;
+ }
}
- // 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);
+ Log.d(TAG, "IPTABLES: " + Utils.getIptables());
+
+ // Test for Redirect Support
+ hasRedirectSupport = Utils.getHasRedirectSupport();
+
+ if (resolved && handleConnection()) {
+ // Connection and forward successful
+ notifyAlert(getString(R.string.forward_success), getString(R.string.service_running));
+
+ handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 500);
+ } 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 void startRedsocksDaemon() {
+ String conf = String.format(REDSOCKS_CONF, localPort);
+ String cmd = String.format("%sredsocks -p %sredsocks.pid -c %sredsocks.conf", BASE, BASE, BASE);
+ Utils.runRootCommand("echo \"" + conf + "\" > " + BASE + "redsocks.conf\n" + cmd);
+ }
+
+ private boolean waitForProcess(final String name) {
+ final int pid = getPid(name);
+ if (pid == -1) return false;
+
+ Exec.hangupProcessGroup(-pid);
+ Thread t = new Thread() {
+ @Override
+ public void run() {
+ Exec.waitFor(-pid);
+ Log.d(TAG, "Successfully exit pid: " + pid);
+ }
+ };
+ t.start();
+ try {
+ t.join(300);
+ } catch (InterruptedException ignored) {
+ }
+ return !t.isAlive();
+ }
+
+ /** Called when the activity is first created. */
+ public boolean handleConnection() {
+
+ if (isHTTPProxy) {
+ startPolipoDaemon();
+ }
+ startShadowsocksDaemon();
+ startDnsDaemon();
+ startRedsocksDaemon();
+ setupIptables();
+
+ 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(this);
+ }
+
+ private void markServiceStopped() {
+ sRunningInstance = null;
+ }
+
+ private void notifyAlert(String title, String info) {
+ notification.icon = R.drawable.ic_stat_shadowsocks;
+ 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_shadowsocks;
+ 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();
+
+ EasyTracker.getTracker().trackEvent("service", "start", getVersionName(), 0L);
+
+ settings = PreferenceManager.getDefaultSharedPreferences(this);
+ notificationManager = (NotificationManager) this.getSystemService(NOTIFICATION_SERVICE);
+
+ Intent intent = new Intent(this, Shadowsocks.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);
+
+ stopForegroundCompat(1);
+
+ notifyAlert(getString(R.string.forward_stop), getString(R.string.service_stopped),
+ Notification.FLAG_AUTO_CANCEL);
+
+ new Thread() {
+ @Override
+ public void run() {
+ // Make sure the connection is closed, important here
+ onDisconnect();
+ }
+ }.start();
+
+ Editor ed = settings.edit();
+ ed.putBoolean("isRunning", false);
+ ed.putBoolean("isConnecting", false);
+ ed.commit();
+
+ try {
+ notificationManager.cancel(0);
+ } catch (Exception ignore) {
+ // Nothing
+ }
+
+ super.onDestroy();
+
+ markServiceStopped();
+ }
+
+ private void onDisconnect() {
+ Utils.runRootCommand(Utils.getIptables() + " -t nat -F OUTPUT");
+ StringBuilder sb = new StringBuilder();
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n");
+ if (!waitForProcess("pdnsd")) {
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`").append("\n");
+ }
+ if (!waitForProcess("shadowsocks")) {
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
+ }
+ if (isHTTPProxy) {
+ if (!waitForProcess("polipo")) {
+ sb.append("kill -9 `cat /data/data/com.github.shadowsocks/polipo.pid`").append("\n");
+ }
+ }
+ Utils.runRootCommand(sb.toString());
+ }
+
+ // 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 setupIptables() {
+
+ StringBuilder init_sb = new StringBuilder();
+
+ StringBuilder http_sb = new StringBuilder();
+
+ init_sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT\n");
+
+ String cmd_bypass = Utils.getIptables() + CMD_IPTABLES_RETURN;
+
+ if (!InetAddressUtils.isIPv6Address(appHost.toUpperCase())) {
+ init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-d " + appHost));
+ }
+ init_sb.append(cmd_bypass.replace("0.0.0.0", "127.0.0.1"));
+ if (!isDNSProxy) {
+ init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport " + 53));
+ init_sb.append(
+ cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + getApplicationInfo().uid));
+ }
+
+ if (hasRedirectSupport) {
+ init_sb.append(Utils.getIptables())
+ .append(" -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to ")
+ .append(DNS_PORT)
+ .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(DNS_PORT)
+ .append("\n");
+ }
+
+ 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_SOCKS
+ : Utils.getIptables() + CMD_IPTABLES_DNAT_ADD_SOCKS);
+ }
+ if (!isGlobalProxy) {
+ // for proxy specified apps
+ if (apps == null || apps.length <= 0) apps = AppManager.getProxyedApps(this);
+
+ HashSet uidSet = new HashSet();
+ for (ProxyedApp app : apps) {
+ if (app.isProxyed()) {
+ uidSet.add(app.getUid());
+ }
+ }
+ for (int uid : uidSet) {
+ if (!isBypassApps) {
+ http_sb.append((hasRedirectSupport ? Utils.getIptables() + CMD_IPTABLES_REDIRECT_ADD_SOCKS
+ : Utils.getIptables() + CMD_IPTABLES_DNAT_ADD_SOCKS).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();
+
+ 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);
+ }
}
diff --git a/src/com/github/shadowsocks/System.java b/src/com/github/shadowsocks/System.java
index db5ef9b8..a34838d1 100755
--- a/src/com/github/shadowsocks/System.java
+++ b/src/com/github/shadowsocks/System.java
@@ -39,9 +39,9 @@
package com.github.shadowsocks;
public class System {
- static {
- java.lang.System.loadLibrary("system");
- }
+ static {
+ java.lang.System.loadLibrary("system");
+ }
- public static native void exec(String cmd);
+ public static native void exec(String cmd);
}
diff --git a/src/com/github/shadowsocks/Utils.java b/src/com/github/shadowsocks/Utils.java
index 296ee8b1..0582be69 100755
--- a/src/com/github/shadowsocks/Utils.java
+++ b/src/com/github/shadowsocks/Utils.java
@@ -45,471 +45,451 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.util.Log;
-import org.apache.http.conn.util.InetAddressUtils;
-
-import java.io.*;
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileDescriptor;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import org.apache.http.conn.util.InetAddressUtils;
public class Utils {
- public final static String TAG = "Shadowsocks";
- public final static String ABI_PROP = "ro.product.cpu.abi";
- public final static String ABI2_PROP = "ro.product.cpu.abi2";
- public final static String ARM_ABI = "arm";
- public final static String X86_ABI = "x86";
- public final static String MIPS_ABI = "mips";
- public final static String DEFAULT_SHELL = "/system/bin/sh";
- public final static String DEFAULT_ROOT = "/system/bin/su";
- public final static String ALTERNATIVE_ROOT = "/system/xbin/su";
- public final static String DEFAULT_IPTABLES = "/data/data/com.github.shadowsocks/iptables";
- public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables";
- public final static int TIME_OUT = -99;
- private static boolean initialized = false;
- private static int hasRedirectSupport = -1;
- private static int isRoot = -1;
- private static String shell = null;
- private static String root_shell = null;
- private static String iptables = null;
- private static String data_path = null;
+ public final static String TAG = "Shadowsocks";
+ public final static String ABI_PROP = "ro.product.cpu.abi";
+ public final static String ABI2_PROP = "ro.product.cpu.abi2";
+ public final static String ARM_ABI = "arm";
+ public final static String X86_ABI = "x86";
+ public final static String MIPS_ABI = "mips";
+ public final static String DEFAULT_SHELL = "/system/bin/sh";
+ public final static String DEFAULT_ROOT = "/system/bin/su";
+ public final static String ALTERNATIVE_ROOT = "/system/xbin/su";
+ public final static String DEFAULT_IPTABLES = "/data/data/com.github.shadowsocks/iptables";
+ public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables";
+ public final static int TIME_OUT = -99;
+ private static boolean initialized = false;
+ private static int hasRedirectSupport = -1;
+ private static int isRoot = -1;
+ private static String shell = null;
+ private static String root_shell = null;
+ private static String iptables = null;
+ private static String data_path = null;
- /**
- * Get IP address from first non-localhost interface
- */
- public static boolean isIPv6Support() {
- try {
- List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
- for (NetworkInterface intf : interfaces) {
- List addrs = Collections.list(intf.getInetAddresses());
- for (InetAddress addr : addrs) {
- if (!addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) {
- String sAddr = addr.getHostAddress().toUpperCase();
- boolean isIPv6 = InetAddressUtils.isIPv6Address(sAddr);
- if (isIPv6) {
- Log.d(TAG, "IPv6 address detected");
- return true;
- }
- }
- }
- }
- } catch (Exception ex) {
- // Ignore
- } // for now eat exceptions
- return false;
- }
-
- public static String getABI() {
- String abi = getSystemProperty(ABI_PROP);
- if (abi != null) {
- if (abi.toLowerCase().contains(ARM_ABI)) {
- return ARM_ABI;
- } else if (abi.toLowerCase().contains(X86_ABI)) {
- return X86_ABI;
+ /** Get IP address from first non-localhost interface */
+ public static boolean isIPv6Support() {
+ try {
+ List interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
+ for (NetworkInterface intf : interfaces) {
+ List addrs = Collections.list(intf.getInetAddresses());
+ for (InetAddress addr : addrs) {
+ if (!addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) {
+ String sAddr = addr.getHostAddress().toUpperCase();
+ boolean isIPv6 = InetAddressUtils.isIPv6Address(sAddr);
+ if (isIPv6) {
+ Log.d(TAG, "IPv6 address detected");
+ return true;
}
+ }
}
+ }
+ } catch (Exception ex) {
+ // Ignore
+ } // for now eat exceptions
+ return false;
+ }
+
+ public static String getABI() {
+ String abi = getSystemProperty(ABI_PROP);
+ if (abi != null) {
+ if (abi.toLowerCase().contains(ARM_ABI)) {
return ARM_ABI;
+ } else if (abi.toLowerCase().contains(X86_ABI)) {
+ return X86_ABI;
+ }
+ }
+ return ARM_ABI;
+ }
+
+ /**
+ * Returns a SystemProperty
+ *
+ * @param propName The Property to retrieve
+ * @return The Property, or NULL if not found
+ */
+ private static String getSystemProperty(String propName) {
+ String line;
+ BufferedReader input = null;
+ try {
+ Process p = Runtime.getRuntime().exec("getprop " + propName);
+ input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
+ line = input.readLine();
+ input.close();
+ } catch (IOException ex) {
+ Log.e(TAG, "Unable to read sysprop " + propName, ex);
+ return null;
+ } finally {
+ if (input != null) {
+ try {
+ input.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Exception while closing InputStream", e);
+ }
+ }
+ }
+ return line;
+ }
+
+ private static void checkIptables() {
+
+ if (!isRoot()) {
+ iptables = DEFAULT_IPTABLES;
+ return;
+ }
+
+ // Check iptables binary
+ iptables = DEFAULT_IPTABLES;
+
+ String lines = null;
+
+ boolean compatible = false;
+ boolean version = false;
+
+ StringBuilder sb = new StringBuilder();
+ String command = iptables + " --version\n" + iptables + " -L -t nat -n\n" + "exit\n";
+
+ int exitcode = runScript(command, sb, 10 * 1000, true);
+
+ if (exitcode == TIME_OUT) return;
+
+ lines = sb.toString();
+
+ if (lines.contains("OUTPUT")) {
+ compatible = true;
+ }
+ if (lines.contains("v1.4.")) {
+ version = true;
+ }
+
+ if (!compatible || !version) {
+ iptables = ALTERNATIVE_IPTABLES;
+ if (!new File(iptables).exists()) iptables = "iptables";
+ }
+ }
+
+ public static Drawable getAppIcon(Context c, int uid) {
+ PackageManager pm = c.getPackageManager();
+ Drawable appIcon = c.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
+ String[] packages = pm.getPackagesForUid(uid);
+
+ if (packages != null) {
+ if (packages.length == 1) {
+ try {
+ ApplicationInfo appInfo = pm.getApplicationInfo(packages[0], 0);
+ appIcon = pm.getApplicationIcon(appInfo);
+ } catch (NameNotFoundException e) {
+ Log.e(c.getPackageName(), "No package found matching with the uid " + uid);
+ }
+ }
+ } else {
+ Log.e(c.getPackageName(), "Package not found for uid " + uid);
+ }
+
+ return appIcon;
+ }
+
+ public static String getDataPath(Context ctx) {
+
+ if (data_path == null) {
+
+ if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
+ data_path = Environment.getExternalStorageDirectory().getAbsolutePath();
+ } else {
+ data_path = ctx.getFilesDir().getAbsolutePath();
+ }
+
+ Log.d(TAG, "Python Data Path: " + data_path);
+ }
+
+ return data_path;
+ }
+
+ public static boolean getHasRedirectSupport() {
+ if (hasRedirectSupport == -1) initHasRedirectSupported();
+ return hasRedirectSupport == 1;
+ }
+
+ public static String getIptables() {
+ if (iptables == null) checkIptables();
+ return iptables;
+ }
+
+ private static String getShell() {
+ if (shell == null) {
+ shell = DEFAULT_SHELL;
+ if (!new File(shell).exists()) shell = "sh";
+ }
+ return shell;
+ }
+
+ public static void initHasRedirectSupported() {
+
+ if (!Utils.isRoot()) return;
+
+ StringBuilder sb = new StringBuilder();
+ String command =
+ Utils.getIptables() + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154";
+
+ int exitcode = runScript(command, sb, 10 * 1000, true);
+
+ String lines = sb.toString();
+
+ hasRedirectSupport = 1;
+
+ // flush the check command
+ Utils.runRootCommand(command.replace("-A", "-D"));
+
+ if (exitcode == TIME_OUT) return;
+
+ if (lines.contains("No chain/target/match")) {
+ hasRedirectSupport = 0;
+ }
+ }
+
+ public static boolean isInitialized() {
+ if (initialized) {
+ return true;
+ } else {
+ initialized = true;
+ return false;
+ }
+ }
+
+ public static boolean isRoot() {
+
+ if (isRoot != -1) return isRoot == 1;
+
+ // switch between binaries
+ if (new File(DEFAULT_ROOT).exists()) {
+ root_shell = DEFAULT_ROOT;
+ } else if (new File(ALTERNATIVE_ROOT).exists()) {
+ root_shell = ALTERNATIVE_ROOT;
+ } else {
+ root_shell = "su";
+ }
+
+ String lines = null;
+
+ StringBuilder sb = new StringBuilder();
+ String command = "ls /\n exit\n";
+
+ int exitcode = runScript(command, sb, 10 * 1000, true);
+
+ if (exitcode == TIME_OUT) {
+ return false;
+ }
+
+ lines = sb.toString();
+
+ if (lines.contains("system")) {
+ isRoot = 1;
+ }
+
+ return isRoot == 1;
+ }
+
+ public static boolean runCommand(String command) {
+
+ return runCommand(command, 10 * 1000);
+ }
+
+ public static boolean runCommand(String command, int timeout) {
+
+ Log.d(TAG, command);
+
+ runScript(command, null, timeout, false);
+
+ return true;
+ }
+
+ public static boolean runRootCommand(String command) {
+
+ return runRootCommand(command, 10 * 1000);
+ }
+
+ public static boolean runRootCommand(String command, int timeout) {
+
+ if (!isRoot()) {
+ Log.e(TAG, "Cannot get ROOT permission: " + root_shell);
+ return false;
+ }
+
+ Log.d(TAG, command);
+
+ runScript(command, null, timeout, true);
+
+ return true;
+ }
+
+ private synchronized static int runScript(String script, StringBuilder result, long timeout,
+ boolean asroot) {
+ final ScriptRunner runner = new ScriptRunner(script, result, asroot);
+ runner.start();
+ try {
+ if (timeout > 0) {
+ runner.join(timeout);
+ } else {
+ runner.join();
+ }
+ if (runner.isAlive()) {
+ // Timed-out
+ runner.destroy();
+ runner.join(1000);
+ return TIME_OUT;
+ }
+ } catch (InterruptedException ex) {
+ return TIME_OUT;
+ }
+ return runner.exitcode;
+ }
+
+ /** Internal thread used to execute scripts (as root or not). */
+ private static final class ScriptRunner extends Thread {
+ private final String scripts;
+ private final StringBuilder result;
+ private final boolean asroot;
+ public int exitcode = -1;
+
+ private int[] pid = new int[1];
+ private FileDescriptor pipe;
+
+ @Override
+ public void destroy() {
+ if (pid[0] != -1) {
+ Exec.hangupProcessGroup(pid[0]);
+ pid[0] = -1;
+ }
+ if (pipe != null) {
+ Exec.close(pipe);
+ pipe = null;
+ }
}
/**
- * Returns a SystemProperty
+ * Creates a new script runner.
*
- * @param propName The Property to retrieve
- * @return The Property, or NULL if not found
+ * @param script script to run
+ * @param result result output
+ * @param asroot if true, executes the script as root
*/
- private static String getSystemProperty(String propName) {
- String line;
- BufferedReader input = null;
- try {
- Process p = Runtime.getRuntime().exec("getprop " + propName);
- input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
- line = input.readLine();
- input.close();
- } catch (IOException ex) {
- Log.e(TAG, "Unable to read sysprop " + propName, ex);
- return null;
- } finally {
- if (input != null) {
- try {
- input.close();
- } catch (IOException e) {
- Log.e(TAG, "Exception while closing InputStream", e);
- }
- }
- }
- return line;
+ public ScriptRunner(String script, StringBuilder result, boolean asroot) {
+ this.scripts = script;
+ this.result = result;
+ this.asroot = asroot;
}
- private static void checkIptables() {
-
- if (!isRoot()) {
- iptables = DEFAULT_IPTABLES;
- return;
- }
-
- // Check iptables binary
- iptables = DEFAULT_IPTABLES;
-
- String lines = null;
-
- boolean compatible = false;
- boolean version = false;
-
- StringBuilder sb = new StringBuilder();
- String command = iptables + " --version\n" + iptables
- + " -L -t nat -n\n" + "exit\n";
-
- int exitcode = runScript(command, sb, 10 * 1000, true);
-
- if (exitcode == TIME_OUT)
- return;
-
- lines = sb.toString();
-
- if (lines.contains("OUTPUT")) {
- compatible = true;
- }
- if (lines.contains("v1.4.")) {
- version = true;
- }
-
- if (!compatible || !version) {
- iptables = ALTERNATIVE_IPTABLES;
- if (!new File(iptables).exists())
- iptables = "iptables";
- }
+ private FileDescriptor createSubprocess(int[] processId, String cmd) {
+ ArrayList argList = parse(cmd);
+ String arg0 = argList.get(0);
+ String[] args = argList.toArray(new String[1]);
+ return Exec.createSubprocess(result != null ? 1 : 0, arg0, args, null, scripts + "\nexit\n",
+ processId);
}
- public static Drawable getAppIcon(Context c, int uid) {
- PackageManager pm = c.getPackageManager();
- Drawable appIcon = c.getResources().getDrawable(
- android.R.drawable.sym_def_app_icon);
- String[] packages = pm.getPackagesForUid(uid);
-
- if (packages != null) {
- if (packages.length == 1) {
- try {
- ApplicationInfo appInfo = pm.getApplicationInfo(
- packages[0], 0);
- appIcon = pm.getApplicationIcon(appInfo);
- } catch (NameNotFoundException e) {
- Log.e(c.getPackageName(),
- "No package found matching with the uid " + uid);
- }
+ private ArrayList parse(String cmd) {
+ final int PLAIN = 0;
+ final int WHITESPACE = 1;
+ final int INQUOTE = 2;
+ int state = WHITESPACE;
+ ArrayList result = new ArrayList();
+ int cmdLen = cmd.length();
+ StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < cmdLen; i++) {
+ char c = cmd.charAt(i);
+ if (state == PLAIN) {
+ if (Character.isWhitespace(c)) {
+ result.add(builder.toString());
+ builder.delete(0, builder.length());
+ state = WHITESPACE;
+ } else if (c == '"') {
+ state = INQUOTE;
+ } else {
+ builder.append(c);
+ }
+ } else if (state == WHITESPACE) {
+ if (Character.isWhitespace(c)) {
+ // do nothing
+ } else if (c == '"') {
+ state = INQUOTE;
+ } else {
+ state = PLAIN;
+ builder.append(c);
+ }
+ } else if (state == INQUOTE) {
+ if (c == '\\') {
+ if (i + 1 < cmdLen) {
+ i += 1;
+ builder.append(cmd.charAt(i));
}
+ } else if (c == '"') {
+ state = PLAIN;
+ } else {
+ builder.append(c);
+ }
+ }
+ }
+ if (builder.length() > 0) {
+ result.add(builder.toString());
+ }
+ return result;
+ }
+
+ @Override
+ public void run() {
+ pid[0] = -1;
+
+ try {
+
+ if (this.asroot) {
+ // Create the "su" request to run the script
+ pipe = createSubprocess(pid, root_shell);
} else {
- Log.e(c.getPackageName(), "Package not found for uid " + uid);
+ // Create the "sh" request to run the script
+ pipe = createSubprocess(pid, getShell());
}
- return appIcon;
- }
-
- public static String getDataPath(Context ctx) {
-
- if (data_path == null) {
-
- if (Environment.MEDIA_MOUNTED.equals(Environment
- .getExternalStorageState())) {
- data_path = Environment.getExternalStorageDirectory()
- .getAbsolutePath();
- } else {
- data_path = ctx.getFilesDir().getAbsolutePath();
- }
-
- Log.d(TAG, "Python Data Path: " + data_path);
- }
-
- return data_path;
- }
-
- public static boolean getHasRedirectSupport() {
- if (hasRedirectSupport == -1)
- initHasRedirectSupported();
- return hasRedirectSupport == 1;
- }
-
- public static String getIptables() {
- if (iptables == null)
- checkIptables();
- return iptables;
- }
-
- private static String getShell() {
- if (shell == null) {
- shell = DEFAULT_SHELL;
- if (!new File(shell).exists())
- shell = "sh";
- }
- return shell;
- }
-
- public static void initHasRedirectSupported() {
-
- if (!Utils.isRoot())
- return;
-
- StringBuilder sb = new StringBuilder();
- String command = Utils.getIptables()
- + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154";
-
- int exitcode = runScript(command, sb, 10 * 1000, true);
-
- String lines = sb.toString();
-
- hasRedirectSupport = 1;
-
- // flush the check command
- Utils.runRootCommand(command.replace("-A", "-D"));
-
- if (exitcode == TIME_OUT)
- return;
-
- if (lines.contains("No chain/target/match")) {
- hasRedirectSupport = 0;
+ if (pid[0] != -1) {
+ exitcode = Exec.waitFor(pid[0]);
}
- }
-
- public static boolean isInitialized() {
- if (initialized)
- return true;
- else {
- initialized = true;
- return false;
- }
-
- }
- public static boolean isRoot() {
+ if (result == null || pipe == null) return;
- if (isRoot != -1)
- return isRoot == 1;
+ final InputStream stdout = new FileInputStream(pipe);
+ final byte buf[] = new byte[8192];
+ int read = 0;
- // switch between binaries
- if (new File(DEFAULT_ROOT).exists()) {
- root_shell = DEFAULT_ROOT;
- } else if (new File(ALTERNATIVE_ROOT).exists()) {
- root_shell = ALTERNATIVE_ROOT;
- } else {
- root_shell = "su";
+ // Read stdout
+ while (stdout.available() > 0) {
+ read = stdout.read(buf);
+ result.append(new String(buf, 0, read));
}
-
- String lines = null;
-
- StringBuilder sb = new StringBuilder();
- String command = "ls /\n exit\n";
-
- int exitcode = runScript(command, sb, 10 * 1000, true);
-
- if (exitcode == TIME_OUT) {
- return false;
+ } catch (Exception ex) {
+ Log.e(TAG, "Cannot execute command", ex);
+ if (result != null) result.append("\n").append(ex);
+ } finally {
+ if (pipe != null) {
+ Exec.close(pipe);
}
-
- lines = sb.toString();
-
- if (lines.contains("system")) {
- isRoot = 1;
- }
-
- return isRoot == 1;
- }
-
- public static boolean runCommand(String command) {
-
- return runCommand(command, 10 * 1000);
-
- }
-
- public static boolean runCommand(String command, int timeout) {
-
- Log.d(TAG, command);
-
- runScript(command, null, timeout, false);
-
- return true;
- }
-
- public static boolean runRootCommand(String command) {
-
- return runRootCommand(command, 10 * 1000);
-
- }
-
- public static boolean runRootCommand(String command, int timeout) {
-
- if (!isRoot()) {
- Log.e(TAG, "Cannot get ROOT permission: " + root_shell);
- return false;
- }
-
- Log.d(TAG, command);
-
- runScript(command, null, timeout, true);
-
- return true;
- }
-
- private synchronized static int runScript(String script, StringBuilder result,
- long timeout, boolean asroot) {
- final ScriptRunner runner = new ScriptRunner(script, result, asroot);
- runner.start();
- try {
- if (timeout > 0) {
- runner.join(timeout);
- } else {
- runner.join();
- }
- if (runner.isAlive()) {
- // Timed-out
- runner.destroy();
- runner.join(1000);
- return TIME_OUT;
- }
- } catch (InterruptedException ex) {
- return TIME_OUT;
- }
- return runner.exitcode;
- }
-
- /**
- * Internal thread used to execute scripts (as root or not).
- */
- private static final class ScriptRunner extends Thread {
- private final String scripts;
- private final StringBuilder result;
- private final boolean asroot;
- public int exitcode = -1;
-
- private int[] pid = new int[1];
- private FileDescriptor pipe;
-
- @Override
- public void destroy() {
- if (pid[0] != -1) {
- Exec.hangupProcessGroup(pid[0]);
- pid[0] = -1;
- }
- if (pipe != null) {
- Exec.close(pipe);
- pipe = null;
- }
- }
-
- /**
- * Creates a new script runner.
- *
- * @param script script to run
- * @param result result output
- * @param asroot if true, executes the script as root
- */
- public ScriptRunner(String script, StringBuilder result,
- boolean asroot) {
- this.scripts = script;
- this.result = result;
- this.asroot = asroot;
- }
-
- private FileDescriptor createSubprocess(int[] processId, String cmd) {
- ArrayList argList = parse(cmd);
- String arg0 = argList.get(0);
- String[] args = argList.toArray(new String[1]);
-
- return Exec.createSubprocess(result != null ? 1 : 0, arg0, args, null,
- scripts + "\nexit\n", processId);
- }
-
- private ArrayList parse(String cmd) {
- final int PLAIN = 0;
- final int WHITESPACE = 1;
- final int INQUOTE = 2;
- int state = WHITESPACE;
- ArrayList result = new ArrayList();
- int cmdLen = cmd.length();
- StringBuilder builder = new StringBuilder();
- for (int i = 0; i < cmdLen; i++) {
- char c = cmd.charAt(i);
- if (state == PLAIN) {
- if (Character.isWhitespace(c)) {
- result.add(builder.toString());
- builder.delete(0, builder.length());
- state = WHITESPACE;
- } else if (c == '"') {
- state = INQUOTE;
- } else {
- builder.append(c);
- }
- } else if (state == WHITESPACE) {
- if (Character.isWhitespace(c)) {
- // do nothing
- } else if (c == '"') {
- state = INQUOTE;
- } else {
- state = PLAIN;
- builder.append(c);
- }
- } else if (state == INQUOTE) {
- if (c == '\\') {
- if (i + 1 < cmdLen) {
- i += 1;
- builder.append(cmd.charAt(i));
- }
- } else if (c == '"') {
- state = PLAIN;
- } else {
- builder.append(c);
- }
- }
- }
- if (builder.length() > 0) {
- result.add(builder.toString());
- }
- return result;
- }
-
- @Override
- public void run() {
- pid[0] = -1;
-
- try {
-
- if (this.asroot) {
- // Create the "su" request to run the script
- pipe = createSubprocess(pid, root_shell);
- } else {
- // Create the "sh" request to run the script
- pipe = createSubprocess(pid, getShell());
- }
-
- if (pid[0] != -1) {
- exitcode = Exec.waitFor(pid[0]);
- }
-
- if (result == null || pipe == null) return;
-
- final InputStream stdout = new FileInputStream(pipe);
- final byte buf[] = new byte[8192];
- int read = 0;
-
- // Read stdout
- while (stdout.available() > 0) {
- read = stdout.read(buf);
- result.append(new String(buf, 0, read));
- }
-
- } catch (Exception ex) {
- Log.e(TAG, "Cannot execute command", ex);
- if (result != null)
- result.append("\n").append(ex);
- } finally {
- if (pipe != null) {
- Exec.close(pipe);
- }
- if (pid[0] != -1) {
- Exec.hangupProcessGroup(pid[0]);
- }
- }
+ if (pid[0] != -1) {
+ Exec.hangupProcessGroup(pid[0]);
}
+ }
}
+ }
}
diff --git a/src/com/github/shadowsocks/preferences/SummaryEditTextPreference.java b/src/com/github/shadowsocks/preferences/SummaryEditTextPreference.java
index 6a7ae974..a108c6e7 100644
--- a/src/com/github/shadowsocks/preferences/SummaryEditTextPreference.java
+++ b/src/com/github/shadowsocks/preferences/SummaryEditTextPreference.java
@@ -3,36 +3,31 @@ package com.github.shadowsocks.preferences;
import android.content.Context;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
-import com.github.shadowsocks.R;
public class SummaryEditTextPreference extends EditTextPreference {
- private CharSequence mDefaultSummary;
+ private CharSequence mDefaultSummary;
- public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- mDefaultSummary = getSummary();
- }
+ public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ mDefaultSummary = getSummary();
+ }
- public SummaryEditTextPreference(Context context, AttributeSet attrs) {
- super(context, attrs);
- mDefaultSummary = getSummary();
- }
+ public SummaryEditTextPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mDefaultSummary = getSummary();
+ }
- public SummaryEditTextPreference(Context context) {
- super(context);
- }
+ public SummaryEditTextPreference(Context context) {
+ super(context);
+ }
- @Override
- public void onDialogClosed(boolean positiveResult) {
- super.onDialogClosed(positiveResult);
- if (positiveResult) {
- String value = getEditText().getText().toString();
- if (value.isEmpty()) {
- setSummary(mDefaultSummary);
- } else {
- setSummary(value);
- }
- }
+ @Override
+ public void setSummary(CharSequence summary) {
+ if (summary.toString().isEmpty()) {
+ super.setSummary(mDefaultSummary);
+ } else {
+ super.setSummary(summary);
}
+ }
}