新增 连接电脑支持运行和保存项目

This commit is contained in:
hyb1996
2018-10-19 15:27:09 +08:00
parent a6bea15ca0
commit 78e0d6b088
15 changed files with 605 additions and 38 deletions

View File

@@ -115,6 +115,10 @@
<intent-filter> <intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE"/>
</intent-filter> </intent-filter>
<intent-filter>
<action android:name="android.intent.action.CREATE_SHORTCUT" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity> </activity>
<activity <activity

View File

@@ -1,5 +1,6 @@
package org.autojs.autojs.model.script; package org.autojs.autojs.model.script;
import android.app.PendingIntent;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.net.Uri; import android.net.Uri;

View File

@@ -1,23 +1,36 @@
package org.autojs.autojs.pluginclient; package org.autojs.autojs.pluginclient;
import android.annotation.SuppressLint;
import android.text.TextUtils; import android.text.TextUtils;
import android.widget.Toast;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonNull; import com.google.gson.JsonNull;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.stardust.app.GlobalAppContext; import com.stardust.app.GlobalAppContext;
import com.stardust.autojs.execution.ScriptExecution; import com.stardust.autojs.execution.ScriptExecution;
import com.stardust.autojs.project.ProjectLauncher;
import com.stardust.autojs.script.StringScriptSource; import com.stardust.autojs.script.StringScriptSource;
import com.stardust.io.Zip;
import com.stardust.pio.PFiles; import com.stardust.pio.PFiles;
import com.stardust.util.MD5;
import org.autojs.autojs.Pref; import org.autojs.autojs.Pref;
import org.autojs.autojs.R; import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs; import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.model.script.Scripts; import org.autojs.autojs.model.script.Scripts;
import java.io.ByteArrayInputStream;
import java.io.File; import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.util.HashMap; import java.util.HashMap;
import io.reactivex.Observable;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
/** /**
* Created by Stardust on 2017/5/11. * Created by Stardust on 2017/5/11.
*/ */
@@ -32,7 +45,7 @@ public class DevPluginResponseHandler implements Handler {
String name = getName(data); String name = getName(data);
String id = data.get("id").getAsString(); String id = data.get("id").getAsString();
runScript(id, name, script); runScript(id, name, script);
return false; return true;
}) })
.handler("stop", data -> { .handler("stop", data -> {
String id = data.get("id").getAsString(); String id = data.get("id").getAsString();
@@ -43,7 +56,7 @@ public class DevPluginResponseHandler implements Handler {
String script = data.get("script").getAsString(); String script = data.get("script").getAsString();
String name = getName(data); String name = getName(data);
saveScript(name, script); saveScript(name, script);
return false; return true;
}) })
.handler("rerun", data -> { .handler("rerun", data -> {
String id = data.get("id").getAsString(); String id = data.get("id").getAsString();
@@ -51,21 +64,54 @@ public class DevPluginResponseHandler implements Handler {
String name = getName(data); String name = getName(data);
stopScript(id); stopScript(id);
runScript(id, name, script); runScript(id, name, script);
return false; return true;
}) })
.handler("stopAll", data -> { .handler("stopAll", data -> {
AutoJs.getInstance().getScriptEngineService().stopAllAndToast(); AutoJs.getInstance().getScriptEngineService().stopAllAndToast();
return false; return true;
}))
.handler("bytes_command", new Router("command")
.handler("run_project", data -> {
launchProject(data.get("dir").getAsString());
return true;
})
.handler("save_project", data -> {
saveProject(data.get("name").getAsString(), data.get("dir").getAsString());
return true;
})); }));
private HashMap<String, ScriptExecution> mScriptExecutions = new HashMap<>(); private HashMap<String, ScriptExecution> mScriptExecutions = new HashMap<>();
private final File mCacheDir;
public DevPluginResponseHandler(File cacheDir) {
mCacheDir = cacheDir;
if (cacheDir.exists()) {
if (cacheDir.isDirectory()) {
PFiles.deleteFilesOfDir(cacheDir);
} else {
cacheDir.delete();
cacheDir.mkdirs();
}
}
}
@Override @Override
public boolean handle(JsonObject data) { public boolean handle(JsonObject data) {
return mRouter.handle(data); return mRouter.handle(data);
} }
public Observable<File> handleBytes(JsonObject data, JsonWebSocket.Bytes bytes) {
String id = data.get("data").getAsJsonObject().get("id").getAsString();
String idMd5 = MD5.md5(id);
return Observable.fromCallable(() -> {
File dir = new File(mCacheDir, idMd5);
Zip.unzip(new ByteArrayInputStream(bytes.byteString.toByteArray()), dir);
return dir;
})
.subscribeOn(Schedulers.io());
}
private void runScript(String viewId, String name, String script) { private void runScript(String viewId, String name, String script) {
if (TextUtils.isEmpty(name)) { if (TextUtils.isEmpty(name)) {
name = "[" + viewId + "]"; name = "[" + viewId + "]";
@@ -75,6 +121,18 @@ public class DevPluginResponseHandler implements Handler {
mScriptExecutions.put(viewId, Scripts.run(new StringScriptSource("[remote]" + name, script))); mScriptExecutions.put(viewId, Scripts.run(new StringScriptSource("[remote]" + name, script)));
} }
private void launchProject(String dir) {
try {
new ProjectLauncher(dir)
.launch(AutoJs.getInstance().getScriptEngineService());
} catch (Exception e) {
e.printStackTrace();
GlobalAppContext.toast(R.string.text_invalid_project);
}
}
private void stopScript(String viewId) { private void stopScript(String viewId) {
ScriptExecution execution = mScriptExecutions.get(viewId); ScriptExecution execution = mScriptExecutions.get(viewId);
if (execution != null) { if (execution != null) {
@@ -104,4 +162,42 @@ public class DevPluginResponseHandler implements Handler {
PFiles.write(file, script); PFiles.write(file, script);
GlobalAppContext.toast(R.string.text_script_save_successfully); GlobalAppContext.toast(R.string.text_script_save_successfully);
} }
@SuppressLint("CheckResult")
private void saveProject(String name, String dir) {
if (TextUtils.isEmpty(name)) {
name = "untitled";
}
name = PFiles.getNameWithoutExtension(name);
File toDir = new File(Pref.getScriptDirPath(), name);
Observable.fromCallable(() -> {
copyDir(new File(dir), toDir);
return toDir.getPath();
}).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dest ->
GlobalAppContext.toast(R.string.text_project_save_success, dest),
err ->
GlobalAppContext.toast(R.string.text_project_save_error, err.getMessage())
);
}
private void copyDir(File fromDir, File toDir) throws FileNotFoundException {
toDir.mkdirs();
File[] files = fromDir.listFiles();
if (files == null || files.length == 0) {
return;
}
for (File file : files) {
if (file.isDirectory()) {
copyDir(file, new File(toDir, file.getName()));
} else {
FileOutputStream fos = new FileOutputStream(new File(toDir, file.getName()));
PFiles.write(new FileInputStream(file), fos, true);
}
}
}
} }

View File

@@ -12,12 +12,15 @@ import android.util.Pair;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.stardust.app.GlobalAppContext;
import com.stardust.util.MapBuilder; import com.stardust.util.MapBuilder;
import org.autojs.autojs.BuildConfig; import org.autojs.autojs.BuildConfig;
import java.io.IOException; import java.io.File;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@@ -36,6 +39,7 @@ public class DevPluginService {
private static final int CLIENT_VERSION = 2; private static final int CLIENT_VERSION = 2;
private static final String LOG_TAG = "DevPluginService"; private static final String LOG_TAG = "DevPluginService";
private static final String TYPE_HELLO = "hello"; private static final String TYPE_HELLO = "hello";
private static final String TYPE_BYTES_COMMAND = "bytes_command";
private static final long HANDSHAKE_TIMEOUT = 10 * 1000; private static final long HANDSHAKE_TIMEOUT = 10 * 1000;
public static class State { public static class State {
@@ -68,15 +72,21 @@ public class DevPluginService {
private static final int PORT = 9317; private static final int PORT = 9317;
private static DevPluginService sInstance = new DevPluginService(); private static DevPluginService sInstance = new DevPluginService();
private final PublishSubject<State> mConnectionState = PublishSubject.create(); private final PublishSubject<State> mConnectionState = PublishSubject.create();
private final DevPluginResponseHandler mResponseHandler = new DevPluginResponseHandler(); private final DevPluginResponseHandler mResponseHandler;
private final Handler mHandshakeTimeoutHandler = new Handler(Looper.getMainLooper()); private final HashMap<String, JsonWebSocket.Bytes> mBytes = new HashMap<>();
private final HashMap<String, JsonObject> mRequiredBytesCommands = new HashMap<>();
private final Handler mHandler = new Handler(Looper.getMainLooper());
private volatile JsonWebSocket mSocket; private volatile JsonWebSocket mSocket;
public static DevPluginService getInstance() { public static DevPluginService getInstance() {
return sInstance; return sInstance;
} }
public DevPluginService() {
File cache = new File(GlobalAppContext.get().getCacheDir(), "remote_project");
mResponseHandler = new DevPluginResponseHandler(cache);
}
@AnyThread @AnyThread
public boolean isConnected() { public boolean isConnected() {
return mSocket != null && !mSocket.isClosed(); return mSocket != null && !mSocket.isClosed();
@@ -134,14 +144,22 @@ public class DevPluginService {
.build())) .build()))
.doOnNext(socket -> { .doOnNext(socket -> {
mSocket = socket; mSocket = socket;
socket.data() subscribeMessage(socket);
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> mConnectionState.onNext(new State(State.DISCONNECTED)))
.subscribe(data -> onSocketData(socket, data), this::onSocketError);
sayHelloToServer(socket); sayHelloToServer(socket);
}); });
} }
@SuppressLint("CheckResult")
private void subscribeMessage(JsonWebSocket socket) {
socket.data()
.observeOn(AndroidSchedulers.mainThread())
.doOnComplete(() -> mConnectionState.onNext(new State(State.DISCONNECTED)))
.subscribe(data -> onSocketData(socket, data), this::onSocketError);
socket.bytes()
.doOnComplete(() -> mConnectionState.onNext(new State(State.DISCONNECTED)))
.subscribe(data -> onSocketData(socket, data), this::onSocketError);
}
@MainThread @MainThread
private void onSocketError(Throwable e) { private void onSocketError(Throwable e) {
e.printStackTrace(); e.printStackTrace();
@@ -158,24 +176,64 @@ public class DevPluginService {
Log.w(LOG_TAG, "onSocketData: not json object: " + element); Log.w(LOG_TAG, "onSocketData: not json object: " + element);
return; return;
} }
JsonObject obj = element.getAsJsonObject(); try {
JsonElement type = obj.get("type"); JsonObject obj = element.getAsJsonObject();
if (type != null && type.isJsonPrimitive() && type.getAsString().equals(TYPE_HELLO)) { JsonElement typeElement = obj.get("type");
onServerHello(jsonWebSocket, obj); if (typeElement == null || !typeElement.isJsonPrimitive()) {
return; return;
}
String type = typeElement.getAsString();
if (type.equals(TYPE_HELLO)) {
onServerHello(jsonWebSocket, obj);
return;
}
if (TYPE_BYTES_COMMAND.equals(type)) {
String md5 = obj.get("md5").getAsString();
JsonWebSocket.Bytes bytes = mBytes.remove(md5);
if (bytes != null) {
handleBytes(obj, bytes);
} else {
mRequiredBytesCommands.put(md5, obj);
}
return;
}
mResponseHandler.handle(obj);
} catch (Exception e) {
e.printStackTrace();
} }
mResponseHandler.handle(obj);
}
@SuppressLint("CheckResult")
private void handleBytes(JsonObject obj, JsonWebSocket.Bytes bytes) {
mResponseHandler.handleBytes(obj, bytes)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(dir -> {
obj.get("data").getAsJsonObject().add("dir", new JsonPrimitive(dir.getPath()));
mResponseHandler.handle(obj);
});
} }
@WorkerThread @WorkerThread
private void sayHelloToServer(JsonWebSocket socket) throws IOException { private void onSocketData(JsonWebSocket jsonWebSocket, JsonWebSocket.Bytes bytes) {
JsonObject command = mRequiredBytesCommands.remove(bytes.md5);
if (command != null) {
handleBytes(command, bytes);
} else {
mBytes.put(bytes.md5, bytes);
}
}
@WorkerThread
private void sayHelloToServer(JsonWebSocket socket) {
writeMap(socket, TYPE_HELLO, new MapBuilder<String, Object>() writeMap(socket, TYPE_HELLO, new MapBuilder<String, Object>()
.put("device_name", Build.BRAND + " " + Build.MODEL) .put("device_name", Build.BRAND + " " + Build.MODEL)
.put("client_version", CLIENT_VERSION) .put("client_version", CLIENT_VERSION)
.put("app_version", BuildConfig.VERSION_NAME) .put("app_version", BuildConfig.VERSION_NAME)
.put("app_version_code", BuildConfig.VERSION_CODE) .put("app_version_code", BuildConfig.VERSION_CODE)
.build()); .build());
mHandshakeTimeoutHandler.postDelayed(() -> { mHandler.postDelayed(() -> {
if (mSocket != socket && !socket.isClosed()) { if (mSocket != socket && !socket.isClosed()) {
onHandshakeTimeout(socket); onHandshakeTimeout(socket);
} }

View File

@@ -19,14 +19,28 @@ import okhttp3.Request;
import okhttp3.Response; import okhttp3.Response;
import okhttp3.WebSocket; import okhttp3.WebSocket;
import okhttp3.WebSocketListener; import okhttp3.WebSocketListener;
import okio.ByteString;
public class JsonWebSocket extends WebSocketListener { public class JsonWebSocket extends WebSocketListener {
public static class Bytes {
public final String md5;
public final ByteString byteString;
public final long timestamp;
public Bytes(String md5, ByteString byteString) {
this.md5 = md5;
this.byteString = byteString;
this.timestamp = System.currentTimeMillis();
}
}
private static final String LOG_TAG = "JsonWebSocket"; private static final String LOG_TAG = "JsonWebSocket";
private final WebSocket mWebSocket; private final WebSocket mWebSocket;
private final JsonParser mJsonParser = new JsonParser(); private final JsonParser mJsonParser = new JsonParser();
private final PublishSubject<JsonElement> mJsonElementPublishSubject = PublishSubject.create(); private final PublishSubject<JsonElement> mJsonElementPublishSubject = PublishSubject.create();
private final PublishSubject<Bytes> mBytesPublishSubject = PublishSubject.create();
private volatile boolean mClosed = false; private volatile boolean mClosed = false;
public JsonWebSocket(OkHttpClient client, Request request) { public JsonWebSocket(OkHttpClient client, Request request) {
@@ -35,14 +49,24 @@ public class JsonWebSocket extends WebSocketListener {
@Override @Override
public void onMessage(WebSocket webSocket, String text) { public void onMessage(WebSocket webSocket, String text) {
Log.d(LOG_TAG, "onMessage: " + text); Log.d(LOG_TAG, "onMessage: text = " + text);
dispatchJson(text); dispatchJson(text);
} }
@Override
public void onMessage(WebSocket webSocket, ByteString bytes) {
Log.d(LOG_TAG, "onMessage: ByteString = " + bytes.toString());
mBytesPublishSubject.onNext(new Bytes(bytes.md5().hex(), bytes));
}
public Observable<JsonElement> data() { public Observable<JsonElement> data() {
return mJsonElementPublishSubject; return mJsonElementPublishSubject;
} }
public Observable<Bytes> bytes(){
return mBytesPublishSubject;
}
public boolean write(JsonElement element) { public boolean write(JsonElement element) {
String json = element.toString(); String json = element.toString();
Log.d(LOG_TAG, "write: length = " + json.length() + ", json = " + element); Log.d(LOG_TAG, "write: length = " + json.length() + ", json = " + element);

View File

@@ -0,0 +1,136 @@
package org.autojs.autojs.tool;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.math.BigDecimal;
import java.math.BigInteger;
public class SafeJsonElement extends JsonElement {
private final JsonElement mJsonElement;
public SafeJsonElement(JsonElement jsonElement) {
mJsonElement = jsonElement;
}
@Override
public JsonElement deepCopy() {
return null;
}
@Override
public boolean isJsonArray() {
return mJsonElement.isJsonArray();
}
@Override
public boolean isJsonObject() {
return mJsonElement.isJsonObject();
}
@Override
public boolean isJsonPrimitive() {
return mJsonElement.isJsonPrimitive();
}
@Override
public boolean isJsonNull() {
return mJsonElement.isJsonNull();
}
public JsonObject getAsJsonObject() {
return mJsonElement.getAsJsonObject();
}
public SafeJsonObject getAsSafeJsonObject() {
try {
return new SafeJsonObject(mJsonElement.getAsJsonObject());
} catch (Exception e) {
return null;
}
}
@Override
public JsonArray getAsJsonArray() {
return mJsonElement.getAsJsonArray();
}
@Override
public JsonPrimitive getAsJsonPrimitive() {
return mJsonElement.getAsJsonPrimitive();
}
@Override
public JsonNull getAsJsonNull() {
return mJsonElement.getAsJsonNull();
}
@Override
public boolean getAsBoolean() {
return mJsonElement.getAsBoolean();
}
@Override
public Number getAsNumber() {
return mJsonElement.getAsNumber();
}
@Override
public String getAsString() {
return mJsonElement.getAsString();
}
@Override
public double getAsDouble() {
return mJsonElement.getAsDouble();
}
@Override
public float getAsFloat() {
return mJsonElement.getAsFloat();
}
@Override
public long getAsLong() {
return mJsonElement.getAsLong();
}
@Override
public int getAsInt() {
return mJsonElement.getAsInt();
}
@Override
public byte getAsByte() {
return mJsonElement.getAsByte();
}
@Override
public char getAsCharacter() {
return mJsonElement.getAsCharacter();
}
@Override
public BigDecimal getAsBigDecimal() {
return mJsonElement.getAsBigDecimal();
}
@Override
public BigInteger getAsBigInteger() {
return mJsonElement.getAsBigInteger();
}
@Override
public short getAsShort() {
return mJsonElement.getAsShort();
}
@Override
public String toString() {
return mJsonElement.toString();
}
}

View File

@@ -0,0 +1,161 @@
package org.autojs.autojs.tool;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;
import java.util.Set;
public class SafeJsonObject extends JsonElement {
private final JsonObject mJsonObject;
public SafeJsonObject(JsonObject jsonObject) {
mJsonObject = jsonObject;
}
public JsonObject deepCopy() {
return mJsonObject.deepCopy();
}
public void add(String property, JsonElement value) {
mJsonObject.add(property, value);
}
public JsonElement remove(String property) {
return mJsonObject.remove(property);
}
public void addProperty(String property, String value) {
mJsonObject.addProperty(property, value);
}
public void addProperty(String property, Number value) {
mJsonObject.addProperty(property, value);
}
public void addProperty(String property, Boolean value) {
mJsonObject.addProperty(property, value);
}
public void addProperty(String property, Character value) {
mJsonObject.addProperty(property, value);
}
public Set<Map.Entry<String, JsonElement>> entrySet() {
return mJsonObject.entrySet();
}
public Set<String> keySet() {
return mJsonObject.keySet();
}
public int size() {
return mJsonObject.size();
}
public boolean has(String memberName) {
return mJsonObject.has(memberName);
}
public JsonElement get(String memberName) {
return mJsonObject.get(memberName);
}
public JsonPrimitive getAsJsonPrimitive(String memberName) {
return mJsonObject.getAsJsonPrimitive(memberName);
}
public JsonArray getAsJsonArray(String memberName) {
return mJsonObject.getAsJsonArray(memberName);
}
public JsonObject getAsJsonObject(String memberName) {
return mJsonObject.getAsJsonObject(memberName);
}
public boolean isJsonArray() {
return mJsonObject.isJsonArray();
}
public boolean isJsonObject() {
return mJsonObject.isJsonObject();
}
public boolean isJsonPrimitive() {
return mJsonObject.isJsonPrimitive();
}
public boolean isJsonNull() {
return mJsonObject.isJsonNull();
}
public JsonObject getAsJsonObject() {
return mJsonObject.getAsJsonObject();
}
public JsonArray getAsJsonArray() {
return mJsonObject.getAsJsonArray();
}
public JsonPrimitive getAsJsonPrimitive() {
return mJsonObject.getAsJsonPrimitive();
}
public JsonNull getAsJsonNull() {
return mJsonObject.getAsJsonNull();
}
public boolean getAsBoolean() {
return mJsonObject.getAsBoolean();
}
public Number getAsNumber() {
return mJsonObject.getAsNumber();
}
public String getAsString() {
return mJsonObject.getAsString();
}
public double getAsDouble() {
return mJsonObject.getAsDouble();
}
public float getAsFloat() {
return mJsonObject.getAsFloat();
}
public long getAsLong() {
return mJsonObject.getAsLong();
}
public int getAsInt() {
return mJsonObject.getAsInt();
}
public byte getAsByte() {
return mJsonObject.getAsByte();
}
public char getAsCharacter() {
return mJsonObject.getAsCharacter();
}
public BigDecimal getAsBigDecimal() {
return mJsonObject.getAsBigDecimal();
}
public BigInteger getAsBigInteger() {
return mJsonObject.getAsBigInteger();
}
public short getAsShort() {
return mJsonObject.getAsShort();
}
}

View File

@@ -145,6 +145,7 @@ public class CircularMenu implements Recorder.OnStateChangedListener, LayoutInsp
explorerView.setOnItemOperatedListener(file -> dialog.dismiss()); explorerView.setOnItemOperatedListener(file -> dialog.dismiss());
explorerView.setOnItemClickListener((view, item) -> Scripts.run(item.toScriptFile())); explorerView.setOnItemClickListener((view, item) -> Scripts.run(item.toScriptFile()));
DialogUtils.showDialog(dialog); DialogUtils.showDialog(dialog);
} }
@Optional @Optional

View File

@@ -424,4 +424,6 @@
<string name="error_pattern_syntax">正则表达式错误</string> <string name="error_pattern_syntax">正则表达式错误</string>
<string name="text_invalid_package_name">非法包名</string> <string name="text_invalid_package_name">非法包名</string>
<string name="error_cannot_rename">重命名失败</string> <string name="error_cannot_rename">重命名失败</string>
<string name="text_project_save_success">项目已经保存到%s</string>
<string name="text_project_save_error">项目保存失败 %s</string>
</resources> </resources>

View File

@@ -119,11 +119,7 @@ public class AndroidClassLoader extends ClassLoader implements GeneratedClassLoa
private String generateDexFileName(File jar) { private String generateDexFileName(File jar) {
String message = jar.getPath() + "_" + jar.lastModified(); String message = jar.getPath() + "_" + jar.lastModified();
try { return MD5.md5(message);
return MD5.md5(message);
} catch (NoSuchAlgorithmException e) {
throw new RuntimeException(e);
}
} }
public DexClassLoader loadDex(File file) throws FileNotFoundException { public DexClassLoader loadDex(File file) throws FileNotFoundException {

View File

@@ -69,6 +69,19 @@ public class GlobalAppContext {
}); });
} }
public static void toast(final int resId, final Object... args) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Toast.makeText(get(), getString(resId, args), Toast.LENGTH_SHORT).show();
return;
}
sHandler.post(new Runnable() {
@Override
public void run() {
Toast.makeText(get(), getString(resId, args), Toast.LENGTH_SHORT).show();
}
});
}
public static void post(Runnable r) { public static void post(Runnable r) {
sHandler.post(r); sHandler.post(r);
} }

View File

@@ -0,0 +1,49 @@
package com.stardust.io;
import com.stardust.pio.PFile;
import com.stardust.pio.PFiles;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static com.stardust.pio.PFiles.closeSilently;
public class Zip {
public static void unzip(InputStream stream, File dir) throws IOException {
FileOutputStream fos = null;
ZipInputStream zis = null;
try {
zis = new ZipInputStream(stream);
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
File file = new File(dir, entry.getName());
if (entry.isDirectory()) {
file.mkdirs();
} else {
PFiles.ensureDir(file.getPath());
fos = new FileOutputStream(file);
PFiles.write(zis, fos, false);
fos.close();
fos = null;
zis.closeEntry();
}
}
} finally {
closeSilently(fos);
closeSilently(stream);
closeSilently(zis);
}
}
public static void unzip(File zipFile, File dir) throws IOException {
unzip(new FileInputStream(zipFile), dir);
}
}

View File

@@ -9,6 +9,7 @@ import android.util.Log;
import com.stardust.util.Func1; import com.stardust.util.Func1;
import java.io.Closeable;
import java.io.File; import java.io.File;
import java.io.FileInputStream; import java.io.FileInputStream;
import java.io.FileNotFoundException; import java.io.FileNotFoundException;
@@ -174,20 +175,28 @@ public class PFiles {
} }
} }
public static void write(InputStream is, OutputStream os) { public static void write(InputStream is, OutputStream os, boolean close) {
byte[] buffer = new byte[DEFAULT_BUFFER_SIZE]; byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
try { try {
while (is.available() > 0) { while (is.available() > 0) {
int n = is.read(buffer); int n = is.read(buffer);
os.write(buffer, 0, n); if (n > 0) {
os.write(buffer, 0, n);
}
}
if (close) {
is.close();
os.close();
} }
is.close();
os.close();
} catch (IOException e) { } catch (IOException e) {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
} }
public static void write(InputStream is, OutputStream os) {
write(is, os, true);
}
public static void write(String path, String text) { public static void write(String path, String text) {
write(new File(path), text); write(new File(path), text);
@@ -296,7 +305,7 @@ public class PFiles {
if (list == null) if (list == null)
throw new IOException("not a directory: " + assetsDir); throw new IOException("not a directory: " + assetsDir);
for (String file : list) { for (String file : list) {
if(TextUtils.isEmpty(file)){ if (TextUtils.isEmpty(file)) {
continue; continue;
} }
String fullAssetsPath = join(assetsDir, file); String fullAssetsPath = join(assetsDir, file);
@@ -500,4 +509,15 @@ public class PFiles {
throw new UncheckedIOException(e); throw new UncheckedIOException(e);
} }
} }
public static void closeSilently(Closeable closeable) {
if (closeable == null) {
return;
}
try {
closeable.close();
} catch (IOException ignored) {
}
}
} }

View File

@@ -5,13 +5,17 @@ import java.security.NoSuchAlgorithmException;
public class MD5 { public class MD5 {
public static byte[] md5Bytes(String message) throws NoSuchAlgorithmException { public static byte[] md5Bytes(String message) {
MessageDigest md5 = MessageDigest.getInstance("MD5"); try {
md5.update(message.getBytes()); MessageDigest md5 = MessageDigest.getInstance("MD5");
return md5.digest(); md5.update(message.getBytes());
return md5.digest();
} catch (Exception e) {
throw new RuntimeException(e);
}
} }
public static String md5(String message) throws NoSuchAlgorithmException { public static String md5(String message) {
byte[] bytes = md5Bytes(message); byte[] bytes = md5Bytes(message);
StringBuilder hexString = new StringBuilder(32); StringBuilder hexString = new StringBuilder(32);
for (byte b : bytes) { for (byte b : bytes) {
@@ -22,5 +26,7 @@ public class MD5 {
hexString.append(hex); hexString.append(hex);
} }
return hexString.toString(); return hexString.toString();
} }
} }

View File

@@ -1,6 +1,6 @@
{ {
"appVersionCode": 433, "appVersionCode": 434,
"appVersionName": "4.0.4 Alpha4", "appVersionName": "4.0.4 Alpha5",
"target": 28, "target": 28,
"mini": 17, "mini": 17,
"compile": 28, "compile": 28,