feat: java class searching and import; fix: editor crash on edit when file is too large

This commit is contained in:
hyb1996 2018-09-15 11:52:44 +08:00
parent fc0f5c479e
commit 02c64cd7b1
23 changed files with 697 additions and 30 deletions

View File

@ -8,8 +8,8 @@ android {
applicationId "org.autojs.autojs"
minSdkVersion 17
targetSdkVersion 23
versionCode 413
versionName "4.0.2 Alpha8"
versionCode 414
versionName "4.0.2 Alpha9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {

File diff suppressed because one or more lines are too long

View File

@ -30,7 +30,6 @@ import org.autojs.autojs.ui.error.ErrorReportActivity;
import com.stardust.theme.ThemeColor;
import com.stardust.theme.ThemeColorManager;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.crashreport.CrashReport;
@ -77,7 +76,7 @@ public class App extends MultiDexApplication {
CrashReport.initCrashReport(getApplicationContext(), BUGLY_APP_ID, false, strategy);
crashHandler.setDefaultHandler(Thread.getDefaultUncaughtExceptionHandler());
crashHandler.setBuglyHandler(Thread.getDefaultUncaughtExceptionHandler());
Thread.setDefaultUncaughtExceptionHandler(crashHandler);
}

View File

@ -8,6 +8,7 @@ import org.autojs.autojs.ui.widget.SimpleTextWatcher;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
/**
@ -20,6 +21,7 @@ public class AnyWordsCompletion implements SimpleTextWatcher.AfterTextChangedLis
private static final String PATTERN = "[\\W]";
private ExecutorService mExecutorService;
private volatile DictionaryTree<String> mDictionaryTree;
private AtomicInteger mExecuteId = new AtomicInteger();
public AnyWordsCompletion(ExecutorService executorService) {
mExecutorService = executorService;
@ -27,13 +29,21 @@ public class AnyWordsCompletion implements SimpleTextWatcher.AfterTextChangedLis
@Override
public void afterTextChanged(Editable s) {
mExecutorService.execute(() -> splitWords(s.toString()));
String str = s.toString();
int id = mExecuteId.incrementAndGet();
mExecutorService.execute(() -> splitWords(id, str));
}
private void splitWords(String s) {
private void splitWords(int id, String s) {
if(id != mExecuteId.get()){
return;
}
DictionaryTree<String> tree = new DictionaryTree<>();
String[] words = s.split(PATTERN);
for (String word : words) {
if(id != mExecuteId.get()){
return;
}
tree.putWord(word, word);
}
mDictionaryTree = tree;

View File

@ -1,18 +1,22 @@
package org.autojs.autojs.model.autocomplete;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.widget.EditText;
import org.autojs.autojs.model.indices.Module;
import org.autojs.autojs.model.indices.Modules;
import org.autojs.autojs.model.indices.Property;
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
import org.mozilla.javascript.ast.Loop;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -39,6 +43,8 @@ public class AutoCompletion {
private AutoCompleteCallback mAutoCompleteCallback;
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private AnyWordsCompletion mAnyWordsCompletion;
private AtomicInteger mExecuteId = new AtomicInteger();
private Handler mHandler = new Handler(Looper.getMainLooper());
public AutoCompletion(Context context, EditText editText) {
buildDictionaryTree(context);
@ -80,10 +86,19 @@ public class AutoCompletion {
if (mPropertyPrefill == null && module == null)
return;
String prefill = mPropertyPrefill;
int id = mExecuteId.incrementAndGet();
mExecutorService.execute(() -> {
if (id != mExecuteId.get())
return;
List<CodeCompletion> completions = findCodeCompletion(module, prefill);
CodeCompletions codeCompletions = new CodeCompletions(cursor, completions);
mAutoCompleteCallback.updateCodeCompletion(codeCompletions);
if (id != mExecuteId.get())
return;
mHandler.post(() -> {
if (id != mExecuteId.get())
return;
mAutoCompleteCallback.updateCodeCompletion(codeCompletions);
});
});
}

View File

@ -0,0 +1,37 @@
package org.autojs.autojs.model.indices;
import com.google.gson.annotations.SerializedName;
public class AndroidClass {
@SerializedName("class_name")
private String mClassName;
@SerializedName("package_name")
private String mPackageName;
@SerializedName("full_name")
private String mFullName;
public String getClassName() {
return mClassName;
}
public void setClassName(String className) {
mClassName = className;
}
public String getPackageName() {
return mPackageName;
}
public void setPackageName(String packageName) {
mPackageName = packageName;
}
public String getFullName() {
return mFullName;
}
public void setFullName(String fullName) {
mFullName = fullName;
}
}

View File

@ -0,0 +1,137 @@
package org.autojs.autojs.model.indices;
import android.annotation.SuppressLint;
import android.content.Context;
import android.text.TextUtils;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import org.autojs.autojs.tool.EmptyObservers;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import io.reactivex.Observable;
import io.reactivex.Single;
import io.reactivex.android.schedulers.AndroidSchedulers;
import io.reactivex.schedulers.Schedulers;
public class AndroidClassIndices {
static class LoadException extends RuntimeException {
LoadException(Throwable cause) {
super(cause);
}
}
@SuppressLint("StaticFieldLeak")
private static AndroidClassIndices sInstance;
private static Type ANDROID_CLASS_LIST_TYPE = new TypeToken<List<AndroidClass>>() {
}.getType();
//packageName -> package classes
private LinkedHashMap<String, ArrayList<ClassSearchingItem>> mPackages = new LinkedHashMap<>();
private Context mContext;
private ExecutorService mSingleThreadExecutor = new ThreadPoolExecutor(1, 1,
2, TimeUnit.MINUTES, new LinkedBlockingQueue<>());
private Throwable mLoadThrowable;
protected AndroidClassIndices(Context context) {
mContext = context.getApplicationContext();
load("indices/all_android_classes.json");
}
public static AndroidClassIndices getInstance(Context context) {
if (sInstance == null) {
sInstance = new AndroidClassIndices(context);
}
return sInstance;
}
public Single<List<ClassSearchingItem>> search(String keywords) {
return Single.fromCallable(() -> {
if (mPackages.isEmpty() && mLoadThrowable != null) {
throw new LoadException(mLoadThrowable);
}
return mPackages.values();
})
.map(packages -> search(packages, keywords))
.subscribeOn(Schedulers.from(mSingleThreadExecutor));
}
private List<ClassSearchingItem> search(Collection<ArrayList<ClassSearchingItem>> packages, String keywords) {
List<ClassSearchingItem> result = new ArrayList<>();
if (TextUtils.isEmpty(keywords)) {
for (ArrayList<ClassSearchingItem> packageClasses : packages) {
result.addAll(packageClasses);
}
} else {
for (ArrayList<ClassSearchingItem> packageClasses : packages) {
for (ClassSearchingItem item : packageClasses) {
if (item.matches(keywords)) {
result.add(item);
}
}
Collections.sort(result);
}
}
return result;
}
@SuppressLint("CheckResult")
private void load(String assetsPath) {
Observable.just(assetsPath)
.map(path -> mContext.getAssets().open(path))
.map(InputStreamReader::new)
.doOnNext(this::load)
.subscribeOn(Schedulers.from(mSingleThreadExecutor))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(EmptyObservers.consumer(), t -> {
mLoadThrowable = t;
t.printStackTrace();
});
}
private void load(Reader reader) throws IOException {
try {
Gson gson = new Gson();
ArrayList<AndroidClass> classes = gson.fromJson(reader, ANDROID_CLASS_LIST_TYPE);
load(classes);
} catch (RuntimeException e) {
throw e;
} finally {
reader.close();
}
}
private void load(ArrayList<AndroidClass> classes) {
mPackages.clear();
for (AndroidClass clazz : classes) {
String packageName = clazz.getPackageName();
ArrayList<ClassSearchingItem> packageClasses = mPackages.get(packageName);
if (packageClasses == null) {
packageClasses = new ArrayList<>();
mPackages.put(packageName, packageClasses);
packageClasses.add(new ClassSearchingItem.PackageItem(packageName));
}
packageClasses.add(new ClassSearchingItem.ClassItem(clazz));
}
}
}

View File

@ -0,0 +1,160 @@
package org.autojs.autojs.model.indices;
import android.support.annotation.NonNull;
import android.util.Log;
public abstract class ClassSearchingItem implements Comparable<ClassSearchingItem> {
static final String BASE_URL = "http://www.android-doc.com/reference/";
protected int rank;
public abstract boolean matches(String keywords);
public abstract String getLabel();
public abstract String getUrl();
@Override
public int compareTo(@NonNull ClassSearchingItem o) {
return Integer.compare(o.rank, rank);
}
protected int rank(String words, String keywords) {
int length = words.length();
int i = words.indexOf(keywords);
if (i < 0) {
return 0;
}
//full matches
if (i == 0 && keywords.length() == length) {
return 10;
}
//words ends with keywords
if (i + keywords.length() == length) {
if (i > 0 && words.charAt(i - 1) == '.') {
return 9;
}
return 8;
}
//package starts with keywords
if (i > 0 && words.charAt(i - 1) == '.') {
//package equals keywords
if (i < length - 1 && words.charAt(i + 1) == '.') {
return 7;
}
return 6;
}
//package ends with keywords
if (i < length - 1 && words.charAt(i + 1) == '.') {
return 6;
}
if (i == 0) {
return 5;
}
return 1;
}
@Override
public String toString() {
return "ClassSearchingItem{" + getLabel() + "}";
}
public abstract String getImportText();
public static class ClassItem extends ClassSearchingItem {
private final AndroidClass mAndroidClass;
public ClassItem(AndroidClass androidClass) {
mAndroidClass = androidClass;
}
@Override
public boolean matches(String keywords) {
rank = rank(mAndroidClass.getFullName(), keywords);
Log.d("ClassSearching", "rank = " + rank + ", word = " + mAndroidClass.getFullName());
return rank > 0;
}
public String getLabel() {
return String.format("%s (%s)", mAndroidClass.getClassName(), mAndroidClass.getPackageName());
}
@Override
public String getUrl() {
return BASE_URL + mAndroidClass.getPackageName().replace('.', '/')
+ "/" + mAndroidClass.getClassName() + ".html";
}
@Override
public String getImportText() {
return String.format("importClass(%s)", mAndroidClass.getFullName());
}
public AndroidClass getAndroidClass() {
return mAndroidClass;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ClassItem classItem = (ClassItem) o;
return mAndroidClass.equals(classItem.mAndroidClass);
}
@Override
public int hashCode() {
return mAndroidClass.hashCode();
}
}
public static class PackageItem extends ClassSearchingItem {
private final String mPackageName;
public PackageItem(String packageName) {
mPackageName = packageName;
}
@Override
public boolean matches(String keywords) {
rank = rank(mPackageName, keywords);
return rank > 0;
}
@Override
public String getLabel() {
return mPackageName;
}
@Override
public String getUrl() {
return BASE_URL + mPackageName.replace('.', '/') + "/package-summary.html";
}
@Override
public String getImportText() {
return String.format("importPackage(%s)", mPackageName);
}
public String getPackageName() {
return mPackageName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PackageItem that = (PackageItem) o;
return mPackageName.equals(that.mPackageName);
}
@Override
public int hashCode() {
return mPackageName.hashCode();
}
}
}

View File

@ -9,25 +9,16 @@ import android.content.Intent;
import android.os.Build;
import android.os.Looper;
import android.util.Log;
import android.view.View;
import android.view.WindowManager;
import android.widget.LinearLayout;
import android.widget.Toast;
import com.stardust.app.GlobalAppContext;
import org.autojs.autojs.App;
import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.R;
import org.mozilla.javascript.RhinoException;
import com.stardust.util.IntentUtil;
import com.stardust.view.accessibility.AccessibilityService;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.crashreport.BuglyLog;
import com.tencent.bugly.crashreport.CrashReport;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Map;
@ -36,21 +27,24 @@ public class CrashHandler extends CrashReport.CrashHandleCallback implements Unc
private static int crashCount = 0;
private static long firstCrashMillis = 0;
private final Class<?> mErrorReportClass;
private UncaughtExceptionHandler mDefaultHandler;
private UncaughtExceptionHandler mBuglyHandler;
private UncaughtExceptionHandler mSystemHandler;
public CrashHandler(Class<?> errorReportClass) {
this.mErrorReportClass = errorReportClass;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler();
mSystemHandler = Thread.getDefaultUncaughtExceptionHandler();
}
public void setDefaultHandler(UncaughtExceptionHandler defaultHandler) {
mDefaultHandler = defaultHandler;
public void setBuglyHandler(UncaughtExceptionHandler buglyHandler) {
mBuglyHandler = buglyHandler;
}
public void uncaughtException(Thread thread, Throwable ex) {
Log.e(TAG, "Uncaught Exception", ex);
if (thread != Looper.getMainLooper().getThread()) {
CrashReport.postCatchedException(ex, thread);
if(!(ex instanceof RhinoException)){
CrashReport.postCatchedException(ex, thread);
}
return;
}
AccessibilityService service = AccessibilityService.getInstance();
@ -60,7 +54,11 @@ public class CrashHandler extends CrashReport.CrashHandleCallback implements Unc
} else {
BuglyLog.d(TAG, "cannot disable service: " + service);
}
mDefaultHandler.uncaughtException(thread, ex);
if (BuildConfig.DEBUG) {
mSystemHandler.uncaughtException(thread, ex);
} else {
mBuglyHandler.uncaughtException(thread, ex);
}
}
@Override

View File

@ -0,0 +1,133 @@
package org.autojs.autojs.ui.edit;
import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import org.autojs.autojs.R;
import org.autojs.autojs.model.indices.AndroidClassIndices;
import org.autojs.autojs.model.indices.ClassSearchingItem;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import org.autojs.autojs.tool.MaterialDialogFactory;
import org.autojs.autojs.ui.widget.AutoAdapter;
import org.autojs.autojs.ui.widget.BindableViewHolder;
import org.autojs.autojs.ui.widget.SimpleTextWatcher;
import io.reactivex.android.schedulers.AndroidSchedulers;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;
public class ClassSearchDialogBuilder extends ThemeColorMaterialDialogBuilder {
public interface OnItemClickListener {
void onItemClick(MaterialDialog dialog, ClassSearchingItem item, int position);
}
private AutoAdapter<ClassSearchingItem> mResultListAdapter;
private MaterialProgressBar mProgressBar;
private EditText mKeywords;
private OnItemClickListener mOnItemClickListener;
private int mSearchId = 0;
private final Handler mHandler;
private MaterialDialog mDialog;
public ClassSearchDialogBuilder(@NonNull Context context) {
super(new ContextThemeWrapper(context, R.style.AppTheme));
mHandler = new Handler();
initViews(getContext());
AndroidClassIndices.getInstance(getContext());
}
public ClassSearchDialogBuilder setQuery(String text) {
mKeywords.setText(text);
return this;
}
public ClassSearchDialogBuilder itemClick(OnItemClickListener listener) {
mOnItemClickListener = listener;
return this;
}
private void initViews(Context context) {
View view = View.inflate(context, R.layout.dialog_class_search, null);
customView(view, true);
mKeywords = view.findViewById(R.id.keywords);
mKeywords.addTextChangedListener(new SimpleTextWatcher(this::postSearch));
mProgressBar = view.findViewById(R.id.progress_bar);
initResultList(view, context);
}
private void initResultList(View view, Context context) {
RecyclerView resultList = view.findViewById(R.id.result_list);
resultList.setLayoutManager(new LinearLayoutManager(context));
mResultListAdapter = new AutoAdapter<>(ClassSearchingItemViewHolder::new,
R.layout.item_class_searching_result_list);
resultList.setAdapter(mResultListAdapter);
}
@Override
public MaterialDialog build() {
mDialog = super.build();
return mDialog;
}
private void postSearch(CharSequence s) {
mSearchId++;
int searchId = mSearchId;
mHandler.postDelayed(() -> {
if (searchId == mSearchId) {
search(s);
}
}, 300);
}
@SuppressLint("CheckResult")
private void search(CharSequence s) {
mResultListAdapter.removeAll();
mProgressBar.setVisibility(View.VISIBLE);
String keywords = s.toString();
AndroidClassIndices.getInstance(getContext())
.search(keywords)
.observeOn(AndroidSchedulers.mainThread())
.subscribe(result -> {
mResultListAdapter.notifyDataSetChanged(result);
mProgressBar.setVisibility(View.GONE);
}, t -> {
t.printStackTrace();
mProgressBar.setVisibility(View.GONE);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_LONG).show();
});
}
private class ClassSearchingItemViewHolder extends BindableViewHolder<ClassSearchingItem> {
private final TextView mLabel;
ClassSearchingItemViewHolder(View itemView) {
super(itemView);
mLabel = itemView.findViewById(R.id.label);
itemView.setOnClickListener(v -> {
int pos = getAdapterPosition();
if (mOnItemClickListener != null) {
mOnItemClickListener.onItemClick(mDialog, mResultListAdapter.get(pos), pos);
}
});
}
@Override
public void bind(ClassSearchingItem data, int position) {
mLabel.setText(data.getLabel());
}
}
}

View File

@ -5,6 +5,7 @@ import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ActionMode;
import android.view.Menu;
import android.view.MenuItem;
@ -43,7 +44,8 @@ import static org.autojs.autojs.ui.edit.EditorView.EXTRA_READ_ONLY;
* Created by Stardust on 2017/1/29.
*/
@EActivity(R.layout.activity_edit)
public class EditActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost, PermissionRequestProxyActivity {
public class
EditActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost, PermissionRequestProxyActivity {
private OnActivityResultDelegate.Mediator mMediator = new OnActivityResultDelegate.Mediator();
@ -84,6 +86,18 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
setUpToolbar();
}
@Nullable
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback) {
return super.onWindowStartingActionMode(callback);
}
@Nullable
@Override
public ActionMode onWindowStartingActionMode(ActionMode.Callback callback, int type) {
return super.onWindowStartingActionMode(callback, type);
}
private void onLoadFileError(String message) {
new ThemeColorMaterialDialogBuilder(this)
.title(getString(R.string.text_cannot_read_file))
@ -117,6 +131,13 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
return super.onPrepareOptionsMenu(menu);
}
public boolean onPrepareActionMode(Menu menu) {
boolean isScriptRunning = mEditorView.getScriptExecutionId() != ScriptExecution.NO_ID;
MenuItem forceStopItem = menu.findItem(R.id.action_force_stop);
forceStopItem.setEnabled(isScriptRunning);
return super.onPrepareOptionsMenu(menu);
}
@Override
public void onActionModeStarted(ActionMode mode) {
Menu menu = mode.getMenu();

View File

@ -1,15 +1,21 @@
package org.autojs.autojs.ui.edit;
import android.annotation.SuppressLint;
import android.content.Context;
import android.support.design.widget.Snackbar;
import android.text.InputType;
import android.text.TextUtils;
import android.view.MenuItem;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.stardust.autojs.script.JavaScriptSource;
import com.stardust.autojs.script.ScriptSource;
import com.stardust.pio.PFiles;
import org.autojs.autojs.R;
import org.autojs.autojs.model.indices.AndroidClass;
import org.autojs.autojs.model.indices.ClassSearchingItem;
import org.autojs.autojs.ui.build.BuildActivity;
import org.autojs.autojs.ui.build.BuildActivity_;
import org.autojs.autojs.ui.common.NotAskAgainDialog;
@ -18,6 +24,7 @@ import org.autojs.autojs.ui.log.LogActivity_;
import org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.ClipboardUtil;
import com.stardust.util.IntentUtil;
import java.util.Locale;
@ -28,6 +35,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
* Created by Stardust on 2017/9/28.
*/
@SuppressLint("CheckResult")
public class EditorMenu {
private EditorView mEditorView;
@ -112,6 +120,9 @@ public class EditorMenu {
case R.id.action_console:
showConsole();
return true;
case R.id.action_import_java_class:
importJavaPackageOrClass();
return true;
case R.id.action_editor_text_size:
mEditorView.selectTextSize();
return true;
@ -132,6 +143,52 @@ public class EditorMenu {
return false;
}
private void importJavaPackageOrClass() {
mEditor.getSelection()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(s ->
new ClassSearchDialogBuilder(mContext)
.setQuery(s)
.itemClick((dialog, item, pos) -> showClassSearchingItem(dialog, item))
.title(R.string.text_search_java_class)
.show()
);
}
private void showClassSearchingItem(MaterialDialog dialog, ClassSearchingItem item) {
String title;
String desc;
if (item instanceof ClassSearchingItem.ClassItem) {
AndroidClass androidClass = ((ClassSearchingItem.ClassItem) item).getAndroidClass();
title = androidClass.getClassName();
desc = androidClass.getFullName();
} else {
title = ((ClassSearchingItem.PackageItem) item).getPackageName();
desc = title;
}
new ThemeColorMaterialDialogBuilder(mContext)
.title(title)
.content(desc)
.positiveText(R.string.text_copy)
.negativeText(R.string.text_en_import)
.neutralText(R.string.text_view_docs)
.onPositive((ignored, which) -> {
ClipboardUtil.setClip(mContext, desc);
Toast.makeText(mContext, R.string.text_already_copy_to_clip, Toast.LENGTH_SHORT).show();
dialog.dismiss();
})
.onNegative((ignored, which) -> {
if (mEditor.getText().startsWith(JavaScriptSource.EXECUTION_MODE_UI_PREFIX)) {
mEditor.insert(1, item.getImportText() + ";\n");
} else {
mEditor.insert(0, item.getImportText() + ";\n");
}
})
.onNeutral((ignored, which) -> IntentUtil.browse(mContext, item.getUrl()))
.onAny((ignored, which) -> dialog.dismiss())
.show();
}
private void startBuildApkActivity() {
BuildActivity_.intent(mContext)
.extra(BuildActivity.EXTRA_SOURCE_FILE, mEditorView.getFile().getPath())

View File

@ -60,7 +60,7 @@ public class CodeEditText extends AppCompatEditText {
protected HVScrollView mParentScrollView;
private final CopyOnWriteArrayList<CodeEditor.CursorChangeCallback> mCursorChangeCallbacks = new CopyOnWriteArrayList<>();
private volatile JavaScriptHighlighter.HighlightTokens mHighlightTokens;
private JavaScriptHighlighter.HighlightTokens mHighlightTokens;
private Theme mTheme;
private TimingLogger mLogger = new TimingLogger(LOG_TAG, "draw");
private Paint mLineHighlightPaint = new Paint();
@ -195,7 +195,7 @@ public class CodeEditText extends AppCompatEditText {
Layout layout = getLayout();
int lineCount = getLineCount();
int textLength = highlightTokens == null ? 0 : highlightTokens.getText().length();
Editable text = getText();
String text = highlightTokens == null ? "" : highlightTokens.getText();
int paddingLeft = getPaddingLeft();
int scrollX = Math.max(getRealScrollX() - paddingLeft, 0);
Paint paint = getPaint();
@ -260,6 +260,12 @@ public class CodeEditText extends AppCompatEditText {
}
paint.setColor(previousColor);
float offsetX = paint.measureText(text, lineStart, previousColorPos);
if(previousColorPos < 0 || visibleCharEnd > textLength || previousColorPos >= visibleCharEnd){
Log.e(LOG_TAG, "IndexOutOfBounds: previousColorPos = " + previousColorPos + ", visibleCharEnd = "
+visibleCharEnd + ", textLength = " + textLength);
postInvalidate();
return;
}
canvas.drawText(text, previousColorPos, visibleCharEnd, paddingLeft + offsetX, lineBaseline, paint);
if (DEBUG) {
mLogger.addSplit("draw line " + line + " (" + (visibleCharEnd - visibleCharStart) + ") ");
@ -407,8 +413,10 @@ public class CodeEditText extends AppCompatEditText {
public void updateHighlightTokens(JavaScriptHighlighter.HighlightTokens highlightTokens) {
mHighlightTokens = highlightTokens;
postInvalidate();
post(() -> {
mHighlightTokens = highlightTokens;
invalidate();
});
}
@Override

View File

@ -317,6 +317,11 @@ public class CodeEditor extends HVScrollView {
mCodeEditText.getText().insert(selection, insertText);
}
public void insert(int line, String insertText) {
int selection = mCodeEditText.getLayout().getLineStart(line);
mCodeEditText.getText().insert(selection, insertText);
}
public void moveCursor(int dCh) {
mCodeEditText.setSelection(mCodeEditText.getSelectionStart() + dCh);
}

View File

@ -1,6 +1,7 @@
package org.autojs.autojs.ui.main;
import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.os.Build;
@ -14,6 +15,7 @@ import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuItem;
@ -32,6 +34,7 @@ import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.Pref;
import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.model.indices.AndroidClassIndices;
import org.autojs.autojs.storage.file.StorageFileProvider;
import org.autojs.autojs.timing.TimedTaskManager;
import org.autojs.autojs.tool.AccessibilityServiceTool;
@ -68,6 +71,8 @@ import org.opencv.core.Core;
import java.util.Arrays;
import io.reactivex.android.schedulers.AndroidSchedulers;
@EActivity(R.layout.activity_main)
public class MainActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost, BackPressedHandler.HostActivity, PermissionRequestProxyActivity {

View File

@ -3,6 +3,8 @@ package org.autojs.autojs.ui.widget;
import android.support.v7.widget.RecyclerView;
import android.view.ViewGroup;
import org.autojs.autojs.model.indices.ClassSearchingItem;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@ -76,4 +78,15 @@ public class AutoAdapter<DT> extends RecyclerView.Adapter<BindableViewHolder<DT>
public List<DT> getData() {
return mData;
}
public void removeAll() {
mData.clear();
notifyDataSetChanged();
}
public void notifyDataSetChanged(List<DT> result) {
mData.clear();
mData.addAll(result);
notifyDataSetChanged();
}
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.design.widget.TextInputLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<android.support.design.widget.TextInputEditText
android:id="@+id/keywords"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/text_class_or_package_name"/>
</android.support.design.widget.TextInputLayout>
<FrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<android.support.v7.widget.RecyclerView
android:id="@+id/result_list"
android:layout_width="match_parent"
android:layout_height="400dp"/>
<me.zhanghai.android.materialprogressbar.MaterialProgressBar
android:id="@+id/progress_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:visibility="gone"/>
</FrameLayout>
</LinearLayout>

View File

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:paddingBottom="10dp"
android:background="?selectableItemBackground"
android:clickable="true"
android:focusable="true"
android:paddingLeft="6dp"
android:paddingRight="6dp"
android:paddingTop="10dp">
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:textColor="#B2B3B7"
android:textSize="15sp"/>
</LinearLayout>

View File

@ -119,6 +119,12 @@
android:id="@+id/action_console"
android:title="@string/text_console"
app:showAsAction="never"/>
<item
android:id="@+id/action_import_java_class"
android:title="@string/text_search_java_class"
app:showAsAction="never"/>
<item
android:id="@+id/action_info"
android:title="@string/text_info"

View File

@ -383,4 +383,8 @@
<string name="error_connect_to_remote">连接失败: %s</string>
<string name="text_are_you_sure_to_delete">确定要删除%s吗</string>
<string name="text_run_on_boot">开机时运行</string>
<string name="text_search_java_class">搜索Java包/类</string>
<string name="text_class_or_package_name">类/包名</string>
<string name="text_view_docs">查看文档</string>
<string name="text_en_import">import</string>
</resources>

View File

@ -17,6 +17,8 @@ public abstract class JavaScriptSource extends ScriptSource {
public static final String ENGINE = "com.stardust.autojs.script.JavaScriptSource.Engine";
public static final String EXECUTION_MODE_UI_PREFIX = "\"ui\";";
public static final int EXECUTION_MODE_NORMAL = 0;
public static final int EXECUTION_MODE_UI = 0x00000001;
public static final int EXECUTION_MODE_AUTO = 0x00000002;

View File

@ -1 +1 @@
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":413},"path":"commonRelease-4.0.2 Alpha8.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]
[{"outputType":{"type":"APK"},"apkInfo":{"type":"MAIN","splits":[],"versionCode":414},"path":"commonRelease-4.0.2 Alpha9.apk","properties":{"packageId":"org.autojs.autojs","split":"","minSdkVersion":"17"}}]