clean up
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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<Integer, Bitmap> cache = new HashMap<Integer, Bitmap>();
|
||||
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<Integer, Bitmap> cache = new HashMap<Integer, Bitmap>();
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Regular → Executable
+1
@@ -4,5 +4,6 @@ public class Node {
|
||||
static {
|
||||
System.loadLibrary("node");
|
||||
}
|
||||
|
||||
public static native void exec(String cmd);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<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,
|
||||
scripts + "\nexit\n", processId);
|
||||
}
|
||||
|
||||
private ArrayList<String> parse(String cmd) {
|
||||
final int PLAIN = 0;
|
||||
final int WHITESPACE = 1;
|
||||
final int INQUOTE = 2;
|
||||
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;
|
||||
}
|
||||
|
||||
@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<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,
|
||||
scripts + "\nexit\n", processId);
|
||||
}
|
||||
|
||||
private ArrayList<String> parse(String cmd) {
|
||||
final int PLAIN = 0;
|
||||
final int WHITESPACE = 1;
|
||||
final int INQUOTE = 2;
|
||||
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;
|
||||
}
|
||||
|
||||
@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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user