Commit 3d664377 authored by Max Lv's avatar Max Lv

clean up

parent a74c3fde
...@@ -18,10 +18,10 @@ ...@@ -18,10 +18,10 @@
--> -->
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true" <item android:state_pressed="true"
android:drawable="@drawable/btn_cab_done_pressed_shadow" /> android:drawable="@drawable/btn_cab_done_pressed_shadow"/>
<item android:state_focused="true" android:state_enabled="true" <item android:state_focused="true" android:state_enabled="true"
android:drawable="@drawable/btn_cab_done_focused_shadow" /> android:drawable="@drawable/btn_cab_done_focused_shadow"/>
<item android:state_enabled="true" <item android:state_enabled="true"
android:drawable="@drawable/btn_cab_done_default_shadow" /> android:drawable="@drawable/btn_cab_done_default_shadow"/>
</selector> </selector>
...@@ -18,5 +18,5 @@ ...@@ -18,5 +18,5 @@
--> -->
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
<solid android:color="@color/pressed_shadow" /> <solid android:color="@color/pressed_shadow"/>
</shape> </shape>
...@@ -19,17 +19,17 @@ ...@@ -19,17 +19,17 @@
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@android:id/background" <item android:id="@android:id/background"
android:drawable="@drawable/progress_bg_shadow" /> android:drawable="@drawable/progress_bg_shadow"/>
<item android:id="@android:id/secondaryProgress"> <item android:id="@android:id/secondaryProgress">
<scale android:scaleWidth="100%" <scale android:scaleWidth="100%"
android:drawable="@drawable/progress_secondary_shadow" /> android:drawable="@drawable/progress_secondary_shadow"/>
</item> </item>
<item android:id="@android:id/progress"> <item android:id="@android:id/progress">
<scale android:scaleWidth="100%" <scale android:scaleWidth="100%"
android:drawable="@drawable/progress_primary_shadow" /> android:drawable="@drawable/progress_primary_shadow"/>
</item> </item>
</layer-list> </layer-list>
...@@ -18,8 +18,9 @@ ...@@ -18,8 +18,9 @@
--> -->
<selector xmlns:android="http://schemas.android.com/apk/res/android" <selector xmlns:android="http://schemas.android.com/apk/res/android"
android:exitFadeDuration="@android:integer/config_mediumAnimTime" > android:exitFadeDuration="@android:integer/config_mediumAnimTime">
<item android:state_pressed="false" android:state_focused="true" android:drawable="@drawable/list_focused_shadow" /> <item android:state_pressed="false" android:state_focused="true"
<item android:state_pressed="true" android:drawable="@drawable/pressed_background_shadow" /> android:drawable="@drawable/list_focused_shadow"/>
<item android:drawable="@android:color/transparent" /> <item android:state_pressed="true" android:drawable="@drawable/pressed_background_shadow"/>
<item android:drawable="@android:color/transparent"/>
</selector> </selector>
\ No newline at end of file
...@@ -18,11 +18,11 @@ ...@@ -18,11 +18,11 @@
--> -->
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_enabled="false" <item android:state_enabled="false"
android:drawable="@drawable/spinner_ab_disabled_shadow" /> android:drawable="@drawable/spinner_ab_disabled_shadow"/>
<item android:state_pressed="true" <item android:state_pressed="true"
android:drawable="@drawable/spinner_ab_pressed_shadow" /> android:drawable="@drawable/spinner_ab_pressed_shadow"/>
<item android:state_pressed="false" android:state_focused="true" <item android:state_pressed="false" android:state_focused="true"
android:drawable="@drawable/spinner_ab_focused_shadow" /> android:drawable="@drawable/spinner_ab_focused_shadow"/>
<item android:drawable="@drawable/spinner_ab_default_shadow" /> <item android:drawable="@drawable/spinner_ab_default_shadow"/>
</selector> </selector>
...@@ -18,20 +18,28 @@ ...@@ -18,20 +18,28 @@
--> -->
<selector xmlns:android="http://schemas.android.com/apk/res/android"> <selector xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Non focused states --> <!-- Non focused states -->
<item android:state_focused="false" android:state_selected="false" android:state_pressed="false" android:drawable="@android:color/transparent" /> <item android:state_focused="false" android:state_selected="false" android:state_pressed="false"
<item android:state_focused="false" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_selected_shadow" /> android:drawable="@android:color/transparent"/>
<item android:state_focused="false" android:state_selected="true" android:state_pressed="false"
android:drawable="@drawable/tab_selected_shadow"/>
<!-- Focused states --> <!-- Focused states -->
<item android:state_focused="true" android:state_selected="false" android:state_pressed="false" android:drawable="@drawable/tab_unselected_focused_shadow" /> <item android:state_focused="true" android:state_selected="false" android:state_pressed="false"
<item android:state_focused="true" android:state_selected="true" android:state_pressed="false" android:drawable="@drawable/tab_selected_focused_shadow" /> android:drawable="@drawable/tab_unselected_focused_shadow"/>
<item android:state_focused="true" android:state_selected="true" android:state_pressed="false"
android:drawable="@drawable/tab_selected_focused_shadow"/>
<!-- Pressed --> <!-- Pressed -->
<!-- Non focused states --> <!-- Non focused states -->
<item android:state_focused="false" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_shadow" /> <item android:state_focused="false" android:state_selected="false" android:state_pressed="true"
<item android:state_focused="false" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_shadow" /> android:drawable="@drawable/tab_unselected_pressed_shadow"/>
<item android:state_focused="false" android:state_selected="true" android:state_pressed="true"
android:drawable="@drawable/tab_selected_pressed_shadow"/>
<!-- Focused states --> <!-- Focused states -->
<item android:state_focused="true" android:state_selected="false" android:state_pressed="true" android:drawable="@drawable/tab_unselected_pressed_shadow" /> <item android:state_focused="true" android:state_selected="false" android:state_pressed="true"
<item android:state_focused="true" android:state_selected="true" android:state_pressed="true" android:drawable="@drawable/tab_selected_pressed_shadow" /> android:drawable="@drawable/tab_unselected_pressed_shadow"/>
<item android:state_focused="true" android:state_selected="true" android:state_pressed="true"
android:drawable="@drawable/tab_selected_pressed_shadow"/>
</selector> </selector>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<LinearLayout android:layout_width="fill_parent" <LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android" android:layout_height="fill_parent" xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical" android:duplicateParentState="false"> android:orientation="vertical" android:duplicateParentState="false">
<TextView android:text="@string/proxied_help" <TextView android:text="@string/proxied_help"
android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_width="fill_parent" android:layout_height="wrap_content"
android:textSize="16sp" android:padding="3dp"/> android:textSize="16sp" android:padding="3dp"/>
<ListView android:layout_width="fill_parent" <ListView android:layout_width="fill_parent"
android:layout_height="wrap_content" android:id="@+id/applistview"/> android:layout_height="wrap_content" android:id="@+id/applistview"/>
</LinearLayout> </LinearLayout>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TableLayout xmlns:android="http://schemas.android.com/apk/res/android" <TableLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:stretchColumns="1"> android:stretchColumns="1">
<TableRow> <TableRow>
<ImageView <ImageView
android:id="@+id/itemicon" android:id="@+id/itemicon"
android:layout_width="48dp" android:layout_width="48dp"
android:layout_height="48dp" android:layout_height="48dp"
android:scaleType="fitCenter" android:scaleType="fitCenter"
android:padding="3dp"/> android:padding="3dp"/>
<TextView <TextView
android:id="@+id/itemtext" android:id="@+id/itemtext"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:text="uid:packages" android:text="uid:packages"
android:gravity="center_vertical" android:gravity="center_vertical"
android:maxLength="25" android:maxLength="25"
android:textSize="18sp" android:textSize="18sp"
android:padding="3dp"/> android:padding="3dp"/>
<CheckBox <CheckBox
android:id="@+id/itemcheck" android:id="@+id/itemcheck"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="fill_parent" android:layout_height="fill_parent"
android:layout_marginRight="6dp"/> android:layout_marginRight="6dp"/>
</TableRow> </TableRow>
</TableLayout> </TableLayout>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<TextView <TextView
android:id="@+id/title" android:id="@+id/title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical" android:gravity="center_vertical"
android:layout_marginLeft="16dp" android:layout_marginLeft="16dp"
android:textSize="28sp" android:textSize="28sp"
android:textColor="@color/title" android:textColor="@color/title"
android:text="@string/app_name" android:text="@string/app_name"
android:layout_alignParentLeft="true" android:layout_alignParentLeft="true"
android:layout_centerVertical="true"/> android:layout_centerVertical="true"/>
<org.jraf.android.backport.switchwidget.Switch <org.jraf.android.backport.switchwidget.Switch
android:id="@+id/switchButton" android:id="@+id/switchButton"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignParentRight="true" android:layout_alignParentRight="true"
android:layout_centerVertical="true"/> android:layout_centerVertical="true"/>
</RelativeLayout> </RelativeLayout>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android" <TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:minWidth="80dp" android:maxWidth="80dp" android:gravity="center" android:minWidth="80dp" android:maxWidth="80dp" android:gravity="center"
android:textSize="70sp" android:textColor="#ffffffff" android:textSize="70sp" android:textColor="#ffffffff"
android:background="#99000088" android:padding="10dp" android:background="#99000088" android:padding="10dp"
android:visibility="invisible" /> android:visibility="invisible"/>
\ No newline at end of file \ No newline at end of file
...@@ -19,38 +19,39 @@ ...@@ -19,38 +19,39 @@
<resources> <resources>
<style name="Theme.Shadow" parent="@style/Theme.Sherlock.Light.DarkActionBar"> <style name="Theme.Shadow" parent="@style/Theme.Sherlock.Light.DarkActionBar">
<item name="android:actionBarItemBackground">@drawable/selectable_background_shadow</item> <item name="android:actionBarItemBackground">@drawable/selectable_background_shadow</item>
<item name="android:popupMenuStyle">@style/PopupMenu.Shadow</item> <item name="android:popupMenuStyle">@style/PopupMenu.Shadow</item>
<item name="android:dropDownListViewStyle">@style/DropDownListView.Shadow</item> <item name="android:dropDownListViewStyle">@style/DropDownListView.Shadow</item>
<item name="android:actionBarTabStyle">@style/ActionBarTabStyle.Shadow</item> <item name="android:actionBarTabStyle">@style/ActionBarTabStyle.Shadow</item>
<item name="android:actionDropDownStyle">@style/DropDownNav.Shadow</item> <item name="android:actionDropDownStyle">@style/DropDownNav.Shadow</item>
<item name="android:actionBarStyle">@style/ActionBar.Solid.Shadow</item> <item name="android:actionBarStyle">@style/ActionBar.Solid.Shadow</item>
<item name="android:actionModeBackground">@drawable/cab_background_top_shadow</item> <item name="android:actionModeBackground">@drawable/cab_background_top_shadow</item>
<item name="android:actionModeSplitBackground">@drawable/cab_background_bottom_shadow</item> <item name="android:actionModeSplitBackground">@drawable/cab_background_bottom_shadow</item>
<item name="android:actionModeCloseButtonStyle">@style/ActionButton.CloseMode.Shadow</item> <item name="android:actionModeCloseButtonStyle">@style/ActionButton.CloseMode.Shadow</item>
<!-- Light.DarkActionBar specific --> <!-- Light.DarkActionBar specific -->
<item name="android:actionBarWidgetTheme">@style/Theme.Shadow.Widget</item> <item name="android:actionBarWidgetTheme">@style/Theme.Shadow.Widget</item>
</style> </style>
<style name="ActionBar.Solid.Shadow" parent="@style/Widget.Sherlock.Light.ActionBar.Solid.Inverse"> <style name="ActionBar.Solid.Shadow"
<item name="android:background">@drawable/ab_solid_shadow</item> parent="@style/Widget.Sherlock.Light.ActionBar.Solid.Inverse">
<item name="android:backgroundStacked">@drawable/ab_stacked_solid_shadow</item> <item name="android:background">@drawable/ab_solid_shadow</item>
<item name="android:backgroundSplit">@drawable/ab_bottom_solid_shadow</item> <item name="android:backgroundStacked">@drawable/ab_stacked_solid_shadow</item>
<item name="android:progressBarStyle">@style/ProgressBar.Shadow</item> <item name="android:backgroundSplit">@drawable/ab_bottom_solid_shadow</item>
</style> <item name="android:progressBarStyle">@style/ProgressBar.Shadow</item>
</style>
<style name="ActionBar.Transparent.Shadow" parent="@style/Widget.Sherlock.ActionBar">
<item name="android:background">@drawable/ab_transparent_shadow</item> <style name="ActionBar.Transparent.Shadow" parent="@style/Widget.Sherlock.ActionBar">
<item name="android:progressBarStyle">@style/ProgressBar.Shadow</item> <item name="android:background">@drawable/ab_transparent_shadow</item>
</style> <item name="android:progressBarStyle">@style/ProgressBar.Shadow</item>
</style>
<!-- this style is only referenced in a Light.DarkActionBar based theme -->
<style name="Theme.Shadow.Widget" parent="@style/Theme.Sherlock"> <!-- this style is only referenced in a Light.DarkActionBar based theme -->
<item name="android:popupMenuStyle">@style/PopupMenu.Shadow</item> <style name="Theme.Shadow.Widget" parent="@style/Theme.Sherlock">
<item name="android:dropDownListViewStyle">@style/DropDownListView.Shadow</item> <item name="android:popupMenuStyle">@style/PopupMenu.Shadow</item>
</style> <item name="android:dropDownListViewStyle">@style/DropDownListView.Shadow</item>
</style>
</resources> </resources>
\ No newline at end of file
...@@ -16,11 +16,11 @@ ...@@ -16,11 +16,11 @@
<resources> <resources>
<color name="title_background">#ff5e5e5e</color> <color name="title_background">#ff5e5e5e</color>
<color name="dialog_title_background">#17170443</color> <color name="dialog_title_background">#17170443</color>
<color name="black">#000000</color> <color name="black">#000000</color>
<color name="grey1">#646464</color> <color name="grey1">#646464</color>
<color name="grey2">#c8c8c8</color> <color name="grey2">#c8c8c8</color>
<color name="grey3">#323232</color> <color name="grey3">#323232</color>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">shadowsocks</string> <string name="app_name">shadowsocks</string>
<!-- function category --> <!-- function category -->
<string name="function_cat">Service Controller</string> <string name="function_cat">Service Controller</string>
<string name="service_controller">Proxy Switch</string> <string name="service_controller">Proxy Switch</string>
<string name="service_summary">Enable / Disable Proxy</string> <string name="service_summary">Enable / Disable Proxy</string>
<!-- proxy category --> <!-- proxy category -->
<string name="proxy_cat">Shadowsocks Settings</string> <string name="proxy_cat">Shadowsocks Settings</string>
<string name="proxy">Server</string> <string name="proxy">Server</string>
<string name="proxy_summary">Hostname of your Shadowsocks server</string> <string name="proxy_summary">Hostname of your Shadowsocks server</string>
<string name="remote_port">Remote Port</string> <string name="remote_port">Remote Port</string>
<string name="remote_port_summary">Remote port of shadowsocks server.</string> <string name="remote_port_summary">Remote port of shadowsocks server.</string>
<string name="port">Local Port</string> <string name="port">Local Port</string>
<string name="port_summary">Local port listening on</string> <string name="port_summary">Local port listening on</string>
<string name="sitekey">Password</string> <string name="sitekey">Password</string>
<string name="sitekey_summary">Password of your Shadowsocks server</string> <string name="sitekey_summary">Password of your Shadowsocks server</string>
<string name="enc_method">Encrypt Method</string> <string name="enc_method">Encrypt Method</string>
<array name="enc_method_entry"> <array name="enc_method_entry">
<item>Table</item> <item>Table</item>
<item>RC4</item> <item>RC4</item>
</array> </array>
<array name="enc_method_value"> <array name="enc_method_value">
<item>table</item> <item>table</item>
<item>rc4</item> <item>rc4</item>
</array> </array>
<!-- feature category --> <!-- feature category -->
<string name="feature_cat">Feature Settings</string> <string name="feature_cat">Feature Settings</string>
<string name="http_proxy">HTTP Proxy</string> <string name="http_proxy">HTTP Proxy</string>
<string name="http_proxy_summary">Local HTTP Proxy</string> <string name="http_proxy_summary">Local HTTP Proxy</string>
<string name="dns_proxy">DNS Proxy</string> <string name="dns_proxy">DNS Proxy</string>
<string name="dns_proxy_summary">Redirect DNS queries to remote server.</string> <string name="dns_proxy_summary">Redirect DNS queries to remote server.</string>
<string name="auto_set_gfwlist">CHN Route</string> <string name="auto_set_gfwlist">CHN Route</string>
<string name="auto_set_gfwlist_summary">Bypass all sites located in China <string name="auto_set_gfwlist_summary">Bypass all sites located in China
</string> </string>
<string name="auto_set_proxy">Global Proxy</string> <string name="auto_set_proxy">Global Proxy</string>
<string name="auto_set_proxy_summary">Set up system wide proxy <string name="auto_set_proxy_summary">Set up system wide proxy
</string> </string>
<string name="proxied_apps">Individual Proxy</string> <string name="proxied_apps">Individual Proxy</string>
<string name="proxied_apps_summary">Set individual proxy for apps <string name="proxied_apps_summary">Set individual proxy for apps
</string> </string>
<string name="proxied_help">Select apps to use with Shadowsocks:</string> <string name="proxied_help">Select apps to use with Shadowsocks:</string>
<string name="bypass_apps">Bypass Mode</string> <string name="bypass_apps">Bypass Mode</string>
<string name="bypass_apps_summary">Enable this option to bypass selected apps</string> <string name="bypass_apps_summary">Enable this option to bypass selected apps</string>
<string name="auto_connect">Auto Connect</string> <string name="auto_connect">Auto Connect</string>
<string name="auto_connect_summary">Start Shadowsocks at the startup</string> <string name="auto_connect_summary">Start Shadowsocks at the startup</string>
<!-- notification category --> <!-- notification category -->
<string name="forward_success">Shadowsocks started</string> <string name="forward_success">Shadowsocks started</string>
<string name="service_running">Running in the background</string> <string name="service_running">Running in the background</string>
<string name="forward_fail">Shadowsocks failed to start</string> <string name="forward_fail">Shadowsocks failed to start</string>
<string name="service_stopped">Shadowsocks stopped</string> <string name="service_stopped">Shadowsocks stopped</string>
<string name="forward_stop">Shadowsocks stopped</string> <string name="forward_stop">Shadowsocks stopped</string>
<string name="service_failed">Please check your network status and try again.</string> <string name="service_failed">Please check your network status and try again.</string>
<!-- alert category --> <!-- alert category -->
<string name="ok_iknow">OK</string> <string name="ok_iknow">OK</string>
<string name="port_alert">The port number should be greater than 1024</string> <string name="port_alert">The port number should be greater than 1024</string>
<string name="port_empty">Port should not be empty</string> <string name="port_empty">Port should not be empty</string>
<string name="proxy_empty">Proxy should not be empty</string> <string name="proxy_empty">Proxy should not be empty</string>
<string name="crash_alert">An unexpected exit detected, context has been recovered <string name="crash_alert">An unexpected exit detected, context has been recovered
</string> </string>
<string name="connecting">Connecting…</string> <string name="connecting">Connecting…</string>
<string name="initializing">Initializing…</string> <string name="initializing">Initializing…</string>
<string name="recovering">Reseting…</string> <string name="recovering">Reseting…</string>
<string name="loading">Loading app list…</string> <string name="loading">Loading app list…</string>
<!-- menu category --> <!-- menu category -->
<string name="recovery">Reset</string> <string name="recovery">Reset</string>
<string name="about">About</string> <string name="about">About</string>
<string name="copy_rights">Shadowsocks is an open source socks proxy.\n\nYou can find more details <string name="copy_rights">Shadowsocks is an open source socks proxy.\n\nYou can find more details
here:\ngithub.com/shadowsocks here:\ngithub.com/shadowsocks
</string> </string>
<!-- array category --> <!-- array category -->
<string-array name="chn_list"> <string-array name="chn_list">
<item>0.0.0.0/7</item> <item>0.0.0.0/7</item>
<item>10.0.0.0/8</item> <item>10.0.0.0/8</item>
<item>14.0.0.0/8</item> <item>14.0.0.0/8</item>
<item>27.0.0.0/8</item> <item>27.0.0.0/8</item>
<item>36.0.0.0/8</item> <item>36.0.0.0/8</item>
<item>39.0.0.0/8</item> <item>39.0.0.0/8</item>
<item>42.0.0.0/7</item> <item>42.0.0.0/7</item>
<item>49.0.0.0/8</item> <item>49.0.0.0/8</item>
<item>58.0.0.0/7</item> <item>58.0.0.0/7</item>
<item>60.0.0.0/7</item> <item>60.0.0.0/7</item>
<item>101.0.0.0/8</item> <item>101.0.0.0/8</item>
<item>103.0.0.0/8</item> <item>103.0.0.0/8</item>
<item>106.0.0.0/8</item> <item>106.0.0.0/8</item>
<item>110.0.0.0/7</item> <item>110.0.0.0/7</item>
<item>112.0.0.0/4</item> <item>112.0.0.0/4</item>
<item>134.128.0.0/9</item> <item>134.128.0.0/9</item>
<item>139.0.0.0/9</item> <item>139.0.0.0/9</item>
<item>139.128.0.0/9</item> <item>139.128.0.0/9</item>
<item>140.0.0.0/9</item> <item>140.0.0.0/9</item>
<item>140.128.0.0/9</item> <item>140.128.0.0/9</item>
<item>144.0.0.0/9</item> <item>144.0.0.0/9</item>
<item>144.128.0.0/9</item> <item>144.128.0.0/9</item>
<item>150.0.0.0/8</item> <item>150.0.0.0/8</item>
<item>153.0.0.0/8</item> <item>153.0.0.0/8</item>
<item>157.0.0.0/9</item> <item>157.0.0.0/9</item>
<item>157.128.0.0/9</item> <item>157.128.0.0/9</item>
<item>159.128.0.0/9</item> <item>159.128.0.0/9</item>
<item>161.128.0.0/9</item> <item>161.128.0.0/9</item>
<item>162.0.0.0/9</item> <item>162.0.0.0/9</item>
<item>163.0.0.0/8</item> <item>163.0.0.0/8</item>
<item>166.0.0.0/9</item> <item>166.0.0.0/9</item>
<item>167.128.0.0/9</item> <item>167.128.0.0/9</item>
<item>168.128.0.0/9</item> <item>168.128.0.0/9</item>
<item>171.0.0.0/8</item> <item>171.0.0.0/8</item>
<item>172.0.0.0/12</item> <item>172.0.0.0/12</item>
<item>175.0.0.0/8</item> <item>175.0.0.0/8</item>
<item>180.0.0.0/8</item> <item>180.0.0.0/8</item>
<item>182.0.0.0/7</item> <item>182.0.0.0/7</item>
<item>192.112.0.0/12</item> <item>192.112.0.0/12</item>
<item>192.128.0.0/9</item> <item>192.128.0.0/9</item>
<item>202.0.0.0/7</item> <item>202.0.0.0/7</item>
<item>210.0.0.0/7</item> <item>210.0.0.0/7</item>
<item>218.0.0.0/7</item> <item>218.0.0.0/7</item>
<item>220.0.0.0/6</item> <item>220.0.0.0/6</item>
</string-array> </string-array>
</resources> </resources>
...@@ -19,64 +19,66 @@ ...@@ -19,64 +19,66 @@
<resources> <resources>
<style name="Theme.Shadow" parent="@style/Theme.Sherlock.Light.DarkActionBar"> <style name="Theme.Shadow" parent="@style/Theme.Sherlock.Light.DarkActionBar">
<item name="actionBarItemBackground">@drawable/selectable_background_shadow</item> <item name="actionBarItemBackground">@drawable/selectable_background_shadow</item>
<item name="popupMenuStyle">@style/PopupMenu.Shadow</item> <item name="popupMenuStyle">@style/PopupMenu.Shadow</item>
<item name="dropDownListViewStyle">@style/DropDownListView.Shadow</item> <item name="dropDownListViewStyle">@style/DropDownListView.Shadow</item>
<item name="actionBarTabStyle">@style/ActionBarTabStyle.Shadow</item> <item name="actionBarTabStyle">@style/ActionBarTabStyle.Shadow</item>
<item name="actionDropDownStyle">@style/DropDownNav.Shadow</item> <item name="actionDropDownStyle">@style/DropDownNav.Shadow</item>
<item name="actionBarStyle">@style/ActionBar.Solid.Shadow</item> <item name="actionBarStyle">@style/ActionBar.Solid.Shadow</item>
<item name="actionModeBackground">@drawable/cab_background_top_shadow</item> <item name="actionModeBackground">@drawable/cab_background_top_shadow</item>
<item name="actionModeSplitBackground">@drawable/cab_background_bottom_shadow</item> <item name="actionModeSplitBackground">@drawable/cab_background_bottom_shadow</item>
<item name="actionModeCloseButtonStyle">@style/ActionButton.CloseMode.Shadow</item> <item name="actionModeCloseButtonStyle">@style/ActionButton.CloseMode.Shadow</item>
<!-- Light.DarkActionBar specific --> <!-- Light.DarkActionBar specific -->
<item name="actionBarWidgetTheme">@style/Theme.Shadow.Widget</item> <item name="actionBarWidgetTheme">@style/Theme.Shadow.Widget</item>
</style> </style>
<style name="ActionBar.Solid.Shadow" parent="@style/Widget.Sherlock.Light.ActionBar.Solid.Inverse"> <style name="ActionBar.Solid.Shadow"
<item name="background">@drawable/ab_solid_shadow</item> parent="@style/Widget.Sherlock.Light.ActionBar.Solid.Inverse">
<item name="backgroundStacked">@drawable/ab_stacked_solid_shadow</item> <item name="background">@drawable/ab_solid_shadow</item>
<item name="backgroundSplit">@drawable/ab_bottom_solid_shadow</item> <item name="backgroundStacked">@drawable/ab_stacked_solid_shadow</item>
<item name="progressBarStyle">@style/ProgressBar.Shadow</item> <item name="backgroundSplit">@drawable/ab_bottom_solid_shadow</item>
</style> <item name="progressBarStyle">@style/ProgressBar.Shadow</item>
</style>
<style name="ActionBar.Transparent.Shadow" parent="@style/Widget.Sherlock.ActionBar">
<item name="background">@drawable/ab_transparent_shadow</item> <style name="ActionBar.Transparent.Shadow" parent="@style/Widget.Sherlock.ActionBar">
<item name="progressBarStyle">@style/ProgressBar.Shadow</item> <item name="background">@drawable/ab_transparent_shadow</item>
</style> <item name="progressBarStyle">@style/ProgressBar.Shadow</item>
</style>
<style name="PopupMenu.Shadow" parent="@style/Widget.Sherlock.ListPopupWindow">
<item name="android:popupBackground">@drawable/menu_dropdown_panel_shadow</item> <style name="PopupMenu.Shadow" parent="@style/Widget.Sherlock.ListPopupWindow">
</style> <item name="android:popupBackground">@drawable/menu_dropdown_panel_shadow</item>
</style>
<style name="DropDownListView.Shadow" parent="@style/Widget.Sherlock.ListView.DropDown">
<item name="android:listSelector">@drawable/selectable_background_shadow</item> <style name="DropDownListView.Shadow" parent="@style/Widget.Sherlock.ListView.DropDown">
</style> <item name="android:listSelector">@drawable/selectable_background_shadow</item>
</style>
<style name="ActionBarTabStyle.Shadow" parent="@style/Widget.Sherlock.ActionBar.TabView">
<item name="android:background">@drawable/tab_indicator_ab_shadow</item> <style name="ActionBarTabStyle.Shadow" parent="@style/Widget.Sherlock.ActionBar.TabView">
</style> <item name="android:background">@drawable/tab_indicator_ab_shadow</item>
</style>
<style name="DropDownNav.Shadow" parent="@style/Widget.Sherlock.Spinner.DropDown.ActionBar">
<item name="android:background">@drawable/spinner_background_ab_shadow</item> <style name="DropDownNav.Shadow" parent="@style/Widget.Sherlock.Spinner.DropDown.ActionBar">
<item name="android:popupBackground">@drawable/menu_dropdown_panel_shadow</item> <item name="android:background">@drawable/spinner_background_ab_shadow</item>
<item name="android:dropDownSelector">@drawable/selectable_background_shadow</item> <item name="android:popupBackground">@drawable/menu_dropdown_panel_shadow</item>
</style> <item name="android:dropDownSelector">@drawable/selectable_background_shadow</item>
</style>
<style name="ProgressBar.Shadow" parent="@style/Widget.Sherlock.ProgressBar.Horizontal">
<item name="android:progressDrawable">@drawable/progress_horizontal_shadow</item> <style name="ProgressBar.Shadow" parent="@style/Widget.Sherlock.ProgressBar.Horizontal">
</style> <item name="android:progressDrawable">@drawable/progress_horizontal_shadow</item>
</style>
<style name="ActionButton.CloseMode.Shadow" parent="@style/Widget.Sherlock.ActionButton.CloseMode">
<item name="android:background">@drawable/btn_cab_done_shadow</item> <style name="ActionButton.CloseMode.Shadow"
</style> parent="@style/Widget.Sherlock.ActionButton.CloseMode">
<item name="android:background">@drawable/btn_cab_done_shadow</item>
<!-- this style is only referenced in a Light.DarkActionBar based theme --> </style>
<style name="Theme.Shadow.Widget" parent="@style/Theme.Sherlock">
<item name="popupMenuStyle">@style/PopupMenu.Shadow</item> <!-- this style is only referenced in a Light.DarkActionBar based theme -->
<item name="dropDownListViewStyle">@style/DropDownListView.Shadow</item> <style name="Theme.Shadow.Widget" parent="@style/Theme.Sherlock">
</style> <item name="popupMenuStyle">@style/PopupMenu.Shadow</item>
<item name="dropDownListViewStyle">@style/DropDownListView.Shadow</item>
</style>
</resources> </resources>
\ No newline at end of file
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:key="isHTTPProxy" android:key="isHTTPProxy"
android:summary="@string/http_proxy_summary" android:summary="@string/http_proxy_summary"
android:title="@string/http_proxy"> android:title="@string/http_proxy">
</CheckBoxPreference> </CheckBoxPreference>
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="isDNSProxy" android:key="isDNSProxy"
android:summary="@string/dns_proxy_summary" android:summary="@string/dns_proxy_summary"
android:title="@string/dns_proxy"> android:title="@string/dns_proxy">
</CheckBoxPreference> </CheckBoxPreference>
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="isGFWList" android:key="isGFWList"
android:summary="@string/auto_set_gfwlist_summary" android:summary="@string/auto_set_gfwlist_summary"
android:title="@string/auto_set_gfwlist"> android:title="@string/auto_set_gfwlist">
</CheckBoxPreference> </CheckBoxPreference>
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="true" android:defaultValue="true"
android:key="isGlobalProxy" android:key="isGlobalProxy"
android:disableDependentsState="true" android:disableDependentsState="true"
android:summary="@string/auto_set_proxy_summary" android:summary="@string/auto_set_proxy_summary"
android:title="@string/auto_set_proxy"> android:title="@string/auto_set_proxy">
</CheckBoxPreference> </CheckBoxPreference>
<CheckBoxPreference <CheckBoxPreference
android:defaultValue="false" android:defaultValue="false"
android:dependency="isGlobalProxy" android:dependency="isGlobalProxy"
android:key="isBypassApps" android:key="isBypassApps"
android:summary="@string/bypass_apps_summary" android:summary="@string/bypass_apps_summary"
android:title="@string/bypass_apps"> android:title="@string/bypass_apps">
</CheckBoxPreference> </CheckBoxPreference>
<Preference <Preference
android:key="proxyedApps" android:key="proxyedApps"
android:dependency="isGlobalProxy" android:dependency="isGlobalProxy"
android:summary="@string/proxied_apps_summary" android:summary="@string/proxied_apps_summary"
android:title="@string/proxied_apps"> android:title="@string/proxied_apps">
<intent android:action="com.github.shadowsocks.AppManager"/> <intent android:action="com.github.shadowsocks.AppManager"/>
</Preference> </Preference>
<CheckBoxPreference <CheckBoxPreference
android:key="isAutoConnect" android:key="isAutoConnect"
android:summary="@string/auto_connect_summary" android:summary="@string/auto_connect_summary"
android:title="@string/auto_connect"> android:title="@string/auto_connect">
</CheckBoxPreference> </CheckBoxPreference>
</PreferenceScreen> </PreferenceScreen>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android">
<com.github.shadowsocks.preferences.SummaryEditTextPreference <com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="198.199.101.152" android:defaultValue="198.199.101.152"
android:key="proxy" android:key="proxy"
android:summary="@string/proxy_summary" android:summary="@string/proxy_summary"
android:title="@string/proxy"> android:title="@string/proxy">
</com.github.shadowsocks.preferences.SummaryEditTextPreference> </com.github.shadowsocks.preferences.SummaryEditTextPreference>
<com.github.shadowsocks.preferences.SummaryEditTextPreference <com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="443" android:defaultValue="443"
android:key="remotePort" android:key="remotePort"
android:summary="@string/remote_port_summary" android:summary="@string/remote_port_summary"
android:title="@string/remote_port"> android:title="@string/remote_port">
</com.github.shadowsocks.preferences.SummaryEditTextPreference> </com.github.shadowsocks.preferences.SummaryEditTextPreference>
<com.github.shadowsocks.preferences.SummaryEditTextPreference <com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="1080" android:defaultValue="1080"
android:key="port" android:key="port"
android:summary="@string/port_summary" android:summary="@string/port_summary"
android:title="@string/port"> android:title="@string/port">
</com.github.shadowsocks.preferences.SummaryEditTextPreference> </com.github.shadowsocks.preferences.SummaryEditTextPreference>
<com.github.shadowsocks.preferences.SummaryEditTextPreference <com.github.shadowsocks.preferences.SummaryEditTextPreference
android:defaultValue="u1rRWTssNv0p" android:defaultValue="u1rRWTssNv0p"
android:key="sitekey" android:key="sitekey"
android:summary="@string/sitekey_summary" android:summary="@string/sitekey_summary"
android:title="@string/sitekey"> android:title="@string/sitekey">
</com.github.shadowsocks.preferences.SummaryEditTextPreference> </com.github.shadowsocks.preferences.SummaryEditTextPreference>
<ListPreference <ListPreference
android:defaultValue="rc4" android:defaultValue="rc4"
android:key="encMethod" android:key="encMethod"
android:entries="@array/enc_method_entry" android:entries="@array/enc_method_entry"
android:entryValues="@array/enc_method_value" android:entryValues="@array/enc_method_value"
android:title="@string/enc_method"> android:title="@string/enc_method">
</ListPreference> </ListPreference>
</PreferenceScreen> </PreferenceScreen>
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<preference-headers xmlns:android="http://schemas.android.com/apk/res/android" <preference-headers
xmlns:unified="http://schemas.android.com/apk/res-auto"> xmlns:unified="http://schemas.android.com/apk/res-auto">
<header <header
unified:fragment="com.github.shadowsocks.Shadowsocks$ProxyFragment" unified:fragment="com.github.shadowsocks.Shadowsocks$ProxyFragment"
unified:title="@string/proxy_cat" unified:title="@string/proxy_cat"
unified:preferenceRes="@xml/pref_proxy"/> unified:preferenceRes="@xml/pref_proxy"/>
<header <header
unified:fragment="com.github.shadowsocks.Shadowsocks$FeatureFragment" unified:fragment="com.github.shadowsocks.Shadowsocks$FeatureFragment"
unified:title="@string/feature_cat" unified:title="@string/feature_cat"
unified:preferenceRes="@xml/pref_feature"/> unified:preferenceRes="@xml/pref_feature"/>
</preference-headers> </preference-headers>
\ No newline at end of file
...@@ -21,388 +21,362 @@ import android.view.View.OnClickListener; ...@@ -21,388 +21,362 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams; import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.*; import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener; 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.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockActivity; 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;
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:
import java.util.*; listApps.setAdapter(adapter);
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:
listApps.setAdapter(adapter);
listApps.setOnScrollListener(new OnScrollListener() {
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<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
Vector<ProxyedApp> vectorApps = new Vector<ProxyedApp>();
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); listApps.setOnScrollListener(new OnScrollListener() {
app.setUsername(pMgr.getNameForUid(app.getUid())); boolean visible;
// check if this application is allowed @Override
if (aInfo.packageName != null public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
&& aInfo.packageName.equals("com.github.shadowsocks")) { int totalItemCount) {
app.setProxyed(true); if (visible) {
} else if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) { String name = apps[firstVisibleItem].getName();
app.setProxyed(true); if (name != null && name.length() > 1) {
} else { overlay.setText(apps[firstVisibleItem].getName().substring(0, 1));
app.setProxyed(false); } else {
overlay.setText("*");
}
overlay.setVisibility(View.VISIBLE);
}
} }
vectorApps.add(app); @Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
} visible = true;
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
ProxyedApp[] apps = new ProxyedApp[vectorApps.size()]; overlay.setVisibility(View.INVISIBLE);
vectorApps.toArray(apps); }
return apps; }
});
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();
} }
public void getApps(Context context) { Arrays.sort(tordApps);
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String tordAppString = prefs.getString(PREFS_KEY_PROXYED, ""); // else load the apps up
String[] tordApps; PackageManager pMgr = context.getPackageManager();
StringTokenizer st = new StringTokenizer(tordAppString, "|"); List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
tordApps = new String[st.countTokens()];
int tordIdx = 0;
while (st.hasMoreTokens()) {
tordApps[tordIdx++] = st.nextToken();
}
Arrays.sort(tordApps); Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
Vector<ProxyedApp> vectorApps = new Vector<ProxyedApp>(); Vector<ProxyedApp> vectorApps = new Vector<ProxyedApp>();
// else load the apps up ApplicationInfo aInfo = null;
PackageManager pMgr = context.getPackageManager();
List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0); while (itAppInfo.hasNext()) {
aInfo = itAppInfo.next();
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator(); // ignore system apps
if (aInfo.uid < 10000) continue;
ApplicationInfo aInfo = null; ProxyedApp app = new ProxyedApp();
while (itAppInfo.hasNext()) { app.setUid(aInfo.uid);
aInfo = itAppInfo.next();
// ignore system apps app.setUsername(pMgr.getNameForUid(app.getUid()));
if (aInfo.uid < 10000)
continue;
if (aInfo.processName == null) // check if this application is allowed
continue; if (aInfo.packageName != null && aInfo.packageName.equals("com.github.shadowsocks")) {
if (pMgr.getApplicationLabel(aInfo) == null app.setProxyed(true);
|| pMgr.getApplicationLabel(aInfo).toString().equals("")) } else if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) {
continue; app.setProxyed(true);
if (pMgr.getApplicationIcon(aInfo) == null) } else {
continue; app.setProxyed(false);
}
ProxyedApp tApp = new ProxyedApp(); vectorApps.add(app);
}
tApp.setEnabled(aInfo.enabled); ProxyedApp[] apps = new ProxyedApp[vectorApps.size()];
tApp.setUid(aInfo.uid); vectorApps.toArray(apps);
tApp.setUsername(pMgr.getNameForUid(tApp.getUid())); return apps;
tApp.setProcname(aInfo.processName); }
tApp.setName(pMgr.getApplicationLabel(aInfo).toString());
// check if this application is allowed public void getApps(Context context) {
if (Arrays.binarySearch(tordApps, tApp.getUsername()) >= 0) {
tApp.setProxyed(true);
} else {
tApp.setProxyed(false);
}
vectorApps.add(tApp); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
}
apps = new ProxyedApp[vectorApps.size()]; String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
vectorApps.toArray(apps); String[] tordApps;
StringTokenizer st = new StringTokenizer(tordAppString, "|");
tordApps = new String[st.countTokens()];
int tordIdx = 0;
while (st.hasMoreTokens()) {
tordApps[tordIdx++] = st.nextToken();
} }
private void loadApps() { Arrays.sort(tordApps);
getApps(this);
Arrays.sort(apps, new Comparator<ProxyedApp>() { Vector<ProxyedApp> vectorApps = new Vector<ProxyedApp>();
@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(); // else load the apps up
PackageManager pMgr = context.getPackageManager();
adapter = new ArrayAdapter<ProxyedApp>(this, R.layout.layout_apps_item, List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
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();
}
final ProxyedApp app = apps[position]; Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
entry.icon.setTag(app.getUid()); ApplicationInfo aInfo = null;
dm.DisplayImage(app.getUid(), while (itAppInfo.hasNext()) {
(Activity) convertView.getContext(), entry.icon); aInfo = itAppInfo.next();
entry.text.setText(app.getName()); // ignore system apps
if (aInfo.uid < 10000) continue;
final CheckBox box = entry.box; if (aInfo.processName == null) continue;
box.setTag(app); if (pMgr.getApplicationLabel(aInfo) == null || pMgr.getApplicationLabel(aInfo)
box.setChecked(app.isProxyed()); .toString()
.equals("")) {
continue;
}
if (pMgr.getApplicationIcon(aInfo) == null) continue;
entry.text.setTag(box); ProxyedApp tApp = new ProxyedApp();
return convertView; tApp.setEnabled(aInfo.enabled);
} tApp.setUid(aInfo.uid);
}; tApp.setUsername(pMgr.getNameForUid(tApp.getUid()));
tApp.setProcname(aInfo.processName);
tApp.setName(pMgr.getApplicationLabel(aInfo).toString());
appsLoaded = true; // 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()];
* Called an application is check/unchecked vectorApps.toArray(apps);
*/ }
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { private void loadApps() {
final ProxyedApp app = (ProxyedApp) buttonView.getTag(); getApps(this);
if (app != null) {
app.setProxyed(isChecked); Arrays.sort(apps, new Comparator<ProxyedApp>() {
@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<ProxyedApp>(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();
} }
saveAppSettings(this); final ProxyedApp app = apps[position];
} entry.icon.setTag(app.getUid());
@Override dm.DisplayImage(app.getUid(), (Activity) convertView.getContext(), entry.icon);
public void onClick(View v) {
CheckBox cbox = (CheckBox) v.getTag(); entry.text.setText(app.getName());
final ProxyedApp app = (ProxyedApp) cbox.getTag(); final CheckBox box = entry.box;
if (app != null) { box.setTag(app);
app.setProxyed(!app.isProxyed()); box.setChecked(app.isProxyed());
cbox.setChecked(app.isProxyed());
}
saveAppSettings(this); entry.text.setTag(box);
} return convertView;
}
};
@Override appsLoaded = true;
protected void onCreate(Bundle savedInstanceState) { }
super.onCreate(savedInstanceState);
this.setContentView(R.layout.layout_apps); /** 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);
}
this.dm = ImageLoaderFactory.getImageLoader(this); saveAppSettings(this);
}
this.overlay = (TextView) View.inflate(this, R.layout.overlay, null); @Override
getWindowManager() public void onClick(View v) {
.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; CheckBox cbox = (CheckBox) v.getTag();
final ProxyedApp app = (ProxyedApp) cbox.getTag();
if (app != null) {
app.setProxyed(!app.isProxyed());
cbox.setChecked(app.isProxyed());
} }
@Override saveAppSettings(this);
protected void onResume() { }
super.onResume();
new Thread() { @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
@Override this.setContentView(R.layout.layout_apps);
public void run() {
handler.sendEmptyMessage(MSG_LOAD_START);
listApps = (ListView) findViewById(R.id.applistview); this.dm = ImageLoaderFactory.getImageLoader(this);
if (!appsLoaded) this.overlay = (TextView) View.inflate(this, R.layout.overlay, null);
loadApps(); getWindowManager().addView(overlay,
handler.sendEmptyMessage(MSG_LOAD_FINISH); new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
} WindowManager.LayoutParams.TYPE_APPLICATION,
}.start(); WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT));
} mAppManager = this;
}
/* @Override
* (non-Javadoc) protected void onResume() {
* super.onResume();
* @see android.app.Activity#onStop()
*/
@Override
protected void onStop() {
super.onStop();
// Log.d(getClass().getName(),"Exiting Preferences"); new Thread() {
}
public void saveAppSettings(Context context) { @Override
if (apps == null) public void run() {
return; handler.sendEmptyMessage(MSG_LOAD_START);
SharedPreferences prefs = PreferenceManager listApps = (ListView) findViewById(R.id.applistview);
.getDefaultSharedPreferences(this);
// final SharedPreferences prefs = if (!appsLoaded) loadApps();
// context.getSharedPreferences(PREFS_KEY, 0); handler.sendEmptyMessage(MSG_LOAD_FINISH);
}
}.start();
}
StringBuilder tordApps = new StringBuilder(); /*
* (non-Javadoc)
*
* @see android.app.Activity#onStop()
*/
@Override
protected void onStop() {
super.onStop();
for (int i = 0; i < apps.length; i++) { // Log.d(getClass().getName(),"Exiting Preferences");
if (apps[i].isProxyed()) { }
tordApps.append(apps[i].getUsername());
tordApps.append("|");
}
}
Editor edit = prefs.edit(); public void saveAppSettings(Context context) {
edit.putString(PREFS_KEY_PROXYED, tordApps.toString()); if (apps == null) return;
edit.commit();
} SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
private static class ListEntry { // final SharedPreferences prefs =
private CheckBox box; // context.getSharedPreferences(PREFS_KEY, 0);
private TextView text;
private ImageView icon; 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;
}
} }
...@@ -27,46 +27,41 @@ import java.io.FileDescriptor; ...@@ -27,46 +27,41 @@ import java.io.FileDescriptor;
*/ */
public class Exec { public class Exec {
static { static {
java.lang.System.loadLibrary("exec"); java.lang.System.loadLibrary("exec");
} }
/** /** Close a given file descriptor. */
* Close a given file descriptor. public static native void close(FileDescriptor fd);
*/
public static native void close(FileDescriptor fd);
/** /**
* Create a subprocess. Differs from java.lang.ProcessBuilder in that a pty * Create a subprocess. Differs from java.lang.ProcessBuilder in that a pty
* is used to communicate with the subprocess. * is used to communicate with the subprocess.
* <p/> * <p/>
* Callers are responsible for calling Exec.close() on the returned file * Callers are responsible for calling Exec.close() on the returned file
* descriptor. * descriptor.
* *
* @param rdt Whether redirect stdout and stderr * @param rdt Whether redirect stdout and stderr
* @param cmd The command to execute. * @param cmd The command to execute.
* @param args An array of arguments to the command. * @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 * @param envVars An array of strings of the form "VAR=value" to be added to the
* environment of the process. * environment of the process.
* @param scripts The scripts to execute. * @param scripts The scripts to execute.
* @param processId A one-element array to which the process ID of the started * @param processId A one-element array to which the process ID of the started
* process will be written. * process will be written.
* @return File descriptor * @return File descriptor
*/ */
public static native FileDescriptor createSubprocess(int rdt, String cmd, public static native FileDescriptor createSubprocess(int rdt, String cmd, String[] args,
String[] args, String[] envVars, String[] envVars, String scripts, int[] processId);
String scripts, int[] processId);
/** /** Send SIGHUP to a process group. */
* Send SIGHUP to a process group. public static native void hangupProcessGroup(int processId);
*/
public static native void hangupProcessGroup(int processId);
/** /**
* Causes the calling thread to wait for the process associated with the * Causes the calling thread to wait for the process associated with the
* receiver to finish executing. * receiver to finish executing.
* *
* @return The exit value of the Process being waited on * @return The exit value of the Process being waited on
*/ */
public static native int waitFor(int processId); public static native int waitFor(int processId);
} }
...@@ -45,7 +45,6 @@ import android.graphics.Bitmap; ...@@ -45,7 +45,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.BitmapDrawable;
import android.widget.ImageView; import android.widget.ImageView;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
...@@ -54,195 +53,190 @@ import java.util.Stack; ...@@ -54,195 +53,190 @@ import java.util.Stack;
public class ImageLoader { public class ImageLoader {
final int stub_id = R.drawable.sym_def_app_icon; final int stub_id = R.drawable.sym_def_app_icon;
PhotosQueue photosQueue = new PhotosQueue(); PhotosQueue photosQueue = new PhotosQueue();
PhotosLoader photoLoaderThread = new PhotosLoader(); PhotosLoader photoLoaderThread = new PhotosLoader();
// the simplest in-memory cache implementation. This should be replaced with // the simplest in-memory cache implementation. This should be replaced with
// something like SoftReference or BitmapOptions.inPurgeable(since 1.6) // something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
private HashMap<Integer, Bitmap> cache = new HashMap<Integer, Bitmap>(); private HashMap<Integer, Bitmap> cache = new HashMap<Integer, Bitmap>();
private File cacheDir; private File cacheDir;
private Context context; private Context context;
public ImageLoader(Context c) { public ImageLoader(Context c) {
// Make the background thead low priority. This way it will not affect // Make the background thead low priority. This way it will not affect
// the UI performance // the UI performance
photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1); photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
context = c; context = c;
// Find the dir to save cached images // Find the dir to save cached images
cacheDir = context.getCacheDir(); 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 clearCache() { }
// clear memory cache
cache.clear(); public void DisplayImage(int uid, Activity activity, ImageView imageView) {
if (cache.containsKey(uid)) {
// clear SD cache imageView.setImageBitmap(cache.get(uid));
File[] files = cacheDir.listFiles(); } else {
for (File f : files) queuePhoto(uid, activity, imageView);
f.delete(); imageView.setImageResource(stub_id);
} }
}
// decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) { private Bitmap getBitmap(int uid) {
try { // I identify images by hashcode. Not a perfect solution, good for the
// decode image size // demo.
BitmapFactory.Options o = new BitmapFactory.Options(); String filename = String.valueOf(uid);
o.inJustDecodeBounds = true; File f = new File(cacheDir, filename);
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// from SD cache
// Find the correct scale value. It should be the power of 2. Bitmap b = decodeFile(f);
final int REQUIRED_SIZE = 70; if (b != null) return b;
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 1; // from web
while (true) { try {
if (width_tmp / 2 < REQUIRED_SIZE BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context, uid);
|| height_tmp / 2 < REQUIRED_SIZE) return icon.getBitmap();
break; } catch (Exception ex) {
width_tmp /= 2; return null;
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)) private void queuePhoto(int uid, Activity activity, ImageView imageView) {
imageView.setImageBitmap(cache.get(uid)); // This ImageView may be used for other images before. So there may be
else { // some old tasks in the queue. We need to discard them.
queuePhoto(uid, activity, imageView); photosQueue.Clean(imageView);
imageView.setImageResource(stub_id); PhotoToLoad p = new PhotoToLoad(uid, imageView);
} synchronized (photosQueue.photosToLoad) {
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
} }
private Bitmap getBitmap(int uid) { // start thread if it's not started yet
// I identify images by hashcode. Not a perfect solution, good for the if (photoLoaderThread.getState() == Thread.State.NEW) photoLoaderThread.start();
// 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) { public void stopThread() {
// This ImageView may be used for other images before. So there may be photoLoaderThread.interrupt();
// 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 // Used to display bitmap in the UI thread
if (photoLoaderThread.getState() == Thread.State.NEW) class BitmapDisplayer implements Runnable {
photoLoaderThread.start(); Bitmap bitmap;
} ImageView imageView;
public void stopThread() { public BitmapDisplayer(Bitmap b, ImageView i) {
photoLoaderThread.interrupt(); bitmap = b;
imageView = i;
} }
// Used to display bitmap in the UI thread @Override
class BitmapDisplayer implements Runnable { public void run() {
Bitmap bitmap; if (bitmap != null) {
ImageView imageView; imageView.setImageBitmap(bitmap);
} else {
public BitmapDisplayer(Bitmap b, ImageView i) { imageView.setImageResource(stub_id);
bitmap = b; }
imageView = i;
}
@Override
public void run() {
if (bitmap != null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(stub_id);
}
} }
}
class PhotosLoader extends Thread {
@Override class PhotosLoader extends Thread {
public void run() { @Override
try { public void run() {
while (true) { try {
// thread waits until there are any images to load in the while (true) {
// queue // thread waits until there are any images to load in the
if (photosQueue.photosToLoad.size() == 0) // queue
synchronized (photosQueue.photosToLoad) { if (photosQueue.photosToLoad.size() == 0) {
photosQueue.photosToLoad.wait(); synchronized (photosQueue.photosToLoad) {
} photosQueue.photosToLoad.wait();
if (photosQueue.photosToLoad.size() != 0) { }
PhotoToLoad photoToLoad; }
synchronized (photosQueue.photosToLoad) { if (photosQueue.photosToLoad.size() != 0) {
photoToLoad = photosQueue.photosToLoad.pop(); PhotoToLoad photoToLoad;
} synchronized (photosQueue.photosToLoad) {
Bitmap bmp = getBitmap(photoToLoad.uid); photoToLoad = photosQueue.photosToLoad.pop();
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
} }
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
}
} }
}
// stores list of photos to download
class PhotosQueue { // stores list of photos to download
private final Stack<PhotoToLoad> photosToLoad = new Stack<PhotoToLoad>(); class PhotosQueue {
private final Stack<PhotoToLoad> photosToLoad = new Stack<PhotoToLoad>();
// removes all instances of this ImageView
public void Clean(ImageView image) { // removes all instances of this ImageView
synchronized (photosToLoad) { public void Clean(ImageView image) {
for (int j = 0; j < photosToLoad.size(); ) { synchronized (photosToLoad) {
if (photosToLoad.get(j).imageView == image) photosToLoad.remove(j); for (int j = 0; j < photosToLoad.size(); ) {
else ++j; if (photosToLoad.get(j).imageView == image) {
} photosToLoad.remove(j);
} } else {
++j;
}
} }
}
} }
}
// Task for the queue // Task for the queue
private class PhotoToLoad { private class PhotoToLoad {
public int uid; public int uid;
public ImageView imageView; public ImageView imageView;
public PhotoToLoad(int u, ImageView i) { public PhotoToLoad(int u, ImageView i) {
uid = u; uid = u;
imageView = i; imageView = i;
}
} }
}
} }
...@@ -42,12 +42,12 @@ import android.content.Context; ...@@ -42,12 +42,12 @@ import android.content.Context;
public class ImageLoaderFactory { public class ImageLoaderFactory {
private static ImageLoader il = null; private static ImageLoader il = null;
public static ImageLoader getImageLoader(Context context) { public static ImageLoader getImageLoader(Context context) {
if (il == null) { if (il == null) {
il = new ImageLoader(context); il = new ImageLoader(context);
}
return il;
} }
return il;
}
} }
...@@ -40,94 +40,70 @@ package com.github.shadowsocks; ...@@ -40,94 +40,70 @@ package com.github.shadowsocks;
public class ProxyedApp { public class ProxyedApp {
private boolean enabled; private boolean enabled;
private int uid; private int uid;
private String username; private String username;
private String procname; private String procname;
private String name; private String name;
private boolean proxyed = false; private boolean proxyed = false;
/** /** @return the name */
* @return the name public String getName() {
*/ return name;
public String getName() { }
return name;
}
/** /** @param name the name to set */
* @param name the name to set public void setName(String name) {
*/ this.name = name;
public void setName(String name) { }
this.name = name;
}
/** /** @return the procname */
* @return the procname public String getProcname() {
*/ return procname;
public String getProcname() { }
return procname;
}
/** /** @param procname the procname to set */
* @param procname the procname to set public void setProcname(String procname) {
*/ this.procname = procname;
public void setProcname(String procname) { }
this.procname = procname;
}
/** /** @return the uid */
* @return the uid public int getUid() {
*/ return uid;
public int getUid() { }
return uid;
}
/** /** @param uid the uid to set */
* @param uid the uid to set public void setUid(int uid) {
*/ this.uid = uid;
public void setUid(int uid) { }
this.uid = uid;
}
/** /** @return the username */
* @return the username public String getUsername() {
*/ return username;
public String getUsername() { }
return username;
}
/** /** @param username the username to set */
* @param username the username to set public void setUsername(String username) {
*/ this.username = username;
public void setUsername(String username) { }
this.username = username;
}
/** /** @return the enabled */
* @return the enabled public boolean isEnabled() {
*/ return enabled;
public boolean isEnabled() { }
return enabled;
}
/** /** @param enabled the enabled to set */
* @param enabled the enabled to set public void setEnabled(boolean enabled) {
*/ this.enabled = enabled;
public void setEnabled(boolean enabled) { }
this.enabled = enabled;
}
/** /** @return the proxyed */
* @return the proxyed public boolean isProxyed() {
*/ return proxyed;
public boolean isProxyed() { }
return proxyed;
}
/** /** @param proxyed the proxyed to set */
* @param proxyed the proxyed to set public void setProxyed(boolean proxyed) {
*/ this.proxyed = proxyed;
public void setProxyed(boolean proxyed) { }
this.proxyed = proxyed;
}
} }
...@@ -52,7 +52,8 @@ import android.graphics.Typeface; ...@@ -52,7 +52,8 @@ import android.graphics.Typeface;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.preference.*; import android.preference.Preference;
import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.widget.CompoundButton; import android.widget.CompoundButton;
...@@ -61,588 +62,554 @@ import android.widget.TextView; ...@@ -61,588 +62,554 @@ import android.widget.TextView;
import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.Menu;
import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.MenuItem;
import com.google.analytics.tracking.android.EasyTracker; 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.Crouton;
import de.keyboardsurfer.android.widget.crouton.Style; import de.keyboardsurfer.android.widget.crouton.Style;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.Hashtable; 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 public class Shadowsocks extends UnifiedSherlockPreferenceActivity
implements CompoundButton.OnCheckedChangeListener, OnSharedPreferenceChangeListener { implements CompoundButton.OnCheckedChangeListener, OnSharedPreferenceChangeListener {
public static final String PREFS_NAME = "Shadowsocks"; public static final String PREFS_NAME = "Shadowsocks";
public static final String[] PROXY_PREFS = {"proxy", "remotePort", "port" public static final String[] PROXY_PREFS = {
, "sitekey", "encMethod"}; "proxy", "remotePort", "port", "sitekey", "encMethod"
public static final String[] FEATRUE_PREFS = {"isHTTPProxy", "isDNSProxy", "isGFWList", "isGlobalProxy", "isBypassApps" };
, "proxyedApps", "isAutoConnect"}; public static final String[] FEATRUE_PREFS = {
private static final String TAG = "Shadowsocks"; "isHTTPProxy", "isDNSProxy", "isGFWList", "isGlobalProxy", "isBypassApps", "proxyedApps",
private static final int MSG_CRASH_RECOVER = 1; "isAutoConnect"
private static final int MSG_INITIAL_FINISH = 2; };
private static ProgressDialog mProgressDialog = null; private static final String TAG = "Shadowsocks";
final Handler handler = new Handler() { private static final int MSG_CRASH_RECOVER = 1;
@Override private static final int MSG_INITIAL_FINISH = 2;
public void handleMessage(Message msg) { private static ProgressDialog mProgressDialog = null;
SharedPreferences settings = PreferenceManager final Handler handler = new Handler() {
.getDefaultSharedPreferences(Shadowsocks.this); @Override
Editor ed = settings.edit(); public void handleMessage(Message msg) {
switch (msg.what) { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(Shadowsocks.this);
case MSG_CRASH_RECOVER: Editor ed = settings.edit();
Crouton.makeText(Shadowsocks.this, R.string.crash_alert, Style.ALERT).show(); switch (msg.what) {
ed.putBoolean("isRunning", false); case MSG_CRASH_RECOVER:
break; Crouton.makeText(Shadowsocks.this, R.string.crash_alert, Style.ALERT).show();
case MSG_INITIAL_FINISH: ed.putBoolean("isRunning", false);
if (mProgressDialog != null) { break;
mProgressDialog.dismiss(); case MSG_INITIAL_FINISH:
mProgressDialog = null; if (mProgressDialog != null) {
} mProgressDialog.dismiss();
break; mProgressDialog = null;
} }
ed.commit(); break;
super.handleMessage(msg); }
} ed.commit();
}; super.handleMessage(msg);
private Switch switchButton; }
};
private Switch switchButton;
private void copyAssets(String path) { private void copyAssets(String path) {
AssetManager assetManager = getAssets(); AssetManager assetManager = getAssets();
String[] files = null; 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 { try {
files = assetManager.list(path); if (path.length() > 0) {
} catch (IOException e) { in = assetManager.open(path + "/" + file);
Log.e(TAG, e.getMessage()); } else {
} in = assetManager.open(file);
if (files != null) { }
for (String file : files) { out = new FileOutputStream("/data/data/com.github.shadowsocks/" + file);
InputStream in = null; copyFile(in, out);
OutputStream out = null; in.close();
try { in = null;
if (path.length() > 0) { out.flush();
in = assetManager.open(path + "/" + file); out.close();
} else { out = null;
in = assetManager.open(file); } catch (Exception e) {
} Log.e(TAG, e.getMessage());
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 { private void copyFile(InputStream in, OutputStream out) throws IOException {
byte[] buffer = new byte[1024]; byte[] buffer = new byte[1024];
int read; int read;
while ((read = in.read(buffer)) != -1) { while ((read = in.read(buffer)) != -1) {
out.write(buffer, 0, read); out.write(buffer, 0, read);
}
} }
}
private void crash_recovery() {
private void crash_recovery() {
StringBuilder sb = new StringBuilder();
sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT").append("\n"); StringBuilder sb = new StringBuilder();
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/pdnsd.pid`").append("\n"); sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT").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/pdnsd.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/redsocks.pid`").append("\n");
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/polipo.pid`").append("\n"); sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
sb.append("killall -9 pdnsd").append("\n"); sb.append("kill -9 `cat /data/data/com.github.shadowsocks/polipo.pid`").append("\n");
sb.append("killall -9 redsocks").append("\n"); sb.append("killall -9 pdnsd").append("\n");
sb.append("killall -9 shadowsocks").append("\n"); sb.append("killall -9 redsocks").append("\n");
sb.append("killall -9 polipo").append("\n"); sb.append("killall -9 shadowsocks").append("\n");
Utils.runRootCommand(sb.toString()); 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;
private boolean isTextEmpty(String s, String msg) { }
if (s == null || s.length() <= 0) {
showAToast(msg); @Override
return true; public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
} if (compoundButton == switchButton) {
return false; if (!serviceStart()) {
switchButton.setChecked(false);
}
} }
}
@Override /** Called when the activity is first created. */
public void onCheckedChanged(CompoundButton compoundButton, boolean b) { @Override
if (compoundButton == switchButton) { public void onCreate(Bundle savedInstanceState) {
if (!serviceStart()) { setHeaderRes(R.xml.shadowsocks_headers);
switchButton.setChecked(false); super.onCreate(savedInstanceState);
}
}
}
/** RelativeLayout switchLayout =
* Called when the activity is first created. (RelativeLayout) getLayoutInflater().inflate(R.layout.layout_switch, null);
*/
@Override
public void onCreate(Bundle savedInstanceState) {
setHeaderRes(R.xml.shadowsocks_headers);
super.onCreate(savedInstanceState);
RelativeLayout switchLayout = (RelativeLayout) getSupportActionBar().setCustomView(switchLayout);
getLayoutInflater().inflate(R.layout.layout_switch, null); getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayShowCustomEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(false);
getSupportActionBar().setCustomView(switchLayout); switchButton = (Switch) switchLayout.findViewById(R.id.switchButton);
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayShowCustomEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(false);
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);
switchButton = (Switch) switchLayout.findViewById(R.id.switchButton); final SharedPreferences settings =
PreferenceManager.getDefaultSharedPreferences(Shadowsocks.this);
TextView title = (TextView) switchLayout.findViewById(R.id.title); boolean init =
Typeface tf = Typefaces.get(this, "fonts/Iceland.ttf"); !settings.getBoolean("isRunning", false) && !settings.getBoolean("isConnecting", false);
if (tf != null) title.setTypeface(tf);
title.setText(R.string.app_name);
} if (init) {
if (mProgressDialog == null) {
mProgressDialog =
ProgressDialog.show(this, "", getString(R.string.initializing), true, true);
}
@Override new Thread() {
public boolean onCreateOptionsMenu(Menu menu) { @Override
menu.add(0, 0, 0, R.string.recovery) public void run() {
.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;
}
/** Utils.isRoot();
* Called when the activity is closed.
*/
@Override
public void onDestroy() {
SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
SharedPreferences.Editor editor = settings.edit(); String versionName;
editor.putBoolean("isConnected", ShadowsocksService.isServiceStarted()); try {
editor.commit(); versionName = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;
} catch (NameNotFoundException e) {
versionName = "NONE";
}
super.onDestroy(); if (!settings.getBoolean(versionName, false)) {
} Editor edit = settings.edit();
edit.putBoolean(versionName, true);
edit.commit();
reset();
}
@Override handler.sendEmptyMessage(MSG_INITIAL_FINISH);
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); }.start();
} }
}
@Override
public boolean onOptionsItemSelected(MenuItem item) { @Override
switch (item.getItemId()) { public boolean onCreateOptionsMenu(Menu menu) {
case 0: menu.add(0, 0, 0, R.string.recovery)
recovery(); .setIcon(android.R.drawable.ic_menu_revert)
break; .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
case 1: menu.add(0, 1, 1, R.string.about)
String versionName = ""; .setIcon(android.R.drawable.ic_menu_info_details)
try { .setShowAsAction(MenuItem.SHOW_AS_ACTION_WITH_TEXT);
versionName = getPackageManager().getPackageInfo( return true;
getPackageName(), 0).versionName; }
} catch (NameNotFoundException e) {
versionName = ""; /** Called when the activity is closed. */
} @Override
showAToast(getString(R.string.about) + " (" + versionName + ")\n\n" public void onDestroy() {
+ getString(R.string.copy_rights)); SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0);
break;
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(
return super.onOptionsItemSelected(item); R.string.copy_rights));
break;
} }
@Override return super.onOptionsItemSelected(item);
protected void onPause() { }
super.onPause();
PreferenceManager.getDefaultSharedPreferences(this)
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override @Override
protected void onResume() { protected void onPause() {
super.onResume(); super.onPause();
SharedPreferences settings = PreferenceManager PreferenceManager.getDefaultSharedPreferences(this)
.getDefaultSharedPreferences(this); .unregisterOnSharedPreferenceChangeListener(this);
}
Editor edit = settings.edit(); @Override
protected void onResume() {
super.onResume();
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
if (ShadowsocksService.isServiceStarted()) { Editor edit = settings.edit();
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);
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);
} }
private void setPreferenceEnabled() { edit.commit();
SharedPreferences settings = PreferenceManager
.getDefaultSharedPreferences(Shadowsocks.this);
boolean enabled = !settings.getBoolean("isRunning", false) setPreferenceEnabled();
&& !settings.getBoolean("isConnecting", false);
for (String name : PROXY_PREFS) { switchButton.setOnCheckedChangeListener(this);
Preference pref = findPreference(name); PreferenceManager.getDefaultSharedPreferences(this)
if (pref != null) { .registerOnSharedPreferenceChangeListener(this);
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 private void setPreferenceEnabled() {
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(Shadowsocks.this);
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")) { boolean enabled =
if (settings.getBoolean("isConnecting", false)) { !settings.getBoolean("isRunning", false) && !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")) { for (String name : PROXY_PREFS) {
setPreferenceEnabled(); Preference pref = findPreference(name);
} if (pref != null) {
pref.setEnabled(enabled);
}
} }
for (String name : FEATRUE_PREFS) {
@Override Preference pref = findPreference(name);
public void onStart() { if (pref != null) {
super.onStart(); if (name.equals("isBypassApps") || name.equals("proxyedApps")) {
EasyTracker.getInstance().activityStart(this); boolean isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
pref.setEnabled(enabled && !isGlobalProxy);
final SharedPreferences settings = PreferenceManager } else {
.getDefaultSharedPreferences(Shadowsocks.this); pref.setEnabled(enabled);
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 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);
}
} }
@Override if (key.equals("isConnecting")) {
public void onStop() { if (settings.getBoolean("isConnecting", false)) {
super.onStop(); Log.d(TAG, "Connecting start");
EasyTracker.getInstance().activityStop(this); if (mProgressDialog == null) {
mProgressDialog =
ProgressDialog.show(this, "", getString(R.string.connecting), true, true);
}
} else {
Log.d(TAG, "Connecting finish");
if (mProgressDialog != null) { if (mProgressDialog != null) {
mProgressDialog.dismiss(); mProgressDialog.dismiss();
mProgressDialog = null; mProgressDialog = null;
} }
}
} }
public void reset() { if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
setPreferenceEnabled();
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"
);
} }
}
@Override
public void onStart() {
super.onStart();
EasyTracker.getInstance().activityStart(this);
}
@Override
public void onStop() {
super.onStop();
EasyTracker.getInstance().activityStop(this);
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
}
private void recovery() { public void reset() {
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)); crash_recovery();
new Thread() { copyAssets("");
@Override copyAssets(Utils.getABI());
public void run() {
reset(); 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");
}
h.sendEmptyMessage(0); private void recovery() {
}
}.start();
if (mProgressDialog == null) {
mProgressDialog = ProgressDialog.show(this, "", getString(R.string.recovering), true, true);
} }
/** final Handler h = new Handler() {
* Called when connect button is clicked. @Override
*/ public void handleMessage(Message msg) {
public boolean serviceStart() { if (mProgressDialog != null) {
mProgressDialog.dismiss();
if (ShadowsocksService.isServiceStarted()) { mProgressDialog = null;
stopService(new Intent(this, ShadowsocksService.class));
return false;
} }
}
};
SharedPreferences settings = PreferenceManager stopService(new Intent(this, ShadowsocksService.class));
.getDefaultSharedPreferences(this);
final String proxy = settings.getString("proxy", ""); new Thread() {
if (isTextEmpty(proxy, getString(R.string.proxy_empty))) @Override
return false; public void run() {
String portText = settings.getString("port", ""); reset();
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); h.sendEmptyMessage(0);
startService(it); }
}.start();
}
return true; /** Called when connect button is clicked. */
} public boolean serviceStart() {
private void showAToast(String msg) { if (ShadowsocksService.isServiceStarted()) {
AlertDialog.Builder builder = new AlertDialog.Builder(this); stopService(new Intent(this, ShadowsocksService.class));
builder.setMessage(msg) return false;
.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 SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
implements OnSharedPreferenceChangeListener {
private void setPreferenceEnabled() { final String proxy = settings.getString("proxy", "");
SharedPreferences settings = PreferenceManager if (isTextEmpty(proxy, getString(R.string.proxy_empty))) return false;
.getDefaultSharedPreferences(getActivity());
boolean enabled = !settings.getBoolean("isRunning", false) String portText = settings.getString("port", "");
&& !settings.getBoolean("isConnecting", false); 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;
}
for (String name : PROXY_PREFS) { Intent it = new Intent(this, ShadowsocksService.class);
Preference pref = findPreference(name); startService(it);
if (pref != null) {
pref.setEnabled(enabled); 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 {
@Override private void setPreferenceEnabled() {
public void onResume() { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
super.onResume();
setPreferenceEnabled();
// Set up a listener whenever a key changes boolean enabled =
getPreferenceScreen().getSharedPreferences() !settings.getBoolean("isRunning", false) && !settings.getBoolean("isConnecting", false);
.registerOnSharedPreferenceChangeListener(this);
for (String name : PROXY_PREFS) {
Preference pref = findPreference(name);
if (pref != null) {
pref.setEnabled(enabled);
} }
}
}
@Override @Override
public void onPause() { public void onResume() {
super.onPause(); super.onResume();
// Unregister the listener whenever a key changes setPreferenceEnabled();
getPreferenceScreen().getSharedPreferences()
.unregisterOnSharedPreferenceChangeListener(this);
}
@Override // Set up a listener whenever a key changes
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
setPreferenceEnabled();
}
}
} }
public static class FeatureFragment extends UnifiedPreferenceFragment @Override
implements OnSharedPreferenceChangeListener { public void onPause() {
super.onPause();
private void setPreferenceEnabled() { // Unregister the listener whenever a key changes
SharedPreferences settings = PreferenceManager getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(this);
.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 @Override
public void onResume() { public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
super.onResume(); if (key.equals("isRunning") || key.equals("isGlobalProxy")) {
setPreferenceEnabled(); setPreferenceEnabled();
}
}
}
// Set up a listener whenever a key changes public static class FeatureFragment extends UnifiedPreferenceFragment
getPreferenceScreen().getSharedPreferences() implements OnSharedPreferenceChangeListener {
.registerOnSharedPreferenceChangeListener(this);
}
@Override private void setPreferenceEnabled() {
public void onPause() { SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(getActivity());
super.onPause();
// Unregister the listener whenever a key changes boolean enabled =
getPreferenceScreen().getSharedPreferences() !settings.getBoolean("isRunning", false) && !settings.getBoolean("isConnecting", false);
.unregisterOnSharedPreferenceChangeListener(this);
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 @Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { public void onResume() {
if (key.equals("isRunning") || key.equals("isGlobalProxy")) { super.onResume();
setPreferenceEnabled(); setPreferenceEnabled();
}
} // Set up a listener whenever a key changes
getPreferenceScreen().getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
} }
public static class Typefaces { @Override
private static final String TAG = "Typefaces"; public void onPause() {
super.onPause();
private static final Hashtable<String, Typeface> cache = new Hashtable<String, Typeface>(); // Unregister the listener whenever a key changes
getPreferenceScreen().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(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 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<String, Typeface> cache = new Hashtable<String, Typeface>();
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);
}
}
}
} }
...@@ -43,16 +43,15 @@ import com.google.analytics.tracking.android.EasyTracker; ...@@ -43,16 +43,15 @@ import com.google.analytics.tracking.android.EasyTracker;
public class ShadowsocksApplication extends Application { public class ShadowsocksApplication extends Application {
private static String sTmpDir; private static String sTmpDir;
public static String getTmpDir() { public static String getTmpDir() {
return sTmpDir; return sTmpDir;
} }
@Override
public void onCreate() {
EasyTracker.getInstance().setContext(this);
sTmpDir = getCacheDir().getAbsolutePath();
}
@Override
public void onCreate() {
EasyTracker.getInstance().setContext(this);
sTmpDir = getCacheDir().getAbsolutePath();
}
} }
...@@ -47,47 +47,47 @@ import android.preference.PreferenceManager; ...@@ -47,47 +47,47 @@ import android.preference.PreferenceManager;
public class ShadowsocksReceiver extends BroadcastReceiver { public class ShadowsocksReceiver extends BroadcastReceiver {
private static final String TAG = "Shadowsocks"; private static final String TAG = "Shadowsocks";
@Override @Override
public void onReceive(Context context, Intent intent) { public void onReceive(Context context, Intent intent) {
if (intent != null) { if (intent != null) {
final String action = intent.getAction(); final String action = intent.getAction();
if (action.equals(Intent.ACTION_SHUTDOWN)) { if (action.equals(Intent.ACTION_SHUTDOWN)) {
context.stopService(new Intent(context, ShadowsocksService.class)); context.stopService(new Intent(context, ShadowsocksService.class));
return; 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); SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
final boolean isInstalled = settings.getBoolean(versionName, false); String versionName;
try {
versionName =
context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
versionName = "NONE";
}
if (isAutoConnect && isInstalled) { final boolean isAutoConnect = settings.getBoolean("isAutoConnect", false);
final String portText = settings.getString("port", ""); final boolean isInstalled = settings.getBoolean(versionName, false);
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); if (isAutoConnect && isInstalled) {
context.startService(it); 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);
}
}
} }
...@@ -55,8 +55,6 @@ import android.os.PowerManager; ...@@ -55,8 +55,6 @@ import android.os.PowerManager;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.util.Log; import android.util.Log;
import com.google.analytics.tracking.android.EasyTracker; import com.google.analytics.tracking.android.EasyTracker;
import org.apache.http.conn.util.InetAddressUtils;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
import java.io.FileReader; import java.io.FileReader;
...@@ -68,601 +66,583 @@ import java.net.Inet6Address; ...@@ -68,601 +66,583 @@ import java.net.Inet6Address;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.HashSet; import java.util.HashSet;
import org.apache.http.conn.util.InetAddressUtils;
public class ShadowsocksService extends Service { public class ShadowsocksService extends Service {
public static final String BASE = "/data/data/com.github.shadowsocks/"; public static final String BASE = "/data/data/com.github.shadowsocks/";
final static String REDSOCKS_CONF = "base {" + final static String REDSOCKS_CONF = "base {" +
" log_debug = off;" + " log_debug = off;" +
" log_info = off;" + " log_info = off;" +
" log = stderr;" + " log = stderr;" +
" daemon = on;" + " daemon = on;" +
" redirector = iptables;" + " redirector = iptables;" +
"}" + "}" +
"redsocks {" + "redsocks {" +
" local_ip = 127.0.0.1;" + " local_ip = 127.0.0.1;" +
" local_port = 8123;" + " local_port = 8123;" +
" ip = 127.0.0.1;" + " ip = 127.0.0.1;" +
" port = %d;" + " port = %d;" +
" type = socks5;" + " type = socks5;" +
"}"; "}";
final static String SHADOWSOCKS_CONF = final static String SHADOWSOCKS_CONF =
"{\"server\": [%s], \"server_port\": %d, \"local_port\": %d, \"password\": %s, \"timeout\": %d}"; "{\"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_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 " final static String CMD_IPTABLES_REDIRECT_ADD_SOCKS =
+ "-j REDIRECT --to 8123\n"; " -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 " final static String CMD_IPTABLES_DNAT_ADD_SOCKS =
+ "-j DNAT --to-destination 127.0.0.1:8123\n"; " -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_START = 0;
private static final int MSG_CONNECT_FINISH = 1; private static final int MSG_CONNECT_FINISH = 1;
private static final int MSG_CONNECT_SUCCESS = 2; private static final int MSG_CONNECT_SUCCESS = 2;
private static final int MSG_CONNECT_FAIL = 3; private static final int MSG_CONNECT_FAIL = 3;
private static final int MSG_HOST_CHANGE = 4; private static final int MSG_HOST_CHANGE = 4;
private static final int MSG_STOP_SELF = 5; private static final int MSG_STOP_SELF = 5;
private static final String TAG = "ShadowsocksService"; private static final String TAG = "ShadowsocksService";
private final static int DNS_PORT = 8153; private final static int DNS_PORT = 8153;
private static final Class<?>[] mStartForegroundSignature = new Class[]{ private static final Class<?>[] mStartForegroundSignature = new Class[] {
int.class, Notification.class}; int.class, Notification.class
private static final Class<?>[] mStopForegroundSignature = new Class[]{boolean.class}; };
private static final Class<?>[] mSetForegroundSignature = new Class[]{boolean.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 * This is a hack see
* .com/msg18298.html we are not really able to decide if the service was * http://www.mail-archive.com/android-developers@googlegroups
* started. So we remember a week reference to it. We set it if we are * .com/msg18298.html we are not really able to decide if the service was
* running and clear it if we are stopped. If anything goes wrong, the * started. So we remember a week reference to it. We set it if we are
* reference will hopefully vanish * running and clear it if we are stopped. If anything goes wrong, the
*/ * reference will hopefully vanish
private static WeakReference<ShadowsocksService> sRunningInstance = null; */
final Handler handler = new Handler() { private static WeakReference<ShadowsocksService> sRunningInstance = null;
@Override final Handler handler = new Handler() {
public void handleMessage(Message msg) { @Override
Editor ed = settings.edit(); public void handleMessage(Message msg) {
switch (msg.what) { Editor ed = settings.edit();
case MSG_CONNECT_START: switch (msg.what) {
ed.putBoolean("isConnecting", true); case MSG_CONNECT_START:
ed.putBoolean("isConnecting", true);
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
mWakeLock = pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
| PowerManager.ON_AFTER_RELEASE, "GAEProxy"); mWakeLock =
pm.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE,
mWakeLock.acquire(); "GAEProxy");
break; mWakeLock.acquire();
case MSG_CONNECT_FINISH:
ed.putBoolean("isConnecting", false); break;
case MSG_CONNECT_FINISH:
if (mWakeLock != null && mWakeLock.isHeld()) ed.putBoolean("isConnecting", false);
mWakeLock.release();
if (mWakeLock != null && mWakeLock.isHeld()) mWakeLock.release();
break;
case MSG_CONNECT_SUCCESS: break;
ed.putBoolean("isRunning", true); case MSG_CONNECT_SUCCESS:
break; ed.putBoolean("isRunning", true);
case MSG_CONNECT_FAIL: break;
ed.putBoolean("isRunning", false); case MSG_CONNECT_FAIL:
break; ed.putBoolean("isRunning", false);
case MSG_HOST_CHANGE: break;
ed.putString("appHost", appHost); case MSG_HOST_CHANGE:
break; ed.putString("appHost", appHost);
case MSG_STOP_SELF: break;
stopSelf(); case MSG_STOP_SELF:
break; stopSelf();
} break;
ed.commit(); }
super.handleMessage(msg); 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) { private Notification notification;
try { private NotificationManager notificationManager;
BufferedReader reader = new BufferedReader(new FileReader(BASE + name + ".pid")); private PendingIntent pendIntent;
final String line = reader.readLine(); private PowerManager.WakeLock mWakeLock;
return Integer.valueOf(line); private String appHost;
} catch (FileNotFoundException e) { private int remotePort;
Log.e(TAG, "Cannot open pid file: " + name); private int localPort;
} catch (IOException e) { private String sitekey;
Log.e(TAG, "Cannot read pid file: " + name); private SharedPreferences settings = null;
} catch (NumberFormatException e) { private boolean hasRedirectSupport = true;
Log.e(TAG, "Invalid pid", e); private boolean isGlobalProxy = false;
} private boolean isGFWList = false;
return -1; 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 void startShadowsocksDaemon() { }
new Thread() {
@Override private int getPid(String name) {
public void run() { try {
final String cmd = String.format(BASE + BufferedReader reader = new BufferedReader(new FileReader(BASE + name + ".pid"));
"shadowsocks -s \"%s\" -p \"%d\" -l \"%d\" -k \"%s\" -m \"%s\" -f " final String line = reader.readLine();
+ BASE + "shadowsocks.pid", return Integer.valueOf(line);
appHost, remotePort, localPort, sitekey, encMethod); } catch (FileNotFoundException e) {
System.exec(cmd); Log.e(TAG, "Cannot open pid file: " + name);
} } catch (IOException e) {
}.start(); Log.e(TAG, "Cannot read pid file: " + name);
} catch (NumberFormatException e) {
Log.e(TAG, "Invalid pid", e);
} }
return -1;
private void startPolipoDaemon() { }
new Thread() {
@Override private void startShadowsocksDaemon() {
public void run() { new Thread() {
final String cmd = String.format(BASE + @Override
"polipo proxyPort=%d socksParentProxy=127.0.0.1:%d daemonise=true pidFile=\"%s\" logLevel=1 logFile=\"%s\"", public void run() {
localPort + 1, localPort, BASE + "polipo.pid", BASE + "polipo.log"); final String cmd = String.format(BASE
System.exec(cmd); +
} "shadowsocks -s \"%s\" -p \"%d\" -l \"%d\" -k \"%s\" -m \"%s\" -f "
}.start(); + 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;
}
private void startDnsDaemon() { public void handleCommand(Intent intent) {
final String cmd = BASE + "pdnsd -c " + BASE + "pdnsd.conf";
Utils.runCommand(cmd);
}
private String getVersionName() { if (intent == null) {
String version; stopSelf();
try { return;
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) { appHost = settings.getString("proxy", "127.0.0.1");
sitekey = settings.getString("sitekey", "default");
if (intent == null) { encMethod = settings.getString("encMethod", "table");
stopSelf(); try {
return; remotePort = Integer.valueOf(settings.getString("remotePort", "1984"));
} } catch (NumberFormatException ex) {
remotePort = 1984;
appHost = settings.getString("proxy", "127.0.0.1"); }
sitekey = settings.getString("sitekey", "default"); try {
encMethod = settings.getString("encMethod", "table"); localPort = Integer.valueOf(settings.getString("port", "1984"));
try { } catch (NumberFormatException ex) {
remotePort = Integer.valueOf(settings.getString("remotePort", "1984")); localPort = 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) { isGlobalProxy = settings.getBoolean("isGlobalProxy", false);
localPort--; isGFWList = settings.getBoolean("isGFWList", false);
} isBypassApps = settings.getBoolean("isBypassApps", false);
isDNSProxy = settings.getBoolean("isDNSProxy", false);
isHTTPProxy = settings.getBoolean("isHTTPProxy", false);
new Thread(new Runnable() { if (isHTTPProxy) {
@Override localPort--;
public void run() { }
handler.sendEmptyMessage(MSG_CONNECT_START); new Thread(new Runnable() {
@Override
public void run() {
boolean resolved = false; handler.sendEmptyMessage(MSG_CONNECT_START);
if (appHost != null) { boolean resolved = false;
boolean isIPv6Support = Utils.isIPv6Support(); if (appHost != null) {
InetAddress addr = null;
if (isIPv6Support) { boolean isIPv6Support = Utils.isIPv6Support();
try { InetAddress addr = null;
addr = Inet6Address.getByName(appHost);
} catch (UnknownHostException ignored) {
addr = null;
}
}
if (addr == null) { if (isIPv6Support) {
try { try {
addr = InetAddress.getByName(appHost); addr = Inet6Address.getByName(appHost);
} catch (UnknownHostException ignored) { } catch (UnknownHostException ignored) {
addr = null; addr = null;
} }
} }
if (addr != null) { if (addr == null) {
appHost = addr.getHostAddress(); try {
resolved = true; addr = InetAddress.getByName(appHost);
} } catch (UnknownHostException ignored) {
} addr = null;
}
}
Log.d(TAG, "IPTABLES: " + Utils.getIptables()); if (addr != null) {
appHost = addr.getHostAddress();
resolved = true;
}
}
// Test for Redirect Support Log.d(TAG, "IPTABLES: " + Utils.getIptables());
hasRedirectSupport = Utils.getHasRedirectSupport();
if (resolved && handleConnection()) { // Test for Redirect Support
// Connection and forward successful hasRedirectSupport = Utils.getHasRedirectSupport();
notifyAlert(getString(R.string.forward_success),
getString(R.string.service_running));
handler.sendEmptyMessageDelayed(MSG_CONNECT_SUCCESS, 500); 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));
} else { stopSelf();
// Connection or forward unsuccessful handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 500);
notifyAlert(getString(R.string.forward_fail), }
getString(R.string.service_failed));
stopSelf(); handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 500);
handler.sendEmptyMessageDelayed(MSG_CONNECT_FAIL, 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();
}
handler.sendEmptyMessageDelayed(MSG_CONNECT_FINISH, 500); /** Called when the activity is first created. */
public boolean handleConnection() {
} if (isHTTPProxy) {
}).start(); startPolipoDaemon();
markServiceStarted();
} }
startShadowsocksDaemon();
private void startRedsocksDaemon() { startDnsDaemon();
String conf = String.format(REDSOCKS_CONF, localPort); startRedsocksDaemon();
String cmd = String.format("%sredsocks -p %sredsocks.pid -c %sredsocks.conf", setupIptables();
BASE, BASE, BASE);
Utils.runRootCommand("echo \"" + conf + "\" > " + BASE + "redsocks.conf\n" return true;
+ cmd); }
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<ShadowsocksService>(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;
} }
private boolean waitForProcess(final String name) { try {
final int pid = getPid(name); mSetForeground = getClass().getMethod("setForeground", mSetForegroundSignature);
if (pid == -1) return false; } catch (NoSuchMethodException e) {
throw new IllegalStateException(
Exec.hangupProcessGroup(-pid); "OS doesn't have Service.startForeground OR Service.setForeground!");
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 closed. */
* Called when the activity is first created. @Override
*/ public void onDestroy() {
public boolean handleConnection() {
if (isHTTPProxy) { EasyTracker.getTracker().trackEvent("service", "stop", getVersionName(), 0L);
startPolipoDaemon();
}
startShadowsocksDaemon();
startDnsDaemon();
startRedsocksDaemon();
setupIptables();
return true; stopForegroundCompat(1);
}
private void initSoundVibrateLights(Notification notification) { notifyAlert(getString(R.string.forward_stop), getString(R.string.service_stopped),
notification.sound = null; Notification.FLAG_AUTO_CANCEL);
notification.defaults |= Notification.DEFAULT_LIGHTS;
}
void invokeMethod(Method method, Object[] args) { new Thread() {
try { @Override
method.invoke(this, mStartForegroundArgs); public void run() {
} catch (InvocationTargetException e) { // Make sure the connection is closed, important here
// Should not happen. onDisconnect();
Log.w(TAG, "Unable to invoke method", e); }
} catch (IllegalAccessException e) { }.start();
// Should not happen.
Log.w(TAG, "Unable to invoke method", e);
}
}
private void markServiceStarted() { Editor ed = settings.edit();
sRunningInstance = new WeakReference<ShadowsocksService>(this); ed.putBoolean("isRunning", false);
} ed.putBoolean("isConnecting", false);
ed.commit();
private void markServiceStopped() { try {
sRunningInstance = null; notificationManager.cancel(0);
} catch (Exception ignore) {
// Nothing
} }
private void notifyAlert(String title, String info) { super.onDestroy();
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) { markServiceStopped();
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 private void onDisconnect() {
public IBinder onBind(Intent intent) { Utils.runRootCommand(Utils.getIptables() + " -t nat -F OUTPUT");
return null; 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")) {
@Override sb.append("kill -9 `cat /data/data/com.github.shadowsocks/shadowsocks.pid`").append("\n");
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!");
}
} }
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
* Called when the activity is closed. // platform. On 2.0 or later we override onStartCommand() so this
*/ // method will not be called.
@Override @Override
public void onDestroy() { public void onStart(Intent intent, int startId) {
EasyTracker.getTracker().trackEvent("service", "stop", handleCommand(intent);
getVersionName(), 0L); }
stopForegroundCompat(1); @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;
}
notifyAlert(getString(R.string.forward_stop), private boolean setupIptables() {
getString(R.string.service_stopped),
Notification.FLAG_AUTO_CANCEL);
new Thread() { StringBuilder init_sb = new StringBuilder();
@Override
public void run() {
// Make sure the connection is closed, important here
onDisconnect();
}
}.start();
Editor ed = settings.edit(); StringBuilder http_sb = new StringBuilder();
ed.putBoolean("isRunning", false);
ed.putBoolean("isConnecting", false);
ed.commit();
try { init_sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT\n");
notificationManager.cancel(0);
} catch (Exception ignore) {
// Nothing
}
super.onDestroy(); String cmd_bypass = Utils.getIptables() + CMD_IPTABLES_RETURN;
markServiceStopped(); 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"));
private void onDisconnect() { if (!isDNSProxy) {
Utils.runRootCommand(Utils.getIptables() + " -t nat -F OUTPUT"); init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "--dport " + 53));
StringBuilder sb = new StringBuilder(); init_sb.append(
sb.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`").append("\n"); cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + getApplicationInfo().uid));
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 if (hasRedirectSupport) {
// platform. On 2.0 or later we override onStartCommand() so this init_sb.append(Utils.getIptables())
// method will not be called. .append(" -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to ")
@Override .append(DNS_PORT)
public void onStart(Intent intent, int startId) { .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");
}
handleCommand(intent); 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) {
@Override http_sb.append(hasRedirectSupport ? Utils.getIptables() + CMD_IPTABLES_REDIRECT_ADD_SOCKS
public int onStartCommand(Intent intent, int flags, int startId) { : Utils.getIptables() + CMD_IPTABLES_DNAT_ADD_SOCKS);
handleCommand(intent);
// We want this service to continue running until it is explicitly
// stopped, so return sticky.
return START_STICKY;
} }
if (!isGlobalProxy) {
private boolean setupIptables() { // for proxy specified apps
if (apps == null || apps.length <= 0) apps = AppManager.getProxyedApps(this);
StringBuilder init_sb = new StringBuilder();
HashSet<Integer> uidSet = new HashSet<Integer>();
StringBuilder http_sb = new StringBuilder(); for (ProxyedApp app : apps) {
if (app.isProxyed()) {
init_sb.append(Utils.getIptables()).append(" -t nat -F OUTPUT\n"); uidSet.add(app.getUid());
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) { for (int uid : uidSet) {
init_sb.append(Utils.getIptables()).append(" -t nat -A OUTPUT -p udp --dport 53 -j REDIRECT --to ").append(DNS_PORT).append("\n"); 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 { } 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"); init_sb.append(cmd_bypass.replace("-d 0.0.0.0", "-m owner --uid-owner " + uid));
}
if (isGFWList) {
String[] chn_list = getResources().getStringArray(R.array.chn_list);
for (String item : chn_list) {
init_sb.append(cmd_bypass.replace("0.0.0.0", item));
}
}
if (isGlobalProxy || isBypassApps) {
http_sb.append(hasRedirectSupport ? Utils.getIptables()
+ CMD_IPTABLES_REDIRECT_ADD_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<Integer> uidSet = new HashSet<Integer>();
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(); String init_rules = init_sb.toString();
Utils.runRootCommand(init_rules, 30 * 1000); Utils.runRootCommand(init_rules, 30 * 1000);
String redt_rules = http_sb.toString(); String redt_rules = http_sb.toString();
Utils.runRootCommand(redt_rules); Utils.runRootCommand(redt_rules);
return true; 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.
* This is a wrapper around the new startForeground method, using the older mSetForegroundArgs[0] = Boolean.TRUE;
* APIs if it is not available. invokeMethod(mSetForeground, mSetForegroundArgs);
*/ notificationManager.notify(id, notification);
void startForegroundCompat(int id, Notification notification) { }
// If we have the new startForeground API, then use it.
if (mStartForeground != null) { /**
mStartForegroundArgs[0] = id; * This is a wrapper around the new stopForeground method, using the older
mStartForegroundArgs[1] = notification; * APIs if it is not available.
invokeMethod(mStartForeground, mStartForegroundArgs); */
return; void stopForegroundCompat(int id) {
} // If we have the new stopForeground API, then use it.
if (mStopForeground != null) {
// Fall back on the old API. mStopForegroundArgs[0] = Boolean.TRUE;
mSetForegroundArgs[0] = Boolean.TRUE; try {
invokeMethod(mSetForeground, mSetForegroundArgs); mStopForeground.invoke(this, mStopForegroundArgs);
notificationManager.notify(id, notification); } catch (InvocationTargetException e) {
} // Should not happen.
Log.w(TAG, "Unable to invoke stopForeground", e);
/** } catch (IllegalAccessException e) {
* This is a wrapper around the new stopForeground method, using the older // Should not happen.
* APIs if it is not available. Log.w(TAG, "Unable to invoke stopForeground", e);
*/ }
void stopForegroundCompat(int id) { return;
// 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);
} }
// 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);
}
} }
...@@ -39,9 +39,9 @@ ...@@ -39,9 +39,9 @@
package com.github.shadowsocks; package com.github.shadowsocks;
public class System { public class System {
static { static {
java.lang.System.loadLibrary("system"); java.lang.System.loadLibrary("system");
} }
public static native void exec(String cmd); public static native void exec(String cmd);
} }
...@@ -45,471 +45,451 @@ import android.content.pm.PackageManager.NameNotFoundException; ...@@ -45,471 +45,451 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable; import android.graphics.drawable.Drawable;
import android.os.Environment; import android.os.Environment;
import android.util.Log; import android.util.Log;
import org.apache.http.conn.util.InetAddressUtils; import java.io.BufferedReader;
import java.io.File;
import java.io.*; 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.InetAddress;
import java.net.NetworkInterface; import java.net.NetworkInterface;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import org.apache.http.conn.util.InetAddressUtils;
public class Utils { public class Utils {
public final static String TAG = "Shadowsocks"; public final static String TAG = "Shadowsocks";
public final static String ABI_PROP = "ro.product.cpu.abi"; public final static String ABI_PROP = "ro.product.cpu.abi";
public final static String ABI2_PROP = "ro.product.cpu.abi2"; public final static String ABI2_PROP = "ro.product.cpu.abi2";
public final static String ARM_ABI = "arm"; public final static String ARM_ABI = "arm";
public final static String X86_ABI = "x86"; public final static String X86_ABI = "x86";
public final static String MIPS_ABI = "mips"; public final static String MIPS_ABI = "mips";
public final static String DEFAULT_SHELL = "/system/bin/sh"; public final static String DEFAULT_SHELL = "/system/bin/sh";
public final static String DEFAULT_ROOT = "/system/bin/su"; public final static String DEFAULT_ROOT = "/system/bin/su";
public final static String ALTERNATIVE_ROOT = "/system/xbin/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 DEFAULT_IPTABLES = "/data/data/com.github.shadowsocks/iptables";
public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables"; public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables";
public final static int TIME_OUT = -99; public final static int TIME_OUT = -99;
private static boolean initialized = false; private static boolean initialized = false;
private static int hasRedirectSupport = -1; private static int hasRedirectSupport = -1;
private static int isRoot = -1; private static int isRoot = -1;
private static String shell = null; private static String shell = null;
private static String root_shell = null; private static String root_shell = null;
private static String iptables = null; private static String iptables = null;
private static String data_path = null; private static String data_path = null;
/** /** Get IP address from first non-localhost interface */
* Get IP address from first non-localhost interface public static boolean isIPv6Support() {
*/ try {
public static boolean isIPv6Support() { List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
try { for (NetworkInterface intf : interfaces) {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces()); List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (NetworkInterface intf : interfaces) { for (InetAddress addr : addrs) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses()); if (!addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) {
for (InetAddress addr : addrs) { String sAddr = addr.getHostAddress().toUpperCase();
if (!addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) { boolean isIPv6 = InetAddressUtils.isIPv6Address(sAddr);
String sAddr = addr.getHostAddress().toUpperCase(); if (isIPv6) {
boolean isIPv6 = InetAddressUtils.isIPv6Address(sAddr); Log.d(TAG, "IPv6 address detected");
if (isIPv6) { return true;
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;
} }
}
} }
}
} 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; 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 * Returns a SystemProperty
* @return The Property, or NULL if not found *
*/ * @param propName The Property to retrieve
private static String getSystemProperty(String propName) { * @return The Property, or NULL if not found
String line; */
BufferedReader input = null; 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 { try {
Process p = Runtime.getRuntime().exec("getprop " + propName); input.close();
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024); } catch (IOException e) {
line = input.readLine(); Log.e(TAG, "Exception while closing InputStream", e);
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; }
} }
return line;
}
private static void checkIptables() { private static void checkIptables() {
if (!isRoot()) {
iptables = DEFAULT_IPTABLES;
return;
}
// Check iptables binary if (!isRoot()) {
iptables = DEFAULT_IPTABLES; iptables = DEFAULT_IPTABLES;
return;
}
String lines = null; // Check iptables binary
iptables = DEFAULT_IPTABLES;
boolean compatible = false; String lines = null;
boolean version = false;
StringBuilder sb = new StringBuilder(); boolean compatible = false;
String command = iptables + " --version\n" + iptables boolean version = false;
+ " -L -t nat -n\n" + "exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true); StringBuilder sb = new StringBuilder();
String command = iptables + " --version\n" + iptables + " -L -t nat -n\n" + "exit\n";
if (exitcode == TIME_OUT) int exitcode = runScript(command, sb, 10 * 1000, true);
return;
lines = sb.toString(); if (exitcode == TIME_OUT) return;
if (lines.contains("OUTPUT")) { lines = sb.toString();
compatible = true;
}
if (lines.contains("v1.4.")) {
version = true;
}
if (!compatible || !version) { if (lines.contains("OUTPUT")) {
iptables = ALTERNATIVE_IPTABLES; compatible = true;
if (!new File(iptables).exists()) }
iptables = "iptables"; 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) { public static Drawable getAppIcon(Context c, int uid) {
PackageManager pm = c.getPackageManager(); PackageManager pm = c.getPackageManager();
Drawable appIcon = c.getResources().getDrawable( Drawable appIcon = c.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
android.R.drawable.sym_def_app_icon); String[] packages = pm.getPackagesForUid(uid);
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; 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);
} }
public static String getDataPath(Context ctx) { return appIcon;
}
if (data_path == null) { public static String getDataPath(Context ctx) {
if (Environment.MEDIA_MOUNTED.equals(Environment if (data_path == null) {
.getExternalStorageState())) {
data_path = Environment.getExternalStorageDirectory()
.getAbsolutePath();
} else {
data_path = ctx.getFilesDir().getAbsolutePath();
}
Log.d(TAG, "Python Data Path: " + data_path); if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
} data_path = Environment.getExternalStorageDirectory().getAbsolutePath();
} else {
data_path = ctx.getFilesDir().getAbsolutePath();
}
return data_path; Log.d(TAG, "Python Data Path: " + data_path);
} }
public static boolean getHasRedirectSupport() { return data_path;
if (hasRedirectSupport == -1) }
initHasRedirectSupported();
return hasRedirectSupport == 1;
}
public static String getIptables() { public static boolean getHasRedirectSupport() {
if (iptables == null) if (hasRedirectSupport == -1) initHasRedirectSupported();
checkIptables(); return hasRedirectSupport == 1;
return iptables; }
}
private static String getShell() { public static String getIptables() {
if (shell == null) { if (iptables == null) checkIptables();
shell = DEFAULT_SHELL; return iptables;
if (!new File(shell).exists()) }
shell = "sh";
} private static String getShell() {
return shell; if (shell == null) {
shell = DEFAULT_SHELL;
if (!new File(shell).exists()) shell = "sh";
} }
return shell;
}
public static void initHasRedirectSupported() { public static void initHasRedirectSupported() {
if (!Utils.isRoot()) if (!Utils.isRoot()) return;
return;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String command = Utils.getIptables() String command =
+ " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154"; Utils.getIptables() + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154";
int exitcode = runScript(command, sb, 10 * 1000, true); int exitcode = runScript(command, sb, 10 * 1000, true);
String lines = sb.toString(); String lines = sb.toString();
hasRedirectSupport = 1; hasRedirectSupport = 1;
// flush the check command // flush the check command
Utils.runRootCommand(command.replace("-A", "-D")); Utils.runRootCommand(command.replace("-A", "-D"));
if (exitcode == TIME_OUT) if (exitcode == TIME_OUT) return;
return;
if (lines.contains("No chain/target/match")) { if (lines.contains("No chain/target/match")) {
hasRedirectSupport = 0; hasRedirectSupport = 0;
}
} }
}
public static boolean isInitialized() {
if (initialized) public static boolean isInitialized() {
return true; if (initialized) {
else { return true;
initialized = true; } else {
return false; initialized = true;
} return false;
} }
}
public static boolean isRoot() { public static boolean isRoot() {
if (isRoot != -1)
return isRoot == 1;
// switch between binaries if (isRoot != -1) return isRoot == 1;
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; // 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";
}
StringBuilder sb = new StringBuilder(); String lines = null;
String command = "ls /\n exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true); StringBuilder sb = new StringBuilder();
String command = "ls /\n exit\n";
if (exitcode == TIME_OUT) { int exitcode = runScript(command, sb, 10 * 1000, true);
return false;
}
lines = sb.toString(); if (exitcode == TIME_OUT) {
return false;
}
if (lines.contains("system")) { lines = sb.toString();
isRoot = 1;
}
return isRoot == 1; if (lines.contains("system")) {
isRoot = 1;
} }
public static boolean runCommand(String command) { return isRoot == 1;
}
return runCommand(command, 10 * 1000); public static boolean runCommand(String command) {
} return runCommand(command, 10 * 1000);
}
public static boolean runCommand(String command, int timeout) { public static boolean runCommand(String command, int timeout) {
Log.d(TAG, command); Log.d(TAG, command);
runScript(command, null, timeout, false); runScript(command, null, timeout, false);
return true; return true;
} }
public static boolean runRootCommand(String command) { public static boolean runRootCommand(String command) {
return runRootCommand(command, 10 * 1000); return runRootCommand(command, 10 * 1000);
}
} public static boolean runRootCommand(String command, int timeout) {
public static boolean runRootCommand(String command, int timeout) { if (!isRoot()) {
Log.e(TAG, "Cannot get ROOT permission: " + root_shell);
return false;
}
if (!isRoot()) { Log.d(TAG, command);
Log.e(TAG, "Cannot get ROOT permission: " + root_shell);
return false; 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;
}
}
Log.d(TAG, command); /**
* 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;
}
runScript(command, null, timeout, true); private FileDescriptor createSubprocess(int[] processId, String cmd) {
ArrayList<String> argList = parse(cmd);
String arg0 = argList.get(0);
String[] args = argList.toArray(new String[1]);
return true; return Exec.createSubprocess(result != null ? 1 : 0, arg0, args, null, scripts + "\nexit\n",
processId);
} }
private synchronized static int runScript(String script, StringBuilder result, private ArrayList<String> parse(String cmd) {
long timeout, boolean asroot) { final int PLAIN = 0;
final ScriptRunner runner = new ScriptRunner(script, result, asroot); final int WHITESPACE = 1;
runner.start(); final int INQUOTE = 2;
try { int state = WHITESPACE;
if (timeout > 0) { ArrayList<String> result = new ArrayList<String>();
runner.join(timeout); int cmdLen = cmd.length();
} else { StringBuilder builder = new StringBuilder();
runner.join(); 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));
} }
if (runner.isAlive()) { } else if (c == '"') {
// Timed-out state = PLAIN;
runner.destroy(); } else {
runner.join(1000); builder.append(c);
return TIME_OUT; }
}
} catch (InterruptedException ex) {
return TIME_OUT;
} }
return runner.exitcode; }
if (builder.length() > 0) {
result.add(builder.toString());
}
return result;
} }
/** @Override
* Internal thread used to execute scripts (as root or not). public void run() {
*/ pid[0] = -1;
private static final class ScriptRunner extends Thread {
private final String scripts; try {
private final StringBuilder result;
private final boolean asroot; if (this.asroot) {
public int exitcode = -1; // Create the "su" request to run the script
pipe = createSubprocess(pid, root_shell);
private int[] pid = new int[1]; } else {
private FileDescriptor pipe; // Create the "sh" request to run the script
pipe = createSubprocess(pid, getShell());
@Override
public void destroy() {
if (pid[0] != -1) {
Exec.hangupProcessGroup(pid[0]);
pid[0] = -1;
}
if (pipe != null) {
Exec.close(pipe);
pipe = null;
}
} }
/** if (pid[0] != -1) {
* Creates a new script runner. exitcode = Exec.waitFor(pid[0]);
*
* @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) { if (result == null || pipe == null) return;
ArrayList<String> 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, final InputStream stdout = new FileInputStream(pipe);
scripts + "\nexit\n", processId); final byte buf[] = new byte[8192];
} int read = 0;
private ArrayList<String> parse(String cmd) { // Read stdout
final int PLAIN = 0; while (stdout.available() > 0) {
final int WHITESPACE = 1; read = stdout.read(buf);
final int INQUOTE = 2; result.append(new String(buf, 0, read));
int state = WHITESPACE;
ArrayList<String> result = new ArrayList<String>();
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;
} }
} catch (Exception ex) {
@Override Log.e(TAG, "Cannot execute command", ex);
public void run() { if (result != null) result.append("\n").append(ex);
pid[0] = -1; } finally {
if (pipe != null) {
try { Exec.close(pipe);
}
if (this.asroot) { if (pid[0] != -1) {
// Create the "su" request to run the script Exec.hangupProcessGroup(pid[0]);
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]);
}
}
} }
}
} }
}
} }
...@@ -3,36 +3,31 @@ package com.github.shadowsocks.preferences; ...@@ -3,36 +3,31 @@ package com.github.shadowsocks.preferences;
import android.content.Context; import android.content.Context;
import android.preference.EditTextPreference; import android.preference.EditTextPreference;
import android.util.AttributeSet; import android.util.AttributeSet;
import com.github.shadowsocks.R;
public class SummaryEditTextPreference extends EditTextPreference { public class SummaryEditTextPreference extends EditTextPreference {
private CharSequence mDefaultSummary; private CharSequence mDefaultSummary;
public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) { public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle); super(context, attrs, defStyle);
mDefaultSummary = getSummary(); mDefaultSummary = getSummary();
} }
public SummaryEditTextPreference(Context context, AttributeSet attrs) { public SummaryEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
mDefaultSummary = getSummary(); mDefaultSummary = getSummary();
} }
public SummaryEditTextPreference(Context context) { public SummaryEditTextPreference(Context context) {
super(context); super(context);
} }
@Override @Override
public void onDialogClosed(boolean positiveResult) { public void setSummary(CharSequence summary) {
super.onDialogClosed(positiveResult); if (summary.toString().isEmpty()) {
if (positiveResult) { super.setSummary(mDefaultSummary);
String value = getEditText().getText().toString(); } else {
if (value.isEmpty()) { super.setSummary(summary);
setSummary(mDefaultSummary);
} else {
setSummary(value);
}
}
} }
}
} }
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment