From 3d664377c1bae045bcc14f1b97d32eb6008ef31b Mon Sep 17 00:00:00 2001 From: Max Lv Date: Mon, 29 Apr 2013 15:18:33 +0800 Subject: [PATCH] clean up --- res/drawable/btn_cab_done_shadow.xml | 12 +- res/drawable/pressed_background_shadow.xml | 2 +- res/drawable/progress_horizontal_shadow.xml | 20 +- res/drawable/selectable_background_shadow.xml | 9 +- res/drawable/spinner_background_ab_shadow.xml | 14 +- res/drawable/tab_indicator_ab_shadow.xml | 34 +- res/layout/layout_apps.xml | 14 +- res/layout/layout_apps_item.xml | 50 +- res/layout/layout_switch.xml | 38 +- res/layout/overlay.xml | 8 +- res/values-v14/styles_shadow.xml | 61 +- res/values/colors.xml | 12 +- res/values/strings.xml | 230 ++-- res/values/styles_shadow.xml | 110 +- res/xml/pref_feature.xml | 88 +- res/xml/pref_proxy.xml | 62 +- res/xml/shadowsocks_headers.xml | 20 +- src/com/github/shadowsocks/AppManager.java | 676 +++++----- src/com/github/shadowsocks/Exec.java | 71 +- src/com/github/shadowsocks/ImageLoader.java | 338 +++-- .../shadowsocks/ImageLoaderFactory.java | 12 +- src/com/github/shadowsocks/ProxyedApp.java | 132 +- src/com/github/shadowsocks/Shadowsocks.java | 1053 ++++++++------- .../shadowsocks/ShadowsocksApplication.java | 19 +- .../shadowsocks/ShadowsocksReceiver.java | 78 +- .../shadowsocks/ShadowsocksService.java | 1134 ++++++++--------- src/com/github/shadowsocks/System.java | 8 +- src/com/github/shadowsocks/Utils.java | 846 ++++++------ .../SummaryEditTextPreference.java | 43 +- 29 files changed, 2533 insertions(+), 2661 deletions(-) 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); } + } }