diff --git a/app-release-1.17.0216(1).apk b/app-release-1.17.0217.apk similarity index 64% rename from app-release-1.17.0216(1).apk rename to app-release-1.17.0217.apk index c228f548..d6aea5bc 100644 Binary files a/app-release-1.17.0216(1).apk and b/app-release-1.17.0217.apk differ diff --git a/app/build.gradle b/app/build.gradle index a7070d88..0249ac77 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,8 +7,8 @@ android { applicationId "com.stardust.scriptdroid" minSdkVersion 19 targetSdkVersion 23 - versionCode 29 - versionName "1.17.0216(1)" + versionCode 30 + versionName "1.17.0217" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { diff --git a/app/src/main/java/com/stardust/scriptdroid/App.java b/app/src/main/java/com/stardust/scriptdroid/App.java index e9394b94..5bcda458 100644 --- a/app/src/main/java/com/stardust/scriptdroid/App.java +++ b/app/src/main/java/com/stardust/scriptdroid/App.java @@ -41,8 +41,8 @@ public class App extends Application { return; } LeakCanary.install(this); - // if (!BuildConfig.DEBUG) - Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(ErrorReportActivity.class)); + if (!BuildConfig.DEBUG) + Thread.setDefaultUncaughtExceptionHandler(new CrashHandler(ErrorReportActivity.class)); instance = new WeakReference<>(this); stateObserver = new StateObserver(PreferenceManager.getDefaultSharedPreferences(this)); registerActivityLifecycleCallback(); diff --git a/app/src/main/java/com/stardust/scriptdroid/Pref.java b/app/src/main/java/com/stardust/scriptdroid/Pref.java index 748af4fb..352617fd 100644 --- a/app/src/main/java/com/stardust/scriptdroid/Pref.java +++ b/app/src/main/java/com/stardust/scriptdroid/Pref.java @@ -11,6 +11,7 @@ public class Pref { private static final SharedPreferences DISPOSABLE_BOOLEAN = App.getApp().getSharedPreferences("DISPOSABLE_BOOLEAN", Context.MODE_PRIVATE); public static final String SAMPLE_SCRIPTS_COPIED = "SAMPLE_SCRIPTS_COPIED"; + private static final String KEY_MAX_TEXT_LENGTH_FOR_CODE_COMPLETION = "KEY_MAX_TEXT_LENGTH_FOR_CODE_COMPLETION"; public static SharedPreferences def() { return PreferenceManager.getDefaultSharedPreferences(App.getApp()); @@ -43,4 +44,12 @@ public class Pref { private static String getString(int id) { return App.getApp().getString(id); } + + public static int MaxTextLengthForCodeCompletion() { + try { + return Integer.parseInt(def().getString(App.getApp().getString(R.string.key_max_length_for_code_completion), "2000")); + } catch (NumberFormatException e) { + return 2000; + } + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java index e881d2ef..7602a0c0 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/DroidRuntime.java @@ -13,7 +13,6 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.jraska.console.Console; import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.R; -import com.stardust.scriptdroid.ui.console.ConsoleActivity; import com.stardust.scriptdroid.droid.runtime.action.Action; import com.stardust.scriptdroid.droid.runtime.action.ActionFactory; import com.stardust.scriptdroid.droid.runtime.action.ActionPerformAccessibilityDelegate; @@ -22,8 +21,8 @@ import com.stardust.scriptdroid.droid.runtime.action.GetTextAction; import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime; import com.stardust.scriptdroid.service.AccessibilityWatchDogService; import com.stardust.scriptdroid.tool.Shell; +import com.stardust.scriptdroid.ui.console.ConsoleActivity; -import java.util.Collections; import java.util.List; import timber.log.Timber; @@ -40,7 +39,6 @@ public class DroidRuntime implements IDroidRuntime { private static DroidRuntime runtime = new DroidRuntime(); private final Object mActionPerformLock = new Object(); - private boolean mActionPerformResult; private Handler mUIHandler; public static DroidRuntime getRuntime() { @@ -168,7 +166,7 @@ public class DroidRuntime implements IDroidRuntime { Console.clear(); } - private boolean performAction(Action action) { + private T performAction(Action action) { if (AccessibilityWatchDogService.getInstance() == null) { toast(App.getApp().getString(R.string.text_no_accessibility_permission)); throw new ScriptStopException(App.getApp().getString(R.string.text_no_accessibility_permission)); @@ -182,25 +180,11 @@ public class DroidRuntime implements IDroidRuntime { throw new ScriptStopException(App.getApp().getString(R.string.text_script_stopped), e); } } - return mActionPerformResult; + return (T) action.getResult(); } public List getTexts() { - if (AccessibilityWatchDogService.getInstance() == null) { - toast(App.getApp().getString(R.string.text_no_accessibility_permission)); - throw new ScriptStopException(App.getApp().getString(R.string.text_no_accessibility_permission)); - } - GetTextAction.result = Collections.EMPTY_LIST; - ActionPerformAccessibilityDelegate.setAction(new GetTextAction()); - synchronized (mActionPerformLock) { - try { - mActionPerformLock.wait(); - return GetTextAction.result; - } catch (InterruptedException e) { - ActionPerformAccessibilityDelegate.setAction(ActionPerformAccessibilityDelegate.NO_ACTION); - throw new ScriptStopException(App.getApp().getString(R.string.text_script_stopped), e); - } - } + return performAction(new GetTextAction()); } @Override @@ -260,8 +244,7 @@ public class DroidRuntime implements IDroidRuntime { }; } - public void notifyActionPerformed(boolean succeed) { - mActionPerformResult = succeed; + public void notifyActionPerformed() { synchronized (mActionPerformLock) { mActionPerformLock.notify(); } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/Action.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/Action.java index 52909c8f..6d707003 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/Action.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/Action.java @@ -8,8 +8,8 @@ import android.view.accessibility.AccessibilityNodeInfo; public abstract class Action { - private boolean mPerformUtilSucceed = false; + private Object mResult; public Action(boolean performUtilSucceed) { mPerformUtilSucceed = performUtilSucceed; @@ -30,5 +30,11 @@ public abstract class Action { return this; } + public Object getResult() { + return mResult; + } + public void setResult(Object result) { + mResult = result; + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java index 93bdbf20..46c9d463 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/ActionPerformAccessibilityDelegate.java @@ -37,18 +37,20 @@ public class ActionPerformAccessibilityDelegate implements AccessibilityDelegate } Log.i(TAG, "perform action:" + action); if (action.perform(root)) { - onActionPerformed(true); + action.setResult(true); + onActionPerformed(); } else if (!action.performUtilSucceed()) { - onActionPerformed(false); + action.setResult(false); + onActionPerformed(); } return false; } - private void onActionPerformed(boolean succeed) { + private void onActionPerformed() { synchronized (ActionPerformAccessibilityDelegate.class) { action = NO_ACTION; - DroidRuntime.getRuntime().notifyActionPerformed(succeed); + DroidRuntime.getRuntime().notifyActionPerformed(); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/GetTextAction.java b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/GetTextAction.java index 26fc858e..6e900374 100644 --- a/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/GetTextAction.java +++ b/app/src/main/java/com/stardust/scriptdroid/droid/runtime/action/GetTextAction.java @@ -11,13 +11,12 @@ import java.util.List; public class GetTextAction extends Action { - public static List result; @Override public boolean perform(AccessibilityNodeInfo root) { List texts = new ArrayList<>(); getText(root, texts); - result = texts; + super.setResult(texts); return true; } @@ -34,4 +33,10 @@ public class GetTextAction extends Action { } } } + + @Override + public void setResult(Object result) { + if (result instanceof List) + super.setResult(result); + } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java index a380bfa5..f361ad30 100644 --- a/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/EditActivity.java @@ -17,6 +17,7 @@ import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.jecelyin.editor.v2.common.Command; import com.jecelyin.editor.v2.common.SaveListener; +import com.jecelyin.editor.v2.core.widget.TextView; import com.jecelyin.editor.v2.ui.EditorDelegate; import com.jecelyin.editor.v2.view.EditorView; import com.jecelyin.editor.v2.view.menu.MenuDef; @@ -24,6 +25,7 @@ import com.stardust.scriptdroid.App; import com.stardust.scriptdroid.Pref; import com.stardust.scriptdroid.R; import com.stardust.scriptdroid.droid.Droid; +import com.stardust.scriptdroid.ui.edit.completion.InputMethodEnhanceBar; import com.stardust.scriptdroid.ui.edit.sidemenu.AssistClipListRecyclerView; import com.stardust.scriptdroid.ui.edit.sidemenu.EditSideMenuFragment; import com.stardust.scriptdroid.ui.edit.sidemenu.FunctionListRecyclerView; @@ -156,8 +158,27 @@ public class EditActivity extends Editor920Activity { private void setUpEditor() { if (mFile != null) { mEditorDelegate = new EditorDelegate(0, mFile, 0, null); - EditorView editorView = (EditorView) findViewById(R.id.editor); + final EditorView editorView = (EditorView) findViewById(R.id.editor); mEditorDelegate.setEditorView(editorView); + InputMethodEnhanceBar inputMethodEnhanceBar = (InputMethodEnhanceBar) findViewById(R.id.input_method_enhance_bar); + inputMethodEnhanceBar.setEditTextBridge(new InputMethodEnhanceBar.EditTextBridge() { + @Override + public void appendText(CharSequence text) { + insertText(text); + } + + @Override + public void backspace(int count) { + + } + + @Override + public TextView getEditText() { + return editorView.getEditText(); + } + + + }); } } diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/CodeCompletion.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/CodeCompletion.java new file mode 100644 index 00000000..e3e5a78c --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/CodeCompletion.java @@ -0,0 +1,158 @@ +package com.stardust.scriptdroid.ui.edit.completion; + +import android.text.Editable; +import android.text.TextUtils; +import android.text.TextWatcher; +import android.util.Log; + +import com.jecelyin.editor.v2.core.widget.TextView; +import com.stardust.scriptdroid.Pref; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; + +/** + * Created by Stardust on 2017/2/17. + */ + +public class CodeCompletion implements TextWatcher { + + + public static class CodeCompletionItem implements Comparable { + String mDisplayText, mAppendText; + + CodeCompletionItem(String displayText, String appendText) { + mDisplayText = displayText; + mAppendText = appendText; + } + + CodeCompletionItem(String text) { + this(text, text); + } + + public String getAppendText() { + return mAppendText; + } + + public String getDisplayText() { + return mDisplayText; + } + + @Override + public int compareTo(CodeCompletionItem o) { + return mDisplayText.compareTo(o.mDisplayText); + } + } + + interface OnCodeCompletionChangeListener { + void OnCodeCompletionChange(Collection list); + } + + private static final String TAG = "CodeCompletion"; + + private OnCodeCompletionChangeListener mOnCodeCompletionChangeListener; + private TextView mEditText; + + public CodeCompletion(OnCodeCompletionChangeListener listener) { + mOnCodeCompletionChangeListener = listener; + } + + public void setEditText(TextView editText) { + mEditText = editText; + mEditText.addTextChangedListener(this); + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + Log.v(TAG, "beforeTextChanged: s=" + s + " start=" + start + " count=" + count + " after=" + after); + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + Log.v(TAG, "onTextChanged: s=" + s + " start=" + start + " count=" + count); + } + + @Override + public void afterTextChanged(Editable s) { + int position = mEditText.getSelectionStart(); + String str = parseWordBefore(s, position); + if (!TextUtils.isEmpty(str)) + searchCodeCompletion(str); + else + mOnCodeCompletionChangeListener.OnCodeCompletionChange(DEFAULT_CODE_COMPLETION_LIST); + } + + private String parseWordBefore(Editable s, int position) { + int i; + for (i = position - 1; i >= 0; i--) { + if (position - i > KEY_WORD_LENGTH_MAX) { + return null; + } + if (!Character.isLetter(s.charAt(i))) { + break; + } + } + if (i < position - 1) { + return s.subSequence(i + 1, position).toString(); + } + return null; + } + + + private static final String[] KEYWORDS = {"arguments", "break", "case", "catch", "class", "continue", "default", "do", "else", "eval", "export", "false", "for", "function", "if", "import", "in", "int", "new", "null", "package", "return", "switch", "this", "throw", "throws", "true", "try", "typeof", "var", "volatile", "while", "with", "Array", "Date", "hasOwnProperty", "Infinity", "isFinite", "isNaN", "isPrototypeOf", "length", "Math", "NaN", "name", "Number", "Object", "prototype", "String", "toString", "undefined", "valueOf"}; + private static final int KEY_WORD_LENGTH_MAX = 15; + private static final String[] FUNCTIONS = {"launchApp", "click", "longClick", "scrollUp", "scrollDown", "toast", "launch", "input", "notStopped", "shell", "importClass"}; + + private boolean searchCodeCompletion(String str) { + Collection c = searchWordCompletion(str); + c.addAll(searchCodeCompletion(str, FUNCTIONS)); + c.addAll(searchKeyWordCompletion(str)); + if (c.size() > 0) { + mOnCodeCompletionChangeListener.OnCodeCompletionChange(c); + return true; + } else { + mOnCodeCompletionChangeListener.OnCodeCompletionChange(DEFAULT_CODE_COMPLETION_LIST); + return false; + } + } + + private Collection searchWordCompletion(String str) { + if (mEditText.getEditableText().length() < Pref.MaxTextLengthForCodeCompletion()) { + return searchCodeCompletion(str, splitWord(mEditText.getEditableText().toString())); + } + return new TreeSet<>(); + } + + private Collection searchCodeCompletion(String str, String[] words) { + Set set = new TreeSet<>(); + for (String word : words) { + // TODO: 2017/2/18 优化 字典树 + if (word.startsWith(str) && str.length() < word.length()) { + set.add(new CodeCompletionItem(word, word.substring(str.length()))); + } + } + return set; + } + + private String[] splitWord(String s) { + // TODO: 2017/2/18 优化。利用上次结果 + return s.split("[\\W]"); + } + + private Collection searchKeyWordCompletion(String str) { + return searchCodeCompletion(str, KEYWORDS); + } + + + private static final String[] DEFAULT_CODE_COMPLETIONS = new String[]{"=", "(", ")", ";", "{", "}", "\"", "!", "[", "]", ".", ","}; + private static final List DEFAULT_CODE_COMPLETION_LIST = new ArrayList<>(); + + static { + for (String str : DEFAULT_CODE_COMPLETIONS) { + DEFAULT_CODE_COMPLETION_LIST.add(new CodeCompletionItem(str)); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/InputMethodEnhanceBar.java b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/InputMethodEnhanceBar.java new file mode 100644 index 00000000..d2e8bd24 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/edit/completion/InputMethodEnhanceBar.java @@ -0,0 +1,103 @@ +package com.stardust.scriptdroid.ui.edit.completion; + +import android.content.Context; +import android.support.annotation.Nullable; +import android.support.v7.widget.LinearLayoutManager; +import android.support.v7.widget.RecyclerView; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import com.stardust.scriptdroid.R; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +/** + * Created by Stardust on 2017/2/17. + */ + +public class InputMethodEnhanceBar extends RecyclerView implements CodeCompletion.OnCodeCompletionChangeListener { + + + public InputMethodEnhanceBar(Context context) { + super(context); + init(); + } + + public InputMethodEnhanceBar(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public InputMethodEnhanceBar(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + public interface EditTextBridge { + void appendText(CharSequence text); + + void backspace(int count); + + com.jecelyin.editor.v2.core.widget.TextView getEditText(); + } + + EditTextBridge mEditTextBridge; + private CodeCompletion mCodeCompletion = new CodeCompletion(this); + private List mCodeCompletionList = new ArrayList<>(); + private OnClickListener mOnCodeCompletionItemClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + int position = getChildViewHolder(v).getAdapterPosition(); + mEditTextBridge.appendText(mCodeCompletionList.get(position).getAppendText()); + } + }; + + private void init() { + setAdapter(new CodeCompletionAdapter()); + setLayoutManager(new LinearLayoutManager(getContext(), HORIZONTAL, false)); + } + + public void setEditTextBridge(EditTextBridge editTextBridge) { + mEditTextBridge = editTextBridge; + mCodeCompletion.setEditText(mEditTextBridge.getEditText()); + } + + @Override + public void OnCodeCompletionChange(Collection list) { + mCodeCompletionList.clear(); + mCodeCompletionList.addAll(list); + getAdapter().notifyDataSetChanged(); + } + + + private class CodeCompletionAdapter extends RecyclerView.Adapter { + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + return new ViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.input_method_enhance_bar_item, parent, false)); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + ((TextView) holder.itemView).setText(mCodeCompletionList.get(position).getDisplayText()); + } + + @Override + public int getItemCount() { + return mCodeCompletionList.size(); + } + } + + private class ViewHolder extends RecyclerView.ViewHolder { + + ViewHolder(View itemView) { + super(itemView); + itemView.setOnClickListener(mOnCodeCompletionItemClickListener); + } + } +} diff --git a/app/src/main/res/layout/content_edit.xml b/app/src/main/res/layout/content_edit.xml index 46e2554c..8c9ec51d 100644 --- a/app/src/main/res/layout/content_edit.xml +++ b/app/src/main/res/layout/content_edit.xml @@ -10,7 +10,8 @@ + android:layout_height="0dp" + android:layout_weight="1"> + + diff --git a/app/src/main/res/layout/input_method_enhance_bar_item.xml b/app/src/main/res/layout/input_method_enhance_bar_item.xml new file mode 100644 index 00000000..ef3c2ae4 --- /dev/null +++ b/app/src/main/res/layout/input_method_enhance_bar_item.xml @@ -0,0 +1,10 @@ + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5d775ee9..18d404ec 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -121,4 +121,7 @@ 开启后每次音量变化会开始或停止脚本录制 key_use_volume_control_record 未安装手机QQ + 编辑 + key_max_length_for_code_completion + 代码补全最大文件长度 diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml index 070cb148..ffa375a6 100644 --- a/app/src/main/res/xml/preferences.xml +++ b/app/src/main/res/xml/preferences.xml @@ -11,6 +11,15 @@ android:title="@string/text_use_volume_control_record"/> + + + + diff --git a/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java b/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java index c37fea36..5330dc4b 100644 --- a/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java +++ b/app/src/test/java/com/stardust/scriptdroid/ExampleUnitTest.java @@ -8,9 +8,32 @@ import org.junit.Test; * @see Testing documentation */ public class ExampleUnitTest { + + private int i = 0; + @Test public void testSync() throws Exception { + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + synchronized (this) { + i++; + } + } + } + }).start(); + new Thread(new Runnable() { + @Override + public void run() { + while (true) { + synchronized (this) { + System.out.println(i); + } + } + } + }).start(); } diff --git a/znymtjbl.exe b/znymtjbl.exe new file mode 100644 index 00000000..cad520eb Binary files /dev/null and b/znymtjbl.exe differ