This commit is contained in:
Max Lv
2013-04-29 15:18:33 +08:00
parent a74c3fde82
commit 3d664377c1
29 changed files with 2533 additions and 2661 deletions
+325 -351
View File
@@ -21,388 +21,362 @@ import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.LayoutParams;
import android.view.WindowManager;
import android.widget.*;
import android.widget.AbsListView;
import android.widget.AbsListView.OnScrollListener;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.TextView;
import com.actionbarsherlock.app.SherlockActivity;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.*;
public class AppManager extends SherlockActivity
implements OnCheckedChangeListener, OnClickListener {
public class AppManager extends SherlockActivity implements OnCheckedChangeListener,
OnClickListener {
public final static String PREFS_KEY_PROXYED = "Proxyed";
private static final int MSG_LOAD_START = 1;
private static final int MSG_LOAD_FINISH = 2;
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOAD_START:
pd = ProgressDialog.show(AppManager.this, "", getString(R.string.loading), true, true);
break;
case MSG_LOAD_FINISH:
public final static String PREFS_KEY_PROXYED = "Proxyed";
private static final int MSG_LOAD_START = 1;
private static final int MSG_LOAD_FINISH = 2;
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_LOAD_START:
pd = ProgressDialog.show(AppManager.this, "",
getString(R.string.loading), true, true);
break;
case MSG_LOAD_FINISH:
listApps.setAdapter(adapter);
listApps.setAdapter(adapter);
listApps.setOnScrollListener(new OnScrollListener() {
listApps.setOnScrollListener(new OnScrollListener() {
boolean visible;
boolean visible;
@Override
public void onScroll(AbsListView view,
int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
if (visible) {
String name = apps[firstVisibleItem].getName();
if (name != null && name.length() > 1)
overlay.setText(apps[firstVisibleItem]
.getName().substring(0, 1));
else
overlay.setText("*");
overlay.setVisibility(View.VISIBLE);
}
}
@Override
public void onScrollStateChanged(AbsListView view,
int scrollState) {
visible = true;
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
overlay.setVisibility(View.INVISIBLE);
}
}
});
if (pd != null) {
pd.dismiss();
pd = null;
}
break;
}
super.handleMessage(msg);
}
};
private ProxyedApp[] apps = null;
private ListView listApps;
private AppManager mAppManager;
private TextView overlay;
private ProgressDialog pd = null;
private ListAdapter adapter;
private ImageLoader dm;
private boolean appsLoaded = false;
public static ProxyedApp[] getProxyedApps(Context context) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
String[] tordApps;
StringTokenizer st = new StringTokenizer(tordAppString, "|");
tordApps = new String[st.countTokens()];
int tordIdx = 0;
while (st.hasMoreTokens()) {
tordApps[tordIdx++] = st.nextToken();
}
Arrays.sort(tordApps);
// else load the apps up
PackageManager pMgr = context.getPackageManager();
List<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);
app.setUsername(pMgr.getNameForUid(app.getUid()));
// check if this application is allowed
if (aInfo.packageName != null
&& aInfo.packageName.equals("com.github.shadowsocks")) {
app.setProxyed(true);
} else if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) {
app.setProxyed(true);
} else {
app.setProxyed(false);
}
vectorApps.add(app);
}
ProxyedApp[] apps = new ProxyedApp[vectorApps.size()];
vectorApps.toArray(apps);
return apps;
}
public void getApps(Context context) {
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(context);
String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
String[] tordApps;
StringTokenizer st = new StringTokenizer(tordAppString, "|");
tordApps = new String[st.countTokens()];
int tordIdx = 0;
while (st.hasMoreTokens()) {
tordApps[tordIdx++] = st.nextToken();
}
Arrays.sort(tordApps);
Vector<ProxyedApp> vectorApps = new Vector<ProxyedApp>();
// else load the apps up
PackageManager pMgr = context.getPackageManager();
List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
ApplicationInfo aInfo = null;
while (itAppInfo.hasNext()) {
aInfo = itAppInfo.next();
// ignore system apps
if (aInfo.uid < 10000)
continue;
if (aInfo.processName == null)
continue;
if (pMgr.getApplicationLabel(aInfo) == null
|| pMgr.getApplicationLabel(aInfo).toString().equals(""))
continue;
if (pMgr.getApplicationIcon(aInfo) == null)
continue;
ProxyedApp tApp = new ProxyedApp();
tApp.setEnabled(aInfo.enabled);
tApp.setUid(aInfo.uid);
tApp.setUsername(pMgr.getNameForUid(tApp.getUid()));
tApp.setProcname(aInfo.processName);
tApp.setName(pMgr.getApplicationLabel(aInfo).toString());
// check if this application is allowed
if (Arrays.binarySearch(tordApps, tApp.getUsername()) >= 0) {
tApp.setProxyed(true);
} else {
tApp.setProxyed(false);
}
vectorApps.add(tApp);
}
apps = new ProxyedApp[vectorApps.size()];
vectorApps.toArray(apps);
}
private void loadApps() {
getApps(this);
Arrays.sort(apps, new Comparator<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);
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
if (visible) {
String name = apps[firstVisibleItem].getName();
if (name != null && name.length() > 1) {
overlay.setText(apps[firstVisibleItem].getName().substring(0, 1));
} else {
// Convert an existing view
entry = (ListEntry) convertView.getTag();
overlay.setText("*");
}
final ProxyedApp app = apps[position];
entry.icon.setTag(app.getUid());
dm.DisplayImage(app.getUid(),
(Activity) convertView.getContext(), entry.icon);
entry.text.setText(app.getName());
final CheckBox box = entry.box;
box.setTag(app);
box.setChecked(app.isProxyed());
entry.text.setTag(box);
return convertView;
overlay.setVisibility(View.VISIBLE);
}
}
};
appsLoaded = true;
}
/**
* Called an application is check/unchecked
*/
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
final ProxyedApp app = (ProxyedApp) buttonView.getTag();
if (app != null) {
app.setProxyed(isChecked);
}
saveAppSettings(this);
}
@Override
public void onClick(View v) {
CheckBox cbox = (CheckBox) v.getTag();
final ProxyedApp app = (ProxyedApp) cbox.getTag();
if (app != null) {
app.setProxyed(!app.isProxyed());
cbox.setChecked(app.isProxyed());
}
saveAppSettings(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.layout_apps);
this.dm = ImageLoaderFactory.getImageLoader(this);
this.overlay = (TextView) View.inflate(this, R.layout.overlay, null);
getWindowManager()
.addView(
overlay,
new WindowManager.LayoutParams(
LayoutParams.WRAP_CONTENT,
LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE,
PixelFormat.TRANSLUCENT));
mAppManager = this;
}
@Override
protected void onResume() {
super.onResume();
new Thread() {
@Override
public void run() {
handler.sendEmptyMessage(MSG_LOAD_START);
listApps = (ListView) findViewById(R.id.applistview);
if (!appsLoaded)
loadApps();
handler.sendEmptyMessage(MSG_LOAD_FINISH);
public void onScrollStateChanged(AbsListView view, int scrollState) {
visible = true;
if (scrollState == OnScrollListener.SCROLL_STATE_IDLE) {
overlay.setVisibility(View.INVISIBLE);
}
}
}.start();
});
if (pd != null) {
pd.dismiss();
pd = null;
}
break;
}
super.handleMessage(msg);
}
};
private ProxyedApp[] apps = null;
private ListView listApps;
private AppManager mAppManager;
private TextView overlay;
private ProgressDialog pd = null;
private ListAdapter adapter;
private ImageLoader dm;
private boolean appsLoaded = false;
public static ProxyedApp[] getProxyedApps(Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
String[] tordApps;
StringTokenizer st = new StringTokenizer(tordAppString, "|");
tordApps = new String[st.countTokens()];
int tordIdx = 0;
while (st.hasMoreTokens()) {
tordApps[tordIdx++] = st.nextToken();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onStop()
*/
@Override
protected void onStop() {
super.onStop();
Arrays.sort(tordApps);
// Log.d(getClass().getName(),"Exiting Preferences");
// else load the apps up
PackageManager pMgr = context.getPackageManager();
List<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);
app.setUsername(pMgr.getNameForUid(app.getUid()));
// check if this application is allowed
if (aInfo.packageName != null && aInfo.packageName.equals("com.github.shadowsocks")) {
app.setProxyed(true);
} else if (Arrays.binarySearch(tordApps, app.getUsername()) >= 0) {
app.setProxyed(true);
} else {
app.setProxyed(false);
}
vectorApps.add(app);
}
public void saveAppSettings(Context context) {
if (apps == null)
return;
ProxyedApp[] apps = new ProxyedApp[vectorApps.size()];
vectorApps.toArray(apps);
return apps;
}
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(this);
public void getApps(Context context) {
// final SharedPreferences prefs =
// context.getSharedPreferences(PREFS_KEY, 0);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
StringBuilder tordApps = new StringBuilder();
String tordAppString = prefs.getString(PREFS_KEY_PROXYED, "");
String[] tordApps;
for (int i = 0; i < apps.length; i++) {
if (apps[i].isProxyed()) {
tordApps.append(apps[i].getUsername());
tordApps.append("|");
}
StringTokenizer st = new StringTokenizer(tordAppString, "|");
tordApps = new String[st.countTokens()];
int tordIdx = 0;
while (st.hasMoreTokens()) {
tordApps[tordIdx++] = st.nextToken();
}
Arrays.sort(tordApps);
Vector<ProxyedApp> vectorApps = new Vector<ProxyedApp>();
// else load the apps up
PackageManager pMgr = context.getPackageManager();
List<ApplicationInfo> lAppInfo = pMgr.getInstalledApplications(0);
Iterator<ApplicationInfo> itAppInfo = lAppInfo.iterator();
ApplicationInfo aInfo = null;
while (itAppInfo.hasNext()) {
aInfo = itAppInfo.next();
// ignore system apps
if (aInfo.uid < 10000) continue;
if (aInfo.processName == null) continue;
if (pMgr.getApplicationLabel(aInfo) == null || pMgr.getApplicationLabel(aInfo)
.toString()
.equals("")) {
continue;
}
if (pMgr.getApplicationIcon(aInfo) == null) continue;
ProxyedApp tApp = new ProxyedApp();
tApp.setEnabled(aInfo.enabled);
tApp.setUid(aInfo.uid);
tApp.setUsername(pMgr.getNameForUid(tApp.getUid()));
tApp.setProcname(aInfo.processName);
tApp.setName(pMgr.getApplicationLabel(aInfo).toString());
// check if this application is allowed
if (Arrays.binarySearch(tordApps, tApp.getUsername()) >= 0) {
tApp.setProxyed(true);
} else {
tApp.setProxyed(false);
}
vectorApps.add(tApp);
}
apps = new ProxyedApp[vectorApps.size()];
vectorApps.toArray(apps);
}
private void loadApps() {
getApps(this);
Arrays.sort(apps, new Comparator<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();
}
Editor edit = prefs.edit();
edit.putString(PREFS_KEY_PROXYED, tordApps.toString());
edit.commit();
final ProxyedApp app = apps[position];
entry.icon.setTag(app.getUid());
dm.DisplayImage(app.getUid(), (Activity) convertView.getContext(), entry.icon);
entry.text.setText(app.getName());
final CheckBox box = entry.box;
box.setTag(app);
box.setChecked(app.isProxyed());
entry.text.setTag(box);
return convertView;
}
};
appsLoaded = true;
}
/** Called an application is check/unchecked */
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
final ProxyedApp app = (ProxyedApp) buttonView.getTag();
if (app != null) {
app.setProxyed(isChecked);
}
private static class ListEntry {
private CheckBox box;
private TextView text;
private ImageView icon;
saveAppSettings(this);
}
@Override
public void onClick(View v) {
CheckBox cbox = (CheckBox) v.getTag();
final ProxyedApp app = (ProxyedApp) cbox.getTag();
if (app != null) {
app.setProxyed(!app.isProxyed());
cbox.setChecked(app.isProxyed());
}
saveAppSettings(this);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(R.layout.layout_apps);
this.dm = ImageLoaderFactory.getImageLoader(this);
this.overlay = (TextView) View.inflate(this, R.layout.overlay, null);
getWindowManager().addView(overlay,
new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE, PixelFormat.TRANSLUCENT));
mAppManager = this;
}
@Override
protected void onResume() {
super.onResume();
new Thread() {
@Override
public void run() {
handler.sendEmptyMessage(MSG_LOAD_START);
listApps = (ListView) findViewById(R.id.applistview);
if (!appsLoaded) loadApps();
handler.sendEmptyMessage(MSG_LOAD_FINISH);
}
}.start();
}
/*
* (non-Javadoc)
*
* @see android.app.Activity#onStop()
*/
@Override
protected void onStop() {
super.onStop();
// Log.d(getClass().getName(),"Exiting Preferences");
}
public void saveAppSettings(Context context) {
if (apps == null) return;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
// final SharedPreferences prefs =
// context.getSharedPreferences(PREFS_KEY, 0);
StringBuilder tordApps = new StringBuilder();
for (int i = 0; i < apps.length; i++) {
if (apps[i].isProxyed()) {
tordApps.append(apps[i].getUsername());
tordApps.append("|");
}
}
Editor edit = prefs.edit();
edit.putString(PREFS_KEY_PROXYED, tordApps.toString());
edit.commit();
}
private static class ListEntry {
private CheckBox box;
private TextView text;
private ImageView icon;
}
}
+33 -38
View File
@@ -27,46 +27,41 @@ import java.io.FileDescriptor;
*/
public class Exec {
static {
java.lang.System.loadLibrary("exec");
}
static {
java.lang.System.loadLibrary("exec");
}
/**
* Close a given file descriptor.
*/
public static native void close(FileDescriptor fd);
/** Close a given file descriptor. */
public static native void close(FileDescriptor fd);
/**
* Create a subprocess. Differs from java.lang.ProcessBuilder in that a pty
* is used to communicate with the subprocess.
* <p/>
* Callers are responsible for calling Exec.close() on the returned file
* descriptor.
*
* @param rdt Whether redirect stdout and stderr
* @param cmd The command to execute.
* @param args An array of arguments to the command.
* @param envVars An array of strings of the form "VAR=value" to be added to the
* environment of the process.
* @param scripts The scripts to execute.
* @param processId A one-element array to which the process ID of the started
* process will be written.
* @return File descriptor
*/
public static native FileDescriptor createSubprocess(int rdt, String cmd,
String[] args, String[] envVars,
String scripts, int[] processId);
/**
* Create a subprocess. Differs from java.lang.ProcessBuilder in that a pty
* is used to communicate with the subprocess.
* <p/>
* Callers are responsible for calling Exec.close() on the returned file
* descriptor.
*
* @param rdt Whether redirect stdout and stderr
* @param cmd The command to execute.
* @param args An array of arguments to the command.
* @param envVars An array of strings of the form "VAR=value" to be added to the
* environment of the process.
* @param scripts The scripts to execute.
* @param processId A one-element array to which the process ID of the started
* process will be written.
* @return File descriptor
*/
public static native FileDescriptor createSubprocess(int rdt, String cmd, String[] args,
String[] envVars, String scripts, int[] processId);
/**
* Send SIGHUP to a process group.
*/
public static native void hangupProcessGroup(int processId);
/** Send SIGHUP to a process group. */
public static native void hangupProcessGroup(int processId);
/**
* Causes the calling thread to wait for the process associated with the
* receiver to finish executing.
*
* @return The exit value of the Process being waited on
*/
public static native int waitFor(int processId);
/**
* Causes the calling thread to wait for the process associated with the
* receiver to finish executing.
*
* @return The exit value of the Process being waited on
*/
public static native int waitFor(int processId);
}
+166 -172
View File
@@ -45,7 +45,6 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.widget.ImageView;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
@@ -54,195 +53,190 @@ import java.util.Stack;
public class ImageLoader {
final int stub_id = R.drawable.sym_def_app_icon;
PhotosQueue photosQueue = new PhotosQueue();
PhotosLoader photoLoaderThread = new PhotosLoader();
// the simplest in-memory cache implementation. This should be replaced with
// something like SoftReference or BitmapOptions.inPurgeable(since 1.6)
private HashMap<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();
// 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);
public ImageLoader(Context c) {
// Make the background thead low priority. This way it will not affect
// the UI performance
photoLoaderThread.setPriority(Thread.NORM_PRIORITY - 1);
context = c;
context = c;
// Find the dir to save cached images
cacheDir = context.getCacheDir();
// Find the dir to save cached images
cacheDir = context.getCacheDir();
}
public void clearCache() {
// clear memory cache
cache.clear();
// clear SD cache
File[] files = cacheDir.listFiles();
for (File f : files)
f.delete();
}
// decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
try {
// decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
// Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE = 70;
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 1;
while (true) {
if (width_tmp / 2 < REQUIRED_SIZE || height_tmp / 2 < REQUIRED_SIZE) break;
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
}
// decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException ignored) {
}
return null;
}
public void DisplayImage(int uid, Activity activity, ImageView imageView) {
if (cache.containsKey(uid)) {
imageView.setImageBitmap(cache.get(uid));
} else {
queuePhoto(uid, activity, imageView);
imageView.setImageResource(stub_id);
}
}
private Bitmap getBitmap(int uid) {
// I identify images by hashcode. Not a perfect solution, good for the
// demo.
String filename = String.valueOf(uid);
File f = new File(cacheDir, filename);
// from SD cache
Bitmap b = decodeFile(f);
if (b != null) return b;
// from web
try {
BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context, uid);
return icon.getBitmap();
} catch (Exception ex) {
return null;
}
}
private void queuePhoto(int uid, Activity activity, ImageView imageView) {
// This ImageView may be used for other images before. So there may be
// some old tasks in the queue. We need to discard them.
photosQueue.Clean(imageView);
PhotoToLoad p = new PhotoToLoad(uid, imageView);
synchronized (photosQueue.photosToLoad) {
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
}
public void clearCache() {
// clear memory cache
cache.clear();
// start thread if it's not started yet
if (photoLoaderThread.getState() == Thread.State.NEW) photoLoaderThread.start();
}
// clear SD cache
File[] files = cacheDir.listFiles();
for (File f : files)
f.delete();
public void stopThread() {
photoLoaderThread.interrupt();
}
// Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable {
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i) {
bitmap = b;
imageView = i;
}
// decodes image and scales it to reduce memory consumption
private Bitmap decodeFile(File f) {
try {
// decode image size
BitmapFactory.Options o = new BitmapFactory.Options();
o.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(f), null, o);
@Override
public void run() {
if (bitmap != null) {
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageResource(stub_id);
}
}
}
// Find the correct scale value. It should be the power of 2.
final int REQUIRED_SIZE = 70;
int width_tmp = o.outWidth, height_tmp = o.outHeight;
int scale = 1;
while (true) {
if (width_tmp / 2 < REQUIRED_SIZE
|| height_tmp / 2 < REQUIRED_SIZE)
break;
width_tmp /= 2;
height_tmp /= 2;
scale *= 2;
class PhotosLoader extends Thread {
@Override
public void run() {
try {
while (true) {
// thread waits until there are any images to load in the
// queue
if (photosQueue.photosToLoad.size() == 0) {
synchronized (photosQueue.photosToLoad) {
photosQueue.photosToLoad.wait();
}
// decode with inSampleSize
BitmapFactory.Options o2 = new BitmapFactory.Options();
o2.inSampleSize = scale;
return BitmapFactory.decodeStream(new FileInputStream(f), null, o2);
} catch (FileNotFoundException ignored) {
}
return null;
}
public void DisplayImage(int uid, Activity activity, ImageView imageView) {
if (cache.containsKey(uid))
imageView.setImageBitmap(cache.get(uid));
else {
queuePhoto(uid, activity, imageView);
imageView.setImageResource(stub_id);
}
}
private Bitmap getBitmap(int uid) {
// I identify images by hashcode. Not a perfect solution, good for the
// demo.
String filename = String.valueOf(uid);
File f = new File(cacheDir, filename);
// from SD cache
Bitmap b = decodeFile(f);
if (b != null)
return b;
// from web
try {
BitmapDrawable icon = (BitmapDrawable) Utils.getAppIcon(context,
uid);
return icon.getBitmap();
} catch (Exception ex) {
return null;
}
}
private void queuePhoto(int uid, Activity activity, ImageView imageView) {
// This ImageView may be used for other images before. So there may be
// some old tasks in the queue. We need to discard them.
photosQueue.Clean(imageView);
PhotoToLoad p = new PhotoToLoad(uid, imageView);
synchronized (photosQueue.photosToLoad) {
photosQueue.photosToLoad.push(p);
photosQueue.photosToLoad.notifyAll();
}
// start thread if it's not started yet
if (photoLoaderThread.getState() == Thread.State.NEW)
photoLoaderThread.start();
}
public void stopThread() {
photoLoaderThread.interrupt();
}
// Used to display bitmap in the UI thread
class BitmapDisplayer implements Runnable {
Bitmap bitmap;
ImageView imageView;
public BitmapDisplayer(Bitmap b, ImageView i) {
bitmap = b;
imageView = i;
}
@Override
public void run() {
if (bitmap != null)
imageView.setImageBitmap(bitmap);
else
imageView.setImageResource(stub_id);
}
}
class PhotosLoader extends Thread {
@Override
public void run() {
try {
while (true) {
// thread waits until there are any images to load in the
// queue
if (photosQueue.photosToLoad.size() == 0)
synchronized (photosQueue.photosToLoad) {
photosQueue.photosToLoad.wait();
}
if (photosQueue.photosToLoad.size() != 0) {
PhotoToLoad photoToLoad;
synchronized (photosQueue.photosToLoad) {
photoToLoad = photosQueue.photosToLoad.pop();
}
Bitmap bmp = getBitmap(photoToLoad.uid);
cache.put(photoToLoad.uid, bmp);
Object tag = photoToLoad.imageView.getTag();
if (tag != null && ((Integer) tag) == photoToLoad.uid) {
BitmapDisplayer bd = new BitmapDisplayer(bmp,
photoToLoad.imageView);
Activity a = (Activity) photoToLoad.imageView
.getContext();
a.runOnUiThread(bd);
}
}
if (Thread.interrupted())
break;
}
} catch (InterruptedException e) {
// allow thread to exit
}
if (photosQueue.photosToLoad.size() != 0) {
PhotoToLoad photoToLoad;
synchronized (photosQueue.photosToLoad) {
photoToLoad = photosQueue.photosToLoad.pop();
}
}
}
// stores list of photos to download
class PhotosQueue {
private final Stack<PhotoToLoad> photosToLoad = new Stack<PhotoToLoad>();
// removes all instances of this ImageView
public void Clean(ImageView image) {
synchronized (photosToLoad) {
for (int j = 0; j < photosToLoad.size(); ) {
if (photosToLoad.get(j).imageView == image) photosToLoad.remove(j);
else ++j;
}
Bitmap bmp = getBitmap(photoToLoad.uid);
cache.put(photoToLoad.uid, bmp);
Object tag = photoToLoad.imageView.getTag();
if (tag != null && ((Integer) tag) == photoToLoad.uid) {
BitmapDisplayer bd = new BitmapDisplayer(bmp, photoToLoad.imageView);
Activity a = (Activity) photoToLoad.imageView.getContext();
a.runOnUiThread(bd);
}
}
if (Thread.interrupted()) break;
}
} catch (InterruptedException e) {
// allow thread to exit
}
}
}
// Task for the queue
private class PhotoToLoad {
public int uid;
public ImageView imageView;
// stores list of photos to download
class PhotosQueue {
private final Stack<PhotoToLoad> photosToLoad = new Stack<PhotoToLoad>();
public PhotoToLoad(int u, ImageView i) {
uid = u;
imageView = i;
// removes all instances of this ImageView
public void Clean(ImageView image) {
synchronized (photosToLoad) {
for (int j = 0; j < photosToLoad.size(); ) {
if (photosToLoad.get(j).imageView == image) {
photosToLoad.remove(j);
} else {
++j;
}
}
}
}
}
// Task for the queue
private class PhotoToLoad {
public int uid;
public ImageView imageView;
public PhotoToLoad(int u, ImageView i) {
uid = u;
imageView = i;
}
}
}
@@ -42,12 +42,12 @@ import android.content.Context;
public class ImageLoaderFactory {
private static ImageLoader il = null;
private static ImageLoader il = null;
public static ImageLoader getImageLoader(Context context) {
if (il == null) {
il = new ImageLoader(context);
}
return il;
public static ImageLoader getImageLoader(Context context) {
if (il == null) {
il = new ImageLoader(context);
}
return il;
}
}
+54 -78
View File
@@ -40,94 +40,70 @@ package com.github.shadowsocks;
public class ProxyedApp {
private boolean enabled;
private int uid;
private String username;
private String procname;
private String name;
private boolean proxyed = false;
private boolean enabled;
private int uid;
private String username;
private String procname;
private String name;
private boolean proxyed = false;
/**
* @return the name
*/
public String getName() {
return name;
}
/** @return the name */
public String getName() {
return name;
}
/**
* @param name the name to set
*/
public void setName(String name) {
this.name = name;
}
/** @param name the name to set */
public void setName(String name) {
this.name = name;
}
/**
* @return the procname
*/
public String getProcname() {
return procname;
}
/** @return the procname */
public String getProcname() {
return procname;
}
/**
* @param procname the procname to set
*/
public void setProcname(String procname) {
this.procname = procname;
}
/** @param procname the procname to set */
public void setProcname(String procname) {
this.procname = procname;
}
/**
* @return the uid
*/
public int getUid() {
return uid;
}
/** @return the uid */
public int getUid() {
return uid;
}
/**
* @param uid the uid to set
*/
public void setUid(int uid) {
this.uid = uid;
}
/** @param uid the uid to set */
public void setUid(int uid) {
this.uid = uid;
}
/**
* @return the username
*/
public String getUsername() {
return username;
}
/** @return the username */
public String getUsername() {
return username;
}
/**
* @param username the username to set
*/
public void setUsername(String username) {
this.username = username;
}
/** @param username the username to set */
public void setUsername(String username) {
this.username = username;
}
/**
* @return the enabled
*/
public boolean isEnabled() {
return enabled;
}
/** @return the enabled */
public boolean isEnabled() {
return enabled;
}
/**
* @param enabled the enabled to set
*/
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/** @param enabled the enabled to set */
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
/**
* @return the proxyed
*/
public boolean isProxyed() {
return proxyed;
}
/** @return the proxyed */
public boolean isProxyed() {
return proxyed;
}
/**
* @param proxyed the proxyed to set
*/
public void setProxyed(boolean proxyed) {
this.proxyed = proxyed;
}
/** @param proxyed the proxyed to set */
public void setProxyed(boolean proxyed) {
this.proxyed = proxyed;
}
}
File diff suppressed because it is too large Load Diff
@@ -43,16 +43,15 @@ import com.google.analytics.tracking.android.EasyTracker;
public class ShadowsocksApplication extends Application {
private static String sTmpDir;
private static String sTmpDir;
public static String getTmpDir() {
return sTmpDir;
}
@Override
public void onCreate() {
EasyTracker.getInstance().setContext(this);
sTmpDir = getCacheDir().getAbsolutePath();
}
public static String getTmpDir() {
return sTmpDir;
}
@Override
public void onCreate() {
EasyTracker.getInstance().setContext(this);
sTmpDir = getCacheDir().getAbsolutePath();
}
}
@@ -47,47 +47,47 @@ import android.preference.PreferenceManager;
public class ShadowsocksReceiver extends BroadcastReceiver {
private static final String TAG = "Shadowsocks";
private static final String TAG = "Shadowsocks";
@Override
public void onReceive(Context context, Intent intent) {
@Override
public void onReceive(Context context, Intent intent) {
if (intent != null) {
final String action = intent.getAction();
if (action.equals(Intent.ACTION_SHUTDOWN)) {
context.stopService(new Intent(context, ShadowsocksService.class));
return;
}
}
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
String versionName;
try {
versionName = context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
versionName = "NONE";
}
final boolean isAutoConnect = settings.getBoolean("isAutoConnect", false);
final boolean isInstalled = settings.getBoolean(versionName, false);
if (isAutoConnect && isInstalled) {
final String portText = settings.getString("port", "");
if (portText == null || portText.length() <= 0) {
return;
}
try {
int port = Integer.valueOf(portText);
if (port <= 1024) {
return;
}
} catch (Exception e) {
return;
}
Intent it = new Intent(context, ShadowsocksService.class);
context.startService(it);
}
if (intent != null) {
final String action = intent.getAction();
if (action.equals(Intent.ACTION_SHUTDOWN)) {
context.stopService(new Intent(context, ShadowsocksService.class));
return;
}
}
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(context);
String versionName;
try {
versionName =
context.getPackageManager().getPackageInfo(context.getPackageName(), 0).versionName;
} catch (PackageManager.NameNotFoundException e) {
versionName = "NONE";
}
final boolean isAutoConnect = settings.getBoolean("isAutoConnect", false);
final boolean isInstalled = settings.getBoolean(versionName, false);
if (isAutoConnect && isInstalled) {
final String portText = settings.getString("port", "");
if (portText == null || portText.length() <= 0) {
return;
}
try {
int port = Integer.valueOf(portText);
if (port <= 1024) {
return;
}
} catch (Exception e) {
return;
}
Intent it = new Intent(context, ShadowsocksService.class);
context.startService(it);
}
}
}
File diff suppressed because it is too large Load Diff
+4 -4
View File
@@ -39,9 +39,9 @@
package com.github.shadowsocks;
public class System {
static {
java.lang.System.loadLibrary("system");
}
static {
java.lang.System.loadLibrary("system");
}
public static native void exec(String cmd);
public static native void exec(String cmd);
}
+413 -433
View File
@@ -45,471 +45,451 @@ import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.os.Environment;
import android.util.Log;
import org.apache.http.conn.util.InetAddressUtils;
import java.io.*;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.apache.http.conn.util.InetAddressUtils;
public class Utils {
public final static String TAG = "Shadowsocks";
public final static String ABI_PROP = "ro.product.cpu.abi";
public final static String ABI2_PROP = "ro.product.cpu.abi2";
public final static String ARM_ABI = "arm";
public final static String X86_ABI = "x86";
public final static String MIPS_ABI = "mips";
public final static String DEFAULT_SHELL = "/system/bin/sh";
public final static String DEFAULT_ROOT = "/system/bin/su";
public final static String ALTERNATIVE_ROOT = "/system/xbin/su";
public final static String DEFAULT_IPTABLES = "/data/data/com.github.shadowsocks/iptables";
public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables";
public final static int TIME_OUT = -99;
private static boolean initialized = false;
private static int hasRedirectSupport = -1;
private static int isRoot = -1;
private static String shell = null;
private static String root_shell = null;
private static String iptables = null;
private static String data_path = null;
public final static String TAG = "Shadowsocks";
public final static String ABI_PROP = "ro.product.cpu.abi";
public final static String ABI2_PROP = "ro.product.cpu.abi2";
public final static String ARM_ABI = "arm";
public final static String X86_ABI = "x86";
public final static String MIPS_ABI = "mips";
public final static String DEFAULT_SHELL = "/system/bin/sh";
public final static String DEFAULT_ROOT = "/system/bin/su";
public final static String ALTERNATIVE_ROOT = "/system/xbin/su";
public final static String DEFAULT_IPTABLES = "/data/data/com.github.shadowsocks/iptables";
public final static String ALTERNATIVE_IPTABLES = "/system/bin/iptables";
public final static int TIME_OUT = -99;
private static boolean initialized = false;
private static int hasRedirectSupport = -1;
private static int isRoot = -1;
private static String shell = null;
private static String root_shell = null;
private static String iptables = null;
private static String data_path = null;
/**
* Get IP address from first non-localhost interface
*/
public static boolean isIPv6Support() {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface intf : interfaces) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (InetAddress addr : addrs) {
if (!addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) {
String sAddr = addr.getHostAddress().toUpperCase();
boolean isIPv6 = InetAddressUtils.isIPv6Address(sAddr);
if (isIPv6) {
Log.d(TAG, "IPv6 address detected");
return true;
}
}
}
}
} catch (Exception ex) {
// Ignore
} // for now eat exceptions
return false;
}
public static String getABI() {
String abi = getSystemProperty(ABI_PROP);
if (abi != null) {
if (abi.toLowerCase().contains(ARM_ABI)) {
return ARM_ABI;
} else if (abi.toLowerCase().contains(X86_ABI)) {
return X86_ABI;
/** Get IP address from first non-localhost interface */
public static boolean isIPv6Support() {
try {
List<NetworkInterface> interfaces = Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface intf : interfaces) {
List<InetAddress> addrs = Collections.list(intf.getInetAddresses());
for (InetAddress addr : addrs) {
if (!addr.isLoopbackAddress() && !addr.isLinkLocalAddress()) {
String sAddr = addr.getHostAddress().toUpperCase();
boolean isIPv6 = InetAddressUtils.isIPv6Address(sAddr);
if (isIPv6) {
Log.d(TAG, "IPv6 address detected");
return true;
}
}
}
}
} catch (Exception ex) {
// Ignore
} // for now eat exceptions
return false;
}
public static String getABI() {
String abi = getSystemProperty(ABI_PROP);
if (abi != null) {
if (abi.toLowerCase().contains(ARM_ABI)) {
return ARM_ABI;
} else if (abi.toLowerCase().contains(X86_ABI)) {
return X86_ABI;
}
}
return ARM_ABI;
}
/**
* Returns a SystemProperty
*
* @param propName The Property to retrieve
* @return The Property, or NULL if not found
*/
private static String getSystemProperty(String propName) {
String line;
BufferedReader input = null;
try {
Process p = Runtime.getRuntime().exec("getprop " + propName);
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
line = input.readLine();
input.close();
} catch (IOException ex) {
Log.e(TAG, "Unable to read sysprop " + propName, ex);
return null;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
Log.e(TAG, "Exception while closing InputStream", e);
}
}
}
return line;
}
private static void checkIptables() {
if (!isRoot()) {
iptables = DEFAULT_IPTABLES;
return;
}
// Check iptables binary
iptables = DEFAULT_IPTABLES;
String lines = null;
boolean compatible = false;
boolean version = false;
StringBuilder sb = new StringBuilder();
String command = iptables + " --version\n" + iptables + " -L -t nat -n\n" + "exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true);
if (exitcode == TIME_OUT) return;
lines = sb.toString();
if (lines.contains("OUTPUT")) {
compatible = true;
}
if (lines.contains("v1.4.")) {
version = true;
}
if (!compatible || !version) {
iptables = ALTERNATIVE_IPTABLES;
if (!new File(iptables).exists()) iptables = "iptables";
}
}
public static Drawable getAppIcon(Context c, int uid) {
PackageManager pm = c.getPackageManager();
Drawable appIcon = c.getResources().getDrawable(android.R.drawable.sym_def_app_icon);
String[] packages = pm.getPackagesForUid(uid);
if (packages != null) {
if (packages.length == 1) {
try {
ApplicationInfo appInfo = pm.getApplicationInfo(packages[0], 0);
appIcon = pm.getApplicationIcon(appInfo);
} catch (NameNotFoundException e) {
Log.e(c.getPackageName(), "No package found matching with the uid " + uid);
}
}
} else {
Log.e(c.getPackageName(), "Package not found for uid " + uid);
}
return appIcon;
}
public static String getDataPath(Context ctx) {
if (data_path == null) {
if (Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState())) {
data_path = Environment.getExternalStorageDirectory().getAbsolutePath();
} else {
data_path = ctx.getFilesDir().getAbsolutePath();
}
Log.d(TAG, "Python Data Path: " + data_path);
}
return data_path;
}
public static boolean getHasRedirectSupport() {
if (hasRedirectSupport == -1) initHasRedirectSupported();
return hasRedirectSupport == 1;
}
public static String getIptables() {
if (iptables == null) checkIptables();
return iptables;
}
private static String getShell() {
if (shell == null) {
shell = DEFAULT_SHELL;
if (!new File(shell).exists()) shell = "sh";
}
return shell;
}
public static void initHasRedirectSupported() {
if (!Utils.isRoot()) return;
StringBuilder sb = new StringBuilder();
String command =
Utils.getIptables() + " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154";
int exitcode = runScript(command, sb, 10 * 1000, true);
String lines = sb.toString();
hasRedirectSupport = 1;
// flush the check command
Utils.runRootCommand(command.replace("-A", "-D"));
if (exitcode == TIME_OUT) return;
if (lines.contains("No chain/target/match")) {
hasRedirectSupport = 0;
}
}
public static boolean isInitialized() {
if (initialized) {
return true;
} else {
initialized = true;
return false;
}
}
public static boolean isRoot() {
if (isRoot != -1) return isRoot == 1;
// switch between binaries
if (new File(DEFAULT_ROOT).exists()) {
root_shell = DEFAULT_ROOT;
} else if (new File(ALTERNATIVE_ROOT).exists()) {
root_shell = ALTERNATIVE_ROOT;
} else {
root_shell = "su";
}
String lines = null;
StringBuilder sb = new StringBuilder();
String command = "ls /\n exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true);
if (exitcode == TIME_OUT) {
return false;
}
lines = sb.toString();
if (lines.contains("system")) {
isRoot = 1;
}
return isRoot == 1;
}
public static boolean runCommand(String command) {
return runCommand(command, 10 * 1000);
}
public static boolean runCommand(String command, int timeout) {
Log.d(TAG, command);
runScript(command, null, timeout, false);
return true;
}
public static boolean runRootCommand(String command) {
return runRootCommand(command, 10 * 1000);
}
public static boolean runRootCommand(String command, int timeout) {
if (!isRoot()) {
Log.e(TAG, "Cannot get ROOT permission: " + root_shell);
return false;
}
Log.d(TAG, command);
runScript(command, null, timeout, true);
return true;
}
private synchronized static int runScript(String script, StringBuilder result, long timeout,
boolean asroot) {
final ScriptRunner runner = new ScriptRunner(script, result, asroot);
runner.start();
try {
if (timeout > 0) {
runner.join(timeout);
} else {
runner.join();
}
if (runner.isAlive()) {
// Timed-out
runner.destroy();
runner.join(1000);
return TIME_OUT;
}
} catch (InterruptedException ex) {
return TIME_OUT;
}
return runner.exitcode;
}
/** Internal thread used to execute scripts (as root or not). */
private static final class ScriptRunner extends Thread {
private final String scripts;
private final StringBuilder result;
private final boolean asroot;
public int exitcode = -1;
private int[] pid = new int[1];
private FileDescriptor pipe;
@Override
public void destroy() {
if (pid[0] != -1) {
Exec.hangupProcessGroup(pid[0]);
pid[0] = -1;
}
if (pipe != null) {
Exec.close(pipe);
pipe = null;
}
}
/**
* Returns a SystemProperty
* Creates a new script runner.
*
* @param propName The Property to retrieve
* @return The Property, or NULL if not found
* @param script script to run
* @param result result output
* @param asroot if true, executes the script as root
*/
private static String getSystemProperty(String propName) {
String line;
BufferedReader input = null;
try {
Process p = Runtime.getRuntime().exec("getprop " + propName);
input = new BufferedReader(new InputStreamReader(p.getInputStream()), 1024);
line = input.readLine();
input.close();
} catch (IOException ex) {
Log.e(TAG, "Unable to read sysprop " + propName, ex);
return null;
} finally {
if (input != null) {
try {
input.close();
} catch (IOException e) {
Log.e(TAG, "Exception while closing InputStream", e);
}
}
}
return line;
public ScriptRunner(String script, StringBuilder result, boolean asroot) {
this.scripts = script;
this.result = result;
this.asroot = asroot;
}
private static void checkIptables() {
if (!isRoot()) {
iptables = DEFAULT_IPTABLES;
return;
}
// Check iptables binary
iptables = DEFAULT_IPTABLES;
String lines = null;
boolean compatible = false;
boolean version = false;
StringBuilder sb = new StringBuilder();
String command = iptables + " --version\n" + iptables
+ " -L -t nat -n\n" + "exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true);
if (exitcode == TIME_OUT)
return;
lines = sb.toString();
if (lines.contains("OUTPUT")) {
compatible = true;
}
if (lines.contains("v1.4.")) {
version = true;
}
if (!compatible || !version) {
iptables = ALTERNATIVE_IPTABLES;
if (!new File(iptables).exists())
iptables = "iptables";
}
private FileDescriptor createSubprocess(int[] processId, String cmd) {
ArrayList<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);
}
public static Drawable getAppIcon(Context c, int uid) {
PackageManager pm = c.getPackageManager();
Drawable appIcon = c.getResources().getDrawable(
android.R.drawable.sym_def_app_icon);
String[] packages = pm.getPackagesForUid(uid);
if (packages != null) {
if (packages.length == 1) {
try {
ApplicationInfo appInfo = pm.getApplicationInfo(
packages[0], 0);
appIcon = pm.getApplicationIcon(appInfo);
} catch (NameNotFoundException e) {
Log.e(c.getPackageName(),
"No package found matching with the uid " + uid);
}
private ArrayList<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() {
pid[0] = -1;
try {
if (this.asroot) {
// Create the "su" request to run the script
pipe = createSubprocess(pid, root_shell);
} else {
Log.e(c.getPackageName(), "Package not found for uid " + uid);
// Create the "sh" request to run the script
pipe = createSubprocess(pid, getShell());
}
return appIcon;
}
public static String getDataPath(Context ctx) {
if (data_path == null) {
if (Environment.MEDIA_MOUNTED.equals(Environment
.getExternalStorageState())) {
data_path = Environment.getExternalStorageDirectory()
.getAbsolutePath();
} else {
data_path = ctx.getFilesDir().getAbsolutePath();
}
Log.d(TAG, "Python Data Path: " + data_path);
}
return data_path;
}
public static boolean getHasRedirectSupport() {
if (hasRedirectSupport == -1)
initHasRedirectSupported();
return hasRedirectSupport == 1;
}
public static String getIptables() {
if (iptables == null)
checkIptables();
return iptables;
}
private static String getShell() {
if (shell == null) {
shell = DEFAULT_SHELL;
if (!new File(shell).exists())
shell = "sh";
}
return shell;
}
public static void initHasRedirectSupported() {
if (!Utils.isRoot())
return;
StringBuilder sb = new StringBuilder();
String command = Utils.getIptables()
+ " -t nat -A OUTPUT -p udp --dport 54 -j REDIRECT --to 8154";
int exitcode = runScript(command, sb, 10 * 1000, true);
String lines = sb.toString();
hasRedirectSupport = 1;
// flush the check command
Utils.runRootCommand(command.replace("-A", "-D"));
if (exitcode == TIME_OUT)
return;
if (lines.contains("No chain/target/match")) {
hasRedirectSupport = 0;
if (pid[0] != -1) {
exitcode = Exec.waitFor(pid[0]);
}
}
public static boolean isInitialized() {
if (initialized)
return true;
else {
initialized = true;
return false;
}
}
public static boolean isRoot() {
if (result == null || pipe == null) return;
if (isRoot != -1)
return isRoot == 1;
final InputStream stdout = new FileInputStream(pipe);
final byte buf[] = new byte[8192];
int read = 0;
// switch between binaries
if (new File(DEFAULT_ROOT).exists()) {
root_shell = DEFAULT_ROOT;
} else if (new File(ALTERNATIVE_ROOT).exists()) {
root_shell = ALTERNATIVE_ROOT;
} else {
root_shell = "su";
// Read stdout
while (stdout.available() > 0) {
read = stdout.read(buf);
result.append(new String(buf, 0, read));
}
String lines = null;
StringBuilder sb = new StringBuilder();
String command = "ls /\n exit\n";
int exitcode = runScript(command, sb, 10 * 1000, true);
if (exitcode == TIME_OUT) {
return false;
} catch (Exception ex) {
Log.e(TAG, "Cannot execute command", ex);
if (result != null) result.append("\n").append(ex);
} finally {
if (pipe != null) {
Exec.close(pipe);
}
lines = sb.toString();
if (lines.contains("system")) {
isRoot = 1;
}
return isRoot == 1;
}
public static boolean runCommand(String command) {
return runCommand(command, 10 * 1000);
}
public static boolean runCommand(String command, int timeout) {
Log.d(TAG, command);
runScript(command, null, timeout, false);
return true;
}
public static boolean runRootCommand(String command) {
return runRootCommand(command, 10 * 1000);
}
public static boolean runRootCommand(String command, int timeout) {
if (!isRoot()) {
Log.e(TAG, "Cannot get ROOT permission: " + root_shell);
return false;
}
Log.d(TAG, command);
runScript(command, null, timeout, true);
return true;
}
private synchronized static int runScript(String script, StringBuilder result,
long timeout, boolean asroot) {
final ScriptRunner runner = new ScriptRunner(script, result, asroot);
runner.start();
try {
if (timeout > 0) {
runner.join(timeout);
} else {
runner.join();
}
if (runner.isAlive()) {
// Timed-out
runner.destroy();
runner.join(1000);
return TIME_OUT;
}
} catch (InterruptedException ex) {
return TIME_OUT;
}
return runner.exitcode;
}
/**
* Internal thread used to execute scripts (as root or not).
*/
private static final class ScriptRunner extends Thread {
private final String scripts;
private final StringBuilder result;
private final boolean asroot;
public int exitcode = -1;
private int[] pid = new int[1];
private FileDescriptor pipe;
@Override
public void destroy() {
if (pid[0] != -1) {
Exec.hangupProcessGroup(pid[0]);
pid[0] = -1;
}
if (pipe != null) {
Exec.close(pipe);
pipe = null;
}
}
/**
* Creates a new script runner.
*
* @param script script to run
* @param result result output
* @param asroot if true, executes the script as root
*/
public ScriptRunner(String script, StringBuilder result,
boolean asroot) {
this.scripts = script;
this.result = result;
this.asroot = asroot;
}
private FileDescriptor createSubprocess(int[] processId, String cmd) {
ArrayList<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() {
pid[0] = -1;
try {
if (this.asroot) {
// Create the "su" request to run the script
pipe = createSubprocess(pid, root_shell);
} else {
// Create the "sh" request to run the script
pipe = createSubprocess(pid, getShell());
}
if (pid[0] != -1) {
exitcode = Exec.waitFor(pid[0]);
}
if (result == null || pipe == null) return;
final InputStream stdout = new FileInputStream(pipe);
final byte buf[] = new byte[8192];
int read = 0;
// Read stdout
while (stdout.available() > 0) {
read = stdout.read(buf);
result.append(new String(buf, 0, read));
}
} catch (Exception ex) {
Log.e(TAG, "Cannot execute command", ex);
if (result != null)
result.append("\n").append(ex);
} finally {
if (pipe != null) {
Exec.close(pipe);
}
if (pid[0] != -1) {
Exec.hangupProcessGroup(pid[0]);
}
}
if (pid[0] != -1) {
Exec.hangupProcessGroup(pid[0]);
}
}
}
}
}
@@ -3,36 +3,31 @@ package com.github.shadowsocks.preferences;
import android.content.Context;
import android.preference.EditTextPreference;
import android.util.AttributeSet;
import com.github.shadowsocks.R;
public class SummaryEditTextPreference extends EditTextPreference {
private CharSequence mDefaultSummary;
private CharSequence mDefaultSummary;
public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDefaultSummary = getSummary();
}
public SummaryEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
mDefaultSummary = getSummary();
}
public SummaryEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mDefaultSummary = getSummary();
}
public SummaryEditTextPreference(Context context, AttributeSet attrs) {
super(context, attrs);
mDefaultSummary = getSummary();
}
public SummaryEditTextPreference(Context context) {
super(context);
}
public SummaryEditTextPreference(Context context) {
super(context);
}
@Override
public void onDialogClosed(boolean positiveResult) {
super.onDialogClosed(positiveResult);
if (positiveResult) {
String value = getEditText().getText().toString();
if (value.isEmpty()) {
setSummary(mDefaultSummary);
} else {
setSummary(value);
}
}
@Override
public void setSummary(CharSequence summary) {
if (summary.toString().isEmpty()) {
super.setSummary(mDefaultSummary);
} else {
super.setSummary(summary);
}
}
}