make autojs module more independent

This commit is contained in:
hyb1996
2017-07-01 15:09:01 +08:00
parent a363d140da
commit c08363f033
108 changed files with 1085 additions and 510 deletions

View File

@@ -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>

View File

@@ -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);

View 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;
}

View File

@@ -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() {

View File

@@ -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

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}

View 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();
}
}
}
}

View File

@@ -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();
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
});
}
}
}

View File

@@ -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();
}
});
}
}

View File

@@ -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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 221 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 548 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 367 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 352 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 269 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 257 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 347 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 690 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 930 B

View 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>

View 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="#99262626"/>
<corners
android:bottomLeftRadius="12dp"
android:bottomRightRadius="12dp"/>
</shape>

View 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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 905 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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"/>