make autojs module more independent
@@ -7,6 +7,20 @@
|
||||
android:label="@string/app_name"
|
||||
>
|
||||
<activity android:name=".execution.ScriptExecuteActivity"/>
|
||||
<service
|
||||
android:name="com.stardust.view.accessibility.AccessibilityService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:label="@string/app_name"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accessibilityservice"
|
||||
android:resource="@xml/accessibility_service_config"/>
|
||||
|
||||
</service>
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
|
||||
@@ -27,10 +27,12 @@ require("__general__")(__runtime__, this);
|
||||
|
||||
|
||||
(function(scope){
|
||||
var modules = ['app', 'automator', 'console', 'io', 'selector', 'shell', 'web', 'ui', "images"];
|
||||
var modules = ['app', 'automator', 'console', 'dialogs', 'io', 'selector', 'shell', 'web', 'ui', "images"];
|
||||
var len = modules.length;
|
||||
for(var i = 0; i < len; i++) {
|
||||
var m = modules[i];
|
||||
scope[m] = require('__' + m + '__')(scope.__runtime__, scope);
|
||||
}
|
||||
})(__that__);
|
||||
|
||||
__importClass__(com.stardust.autojs.runtime.api.Shell);
|
||||
|
||||
67
autojs/src/main/assets/modules/__dialogs__.js
Normal file
@@ -0,0 +1,67 @@
|
||||
|
||||
module.exports = function(__runtime__, scope){
|
||||
var dialogs = {};
|
||||
|
||||
dialogs.rawInput = function(title, prefill){
|
||||
prefill = prefill || "";
|
||||
var s = __runtime__.dialogs.rawInput(title, prefill);
|
||||
return s ? String(s) : null;
|
||||
};
|
||||
|
||||
dialogs.input = function(title, prefill){
|
||||
return eval(dialogs.rawInput(title, prefill));
|
||||
}
|
||||
|
||||
dialogs.prompt = dialogs.rawInput;
|
||||
|
||||
dialogs.alert = function(title, prefill){
|
||||
prefill = prefill || "";
|
||||
return __runtime__.dialogs.alert(title, prefill);
|
||||
}
|
||||
|
||||
dialogs.confirm = function(title, prefill){
|
||||
prefill = prefill || "";
|
||||
return __runtime__.dialogs.confirm(title, prefill);
|
||||
}
|
||||
|
||||
dialogs.select = function(title, items){
|
||||
if(items instanceof Array){
|
||||
return __runtime__.dialogs.select(title, items);
|
||||
}
|
||||
return __runtime__.dialogs.select(title, [].slice.call(arguments, 1));
|
||||
}
|
||||
|
||||
dialogs.singleChoice = function(title, items, index){
|
||||
index = index || 0;
|
||||
return __runtime__.dialogs.singleChoice(title, index, items);
|
||||
}
|
||||
|
||||
dialogs.multiChoice = function(title, items, index){
|
||||
index = index || [];
|
||||
var javaArray = __runtime__.dialogs.multiChoice(title, index, items);
|
||||
var jsArray = [];
|
||||
var len = javaArray.length;
|
||||
for (var i = 0;i < len;i++){
|
||||
jsArray.push(javaArray[i]);
|
||||
}
|
||||
return jsArray;
|
||||
}
|
||||
|
||||
scope.rawInput = function(title, prefill){
|
||||
return dialogs.rawInput(title, prefill);
|
||||
}
|
||||
|
||||
scope.alert = function(title, prefill){
|
||||
dialogs.alert(title, prefill);
|
||||
}
|
||||
|
||||
scope.confirm = function(title, prefill){
|
||||
return dialogs.confirm(title, prefill);
|
||||
}
|
||||
|
||||
scope.prompt = function(title, prefill){
|
||||
return dialogs.prompt(title, prefill);
|
||||
}
|
||||
|
||||
return dialogs;
|
||||
}
|
||||
@@ -13,6 +13,7 @@ import com.stardust.autojs.execution.ScriptExecutionListener;
|
||||
import com.stardust.autojs.execution.ScriptExecutionObserver;
|
||||
import com.stardust.autojs.execution.ScriptExecutionTask;
|
||||
import com.stardust.autojs.execution.SimpleScriptExecutionListener;
|
||||
import com.stardust.autojs.runtime.AbstractScriptRuntime;
|
||||
import com.stardust.autojs.runtime.ScriptRuntime;
|
||||
import com.stardust.autojs.runtime.api.Console;
|
||||
import com.stardust.autojs.script.ScriptSource;
|
||||
@@ -87,6 +88,7 @@ public class ScriptEngineService {
|
||||
mScriptExecutionObserver.registerScriptExecutionListener(GLOBAL_LISTENER);
|
||||
EVENT_BUS.register(this);
|
||||
mScriptEngineManager.putGlobal("context", mUiHandler.getContext());
|
||||
AbstractScriptRuntime.setApplicationContext(builder.mUiHandler.getContext());
|
||||
}
|
||||
|
||||
public Console getGlobalConsole() {
|
||||
|
||||
@@ -10,11 +10,15 @@ import com.stardust.autojs.runtime.api.Console;
|
||||
import com.stardust.autojs.runtime.api.UiSelector;
|
||||
import com.stardust.autojs.runtime.api.image.Images;
|
||||
import com.stardust.autojs.runtime.api.image.ScreenCaptureRequester;
|
||||
import com.stardust.autojs.runtime.api.ui.Dialogs;
|
||||
import com.stardust.autojs.runtime.api.ui.UI;
|
||||
import com.stardust.autojs.runtime.simpleaction.SimpleActionAutomator;
|
||||
import com.stardust.util.ScreenMetrics;
|
||||
import com.stardust.util.UiHandler;
|
||||
import com.stardust.view.accessibility.AccessibilityInfoProvider;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/4.
|
||||
*/
|
||||
@@ -39,13 +43,30 @@ public abstract class AbstractScriptRuntime {
|
||||
@ScriptVariable
|
||||
public Images images;
|
||||
|
||||
public AbstractScriptRuntime(Context context, Console console, AccessibilityBridge bridge, AppUtils appUtils, ScreenCaptureRequester screenCaptureRequester) {
|
||||
@ScriptVariable
|
||||
public Dialogs dialogs;
|
||||
|
||||
private static WeakReference<Context> applicationContext;
|
||||
|
||||
public AbstractScriptRuntime(UiHandler uiHandler, Console console, AccessibilityBridge bridge, AppUtils appUtils, ScreenCaptureRequester screenCaptureRequester) {
|
||||
this.app = appUtils;
|
||||
this.console = console;
|
||||
this.automator = new SimpleActionAutomator(bridge, this);
|
||||
this.info = bridge.getInfoProvider();
|
||||
this.ui = new UI(context);
|
||||
images = new Images(context, this, screenCaptureRequester);
|
||||
this.ui = new UI(uiHandler.getContext());
|
||||
images = new Images(uiHandler.getContext(), this, screenCaptureRequester);
|
||||
dialogs = new Dialogs(app, uiHandler);
|
||||
}
|
||||
|
||||
public static void setApplicationContext(Context context) {
|
||||
applicationContext = new WeakReference<>(context);
|
||||
}
|
||||
|
||||
public static Context getApplicationContext() {
|
||||
if (applicationContext == null || applicationContext.get() == null) {
|
||||
throw new ScriptEnvironmentException("No application context");
|
||||
}
|
||||
return applicationContext.get();
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
package com.stardust.autojs.runtime;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/7/1.
|
||||
*/
|
||||
|
||||
public class ScriptEnvironmentException extends RuntimeException {
|
||||
public ScriptEnvironmentException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
@@ -92,7 +92,7 @@ public class ScriptRuntime extends AbstractScriptRuntime {
|
||||
|
||||
|
||||
protected ScriptRuntime(Builder builder) {
|
||||
super(builder.mUiHandler.getContext(), builder.mConsole, builder.mAccessibilityBridge, builder.mAppUtils, builder.mScreenCaptureRequester);
|
||||
super(builder.mUiHandler, builder.mConsole, builder.mAccessibilityBridge, builder.mAppUtils, builder.mScreenCaptureRequester);
|
||||
mAccessibilityBridge = builder.mAccessibilityBridge;
|
||||
mUiHandler = builder.mUiHandler;
|
||||
mShellSupplier = builder.mShellSupplier;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.stardust.autojs.runtime.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.stardust.util.ScreenMetrics;
|
||||
@@ -36,12 +37,18 @@ public abstract class AbstractShell {
|
||||
private ScreenMetrics mScreenMetrics;
|
||||
|
||||
private boolean mRoot;
|
||||
protected Context mContext;
|
||||
|
||||
public AbstractShell() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public AbstractShell(boolean root) {
|
||||
this(null, root);
|
||||
}
|
||||
|
||||
public AbstractShell(Context context, boolean root) {
|
||||
mContext = context;
|
||||
mRoot = root;
|
||||
init(root ? COMMAND_SU : COMMAND_SH);
|
||||
}
|
||||
|
||||
294
autojs/src/main/java/com/stardust/autojs/runtime/api/Shell.java
Normal file
@@ -0,0 +1,294 @@
|
||||
package com.stardust.autojs.runtime.api;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.util.Log;
|
||||
|
||||
import com.stardust.autojs.runtime.AbstractScriptRuntime;
|
||||
import com.stardust.autojs.runtime.ScriptInterruptedException;
|
||||
import com.stardust.lang.ThreadCompat;
|
||||
import com.stardust.pio.UncheckedIOException;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.OutputStream;
|
||||
import java.io.PipedInputStream;
|
||||
import java.io.PipedOutputStream;
|
||||
|
||||
import jackpal.androidterm.ShellTermSession;
|
||||
import jackpal.androidterm.emulatorview.TermSession;
|
||||
import jackpal.androidterm.util.TermSettings;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/4/24.
|
||||
*/
|
||||
|
||||
public class Shell extends AbstractShell implements AutoCloseable {
|
||||
|
||||
public interface Callback {
|
||||
|
||||
void onOutput(String str);
|
||||
|
||||
void onNewLine(String line);
|
||||
|
||||
void onInitialized();
|
||||
|
||||
void onInterrupted(InterruptedException e);
|
||||
}
|
||||
|
||||
public static class SimpleCallback implements Callback {
|
||||
|
||||
@Override
|
||||
public void onOutput(String str) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewLine(String str) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInitialized() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onInterrupted(InterruptedException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private static final String TAG = "Shell";
|
||||
|
||||
private TermSession mTermSession;
|
||||
private final Object mInitLock = new Object();
|
||||
private final Object mExitLock = new Object();
|
||||
private volatile RuntimeException mInitException;
|
||||
private volatile boolean mInitialized = false;
|
||||
private volatile boolean mWaitingExit = false;
|
||||
private Callback mCallback;
|
||||
|
||||
public Shell(Context context) {
|
||||
this(context, false);
|
||||
}
|
||||
|
||||
public Shell(Context context, boolean root) {
|
||||
super(context, root);
|
||||
}
|
||||
|
||||
public Shell() {
|
||||
this(false);
|
||||
}
|
||||
|
||||
public Shell(boolean root) {
|
||||
this(AbstractScriptRuntime.getApplicationContext(), root);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void init(final String initialCommand) {
|
||||
Handler uiHandler = new Handler(mContext.getMainLooper());
|
||||
uiHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
TermSettings settings = new TermSettings(mContext.getResources(), PreferenceManager.getDefaultSharedPreferences(mContext));
|
||||
try {
|
||||
mTermSession = new MyShellTermSession(settings, initialCommand);
|
||||
mTermSession.initializeEmulator(40, 40);
|
||||
} catch (IOException e) {
|
||||
mInitException = new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void exec(String command) {
|
||||
ensureInitialized();
|
||||
mTermSession.write(command + "\n");
|
||||
}
|
||||
|
||||
public void setCallback(Callback callback) {
|
||||
mCallback = callback;
|
||||
}
|
||||
|
||||
public boolean isInitialized() {
|
||||
return mInitialized;
|
||||
}
|
||||
|
||||
private void ensureInitialized() {
|
||||
if (mTermSession == null) {
|
||||
checkInitException();
|
||||
waitInitialization();
|
||||
if (mTermSession == null) {
|
||||
checkInitException();
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void checkInitException() {
|
||||
if (mInitException != null) {
|
||||
throw mInitException;
|
||||
}
|
||||
}
|
||||
|
||||
private void waitInitialization() {
|
||||
if (mInitialized)
|
||||
throw new IllegalStateException("already initialized");
|
||||
synchronized (mInitLock) {
|
||||
try {
|
||||
mInitLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
onInterrupted(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void onInterrupted(InterruptedException e) {
|
||||
if (mCallback == null) {
|
||||
exit();
|
||||
throw new ScriptInterruptedException();
|
||||
} else {
|
||||
mCallback.onInterrupted(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exit() {
|
||||
mTermSession.finish();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exitAndWaitFor() {
|
||||
execExitAndWait();
|
||||
if (!isRoot()) {
|
||||
return;
|
||||
}
|
||||
execExitAndWait();
|
||||
}
|
||||
|
||||
private void execExitAndWait() {
|
||||
synchronized (mExitLock) {
|
||||
mWaitingExit = true;
|
||||
exec("exit");
|
||||
try {
|
||||
mExitLock.wait();
|
||||
} catch (InterruptedException e) {
|
||||
onInterrupted(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
exit();
|
||||
}
|
||||
|
||||
private class MyShellTermSession extends ShellTermSession {
|
||||
|
||||
private BufferedReader mBufferedReader;
|
||||
private OutputStream mOutputStream;
|
||||
private Thread mReadingThread;
|
||||
|
||||
public MyShellTermSession(TermSettings settings, String initialCommand) throws IOException {
|
||||
super(settings, initialCommand);
|
||||
PipedInputStream pipedInputStream = new PipedInputStream(8192);
|
||||
mBufferedReader = new BufferedReader(new InputStreamReader(pipedInputStream));
|
||||
mOutputStream = new PipedOutputStream(pipedInputStream);
|
||||
startReadingThread();
|
||||
}
|
||||
|
||||
private void startReadingThread() {
|
||||
mReadingThread = new ThreadCompat(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
String line;
|
||||
try {
|
||||
while (!Thread.currentThread().isInterrupted()
|
||||
&& (line = mBufferedReader.readLine()) != null) {
|
||||
onNewLine(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
});
|
||||
mReadingThread.start();
|
||||
}
|
||||
|
||||
private void onNewLine(String line) {
|
||||
Log.d(TAG, line);
|
||||
if (!mInitialized) {
|
||||
if (isRoot() && line.endsWith(" $ su")) {
|
||||
notifyInitialized();
|
||||
return;
|
||||
}
|
||||
if (!isRoot() && line.endsWith(" $ sh")) {
|
||||
notifyInitialized();
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (mCallback != null) {
|
||||
mCallback.onNewLine(line);
|
||||
}
|
||||
if (mWaitingExit && line.endsWith(" exit")) {
|
||||
notifyExit();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void processInput(byte[] data, int offset, int count) {
|
||||
try {
|
||||
if (mCallback != null) {
|
||||
mCallback.onOutput(new String(data, offset, count));
|
||||
}
|
||||
mOutputStream.write(data, offset, count);
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new UncheckedIOException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyExit() {
|
||||
synchronized (mExitLock) {
|
||||
mWaitingExit = false;
|
||||
mExitLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
private void notifyInitialized() {
|
||||
mInitialized = true;
|
||||
synchronized (mInitLock) {
|
||||
mInitLock.notifyAll();
|
||||
}
|
||||
if (mCallback != null) {
|
||||
mCallback.onInitialized();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onProcessExit() {
|
||||
super.onProcessExit();
|
||||
synchronized (mExitLock) {
|
||||
mWaitingExit = false;
|
||||
mExitLock.notify();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void finish() {
|
||||
super.finish();
|
||||
mReadingThread.interrupt();
|
||||
try {
|
||||
mBufferedReader.close();
|
||||
mOutputStream.close();
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -4,10 +4,16 @@ import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.media.Image;
|
||||
import android.media.ImageReader;
|
||||
import android.media.ImageWriter;
|
||||
import android.media.MediaCodec;
|
||||
import android.os.Build;
|
||||
import android.provider.ContactsContract;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.view.Surface;
|
||||
|
||||
import com.stardust.autojs.runtime.AbstractScriptRuntime;
|
||||
import com.stardust.autojs.runtime.ScriptInterruptedException;
|
||||
@@ -100,6 +106,19 @@ public class Images {
|
||||
return bitmap;
|
||||
}
|
||||
|
||||
public static int pixel(Image image, int x, int y) {
|
||||
Image.Plane plane = image.getPlanes()[0];
|
||||
int offset = y * plane.getRowStride() + x * plane.getPixelStride();
|
||||
return plane.getBuffer().getInt(offset);
|
||||
}
|
||||
|
||||
public static int pixel(Bitmap bitmap, int x, int y) {
|
||||
return bitmap.getPixel(x, y);
|
||||
}
|
||||
|
||||
public static Bitmap read(String path) {
|
||||
return BitmapFactory.decodeFile(path);
|
||||
}
|
||||
|
||||
public static void saveBitmap(Bitmap bitmap, String path) {
|
||||
try {
|
||||
@@ -109,12 +128,21 @@ public class Images {
|
||||
}
|
||||
}
|
||||
|
||||
public static void saveBitmap(Bitmap bitmap, String path, int width, int height) {
|
||||
if (width != bitmap.getWidth() || height != bitmap.getHeight()) {
|
||||
Bitmap scaleBitmap = scaleBitmap(bitmap, width, height);
|
||||
saveBitmap(scaleBitmap, path);
|
||||
if (scaleBitmap != bitmap) {
|
||||
scaleBitmap.recycle();
|
||||
}
|
||||
} else {
|
||||
saveBitmap(bitmap, path);
|
||||
}
|
||||
}
|
||||
|
||||
public void saveImage(Image image, String path, int width, int height) {
|
||||
Bitmap bitmap = toBitmap(image);
|
||||
if (width != bitmap.getWidth() || height != bitmap.getHeight()) {
|
||||
bitmap = scaleBitmap(bitmap, width, height);
|
||||
}
|
||||
saveBitmap(bitmap, path);
|
||||
saveBitmap(bitmap, path, width, height);
|
||||
bitmap.recycle();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.stardust.autojs.runtime.api.image;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.annotation.RequiresApi;
|
||||
import android.support.v7.app.AppCompatActivity;
|
||||
|
||||
import com.stardust.app.OnActivityResultDelegate;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/22.
|
||||
*/
|
||||
|
||||
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
|
||||
public class ScreenCaptureRequestActivity extends AppCompatActivity {
|
||||
|
||||
|
||||
private static ScreenCaptureRequester.Callback sCallback;
|
||||
|
||||
public static void request(Context context, ScreenCaptureRequester.Callback callback) {
|
||||
if (sCallback != null) {
|
||||
return;
|
||||
}
|
||||
sCallback = callback;
|
||||
context.startActivity(new Intent(context, ScreenCaptureRequestActivity.class)
|
||||
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
|
||||
}
|
||||
|
||||
private OnActivityResultDelegate.Mediator mOnActivityResultDelegateMediator = new OnActivityResultDelegate.Mediator();
|
||||
|
||||
@Override
|
||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
ScreenCaptureRequester requester = new ScreenCaptureRequester.ActivityScreenCaptureRequester(mOnActivityResultDelegateMediator, this);
|
||||
requester.setOnActivityResultCallback(sCallback);
|
||||
requester.request();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
|
||||
mOnActivityResultDelegateMediator.onActivityResult(requestCode, resultCode, data);
|
||||
finish();
|
||||
sCallback = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
package com.stardust.autojs.runtime.api.ui;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.content.DialogInterface;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.afollestad.materialdialogs.DialogAction;
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.stardust.concurrent.VolatileBox;
|
||||
import com.stardust.util.UiHandler;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/8.
|
||||
*/
|
||||
|
||||
public class BlockedMaterialDialog extends MaterialDialog {
|
||||
|
||||
protected BlockedMaterialDialog(MaterialDialog.Builder builder) {
|
||||
super(builder);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if (!isActivityContext(getContext())) {
|
||||
getWindow().setType(WindowManager.LayoutParams.TYPE_PHONE);
|
||||
}
|
||||
super.show();
|
||||
}
|
||||
|
||||
private boolean isActivityContext(Context context) {
|
||||
if (context == null)
|
||||
return false;
|
||||
if (context instanceof Activity)
|
||||
return true;
|
||||
if (context instanceof ContextWrapper) {
|
||||
return isActivityContext(((ContextWrapper) context).getBaseContext());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static class Builder extends MaterialDialog.Builder {
|
||||
|
||||
private UiHandler mUiHandler;
|
||||
|
||||
public Builder(Context context, UiHandler uiHandler) {
|
||||
super(context);
|
||||
mUiHandler = uiHandler;
|
||||
}
|
||||
|
||||
public MaterialDialog.Builder input(@Nullable CharSequence hint, @Nullable CharSequence prefill, boolean allowEmptyInput, final VolatileBox<String> result) {
|
||||
dismissListener(result);
|
||||
super.input(hint, prefill, allowEmptyInput, new MaterialDialog.InputCallback() {
|
||||
@Override
|
||||
public void onInput(@NonNull MaterialDialog dialog, CharSequence input) {
|
||||
result.set(input.toString());
|
||||
synchronized (result) {
|
||||
result.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder confirm(final VolatileBox<Boolean> result) {
|
||||
onAny(new SingleButtonCallback() {
|
||||
@Override
|
||||
public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) {
|
||||
if (which == DialogAction.POSITIVE) {
|
||||
result.setAndNotify(true);
|
||||
} else {
|
||||
result.setAndNotify(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public MaterialDialog.Builder itemsCallback(final VolatileBox<Integer> result) {
|
||||
dismissListener(result);
|
||||
super.itemsCallback(new ListCallback() {
|
||||
@Override
|
||||
public void onSelection(MaterialDialog dialog, View itemView, int position, CharSequence text) {
|
||||
result.setAndNotify(position);
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public MaterialDialog.Builder itemsCallbackMultiChoice(@Nullable Integer[] selectedIndices, final VolatileBox<Integer[]> result) {
|
||||
dismissListener(result);
|
||||
super.itemsCallbackMultiChoice(selectedIndices, new ListCallbackMultiChoice() {
|
||||
@Override
|
||||
public boolean onSelection(MaterialDialog dialog, Integer[] which, CharSequence[] text) {
|
||||
result.setAndNotify(which);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public MaterialDialog.Builder itemsCallbackSingleChoice(int selectedIndex, final VolatileBox<Integer> result) {
|
||||
dismissListener(result);
|
||||
super.itemsCallbackSingleChoice(selectedIndex, new ListCallbackSingleChoice() {
|
||||
@Override
|
||||
public boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) {
|
||||
result.setAndNotify(which);
|
||||
return true;
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder dismissListener(final VolatileBox<?> result) {
|
||||
super.dismissListener(new OnDismissListener() {
|
||||
@Override
|
||||
public void onDismiss(DialogInterface dialog) {
|
||||
synchronized (result) {
|
||||
result.notify();
|
||||
}
|
||||
}
|
||||
});
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialDialog show() {
|
||||
mUiHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Builder.super.show();
|
||||
}
|
||||
});
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MaterialDialog build() {
|
||||
return new BlockedMaterialDialog(this);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package com.stardust.autojs.runtime.api.ui;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.text.TextUtils;
|
||||
import android.view.ContextThemeWrapper;
|
||||
|
||||
import com.afollestad.materialdialogs.MaterialDialog;
|
||||
import com.afollestad.materialdialogs.Theme;
|
||||
import com.stardust.autojs.R;
|
||||
import com.stardust.autojs.runtime.ScriptInterface;
|
||||
import com.stardust.autojs.runtime.ScriptInterruptedException;
|
||||
import com.stardust.autojs.runtime.api.AppUtils;
|
||||
import com.stardust.concurrent.VolatileBox;
|
||||
import com.stardust.util.ArrayUtils;
|
||||
import com.stardust.util.UiHandler;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/8.
|
||||
*/
|
||||
|
||||
public class Dialogs {
|
||||
|
||||
private AppUtils mAppUtils;
|
||||
private UiHandler mUiHandler;
|
||||
|
||||
public Dialogs(AppUtils appUtils, UiHandler uiHandler) {
|
||||
mAppUtils = appUtils;
|
||||
mUiHandler = uiHandler;
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public String rawInput(String title, String prefill) {
|
||||
VolatileBox<String> result = new VolatileBox<>(null);
|
||||
dialogBuilder()
|
||||
.input(null, prefill, true, result)
|
||||
.title(title)
|
||||
.show();
|
||||
return result.blockedGetOrThrow(ScriptInterruptedException.class);
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public void alert(String title, String content) {
|
||||
VolatileBox<Void> lock = new VolatileBox<>();
|
||||
MaterialDialog.Builder builder = dialogBuilder()
|
||||
.dismissListener(lock)
|
||||
.title(title)
|
||||
.positiveText(R.string.ok);
|
||||
if (!TextUtils.isEmpty(content)) {
|
||||
builder.content(content);
|
||||
}
|
||||
builder.show();
|
||||
lock.blockedGetOrThrow(ScriptInterruptedException.class);
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public boolean confirm(String title, String content) {
|
||||
VolatileBox<Boolean> result = new VolatileBox<>(false);
|
||||
MaterialDialog.Builder builder = dialogBuilder()
|
||||
.dismissListener(result)
|
||||
.confirm(result)
|
||||
.title(title)
|
||||
.positiveText(R.string.ok)
|
||||
.negativeText(R.string.cancel);
|
||||
if (!TextUtils.isEmpty(content)) {
|
||||
builder.content(content);
|
||||
}
|
||||
builder.show();
|
||||
return result.blockedGetOrThrow(ScriptInterruptedException.class);
|
||||
}
|
||||
|
||||
private Context getContext() {
|
||||
return mUiHandler.getContext();
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public int select(String title, String... items) {
|
||||
VolatileBox<Integer> result = new VolatileBox<>(-1);
|
||||
dialogBuilder()
|
||||
.itemsCallback(result)
|
||||
.title(title)
|
||||
.items((CharSequence[]) items)
|
||||
.show();
|
||||
return result.blockedGetOrThrow(ScriptInterruptedException.class);
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public int singleChoice(String title, int selectedIndex, String... items) {
|
||||
VolatileBox<Integer> result = new VolatileBox<>(-1);
|
||||
dialogBuilder()
|
||||
.itemsCallbackSingleChoice(selectedIndex, result)
|
||||
.title(title)
|
||||
.positiveText(R.string.ok)
|
||||
.items((CharSequence[]) items)
|
||||
.show();
|
||||
return result.blockedGetOrThrow(ScriptInterruptedException.class);
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public int[] multiChoice(String title, int[] indices, String... items) {
|
||||
VolatileBox<Integer[]> result = new VolatileBox<>(new Integer[0]);
|
||||
dialogBuilder()
|
||||
.itemsCallbackMultiChoice(ArrayUtils.box(indices), result)
|
||||
.title(title)
|
||||
.positiveText(R.string.ok)
|
||||
.items((CharSequence[]) items)
|
||||
.show();
|
||||
return ArrayUtils.unbox(result.blockedGetOrThrow(ScriptInterruptedException.class));
|
||||
}
|
||||
|
||||
|
||||
private BlockedMaterialDialog.Builder dialogBuilder() {
|
||||
Context context = mAppUtils.getCurrentActivity();
|
||||
if (context == null) {
|
||||
context = getContext();
|
||||
}
|
||||
return (BlockedMaterialDialog.Builder) new BlockedMaterialDialog.Builder(context, mUiHandler)
|
||||
.theme(Theme.LIGHT);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package com.stardust.autojs.runtime.console;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.ContextWrapper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.view.ContextThemeWrapper;
|
||||
import android.view.View;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.stardust.enhancedfloaty.FloatyService;
|
||||
import com.stardust.enhancedfloaty.ResizableExpandableFloaty;
|
||||
import com.stardust.enhancedfloaty.ResizableExpandableFloatyWindow;
|
||||
import com.stardust.util.ViewUtil;
|
||||
import com.stardust.autojs.R;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/4/20.
|
||||
*/
|
||||
|
||||
public class ConsoleFloaty extends ResizableExpandableFloaty.AbstractResizableExpandableFloaty {
|
||||
|
||||
private ContextWrapper mContextWrapper;
|
||||
private View mResizer, mMoveCursor;
|
||||
private TextView mTitleView;
|
||||
private StardustConsole mConsole;
|
||||
private CharSequence mTitle;
|
||||
|
||||
public ConsoleFloaty(StardustConsole console) {
|
||||
mConsole = console;
|
||||
setShouldRequestFocusWhenExpand(false);
|
||||
setInitialX(100);
|
||||
setInitialY(1000);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View inflateCollapsedView(FloatyService service, final ResizableExpandableFloatyWindow window) {
|
||||
ensureContextWrapper(service);
|
||||
View view = View.inflate(mContextWrapper, R.layout.floating_window_collapse, null);
|
||||
view.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
window.expand();
|
||||
}
|
||||
});
|
||||
return view;
|
||||
}
|
||||
|
||||
private void ensureContextWrapper(Context context) {
|
||||
if (mContextWrapper == null) {
|
||||
mContextWrapper = new ContextThemeWrapper(context, R.style.ConsoleTheme);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public View inflateExpandedView(FloatyService service, ResizableExpandableFloatyWindow window) {
|
||||
ensureContextWrapper(service);
|
||||
View view = View.inflate(mContextWrapper, R.layout.floating_console_expand, null);
|
||||
setListeners(view, window);
|
||||
setUpConsole(view, window);
|
||||
setInitialMeasure(view);
|
||||
return view;
|
||||
}
|
||||
|
||||
private void setInitialMeasure(final View view) {
|
||||
view.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ViewUtil.setViewMeasure(view, 800, 800);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initConsoleTitle(View view) {
|
||||
mTitleView = (TextView) view.findViewById(R.id.title);
|
||||
if (mTitle != null) {
|
||||
mTitleView.setText(mTitle);
|
||||
}
|
||||
}
|
||||
|
||||
private void setListeners(final View view, final ResizableExpandableFloatyWindow window) {
|
||||
setWindowOperationIconListeners(view, window);
|
||||
}
|
||||
|
||||
private void setUpConsole(View view, ResizableExpandableFloatyWindow window) {
|
||||
ConsoleView consoleView = (ConsoleView) view.findViewById(R.id.console);
|
||||
consoleView.setConsole(mConsole);
|
||||
consoleView.setWindow(window);
|
||||
initConsoleTitle(view);
|
||||
}
|
||||
|
||||
private void setWindowOperationIconListeners(View view, final ResizableExpandableFloatyWindow window) {
|
||||
view.findViewById(R.id.close).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
window.close();
|
||||
}
|
||||
});
|
||||
view.findViewById(R.id.move_or_resize).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mMoveCursor.getVisibility() == View.VISIBLE) {
|
||||
mMoveCursor.setVisibility(View.GONE);
|
||||
mResizer.setVisibility(View.GONE);
|
||||
} else {
|
||||
mMoveCursor.setVisibility(View.VISIBLE);
|
||||
mResizer.setVisibility(View.VISIBLE);
|
||||
}
|
||||
}
|
||||
});
|
||||
view.findViewById(R.id.minimize).setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
window.collapse();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View getResizerView(View expandedView) {
|
||||
mResizer = expandedView.findViewById(R.id.resizer);
|
||||
return mResizer;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public View getMoveCursorView(View expandedView) {
|
||||
mMoveCursor = expandedView.findViewById(R.id.move_cursor);
|
||||
return mMoveCursor;
|
||||
}
|
||||
|
||||
public void setTitle(final CharSequence title) {
|
||||
mTitle = title;
|
||||
if (mTitleView != null) {
|
||||
mTitleView.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTitleView.setText(title);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
package com.stardust.autojs.runtime.console;
|
||||
|
||||
import android.content.Context;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.Spanned;
|
||||
import android.text.method.ScrollingMovementMethod;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import android.util.AttributeSet;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
import android.widget.Button;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.ScrollView;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.stardust.autojs.R;
|
||||
import com.stardust.autojs.runtime.console.StardustConsole;
|
||||
import com.stardust.enhancedfloaty.ResizableExpandableFloatyWindow;
|
||||
import com.stardust.util.SparseArrayEntries;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/2.
|
||||
*/
|
||||
public class ConsoleView extends FrameLayout implements StardustConsole.LogListener {
|
||||
|
||||
private static final SparseArray<Integer> COLORS = new SparseArrayEntries<Integer>()
|
||||
.entry(Log.VERBOSE, 0xdfc0c0c0)
|
||||
.entry(Log.DEBUG, 0xdfffffff)
|
||||
.entry(Log.INFO, 0xff64dd17)
|
||||
.entry(Log.WARN, 0xff2962ff)
|
||||
.entry(Log.ERROR, 0xffd50000)
|
||||
.entry(Log.ASSERT, 0xffff534e)
|
||||
.sparseArray();
|
||||
|
||||
private StardustConsole mConsole;
|
||||
private TextView mTextView;
|
||||
private EditText mEditText;
|
||||
private ResizableExpandableFloatyWindow mWindow;
|
||||
private LinearLayout mInputContainer;
|
||||
private ScrollView mContentContainer;
|
||||
|
||||
public ConsoleView(Context context) {
|
||||
super(context);
|
||||
init();
|
||||
}
|
||||
|
||||
public ConsoleView(Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
init();
|
||||
}
|
||||
|
||||
public ConsoleView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
init();
|
||||
}
|
||||
|
||||
private void init() {
|
||||
inflate(getContext(), R.layout.console_view, this);
|
||||
mTextView = (TextView) findViewById(R.id.content);
|
||||
mTextView.setMovementMethod(new ScrollingMovementMethod());
|
||||
mContentContainer = (ScrollView) findViewById(R.id.content_container);
|
||||
initEditText();
|
||||
initSubmitButton();
|
||||
}
|
||||
|
||||
private void initSubmitButton() {
|
||||
final Button submit = (Button) findViewById(R.id.submit);
|
||||
submit.setOnClickListener(new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
CharSequence input = mEditText.getText();
|
||||
submitInput(input);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void submitInput(CharSequence input) {
|
||||
if (android.text.TextUtils.isEmpty(input)) {
|
||||
return;
|
||||
}
|
||||
if (mConsole.submitInput(input)) {
|
||||
mEditText.setText("");
|
||||
}
|
||||
}
|
||||
|
||||
private void initEditText() {
|
||||
mEditText = (EditText) findViewById(R.id.input);
|
||||
mEditText.setFocusableInTouchMode(true);
|
||||
mInputContainer = (LinearLayout) findViewById(R.id.input_container);
|
||||
OnClickListener listener = new OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
if (mWindow != null) {
|
||||
mWindow.requestWindowFocus();
|
||||
mEditText.requestFocus();
|
||||
}
|
||||
}
|
||||
};
|
||||
mEditText.setOnClickListener(listener);
|
||||
mInputContainer.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
public void setConsole(StardustConsole console) {
|
||||
mConsole = console;
|
||||
mConsole.setConsoleView(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onNewLog(StardustConsole.Log log) {
|
||||
log(log.level, log.content);
|
||||
}
|
||||
|
||||
private void log(int level, CharSequence log) {
|
||||
int color = getColorForLevel(level);
|
||||
final Spannable spannable = buildColorSpannable(log, color);
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mTextView.append(spannable);
|
||||
mContentContainer.fullScroll(View.FOCUS_DOWN);
|
||||
mEditText.requestFocus();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
private Spannable buildColorSpannable(CharSequence log, int color) {
|
||||
Spannable spannable = new SpannableString(log);
|
||||
spannable.setSpan(new ForegroundColorSpan(color), 0, log.length(), Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
|
||||
return spannable;
|
||||
}
|
||||
|
||||
private int getColorForLevel(int level) {
|
||||
return COLORS.get(level);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLogClear() {
|
||||
mTextView.setText("");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onWindowVisibilityChanged(int visibility) {
|
||||
super.onWindowVisibilityChanged(visibility);
|
||||
if (visibility == VISIBLE) {
|
||||
refreshLog();
|
||||
}
|
||||
}
|
||||
|
||||
private void refreshLog() {
|
||||
if (mConsole != null) {
|
||||
mTextView.setText("");
|
||||
for (StardustConsole.Log log : mConsole.getLogs()) {
|
||||
onNewLog(log);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setWindow(ResizableExpandableFloatyWindow window) {
|
||||
mWindow = window;
|
||||
}
|
||||
|
||||
public void showEditText() {
|
||||
post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
mWindow.requestWindowFocus();
|
||||
mInputContainer.setVisibility(VISIBLE);
|
||||
mEditText.requestFocus();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,194 @@
|
||||
package com.stardust.autojs.runtime.console;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import com.stardust.autojs.runtime.ScriptInterface;
|
||||
import com.stardust.autojs.runtime.ScriptInterruptedException;
|
||||
import com.stardust.autojs.runtime.api.AbstractConsole;
|
||||
import com.stardust.autojs.runtime.api.Console;
|
||||
import com.stardust.enhancedfloaty.FloatyService;
|
||||
import com.stardust.enhancedfloaty.ResizableExpandableFloatyWindow;
|
||||
import com.stardust.util.FloatingWindowUtils;
|
||||
import com.stardust.util.IntentUtil;
|
||||
import com.stardust.util.UiHandler;
|
||||
import com.stardust.autojs.R;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/5/2.
|
||||
*/
|
||||
|
||||
public class StardustConsole extends AbstractConsole {
|
||||
|
||||
public static class Log {
|
||||
|
||||
public int level;
|
||||
public CharSequence content;
|
||||
|
||||
public Log(int level, CharSequence content) {
|
||||
this.level = level;
|
||||
this.content = content;
|
||||
}
|
||||
}
|
||||
|
||||
public interface LogListener {
|
||||
void onNewLog(Log log);
|
||||
|
||||
void onLogClear();
|
||||
}
|
||||
|
||||
private final Console mGlobalConsole;
|
||||
private List<Log> mLogs = new ArrayList<>();
|
||||
private ResizableExpandableFloatyWindow mFloatyWindow;
|
||||
private ConsoleFloaty mConsoleFloaty;
|
||||
private LogListener mLogListener;
|
||||
private UiHandler mUiHandler;
|
||||
private BlockingQueue<String> mInput = new ArrayBlockingQueue<>(1);
|
||||
private ConsoleView mConsoleView;
|
||||
private volatile boolean mShown = false;
|
||||
|
||||
public StardustConsole(UiHandler uiHandler) {
|
||||
this(uiHandler, null);
|
||||
}
|
||||
|
||||
public StardustConsole(UiHandler uiHandler, Console globalConsole) {
|
||||
mUiHandler = uiHandler;
|
||||
mConsoleFloaty = new ConsoleFloaty(this);
|
||||
mFloatyWindow = new ResizableExpandableFloatyWindow(mConsoleFloaty);
|
||||
mGlobalConsole = globalConsole;
|
||||
}
|
||||
|
||||
public void setConsoleView(ConsoleView consoleView) {
|
||||
mConsoleView = consoleView;
|
||||
setLogListener(consoleView);
|
||||
synchronized (this) {
|
||||
this.notify();
|
||||
}
|
||||
}
|
||||
|
||||
public void setLogListener(LogListener logListener) {
|
||||
mLogListener = logListener;
|
||||
}
|
||||
|
||||
public List<Log> getLogs() {
|
||||
return mLogs;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void println(int level, CharSequence charSequence) {
|
||||
Log log = new Log(level, charSequence + "\n");
|
||||
mLogs.add(log);
|
||||
if (mGlobalConsole != null) {
|
||||
mGlobalConsole.println(level, charSequence);
|
||||
}
|
||||
if (mLogListener != null) {
|
||||
mLogListener.onNewLog(log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void write(int level, CharSequence charSequence) {
|
||||
Log log = new Log(level, charSequence);
|
||||
mLogs.add(log);
|
||||
if (mGlobalConsole != null) {
|
||||
mGlobalConsole.print(level, charSequence);
|
||||
}
|
||||
if (mLogListener != null) {
|
||||
mLogListener.onNewLog(log);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void clear() {
|
||||
mLogs.clear();
|
||||
if (mLogListener != null) {
|
||||
mLogListener.onLogClear();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void show() {
|
||||
if (!FloatingWindowUtils.hasOverlayPermission(mUiHandler.getContext())) {
|
||||
IntentUtil.goToAppDetailSettings(mUiHandler.getContext());
|
||||
mUiHandler.toast(R.string.text_no_floating_window_permission);
|
||||
}
|
||||
startFloatyService();
|
||||
mUiHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
FloatyService.addWindow(mFloatyWindow);
|
||||
} catch (WindowManager.BadTokenException e) {
|
||||
e.printStackTrace();
|
||||
mUiHandler.toast(R.string.text_no_floating_window_permission);
|
||||
}
|
||||
}
|
||||
});
|
||||
mShown = true;
|
||||
}
|
||||
|
||||
private void startFloatyService() {
|
||||
Context context = mUiHandler.getContext();
|
||||
context.startService(new Intent(context, FloatyService.class));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void hide() {
|
||||
try {
|
||||
mFloatyWindow.close();
|
||||
} catch (IllegalArgumentException ignored) {
|
||||
|
||||
}
|
||||
mShown = false;
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public String rawInput() {
|
||||
if (mConsoleView == null) {
|
||||
if (!mShown) {
|
||||
show();
|
||||
}
|
||||
waitConsoleView();
|
||||
}
|
||||
mConsoleView.showEditText();
|
||||
try {
|
||||
return mInput.take();
|
||||
} catch (InterruptedException e) {
|
||||
throw new ScriptInterruptedException();
|
||||
}
|
||||
}
|
||||
|
||||
private void waitConsoleView() {
|
||||
synchronized (this) {
|
||||
try {
|
||||
this.wait();
|
||||
} catch (InterruptedException e) {
|
||||
throw new ScriptInterruptedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ScriptInterface
|
||||
public String rawInput(Object data, Object... param) {
|
||||
log(data, param);
|
||||
return rawInput();
|
||||
}
|
||||
|
||||
boolean submitInput(@NonNull CharSequence input) {
|
||||
return mInput.offer(input.toString());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setTitle(CharSequence title) {
|
||||
mConsoleFloaty.setTitle(title);
|
||||
}
|
||||
}
|
||||
BIN
autojs/src/main/res/drawable-hdpi/ic_close_white_24dp.png
Normal file
|
After Width: | Height: | Size: 221 B |
BIN
autojs/src/main/res/drawable-hdpi/ic_debug_console.png
Normal file
|
After Width: | Height: | Size: 548 B |
|
After Width: | Height: | Size: 367 B |
BIN
autojs/src/main/res/drawable-mdpi/ic_close_white_24dp.png
Normal file
|
After Width: | Height: | Size: 175 B |
BIN
autojs/src/main/res/drawable-mdpi/ic_debug_console.png
Normal file
|
After Width: | Height: | Size: 352 B |
|
After Width: | Height: | Size: 269 B |
BIN
autojs/src/main/res/drawable-xhdpi/ic_close_white_24dp.png
Normal file
|
After Width: | Height: | Size: 257 B |
BIN
autojs/src/main/res/drawable-xhdpi/ic_debug_console.png
Normal file
|
After Width: | Height: | Size: 726 B |
|
After Width: | Height: | Size: 456 B |
BIN
autojs/src/main/res/drawable-xxhdpi/ic_close_white_24dp.png
Normal file
|
After Width: | Height: | Size: 347 B |
BIN
autojs/src/main/res/drawable-xxhdpi/ic_debug_console.png
Normal file
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 690 B |
BIN
autojs/src/main/res/drawable-xxxhdpi/ic_close_white_24dp.png
Normal file
|
After Width: | Height: | Size: 436 B |
BIN
autojs/src/main/res/drawable-xxxhdpi/ic_debug_console.png
Normal file
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 930 B |
8
autojs/src/main/res/drawable/circle_cool_black.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:innerRadius="0dp"
|
||||
android:shape="ring"
|
||||
android:thicknessRatio="2"
|
||||
android:useLevel="false">
|
||||
<solid android:color="#161616"/>
|
||||
</shape>
|
||||
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<solid android:color="#99262626"/>
|
||||
|
||||
<corners
|
||||
android:bottomLeftRadius="12dp"
|
||||
android:bottomRightRadius="12dp"/>
|
||||
</shape>
|
||||
9
autojs/src/main/res/drawable/floating_console_bg_top.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<solid android:color="#cc181818"/>
|
||||
|
||||
<corners
|
||||
android:topLeftRadius="12dp"
|
||||
android:topRightRadius="12dp"/>
|
||||
</shape>
|
||||
BIN
autojs/src/main/res/drawable/ic_move_cursor.png
Normal file
|
After Width: | Height: | Size: 905 B |
BIN
autojs/src/main/res/drawable/ic_remove_white_24dp.png
Normal file
|
After Width: | Height: | Size: 82 B |
BIN
autojs/src/main/res/drawable/ic_resizer.png
Normal file
|
After Width: | Height: | Size: 1.1 KiB |
53
autojs/src/main/res/layout/console_view.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ScrollView
|
||||
android:id="@+id/content_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/content"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:paddingLeft="3dp"
|
||||
android:textIsSelectable="true"
|
||||
android:textSize="14sp"
|
||||
tools:text="12345\n678\n9\n0"/>
|
||||
</ScrollView>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/input_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/input"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:lines="1"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="14sp"/>
|
||||
|
||||
<Button
|
||||
android:id="@+id/submit"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:text="@string/ok"
|
||||
android:textColor="@android:color/white"
|
||||
android:theme="@style/ConsoleTheme"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
106
autojs/src/main/res/layout/floating_console_expand.xml
Normal file
@@ -0,0 +1,106 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="12dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="40dp"
|
||||
android:background="@drawable/floating_console_bg_top"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/title"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_weight="1"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_vertical|left"
|
||||
android:maxLines="1"
|
||||
android:text="@string/text_console"
|
||||
android:textColor="@android:color/white"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentRight="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/minimize"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:paddingBottom="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingTop="24dp"
|
||||
android:scaleType="center"
|
||||
android:src="@drawable/ic_remove_white_24dp"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/move_or_resize"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_settings_ethernet_white_24dp"
|
||||
android:tint="@android:color/white"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/close"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?selectableItemBackgroundBorderless"
|
||||
android:padding="8dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_close_white_24dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<com.stardust.autojs.runtime.console.ConsoleView
|
||||
android:id="@+id/console"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/floating_console_bg_bottom"
|
||||
android:minHeight="200dp"/>
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/resizer"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:background="@drawable/circle_cool_black"
|
||||
android:padding="6dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_resizer"
|
||||
android:tint="@android:color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/move_cursor"
|
||||
android:layout_width="25dp"
|
||||
android:layout_height="25dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="@drawable/circle_cool_black"
|
||||
android:padding="5dp"
|
||||
android:scaleType="fitXY"
|
||||
android:src="@drawable/ic_move_cursor"
|
||||
android:tint="@android:color/white"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</RelativeLayout>
|
||||
13
autojs/src/main/res/layout/floating_window_collapse.xml
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="46dp"
|
||||
android:layout_height="46dp"
|
||||
android:background="@android:color/transparent"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="46dp"
|
||||
android:layout_height="46dp"
|
||||
android:padding="5dp"
|
||||
android:src="@drawable/ic_debug_console"/>
|
||||
</LinearLayout>
|
||||
@@ -8,5 +8,11 @@
|
||||
<string name="text_no_running_script">没有正在运行的脚本</string>
|
||||
<string name="text_already_stop_n_scripts">已停止%d个正在运行的脚本</string>
|
||||
<string name="text_requires_sdk_version_to_run_the_script">本脚本需要此安卓版本以上才能运行:</string>
|
||||
<string name="ok">确定</string>
|
||||
<string name="cancel">取消</string>
|
||||
<string name="text_console">控制台</string>
|
||||
<string name="text_no_floating_window_permission">没有悬浮窗权限</string>
|
||||
<string name="text_accessibility_service_description">使脚本自动操作(点击、长按、滑动等)所需,若关闭则只能执行不涉及自动操作的脚本。</string>
|
||||
|
||||
|
||||
</resources>
|
||||
|
||||
8
autojs/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<style name="ConsoleTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorButtonNormal">#cc181818</item>
|
||||
</style>
|
||||
</resources>
|
||||
10
autojs/src/main/res/xml/accessibility_service_config.xml
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes="typeAllMask"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFlags="flagReportViewIds|flagRequestEnhancedWebAccessibility|flagRequestFilterKeyEvents"
|
||||
android:canPerformGestures="true"
|
||||
android:canRequestEnhancedWebAccessibility="true"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:description="@string/text_accessibility_service_description"
|
||||
android:notificationTimeout="100"/>
|
||||