diff --git a/src/com/github/shadowsocks/AppManager.java b/src/com/github/shadowsocks/AppManager.java index f331864c..e11b69ac 100755 --- a/src/com/github/shadowsocks/AppManager.java +++ b/src/com/github/shadowsocks/AppManager.java @@ -30,11 +30,67 @@ import java.util.*; public class AppManager extends Activity implements OnCheckedChangeListener, OnClickListener { - private static class ListEntry { - private CheckBox box; - private TextView text; - private ImageView icon; - } + 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) { @@ -96,77 +152,6 @@ public class AppManager extends Activity implements OnCheckedChangeListener, return apps; } - private ProxyedApp[] apps = null; - - private ListView listApps; - - private AppManager mAppManager; - private TextView overlay; - - private ProgressDialog pd = null; - private ListAdapter adapter; - - private ImageLoader dm; - - private static final int MSG_LOAD_START = 1; - - private static final int MSG_LOAD_FINISH = 2; - - public final static String PREFS_KEY_PROXYED = "Proxyed"; - - private boolean appsLoaded = false; - - 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); - } - }; - public void getApps(Context context) { SharedPreferences prefs = PreferenceManager @@ -413,4 +398,10 @@ public class AppManager extends Activity implements OnCheckedChangeListener, } + private static class ListEntry { + private CheckBox box; + private TextView text; + private ImageView icon; + } + } diff --git a/src/com/github/shadowsocks/Exec.java b/src/com/github/shadowsocks/Exec.java index 0c6009dc..ef450766 100755 --- a/src/com/github/shadowsocks/Exec.java +++ b/src/com/github/shadowsocks/Exec.java @@ -51,7 +51,7 @@ public class Exec { * @param scripts The scripts to execute. * @param processId A one-element array to which the process ID of the started * process will be written. - * @return File descriptor + * @return File descriptor */ public static native FileDescriptor createSubprocess(int rdt, String cmd, String[] args, String[] envVars, diff --git a/src/com/github/shadowsocks/ImageLoader.java b/src/com/github/shadowsocks/ImageLoader.java index 6d4378d1..4d2fee59 100755 --- a/src/com/github/shadowsocks/ImageLoader.java +++ b/src/com/github/shadowsocks/ImageLoader.java @@ -16,6 +16,116 @@ import java.util.Stack; public class ImageLoader { + final int stub_id = R.drawable.sym_def_app_icon; + PhotosQueue photosQueue = new PhotosQueue(); + PhotosLoader photoLoaderThread = new PhotosLoader(); + // the simplest in-memory cache implementation. This should be replaced with + // something like SoftReference or BitmapOptions.inPurgeable(since 1.6) + private HashMap cache = new HashMap(); + private File cacheDir; + private Context context; + + public ImageLoader(Context c) { + // Make the background thead low priority. This way it will not affect + // the UI performance + photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1); + + context = c; + + // Find the dir to save cached images + cacheDir = context.getCacheDir(); + + } + + public void clearCache() { + // clear memory cache + cache.clear(); + + // clear SD cache + File[] files = cacheDir.listFiles(); + for (File f : files) + f.delete(); + } + + // decodes image and scales it to reduce memory consumption + private Bitmap decodeFile(File f) { + try { + // decode image size + BitmapFactory.Options o = new BitmapFactory.Options(); + o.inJustDecodeBounds = true; + BitmapFactory.decodeStream(new FileInputStream(f), null, o); + + // Find the correct scale value. It should be the power of 2. + final int REQUIRED_SIZE = 70; + int width_tmp = o.outWidth, height_tmp = o.outHeight; + int scale = 1; + while (true) { + if (width_tmp / 2 < REQUIRED_SIZE + || height_tmp / 2 < REQUIRED_SIZE) + break; + width_tmp /= 2; + height_tmp /= 2; + scale *= 2; + } + + // decode with inSampleSize + BitmapFactory.Options o2 = new BitmapFactory.Options(); + o2.inSampleSize = scale; + return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); + } catch (FileNotFoundException ignored) { + } + return null; + } + + public void DisplayImage(int uid, Activity activity, ImageView imageView) { + if (cache.containsKey(uid)) + imageView.setImageBitmap(cache.get(uid)); + else { + queuePhoto(uid, activity, imageView); + imageView.setImageResource(stub_id); + } + } + + private Bitmap getBitmap(int uid) { + // I identify images by hashcode. Not a perfect solution, good for the + // demo. + String filename = String.valueOf(uid); + File f = new File(cacheDir, filename); + + // from SD cache + Bitmap b = decodeFile(f); + if (b != null) + return b; + + // from web + try { + BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context, + uid); + return icon.getBitmap(); + } catch (Exception ex) { + return null; + } + } + + private void queuePhoto(int uid, Activity activity, ImageView imageView) { + // This ImageView may be used for other images before. So there may be + // some old tasks in the queue. We need to discard them. + photosQueue.Clean(imageView); + PhotoToLoad p = new PhotoToLoad(uid, imageView); + synchronized (photosQueue.photosToLoad) { + photosQueue.photosToLoad.push(p); + photosQueue.photosToLoad.notifyAll(); + } + + // start thread if it's not started yet + if (photoLoaderThread.getState() == Thread.State.NEW) + photoLoaderThread.start(); + } + + public void stopThread() { + photoLoaderThread.interrupt(); + } + // Used to display bitmap in the UI thread class BitmapDisplayer implements Runnable { Bitmap bitmap; @@ -97,119 +207,4 @@ public class ImageLoader { } } - // the simplest in-memory cache implementation. This should be replaced with - // something like SoftReference or BitmapOptions.inPurgeable(since 1.6) - private HashMap cache = new HashMap(); - - private File cacheDir; - - private Context context; - - final int stub_id = R.drawable.sym_def_app_icon; - - PhotosQueue photosQueue = new PhotosQueue(); - - PhotosLoader photoLoaderThread = new PhotosLoader(); - - public ImageLoader(Context c) { - // Make the background thead low priority. This way it will not affect - // the UI performance - photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1); - - context = c; - - // Find the dir to save cached images - cacheDir = context.getCacheDir(); - - } - - public void clearCache() { - // clear memory cache - cache.clear(); - - // clear SD cache - File[] files = cacheDir.listFiles(); - for (File f : files) - f.delete(); - } - - // decodes image and scales it to reduce memory consumption - private Bitmap decodeFile(File f) { - try { - // decode image size - BitmapFactory.Options o = new BitmapFactory.Options(); - o.inJustDecodeBounds = true; - BitmapFactory.decodeStream(new FileInputStream(f), null, o); - - // Find the correct scale value. It should be the power of 2. - final int REQUIRED_SIZE = 70; - int width_tmp = o.outWidth, height_tmp = o.outHeight; - int scale = 1; - while (true) { - if (width_tmp / 2 < REQUIRED_SIZE - || height_tmp / 2 < REQUIRED_SIZE) - break; - width_tmp /= 2; - height_tmp /= 2; - scale *= 2; - } - - // decode with inSampleSize - BitmapFactory.Options o2 = new BitmapFactory.Options(); - o2.inSampleSize = scale; - return BitmapFactory.decodeStream(new FileInputStream(f), null, o2); - } catch (FileNotFoundException e) { - } - return null; - } - - public void DisplayImage(int uid, Activity activity, ImageView imageView) { - if (cache.containsKey(uid)) - imageView.setImageBitmap(cache.get(uid)); - else { - queuePhoto(uid, activity, imageView); - imageView.setImageResource(stub_id); - } - } - - private Bitmap getBitmap(int uid) { - // I identify images by hashcode. Not a perfect solution, good for the - // demo. - String filename = String.valueOf(uid); - File f = new File(cacheDir, filename); - - // from SD cache - Bitmap b = decodeFile(f); - if (b != null) - return b; - - // from web - try { - BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context, - uid); - return icon.getBitmap(); - } catch (Exception ex) { - return null; - } - } - - private void queuePhoto(int uid, Activity activity, ImageView imageView) { - // This ImageView may be used for other images before. So there may be - // some old tasks in the queue. We need to discard them. - photosQueue.Clean(imageView); - PhotoToLoad p = new PhotoToLoad(uid, imageView); - synchronized (photosQueue.photosToLoad) { - photosQueue.photosToLoad.push(p); - photosQueue.photosToLoad.notifyAll(); - } - - // start thread if it's not started yet - if (photoLoaderThread.getState() == Thread.State.NEW) - photoLoaderThread.start(); - } - - public void stopThread() { - photoLoaderThread.interrupt(); - } - } diff --git a/src/com/github/shadowsocks/Node.java b/src/com/github/shadowsocks/Node.java old mode 100644 new mode 100755 index 8487c3c9..8ecbe0b1 --- a/src/com/github/shadowsocks/Node.java +++ b/src/com/github/shadowsocks/Node.java @@ -4,5 +4,6 @@ public class Node { static { System.loadLibrary("node"); } + public static native void exec(String cmd); } diff --git a/src/com/github/shadowsocks/ProxyedApp.java b/src/com/github/shadowsocks/ProxyedApp.java index 7e747c25..bba540c2 100755 --- a/src/com/github/shadowsocks/ProxyedApp.java +++ b/src/com/github/shadowsocks/ProxyedApp.java @@ -7,7 +7,6 @@ public class ProxyedApp { private String username; private String procname; private String name; - private boolean proxyed = false; /** @@ -17,48 +16,6 @@ public class ProxyedApp { return name; } - /** - * @return the procname - */ - public String getProcname() { - return procname; - } - - /** - * @return the uid - */ - public int getUid() { - return uid; - } - - /** - * @return the username - */ - public String getUsername() { - return username; - } - - /** - * @return the enabled - */ - public boolean isEnabled() { - return enabled; - } - - /** - * @return the proxyed - */ - public boolean isProxyed() { - return proxyed; - } - - /** - * @param enabled the enabled to set - */ - public void setEnabled(boolean enabled) { - this.enabled = enabled; - } - /** * @param name the name to set */ @@ -66,6 +23,13 @@ public class ProxyedApp { this.name = name; } + /** + * @return the procname + */ + public String getProcname() { + return procname; + } + /** * @param procname the procname to set */ @@ -74,10 +38,10 @@ public class ProxyedApp { } /** - * @param proxyed the proxyed to set + * @return the uid */ - public void setProxyed(boolean proxyed) { - this.proxyed = proxyed; + public int getUid() { + return uid; } /** @@ -87,10 +51,45 @@ public class ProxyedApp { this.uid = uid; } + /** + * @return the username + */ + public String getUsername() { + return username; + } + /** * @param username the username to set */ public void setUsername(String username) { this.username = username; } + + /** + * @return the enabled + */ + public boolean isEnabled() { + return enabled; + } + + /** + * @param enabled the enabled to set + */ + public void setEnabled(boolean enabled) { + this.enabled = enabled; + } + + /** + * @return the proxyed + */ + public boolean isProxyed() { + return proxyed; + } + + /** + * @param proxyed the proxyed to set + */ + public void setProxyed(boolean proxyed) { + this.proxyed = proxyed; + } } \ No newline at end of file diff --git a/src/com/github/shadowsocks/Shadowsocks.java b/src/com/github/shadowsocks/Shadowsocks.java index 118a83d3..55cb8639 100755 --- a/src/com/github/shadowsocks/Shadowsocks.java +++ b/src/com/github/shadowsocks/Shadowsocks.java @@ -66,13 +66,11 @@ import java.io.OutputStream; public class Shadowsocks extends PreferenceActivity implements OnSharedPreferenceChangeListener { - private static final String TAG = "Shadowsocks"; - public static final String PREFS_NAME = "Shadowsocks"; - + private static final String TAG = "Shadowsocks"; private static final int MSG_CRASH_RECOVER = 1; private static final int MSG_INITIAL_FINISH = 2; - + private static ProgressDialog mProgressDialog = null; final Handler handler = new Handler() { @Override public void handleMessage(Message msg) { @@ -96,9 +94,6 @@ public class Shadowsocks extends PreferenceActivity implements super.handleMessage(msg); } }; - - private static ProgressDialog mProgressDialog = null; - private EditTextPreference proxyText; private EditTextPreference portText; private EditTextPreference remotePortText; diff --git a/src/com/github/shadowsocks/ShadowsocksApplication.java b/src/com/github/shadowsocks/ShadowsocksApplication.java index ccb2a208..13a81b2f 100755 --- a/src/com/github/shadowsocks/ShadowsocksApplication.java +++ b/src/com/github/shadowsocks/ShadowsocksApplication.java @@ -1,7 +1,6 @@ package com.github.shadowsocks; import android.app.Application; -import android.content.Context; import com.google.analytics.tracking.android.EasyTracker; public class ShadowsocksApplication extends Application { diff --git a/src/com/github/shadowsocks/Utils.java b/src/com/github/shadowsocks/Utils.java index 86e04722..3f3c5c69 100755 --- a/src/com/github/shadowsocks/Utils.java +++ b/src/com/github/shadowsocks/Utils.java @@ -16,151 +16,19 @@ import java.util.ArrayList; public class Utils { - /** - * 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; - - /** - * Creates a new script runner. - * - * @param script script to run - * @param result result output - * @param asroot if true, executes the script as root - */ - public ScriptRunner(String script, StringBuilder result, - boolean asroot) { - this.scripts = script; - this.result = result; - this.asroot = asroot; - } - - private FileDescriptor createSubprocess(int[] processId, String cmd) { - ArrayList argList = parse(cmd); - String arg0 = argList.get(0); - String[] args = argList.toArray(new String[1]); - - return Exec.createSubprocess(result != null ? 1 : 0, arg0, args, null, - scripts + "\nexit\n", processId); - } - - private ArrayList parse(String cmd) { - final int PLAIN = 0; - final int WHITESPACE = 1; - final int INQUOTE = 2; - int state = WHITESPACE; - ArrayList result = new ArrayList(); - int cmdLen = cmd.length(); - StringBuilder builder = new StringBuilder(); - for (int i = 0; i < cmdLen; i++) { - char c = cmd.charAt(i); - if (state == PLAIN) { - if (Character.isWhitespace(c)) { - result.add(builder.toString()); - builder.delete(0, builder.length()); - state = WHITESPACE; - } else if (c == '"') { - state = INQUOTE; - } else { - builder.append(c); - } - } else if (state == WHITESPACE) { - if (Character.isWhitespace(c)) { - // do nothing - } else if (c == '"') { - state = INQUOTE; - } else { - state = PLAIN; - builder.append(c); - } - } else if (state == INQUOTE) { - if (c == '\\') { - if (i + 1 < cmdLen) { - i += 1; - builder.append(cmd.charAt(i)); - } - } else if (c == '"') { - state = PLAIN; - } else { - builder.append(c); - } - } - } - if (builder.length() > 0) { - result.add(builder.toString()); - } - return result; - } - - @Override - public void run() { - FileDescriptor pipe = null; - int pid[] = new int[1]; - pid[0] = -1; - - try { - - if (this.asroot) { - // Create the "su" request to run the script - pipe = createSubprocess(pid, root_shell); - } else { - // Create the "sh" request to run the script - pipe = createSubprocess(pid, getShell()); - } - - if (pid[0] != -1) { - exitcode = Exec.waitFor(pid[0]); - } - - if (result == null || pipe == null) return; - - final InputStream stdout = new FileInputStream(pipe); - final byte buf[] = new byte[8192]; - int read = 0; - - // Read stdout - while (stdout.available() > 0) { - read = stdout.read(buf); - result.append(new String(buf, 0, read)); - } - - } catch (Exception ex) { - Log.e(TAG, "Cannot execute command", ex); - if (result != null) - result.append("\n").append(ex); - } finally { - if (pipe != null) { - Exec.close(pipe); - } - if (pid[0] != -1) { - Exec.hangupProcessGroup(pid[0]); - } - } - } - } - public final static String TAG = "Shadowsocks"; - public final static String DEFAULT_SHELL = "/system/bin/sh"; public final static String DEFAULT_ROOT = "/system/bin/su"; - public final static String ALTERNATIVE_ROOT = "/system/xbin/su"; public final static String DEFAULT_IPTABLES = "/data/data/com.github.shadowsocks/iptables"; public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables"; - public final static int TIME_OUT = -99; private static boolean initialized = false; private static int hasRedirectSupport = -1; - private static int isRoot = -1; private static String shell = null; private static String root_shell = null; private static String iptables = null; - private static String data_path = null; private static void checkIptables() { @@ -393,4 +261,131 @@ public class Utils { } 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; + + /** + * Creates a new script runner. + * + * @param script script to run + * @param result result output + * @param asroot if true, executes the script as root + */ + public ScriptRunner(String script, StringBuilder result, + boolean asroot) { + this.scripts = script; + this.result = result; + this.asroot = asroot; + } + + private FileDescriptor createSubprocess(int[] processId, String cmd) { + ArrayList argList = parse(cmd); + String arg0 = argList.get(0); + String[] args = argList.toArray(new String[1]); + + return Exec.createSubprocess(result != null ? 1 : 0, arg0, args, null, + scripts + "\nexit\n", processId); + } + + private ArrayList parse(String cmd) { + final int PLAIN = 0; + final int WHITESPACE = 1; + final int INQUOTE = 2; + int state = WHITESPACE; + ArrayList result = new ArrayList(); + int cmdLen = cmd.length(); + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < cmdLen; i++) { + char c = cmd.charAt(i); + if (state == PLAIN) { + if (Character.isWhitespace(c)) { + result.add(builder.toString()); + builder.delete(0, builder.length()); + state = WHITESPACE; + } else if (c == '"') { + state = INQUOTE; + } else { + builder.append(c); + } + } else if (state == WHITESPACE) { + if (Character.isWhitespace(c)) { + // do nothing + } else if (c == '"') { + state = INQUOTE; + } else { + state = PLAIN; + builder.append(c); + } + } else if (state == INQUOTE) { + if (c == '\\') { + if (i + 1 < cmdLen) { + i += 1; + builder.append(cmd.charAt(i)); + } + } else if (c == '"') { + state = PLAIN; + } else { + builder.append(c); + } + } + } + if (builder.length() > 0) { + result.add(builder.toString()); + } + return result; + } + + @Override + public void run() { + FileDescriptor pipe = null; + int pid[] = new int[1]; + pid[0] = -1; + + try { + + if (this.asroot) { + // Create the "su" request to run the script + pipe = createSubprocess(pid, root_shell); + } else { + // Create the "sh" request to run the script + pipe = createSubprocess(pid, getShell()); + } + + if (pid[0] != -1) { + exitcode = Exec.waitFor(pid[0]); + } + + if (result == null || pipe == null) return; + + final InputStream stdout = new FileInputStream(pipe); + final byte buf[] = new byte[8192]; + int read = 0; + + // Read stdout + while (stdout.available() > 0) { + read = stdout.read(buf); + result.append(new String(buf, 0, read)); + } + + } catch (Exception ex) { + Log.e(TAG, "Cannot execute command", ex); + if (result != null) + result.append("\n").append(ex); + } finally { + if (pipe != null) { + Exec.close(pipe); + } + if (pid[0] != -1) { + Exec.hangupProcessGroup(pid[0]); + } + } + } + } }