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

Binary file not shown.

View File

@@ -8,8 +8,8 @@ android {
applicationId "org.autojs.autojs" applicationId "org.autojs.autojs"
minSdkVersion 17 minSdkVersion 17
targetSdkVersion 23 targetSdkVersion 23
versionCode 413 versionCode 414
versionName "4.0.2 Alpha8" versionName "4.0.2 Alpha9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true multiDexEnabled true
ndk { 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.ThemeColor;
import com.stardust.theme.ThemeColorManager; import com.stardust.theme.ThemeColorManager;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.crashreport.CrashReport; import com.tencent.bugly.crashreport.CrashReport;
@@ -77,7 +76,7 @@ public class App extends MultiDexApplication {
CrashReport.initCrashReport(getApplicationContext(), BUGLY_APP_ID, false, strategy); CrashReport.initCrashReport(getApplicationContext(), BUGLY_APP_ID, false, strategy);
crashHandler.setDefaultHandler(Thread.getDefaultUncaughtExceptionHandler()); crashHandler.setBuglyHandler(Thread.getDefaultUncaughtExceptionHandler());
Thread.setDefaultUncaughtExceptionHandler(crashHandler); Thread.setDefaultUncaughtExceptionHandler(crashHandler);
} }

View File

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

View File

@@ -1,18 +1,22 @@
package org.autojs.autojs.model.autocomplete; package org.autojs.autojs.model.autocomplete;
import android.content.Context; import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.widget.EditText; import android.widget.EditText;
import org.autojs.autojs.model.indices.Module; import org.autojs.autojs.model.indices.Module;
import org.autojs.autojs.model.indices.Modules; import org.autojs.autojs.model.indices.Modules;
import org.autojs.autojs.model.indices.Property; import org.autojs.autojs.model.indices.Property;
import org.autojs.autojs.ui.widget.SimpleTextWatcher; import org.autojs.autojs.ui.widget.SimpleTextWatcher;
import org.mozilla.javascript.ast.Loop;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@@ -39,6 +43,8 @@ public class AutoCompletion {
private AutoCompleteCallback mAutoCompleteCallback; private AutoCompleteCallback mAutoCompleteCallback;
private ExecutorService mExecutorService = Executors.newSingleThreadExecutor(); private ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private AnyWordsCompletion mAnyWordsCompletion; private AnyWordsCompletion mAnyWordsCompletion;
private AtomicInteger mExecuteId = new AtomicInteger();
private Handler mHandler = new Handler(Looper.getMainLooper());
public AutoCompletion(Context context, EditText editText) { public AutoCompletion(Context context, EditText editText) {
buildDictionaryTree(context); buildDictionaryTree(context);
@@ -80,10 +86,19 @@ public class AutoCompletion {
if (mPropertyPrefill == null && module == null) if (mPropertyPrefill == null && module == null)
return; return;
String prefill = mPropertyPrefill; String prefill = mPropertyPrefill;
int id = mExecuteId.incrementAndGet();
mExecutorService.execute(() -> { mExecutorService.execute(() -> {
if (id != mExecuteId.get())
return;
List<CodeCompletion> completions = findCodeCompletion(module, prefill); List<CodeCompletion> completions = findCodeCompletion(module, prefill);
CodeCompletions codeCompletions = new CodeCompletions(cursor, completions); 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.Build;
import android.os.Looper; import android.os.Looper;
import android.util.Log; 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 com.stardust.app.GlobalAppContext;
import org.autojs.autojs.App;
import org.autojs.autojs.BuildConfig; 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.stardust.view.accessibility.AccessibilityService;
import com.tencent.bugly.Bugly;
import com.tencent.bugly.crashreport.BuglyLog; import com.tencent.bugly.crashreport.BuglyLog;
import com.tencent.bugly.crashreport.CrashReport; import com.tencent.bugly.crashreport.CrashReport;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler; import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Map; import java.util.Map;
@@ -36,21 +27,24 @@ public class CrashHandler extends CrashReport.CrashHandleCallback implements Unc
private static int crashCount = 0; private static int crashCount = 0;
private static long firstCrashMillis = 0; private static long firstCrashMillis = 0;
private final Class<?> mErrorReportClass; private final Class<?> mErrorReportClass;
private UncaughtExceptionHandler mDefaultHandler; private UncaughtExceptionHandler mBuglyHandler;
private UncaughtExceptionHandler mSystemHandler;
public CrashHandler(Class<?> errorReportClass) { public CrashHandler(Class<?> errorReportClass) {
this.mErrorReportClass = errorReportClass; this.mErrorReportClass = errorReportClass;
mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler(); mSystemHandler = Thread.getDefaultUncaughtExceptionHandler();
} }
public void setDefaultHandler(UncaughtExceptionHandler defaultHandler) { public void setBuglyHandler(UncaughtExceptionHandler buglyHandler) {
mDefaultHandler = defaultHandler; mBuglyHandler = buglyHandler;
} }
public void uncaughtException(Thread thread, Throwable ex) { public void uncaughtException(Thread thread, Throwable ex) {
Log.e(TAG, "Uncaught Exception", ex); Log.e(TAG, "Uncaught Exception", ex);
if (thread != Looper.getMainLooper().getThread()) { if (thread != Looper.getMainLooper().getThread()) {
CrashReport.postCatchedException(ex, thread); if(!(ex instanceof RhinoException)){
CrashReport.postCatchedException(ex, thread);
}
return; return;
} }
AccessibilityService service = AccessibilityService.getInstance(); AccessibilityService service = AccessibilityService.getInstance();
@@ -60,7 +54,11 @@ public class CrashHandler extends CrashReport.CrashHandleCallback implements Unc
} else { } else {
BuglyLog.d(TAG, "cannot disable service: " + service); BuglyLog.d(TAG, "cannot disable service: " + service);
} }
mDefaultHandler.uncaughtException(thread, ex); if (BuildConfig.DEBUG) {
mSystemHandler.uncaughtException(thread, ex);
} else {
mBuglyHandler.uncaughtException(thread, ex);
}
} }
@Override @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.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; 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. * Created by Stardust on 2017/1/29.
*/ */
@EActivity(R.layout.activity_edit) @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(); private OnActivityResultDelegate.Mediator mMediator = new OnActivityResultDelegate.Mediator();
@@ -84,6 +86,18 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
setUpToolbar(); 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) { private void onLoadFileError(String message) {
new ThemeColorMaterialDialogBuilder(this) new ThemeColorMaterialDialogBuilder(this)
.title(getString(R.string.text_cannot_read_file)) .title(getString(R.string.text_cannot_read_file))
@@ -117,6 +131,13 @@ public class EditActivity extends BaseActivity implements OnActivityResultDelega
return super.onPrepareOptionsMenu(menu); 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 @Override
public void onActionModeStarted(ActionMode mode) { public void onActionModeStarted(ActionMode mode) {
Menu menu = mode.getMenu(); Menu menu = mode.getMenu();

View File

@@ -1,15 +1,21 @@
package org.autojs.autojs.ui.edit; package org.autojs.autojs.ui.edit;
import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.support.design.widget.Snackbar; import android.support.design.widget.Snackbar;
import android.text.InputType; import android.text.InputType;
import android.text.TextUtils; import android.text.TextUtils;
import android.view.MenuItem; 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 com.stardust.pio.PFiles;
import org.autojs.autojs.R; 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.build.BuildActivity_; import org.autojs.autojs.ui.build.BuildActivity_;
import org.autojs.autojs.ui.common.NotAskAgainDialog; 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 org.autojs.autojs.theme.dialog.ThemeColorMaterialDialogBuilder;
import com.stardust.util.ClipboardUtil; import com.stardust.util.ClipboardUtil;
import com.stardust.util.IntentUtil;
import java.util.Locale; import java.util.Locale;
@@ -28,6 +35,7 @@ import io.reactivex.android.schedulers.AndroidSchedulers;
* Created by Stardust on 2017/9/28. * Created by Stardust on 2017/9/28.
*/ */
@SuppressLint("CheckResult")
public class EditorMenu { public class EditorMenu {
private EditorView mEditorView; private EditorView mEditorView;
@@ -112,6 +120,9 @@ public class EditorMenu {
case R.id.action_console: case R.id.action_console:
showConsole(); showConsole();
return true; return true;
case R.id.action_import_java_class:
importJavaPackageOrClass();
return true;
case R.id.action_editor_text_size: case R.id.action_editor_text_size:
mEditorView.selectTextSize(); mEditorView.selectTextSize();
return true; return true;
@@ -132,6 +143,52 @@ public class EditorMenu {
return false; 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() { private void startBuildApkActivity() {
BuildActivity_.intent(mContext) BuildActivity_.intent(mContext)
.extra(BuildActivity.EXTRA_SOURCE_FILE, mEditorView.getFile().getPath()) .extra(BuildActivity.EXTRA_SOURCE_FILE, mEditorView.getFile().getPath())

View File

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

View File

@@ -317,6 +317,11 @@ public class CodeEditor extends HVScrollView {
mCodeEditText.getText().insert(selection, insertText); 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) { public void moveCursor(int dCh) {
mCodeEditText.setSelection(mCodeEditText.getSelectionStart() + dCh); mCodeEditText.setSelection(mCodeEditText.getSelectionStart() + dCh);
} }

View File

@@ -1,6 +1,7 @@
package org.autojs.autojs.ui.main; package org.autojs.autojs.ui.main;
import android.Manifest; import android.Manifest;
import android.annotation.SuppressLint;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
@@ -14,6 +15,7 @@ import android.support.v4.view.ViewPager;
import android.support.v4.widget.DrawerLayout; import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle; import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.widget.Toolbar; import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
@@ -32,6 +34,7 @@ import org.autojs.autojs.BuildConfig;
import org.autojs.autojs.Pref; import org.autojs.autojs.Pref;
import org.autojs.autojs.R; import org.autojs.autojs.R;
import org.autojs.autojs.autojs.AutoJs; import org.autojs.autojs.autojs.AutoJs;
import org.autojs.autojs.model.indices.AndroidClassIndices;
import org.autojs.autojs.storage.file.StorageFileProvider; import org.autojs.autojs.storage.file.StorageFileProvider;
import org.autojs.autojs.timing.TimedTaskManager; import org.autojs.autojs.timing.TimedTaskManager;
import org.autojs.autojs.tool.AccessibilityServiceTool; import org.autojs.autojs.tool.AccessibilityServiceTool;
@@ -68,6 +71,8 @@ import org.opencv.core.Core;
import java.util.Arrays; import java.util.Arrays;
import io.reactivex.android.schedulers.AndroidSchedulers;
@EActivity(R.layout.activity_main) @EActivity(R.layout.activity_main)
public class MainActivity extends BaseActivity implements OnActivityResultDelegate.DelegateHost, BackPressedHandler.HostActivity, PermissionRequestProxyActivity { 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.support.v7.widget.RecyclerView;
import android.view.ViewGroup; import android.view.ViewGroup;
import org.autojs.autojs.model.indices.ClassSearchingItem;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -76,4 +78,15 @@ public class AutoAdapter<DT> extends RecyclerView.Adapter<BindableViewHolder<DT>
public List<DT> getData() { public List<DT> getData() {
return mData; 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:id="@+id/action_console"
android:title="@string/text_console" android:title="@string/text_console"
app:showAsAction="never"/> app:showAsAction="never"/>
<item
android:id="@+id/action_import_java_class"
android:title="@string/text_search_java_class"
app:showAsAction="never"/>
<item <item
android:id="@+id/action_info" android:id="@+id/action_info"
android:title="@string/text_info" android:title="@string/text_info"

View File

@@ -383,4 +383,8 @@
<string name="error_connect_to_remote">连接失败: %s</string> <string name="error_connect_to_remote">连接失败: %s</string>
<string name="text_are_you_sure_to_delete">确定要删除%s吗</string> <string name="text_are_you_sure_to_delete">确定要删除%s吗</string>
<string name="text_run_on_boot">开机时运行</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> </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 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_NORMAL = 0;
public static final int EXECUTION_MODE_UI = 0x00000001; public static final int EXECUTION_MODE_UI = 0x00000001;
public static final int EXECUTION_MODE_AUTO = 0x00000002; 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"}}]