fix crash on android5.1 and add console api

This commit is contained in:
hyb1996
2017-02-12 22:43:59 +08:00
parent 6b3a71ef50
commit 564cbb083a
29 changed files with 471 additions and 74 deletions

View File

@@ -1,10 +1,14 @@
package com.stardust.scriptdroid;
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 android.support.v7.widget.Toolbar;
import android.view.View;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -18,9 +22,13 @@ import static android.content.pm.PackageManager.PERMISSION_GRANTED;
public class BaseActivity extends AppCompatActivity {
protected static final int PERMISSION_REQUEST_CODE = 11186;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@SuppressWarnings("unchecked")
public <T extends View> T $(int resId) {
return (T) findViewById(resId);
@@ -49,4 +57,18 @@ public class BaseActivity extends AppCompatActivity {
return list.toArray(new String[list.size()]);
}
public void setToolbarAsBack(String title) {
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitle(title);
setSupportActionBar(toolbar);
if (getSupportActionBar() != null) {
toolbar.setNavigationOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
}
}
}

View File

@@ -94,6 +94,7 @@ public class MainActivity extends BaseActivity implements FileChooserDialog.File
setUpToolbar();
setUpScriptList();
ViewBinder.bind(this);
}
private void setUpToolbar() {

View File

@@ -0,0 +1,74 @@
package com.stardust.scriptdroid.droid;
import android.content.Context;
import android.graphics.Color;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.Menu;
import android.view.MenuItem;
import android.widget.TextView;
import com.jraska.console.Console;
import com.stardust.scriptdroid.BaseActivity;
import com.stardust.scriptdroid.R;
/**
* Created by Stardust on 2017/2/12.
*/
public class ConsoleActivity extends BaseActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setUpUI();
}
private void setUpUI() {
setContentView(R.layout.activity_console);
setToolbarAsBack(getString(R.string.text_console));
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.menu_console, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
Console.clear();
return super.onOptionsItemSelected(item);
}
public static class ConsoleView extends Console {
public ConsoleView(Context context) {
super(context);
init();
}
public ConsoleView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ConsoleView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
public ConsoleView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
init();
}
private void init() {
findViewById(R.id.console_scroll_view).setBackgroundColor(Color.WHITE);
((TextView) findViewById(R.id.console_text)).setTextIsSelectable(true);
}
}
}

View File

@@ -1,9 +1,8 @@
package com.stardust.scriptdroid.droid;
import android.app.Activity;
import android.content.DialogInterface;
import com.afollestad.materialdialogs.MaterialDialog;
import com.jraska.console.timber.ConsoleTree;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.runtime.DroidRuntime;
@@ -15,12 +14,20 @@ import com.stardust.scriptdroid.file.FileUtils;
import java.io.File;
import java.io.FileNotFoundException;
import timber.log.Timber;
/**
* Created by Stardust on 2017/1/23.
*/
public class Droid {
static {
Timber.plant(new ConsoleTree.Builder()
.infoColor(0xcc000000)
.build());
}
public static final String UI = "\"ui\";";
public interface OnRunFinishedListener {
@@ -34,6 +41,7 @@ public class Droid {
public void onRunFinished(Object result, Exception e) {
if (e != null) {
RUNTIME.toast(App.getApp().getString(R.string.text_error) + ": " + e.getMessage());
Timber.e(App.getApp().getString(R.string.text_error), e);
}
}
};

View File

@@ -5,12 +5,15 @@ import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.graphics.Rect;
import android.os.Handler;
import android.support.annotation.Nullable;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
import com.afollestad.materialdialogs.MaterialDialog;
import com.jraska.console.Console;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.ConsoleActivity;
import com.stardust.scriptdroid.droid.runtime.action.Action;
import com.stardust.scriptdroid.droid.runtime.action.ActionFactory;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformService;
@@ -19,6 +22,8 @@ import com.stardust.scriptdroid.droid.runtime.api.IDroidRuntime;
import java.util.List;
import timber.log.Timber;
;
/**
@@ -30,7 +35,7 @@ public class DroidRuntime implements IDroidRuntime {
private static final String TAG = "DroidRuntime";
private static DroidRuntime runtime = new DroidRuntime();
private final Object mLock = new Object();
private final Object mActionPerformLock = new Object();
private boolean mActionPerformResult;
private Handler mUIHandler;
@@ -105,12 +110,22 @@ public class DroidRuntime implements IDroidRuntime {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
@Override
public boolean scrollUp(int i) {
return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD, i));
}
@Override
public boolean scrollDown(int i) {
return performAction(ActionFactory.createScrollAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD, i));
}
public boolean scrollAllUp() {
return performAction(ActionFactory.createScrollAllAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD));
}
public boolean scrollAllDown() {
return performAction(ActionFactory.createScrollAllAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
return performAction(ActionFactory.createScrollMaxAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD));
}
@Override
@@ -133,6 +148,21 @@ public class DroidRuntime implements IDroidRuntime {
return performAction(target.createAction(AccessibilityNodeInfo.ACTION_PASTE));
}
public void log(@Nullable Object str) {
Timber.i("" + str);
}
public void err(@Nullable Object o) {
Timber.e("" + o);
}
public void console() {
App.getApp().startActivity(new Intent(App.getApp(), ConsoleActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
public void clearConsole(){
Console.clear();
}
private boolean performAction(Action action) {
if (ActionPerformService.getInstance() == null) {
@@ -140,9 +170,9 @@ public class DroidRuntime implements IDroidRuntime {
throw new ScriptStopException(App.getApp().getString(R.string.text_no_accessibility_permission));
}
ActionPerformService.setAction(action);
synchronized (mLock) {
synchronized (mActionPerformLock) {
try {
mLock.wait();
mActionPerformLock.wait();
} catch (InterruptedException e) {
ActionPerformService.setActions(ActionPerformService.NO_ACTION);
throw new ScriptStopException(App.getApp().getString(R.string.text_script_stopped), e);
@@ -161,7 +191,6 @@ public class DroidRuntime implements IDroidRuntime {
});
}
@Override
public void sleep(long millis) {
try {
@@ -207,8 +236,8 @@ public class DroidRuntime implements IDroidRuntime {
public void notifyActionPerformed(boolean succeed) {
mActionPerformResult = succeed;
synchronized (mLock) {
mLock.notify();
synchronized (mActionPerformLock) {
mActionPerformLock.notify();
}
}

View File

@@ -50,7 +50,11 @@ public class ActionFactory {
};
}
public static Action createScrollAllAction(int action) {
return new ScrollAllAction(action);
public static Action createScrollMaxAction(int action) {
return new ScrollMaxAction(action);
}
public static Action createScrollAction(int action, int i){
return new ScrollAction(action, i);
}
}

View File

@@ -24,7 +24,7 @@ import java.util.List;
public class ActionPerformService extends AccessibilityService {
private static final String TAG = "ActionPerformService";
private static ActionPerformService instance;
private static volatile ActionPerformService instance;
@SuppressWarnings("unchecked")
public static final List<Action> NO_ACTION = Collections.EMPTY_LIST;
@@ -98,17 +98,22 @@ public class ActionPerformService extends AccessibilityService {
@Override
public void onServiceConnected() {
// FIXME: 2017/2/12 有时在无障碍中开启服务后这里不会调用服务也不会运行安卓的BUG???
Log.v(TAG, "onServiceConnected");
}
@Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "onCreate");
instance = this;
}
public static void disable() {
if (instance != null) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
instance.disableSelf();
} else {
AccessibilityServiceUtils.goToAccessibilitySetting(App.getApp());
}
if (instance != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
instance.disableSelf();
} else {
AccessibilityServiceUtils.goToAccessibilitySetting(App.getApp());
}
}

View File

@@ -0,0 +1,57 @@
package com.stardust.scriptdroid.droid.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
import java.util.ArrayList;
import java.util.List;
/**
* Created by Stardust on 2017/2/12.
*/
public class ScrollAction extends Action {
private int mIndex, mAction;
public ScrollAction(int action, int i) {
mAction = action;
mIndex = i;
}
@Override
public boolean perform(AccessibilityNodeInfo root) {
List<AccessibilityNodeInfo> scrollableNodes = findScrollableNodes(root);
boolean result = mIndex < scrollableNodes.size() && scrollableNodes.get(mIndex).performAction(mAction);
recycle(scrollableNodes, root);
return result;
}
private void recycle(List<AccessibilityNodeInfo> list, AccessibilityNodeInfo root) {
for (AccessibilityNodeInfo nodeInfo : list) {
if (nodeInfo != root)
nodeInfo.recycle();
}
}
private List<AccessibilityNodeInfo> findScrollableNodes(AccessibilityNodeInfo root) {
List<AccessibilityNodeInfo> list = new ArrayList<>();
findScrollableNodes(root, list);
return list;
}
private static boolean findScrollableNodes(AccessibilityNodeInfo node, List<AccessibilityNodeInfo> list) {
if (node == null) {
return false;
}
if (node.isScrollable()) {
list.add(node);
}
for (int i = 0; i < node.getChildCount(); i++) {
AccessibilityNodeInfo child = node.getChild(i);
if (child == null)
continue;
if (!findScrollableNodes(child, list))
child.recycle();
}
return node.isScrollable();
}
}

View File

@@ -1,38 +0,0 @@
package com.stardust.scriptdroid.droid.runtime.action;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public class ScrollAllAction extends Action {
private int mScrollAction;
public ScrollAllAction(int scrollAction) {
mScrollAction = scrollAction;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
AccessibilityNodeInfo scrollableNodeInfo = findScrollableNodeInfo(rootNodeInfo);
return scrollableNodeInfo != null && scrollableNodeInfo.performAction(mScrollAction);
}
private AccessibilityNodeInfo findScrollableNodeInfo(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null)
return null;
if (nodeInfo.isScrollable()) {
return nodeInfo;
}
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo node = findScrollableNodeInfo(nodeInfo.getChild(i));
if (node != null) {
return node;
}
}
return null;
}
}

View File

@@ -0,0 +1,70 @@
package com.stardust.scriptdroid.droid.runtime.action;
import android.graphics.Rect;
import android.util.Log;
import android.view.accessibility.AccessibilityNodeInfo;
/**
* Created by Stardust on 2017/1/27.
*/
public class ScrollMaxAction extends Action {
private static final String TAG = ScrollMaxAction.class.getSimpleName();
private int mScrollAction;
private AccessibilityNodeInfo mMaxScrollableNode;
private AccessibilityNodeInfo mRootNode;
public ScrollMaxAction(int scrollAction) {
mScrollAction = scrollAction;
}
@Override
public boolean perform(AccessibilityNodeInfo rootNodeInfo) {
reset();
mRootNode = rootNodeInfo;
findMaxScrollableNodeInfo(rootNodeInfo);
boolean result = mMaxScrollableNode != null && mMaxScrollableNode.performAction(mScrollAction);
reset();
return result;
}
private void reset() {
if (mMaxScrollableNode != null && mMaxScrollableNode != mRootNode) {
mMaxScrollableNode.recycle();
}
mMaxScrollableNode = mRootNode = null;
}
private void findMaxScrollableNodeInfo(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null)
return;
if (nodeInfo.isScrollable()) {
if (mMaxScrollableNode == null) {
mMaxScrollableNode = nodeInfo;
} else if (getAreaInScreen(mMaxScrollableNode) < getAreaInScreen(nodeInfo)) {
if (mMaxScrollableNode != mRootNode)
mMaxScrollableNode.recycle();
mMaxScrollableNode = nodeInfo;
}
}
for (int i = 0; i < nodeInfo.getChildCount(); i++) {
AccessibilityNodeInfo child = nodeInfo.getChild(i);
if (child != null) {
findMaxScrollableNodeInfo(child);
if (mMaxScrollableNode != child) {
child.recycle();
}
}
}
}
private long getAreaInScreen(AccessibilityNodeInfo nodeInfo) {
Rect rect = new Rect();
nodeInfo.getBoundsInScreen(rect);
long area = ((long) rect.width()) * rect.height();
Log.v(TAG, "area=" + area);
return area;
}
}

View File

@@ -30,6 +30,10 @@ public interface IDroidRuntime {
boolean scrollDown(ActionTarget target);
boolean scrollUp(int i);
boolean scrollDown(int i);
boolean focus(ActionTarget target);
boolean select(ActionTarget target);

View File

@@ -26,7 +26,7 @@ public class ScriptExecuteActivity extends Activity {
ScriptExecuteActivity.script = script;
ScriptExecuteActivity.onRunFinishedListener = listener;
ScriptExecuteActivity.runningConfig = config;
App.getApp().startActivity(new Intent(App.getApp(), ScriptExecuteActivity.class));
App.getApp().startActivity(new Intent(App.getApp(), ScriptExecuteActivity.class).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
}
@Override

View File

@@ -12,6 +12,7 @@ import android.view.ViewGroup;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.DocumentActivity;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.ConsoleActivity;
import com.stardust.scriptdroid.droid.assist.BoundsAssistant;
import com.stardust.view.ViewBinder;
import com.stardust.view.ViewBinding;
@@ -86,6 +87,11 @@ public class EditSideMenuFragment extends com.stardust.app.Fragment {
startActivity(new Intent(getContext(), DocumentActivity.class));
}
@ViewBinding.Click(R.id.console)
private void startConsoleActivity() {
startActivity(new Intent(getContext(), ConsoleActivity.class));
}
@ViewBinding.Check(R.id.sw_assist_service)
private void setAssistServiceEnable(boolean enable) {
BoundsAssistant.setAssistModeEnable(enable);

View File

@@ -14,6 +14,7 @@ import com.stardust.app.Fragment;
import com.stardust.scriptdroid.App;
import com.stardust.scriptdroid.DocumentActivity;
import com.stardust.scriptdroid.R;
import com.stardust.scriptdroid.droid.ConsoleActivity;
import com.stardust.scriptdroid.droid.Droid;
import com.stardust.scriptdroid.droid.assist.BoundsAssistant;
import com.stardust.scriptdroid.droid.runtime.action.ActionPerformService;
@@ -72,6 +73,12 @@ public class SlideMenuFragment extends Fragment {
App.getStateObserver().register(KEY_ASSIST_MODE_NOTIFICATION, mAssistServiceNotificationSwitch);
}
@ViewBinding.Click(R.id.console)
private void startConsoleActivity() {
startActivity(new Intent(getContext(), ConsoleActivity.class));
}
@ViewBinding.Click(R.id.syntax_and_api)
private void startSyntaxHelpActivity() {
startActivity(new Intent(getContext(), DocumentActivity.class));

View File

@@ -17,6 +17,8 @@ import java.lang.Thread.UncaughtExceptionHandler;
public class CrashHandler implements UncaughtExceptionHandler {
private static final String TAG = "CrashHandler";
private static int crashCount = 0;
private static long firstCrashMillis = 0;
private final Class<?> mErrorReportClass;
public CrashHandler(Class<?> errorReportClass) {
@@ -26,16 +28,40 @@ public class CrashHandler implements UncaughtExceptionHandler {
public void uncaughtException(Thread thread, Throwable ex) {
try {
Log.e(TAG, "Uncaught Exception", ex);
String t = App.getApp().getString(R.string.sorry_for_crash) + ex.toString();
Intent intent = new Intent(App.getApp(), this.mErrorReportClass);
intent.putExtra("message", t);
intent.putExtra("error", throwableToString(ex));
App.getApp().startActivity(intent);
if (crashTooManyTimes())
return;
String msg = App.getApp().getString(R.string.sorry_for_crash) + ex.toString();
startErrorReportActivity(msg, throwableToString(ex));
System.exit(0);
} catch (Throwable var6) {
var6.printStackTrace();
} catch (Throwable throwable) {
throwable.printStackTrace();
}
}
private void startErrorReportActivity(String msg, String detail) {
Intent intent = new Intent(App.getApp(), this.mErrorReportClass);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.putExtra("message", msg);
intent.putExtra("error", detail);
App.getApp().startActivity(intent);
}
private boolean crashTooManyTimes() {
if (crashIntervalTooLong()) {
resetCrashCount();
return false;
}
crashCount++;
return crashCount >= 5;
}
private void resetCrashCount() {
firstCrashMillis = System.currentTimeMillis();
crashCount = 0;
}
private boolean crashIntervalTooLong() {
return System.currentTimeMillis() - firstCrashMillis > 3000;
}
public static String throwableToString(Throwable throwable) {