commit 307dbe8859692c48f1d3594d99077c0a2638bb39 Author: hyb1996 <946994919@qq.com> Date: Fri Jan 27 09:05:38 2017 +0800 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..39fb081a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 00000000..96cc43ef --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 00000000..e7bedf33 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/encodings.xml b/.idea/encodings.xml new file mode 100644 index 00000000..97626ba4 --- /dev/null +++ b/.idea/encodings.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 00000000..7ac24c77 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 00000000..65f3da7d --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,10 @@ + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml new file mode 100644 index 00000000..3b312839 --- /dev/null +++ b/.idea/inspectionProfiles/profiles_settings.xml @@ -0,0 +1,7 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 00000000..8cdb7ebe --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,61 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 00000000..24411114 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 00000000..7f68460d --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 00000000..796b96d1 --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 00000000..acf4a50f --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,43 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + defaultConfig { + applicationId "com.stardust.scriptdroid" + minSdkVersion 24 + //由于不知道在API24以上如何调起其他文件编辑器应用编辑文件,只能改到API23。FileProvider貌似只能查看编辑后不能保存? + //参照ScriptFileOperation.Edit + targetSdkVersion 23 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + jackOptions { + enabled true + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + encoding = 'utf-8' + } +} + +dependencies { + compile fileTree(include: ['*.jar'], dir: 'libs') + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:25.0.0' + compile 'com.android.support:design:25.0.0' + testCompile 'junit:junit:4.12' + compile 'com.afollestad.material-dialogs:core:0.9.2.3' + compile 'com.google.code.gson:gson:2.8.0' + compile 'com.sothree.slidinguppanel:library:3.3.1' +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 00000000..fba55431 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,17 @@ +# Add project specific ProGuard rules here. +# By default, the flags in this file are appended to flags specified +# in E:\YiBin\eclipse\Android_SDK_windows/tools/proguard/proguard-android.txt +# You can edit the include path and order by changing the proguardFiles +# directive in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# Add any project specific keep options here: + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} diff --git a/app/src/androidTest/java/com/stardust/scriptdroid/ExampleInstrumentedTest.java b/app/src/androidTest/java/com/stardust/scriptdroid/ExampleInstrumentedTest.java new file mode 100644 index 00000000..11c68b43 --- /dev/null +++ b/app/src/androidTest/java/com/stardust/scriptdroid/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.stardust.scriptdroid; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.stardust.scriptdroid", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 00000000..2be358e3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,76 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/BaseActivity.java b/app/src/main/java/com/stardust/scriptdroid/BaseActivity.java new file mode 100644 index 00000000..cef1e52b --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/BaseActivity.java @@ -0,0 +1,32 @@ +package com.stardust.scriptdroid; + +import android.os.Build; +import android.support.v7.app.AppCompatActivity; +import android.view.View; + +import java.util.stream.Stream; + +import static android.content.pm.PackageManager.PERMISSION_DENIED; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class BaseActivity extends AppCompatActivity { + + + @SuppressWarnings("unchecked") + public T $(int resId) { + return (T) findViewById(resId); + } + + protected void checkPermission(String... permissions) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + String[] requestPermissions = Stream.of(permissions).filter(permission -> checkSelfPermission(permission) == PERMISSION_DENIED).toArray(String[]::new); + + if (requestPermissions.length > 0) + requestPermissions(requestPermissions, 0); + } + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/MainActivity.java b/app/src/main/java/com/stardust/scriptdroid/MainActivity.java new file mode 100644 index 00000000..29714dc3 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/MainActivity.java @@ -0,0 +1,162 @@ +package com.stardust.scriptdroid; + +import android.content.Intent; +import android.os.Bundle; +import android.support.design.widget.Snackbar; +import android.support.v7.widget.Toolbar; +import android.text.InputType; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.stardust.scriptdroid.action.ActionPerformService; +import com.stardust.scriptdroid.data.ScriptFile; +import com.stardust.scriptdroid.data.ScriptFileList; +import com.stardust.scriptdroid.data.SharedPrefScriptFileList; +import com.stardust.scriptdroid.file.FileChooser; +import com.stardust.scriptdroid.file.FileUtils; +import com.stardust.scriptdroid.ui.ScriptFileOperation; +import com.stardust.scriptdroid.ui.ScriptListRecyclerView; +import com.stardust.scriptdroid.ui.SlidingUpPanel; +import com.stardust.util.MapEntries; + +import java.util.Map; +import java.util.Optional; + + +public class MainActivity extends BaseActivity { + + private View mView; + private SlidingUpPanel mSlidingUpPanel; + private ScriptListRecyclerView mScriptListRecyclerView; + private ScriptFileList mScriptFileList; + private FileChooser mFileChooser; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setUpUI(); + + setUpFileChooser(); + + checkPermissions(); + } + + private void checkPermissions() { + //checkPermission(Manifest.permission.READ_EXTERNAL_STORAGE); + ActionPerformService.goToPermissionSettingIfDisabled(this); + } + + + private void setUpFileChooser() { + mFileChooser = new FileChooser(this); + mFileChooser.setOnFileChoseListener(inputStream -> Optional.ofNullable(FileUtils.getPath(inputStream)).ifPresent(this::addScriptFile)); + } + + private void addScriptFile(final String path) { + new MaterialDialog.Builder(this).title(R.string.text_name) + .inputType(InputType.TYPE_CLASS_TEXT) + .input(getString(R.string.text_please_input_name), "", (dialog, input) -> addScriptFile(input.toString(), path)).show(); + } + + private void addScriptFile(String name, String path) { + mScriptFileList.add(new ScriptFile(name, path)); + mScriptListRecyclerView.getAdapter().notifyItemInserted(mScriptFileList.size() - 1); + } + + + private void setUpUI() { + mView = View.inflate(this, R.layout.activity_main, null); + setContentView(mView); + mSlidingUpPanel = $(R.id.bottom_menu); + + setUpToolbar(); + setUpScriptList(); + setUpListener(); + } + + private void setUpToolbar() { + Toolbar toolbar = $(R.id.toolbar); + setSupportActionBar(toolbar); + toolbar.setNavigationIcon(R.drawable.script_droid_50); + toolbar.setTitle(R.string.app_name); + } + + private void setUpScriptList() { + mScriptListRecyclerView = $(R.id.script_list); + mScriptFileList = new SharedPrefScriptFileList(this); + mScriptListRecyclerView.setScriptFileList(mScriptFileList); + } + + private void setUpListener() { + $(R.id.fab).setOnClickListener(view -> mSlidingUpPanel.show()); + $(R.id.import_from_file).setOnClickListener(v -> showFileChooser()); + $(R.id.create_new_file).setOnClickListener(v -> createScriptFile()); + } + + private void createScriptFile() { + new MaterialDialog.Builder(this).title(R.string.text_name) + .inputType(InputType.TYPE_CLASS_TEXT) + .input(getString(R.string.text_please_input_name), "", (dialog, input) -> { + String path = ScriptFile.DEFAULT_FOLDER + input + ".text"; + createScriptFile(input.toString(), path); + }).show(); + } + + private void createScriptFile(String name, String path) { + if (FileUtils.createFileIfNotExists(path)) { + addScriptFile(name, path); + new ScriptFileOperation.Edit().operate(mScriptListRecyclerView, mScriptFileList, mScriptFileList.size() - 1); + } else { + Snackbar.make(mView, R.string.text_file_create_fail, Snackbar.LENGTH_LONG).show(); + } + } + + private void showFileChooser() { + mFileChooser.startFileManagerToChoose("*/*", (exception, mimeType) -> { + exception.printStackTrace(); + Snackbar.make(mView, R.string.text_file_manager_not_found, Snackbar.LENGTH_SHORT).show(); + }); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.menu_main, menu); + return true; + } + + + private final Map mOptionActionMap = new MapEntries() + .entry(R.id.action_exit, this::finish) + .entry(R.id.action_disable_service, this::disableAccessibilityService) + .entry(R.id.action_settings, () -> ActionPerformService.setActions(ActionPerformService.NO_ACTION)) + .map(); + + + private void startSettingActivity() { + //TODO create Setting Activity + startActivity(new Intent(this, MainActivity.class)); + } + + private void disableAccessibilityService() { + Optional.ofNullable(ActionPerformService.getInstance()).ifPresent(ActionPerformService::disableSelf); + Snackbar.make(mView, R.string.text_service_disabled, Snackbar.LENGTH_SHORT).show(); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + Runnable action = mOptionActionMap.get(item.getItemId()); + if (action != null) { + action.run(); + return true; + } else { + return super.onOptionsItemSelected(item); + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + mFileChooser.onActivityResult(requestCode, resultCode, data); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/scriptdroid/ShortcutActivity.java b/app/src/main/java/com/stardust/scriptdroid/ShortcutActivity.java new file mode 100644 index 00000000..4a6bd36f --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ShortcutActivity.java @@ -0,0 +1,89 @@ +package com.stardust.scriptdroid; + +import android.app.Activity; +import android.os.Build; +import android.os.Bundle; +import android.text.TextUtils; +import android.widget.Toast; + +import com.stardust.scriptdroid.droid.Droid; + +import java.io.File; +import java.util.LinkedList; +import java.util.List; +import java.util.function.BooleanSupplier; + +import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; + +/** + * Created by Stardust on 2017/1/23. + */ +public class ShortcutActivity extends Activity { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final String path = getIntent().getStringExtra("path"); + if (!ensure(() -> !TextUtils.isEmpty(path), R.string.text_path_is_empty)) + return; + final File scriptFile = new File(path); + new Domino() + .then(() -> ensure(scriptFile::exists, R.string.text_file_not_exists)) + .then(() -> ensure(this::hasStorageReadPermission, R.string.text_no_file_rw_permission)) + .then(() -> { + runScriptFile(path); + return true; + }) + .fall(); + } + + private void runScriptFile(String path) { + try { + Droid.run(this, new File(path)); + } catch (Exception e) { + e.printStackTrace(); + Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show(); + } + } + + private boolean ensure(BooleanSupplier bool, String message) { + boolean b = bool.getAsBoolean(); + if (!b) { + Toast.makeText(this, message, Toast.LENGTH_SHORT).show(); + } + return b; + } + + private boolean ensure(BooleanSupplier bool, int resId) { + return ensure(bool, getString(resId)); + } + + private boolean hasStorageReadPermission() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && + checkSelfPermission(READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED; + } + + @FunctionalInterface + interface Tile { + boolean fall(); + } + + public static class Domino { + + private List mTiles = new LinkedList<>(); + + Domino then(Tile next) { + mTiles.add(next); + return this; + } + + void fall() { + for (Tile tile : mTiles) { + if (!tile.fall()) + break; + } + } + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/action/Action.java b/app/src/main/java/com/stardust/scriptdroid/action/Action.java new file mode 100644 index 00000000..a2cfc429 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/action/Action.java @@ -0,0 +1,466 @@ +package com.stardust.scriptdroid.action; + +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.os.Build; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.annotation.RequiresApi; +import android.util.SparseArray; +import android.view.accessibility.AccessibilityNodeInfo; + +import java.lang.ref.WeakReference; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; + +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SET_TEXT; + +/** + * Created by Stardust on 2017/1/21. + */ + +public abstract class Action { + + private static WeakReference mContext; + + public static void setActionContext(Context context) { + mContext = new WeakReference<>(context); + } + + public abstract boolean perform(AccessibilityNodeInfo rootNodeInfo); + + interface TargetFilter { + AccessibilityNodeInfo findTarget(AccessibilityNodeInfo nodeInfo); + } + + public static class MultiAction extends Action { + + private List mActions; + private boolean mDependent; + + public MultiAction(List actions, boolean dependent) { + if (actions == null) + throw new NullPointerException("actions = null"); + mDependent = dependent; + mActions = actions; + } + + @Override + public boolean perform(AccessibilityNodeInfo rootNodeInfo) { + boolean succeed = true; + for (Action action : mActions) { + if (!action.perform(rootNodeInfo)) { + succeed = false; + if (!mDependent) + break; + } + } + return succeed; + } + } + + public static abstract class TargetAction extends Action { + + static final int TYPE_ID = 1; + static final int TYPE_TEXT = 2; + static final int TYPE_BOUNDS = 3; + static final int TYPE_DESCRIPTION = 4; + + private int mType; + private String mString; + private Rect mBoundsInScreen; + + private TargetAction(String str, int type) { + if (str == null) + throw new NullPointerException("str == null"); + if (type != TYPE_TEXT && type != TYPE_ID && type != TYPE_DESCRIPTION) + throw new IllegalArgumentException("type illegal"); + mString = str; + mType = type; + } + + private TargetAction(Rect boundsInScreen) { + mType = TYPE_BOUNDS; + mBoundsInScreen = boundsInScreen; + } + + @Override + public boolean perform(AccessibilityNodeInfo rootNodeInfo) { + List target = null; + switch (mType) { + case TYPE_TEXT: + target = rootNodeInfo.findAccessibilityNodeInfosByText(mString); + break; + case TYPE_ID: + target = rootNodeInfo.findAccessibilityNodeInfosByViewId(mString); + break; + case TYPE_BOUNDS: + AccessibilityNodeInfo nodeInfo = findAccessibilityNodeInfosByBounds(rootNodeInfo, mBoundsInScreen); + target = nodeInfo == null ? Collections.EMPTY_LIST : Collections.singletonList(nodeInfo); + break; + case TYPE_DESCRIPTION: + target = findAccessibilityNodeInfosByDescription(rootNodeInfo, mString); + } + return target != null && perform(target); + } + + + private List findAccessibilityNodeInfosByDescription(AccessibilityNodeInfo rootNodeInfo, String description) { + if (description.startsWith("edittext") && !description.equals("edittext")) { + int i = Integer.parseInt(description.substring(8)); + return Collections.singletonList(findEditText(rootNodeInfo).get(i)); + } + if (description.equals("edittext")) { + return findEditText(rootNodeInfo); + } + return null; + } + + private List findEditText(AccessibilityNodeInfo rootNodeInfo) { + if (rootNodeInfo == null) { + return Collections.EMPTY_LIST; + } + if (rootNodeInfo.isEditable()) { + return Collections.singletonList(rootNodeInfo); + } + List list = new LinkedList<>(); + for (int i = 0; i < rootNodeInfo.getChildCount(); i++) { + list.addAll(findEditText(rootNodeInfo.getChild(i))); + } + return list; + } + + //(882, 1722 - 1036, 1876) + private AccessibilityNodeInfo findAccessibilityNodeInfosByBounds(AccessibilityNodeInfo root, Rect boundsInScreen) { + if (root == null) + return null; + Rect rect = new Rect(); + root.getBoundsInScreen(rect); + if (rect.equals(boundsInScreen)) { + return root; + } + for (int i = 0; i < root.getChildCount(); i++) { + AccessibilityNodeInfo child = root.getChild(i); + if (child == null) + continue; + AccessibilityNodeInfo nodeInfo = findAccessibilityNodeInfosByBounds(child, boundsInScreen); + if (nodeInfo != null) + return nodeInfo; + else + child.recycle(); + } + return null; + } + + abstract boolean perform(@NonNull List nodeInfoList); + } + + public static class SimpleAction extends TargetAction { + + private int mAction; + + SimpleAction(int action, String str, int type) { + super(str, type); + mAction = action; + } + + SimpleAction(int action, Rect boundsInScreen) { + super(boundsInScreen); + mAction = action; + } + + @Override + boolean perform(@NonNull List nodeInfoList) { + for (AccessibilityNodeInfo nodeInfo : nodeInfoList) { + performAction(nodeInfo); + } + return true; + } + + boolean performAction(AccessibilityNodeInfo nodeInfo) { + return nodeInfo.performAction(mAction); + } + + void setAction(int action) { + mAction = action; + } + + int getAction() { + return mAction; + } + } + + public static class SimpleFilterAction extends SimpleAction { + + private TargetFilter mFilter; + + SimpleFilterAction(int action, String str, int type, TargetFilter filter) { + super(action, str, type); + mFilter = filter; + } + + SimpleFilterAction(int action, Rect boundsInScreen, TargetFilter filter) { + super(action, boundsInScreen); + mFilter = filter; + } + + @Override + boolean perform(@NonNull List nodeInfoList) { + boolean performed = false; + for (AccessibilityNodeInfo node : nodeInfoList) { + node = mFilter.findTarget(node); + if (node != null) { + performAction(node); + performed = true; + } + } + return performed; + } + + } + + interface Able { + boolean isAble(AccessibilityNodeInfo node); + } + + private static final SparseArray ACTION_ABLE_MAP = new SparseArray<>(); + + static { + ACTION_ABLE_MAP.put(ACTION_CLICK, new Able() { + @Override + public boolean isAble(AccessibilityNodeInfo node) { + return node.isClickable(); + } + }); + ACTION_ABLE_MAP.put(ACTION_LONG_CLICK, new Able() { + @Override + public boolean isAble(AccessibilityNodeInfo node) { + return node.isLongClickable(); + } + }); + ACTION_ABLE_MAP.put(ACTION_FOCUS, new Able() { + @Override + public boolean isAble(AccessibilityNodeInfo node) { + return node.isFocusable(); + } + }); + ACTION_ABLE_MAP.put(ACTION_SCROLL_FORWARD, new Able() { + @Override + public boolean isAble(AccessibilityNodeInfo node) { + return node.isScrollable(); + } + }); + ACTION_ABLE_MAP.put(ACTION_SCROLL_BACKWARD, new Able() { + @Override + public boolean isAble(AccessibilityNodeInfo node) { + return node.isScrollable(); + } + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + ACTION_ABLE_MAP.put(ACTION_SET_TEXT, new Able() { + @Override + public boolean isAble(AccessibilityNodeInfo node) { + return node.isEditable(); + } + }); + } + } + + + public static class FindDownwardlyDfsFilterAction extends SimpleFilterAction { + + public static FindDownwardlyDfsFilterAction createActionByBounds(int action, Rect boundInScreen) { + Able able = ACTION_ABLE_MAP.get(action); + return new FindDownwardlyDfsFilterAction(action, boundInScreen, able); + } + + FindDownwardlyDfsFilterAction(int action, Rect boundInScreen, Able able) { + super(action, boundInScreen, new FindDownwardlyDfsTargetFilter(able)); + } + + FindDownwardlyDfsFilterAction(int action, String str, int type, Able able) { + super(action, str, type, new FindDownwardlyDfsTargetFilter(able)); + } + + + private static class FindDownwardlyDfsTargetFilter implements TargetFilter { + Able mAble; + + FindDownwardlyDfsTargetFilter(Able able) { + mAble = able; + } + + @Override + public AccessibilityNodeInfo findTarget(AccessibilityNodeInfo n) { + if (n == null) + return null; + if (mAble.isAble(n)) + return n; + for (int i = 0; i < n.getChildCount(); i++) { + AccessibilityNodeInfo child = n.getChild(i); + if (child == null) + continue; + AccessibilityNodeInfo node = findTarget(child); + if (node != null) + return node; + else + child.recycle(); + } + return null; + } + } + + } + + public static class FindUpwardlyFilterAction extends SimpleFilterAction { + + + public static FindUpwardlyFilterAction createActionById(int action, String id) { + return createActionInner(action, id, TYPE_ID); + } + + public static FindUpwardlyFilterAction createActionByText(int action, String text) { + return createActionInner(action, text, TYPE_TEXT); + } + + public static Action createActionByDescription(int action, String description) { + return createActionInner(action, description, TYPE_DESCRIPTION); + } + + private static FindUpwardlyFilterAction createActionInner(int action, String str, int type) { + Able able = ACTION_ABLE_MAP.get(action); + return new FindUpwardlyFilterAction(action, str, type, able); + } + + private static class FindUpwardlyTargetFilter implements TargetFilter { + Able mAble; + + FindUpwardlyTargetFilter(Able able) { + mAble = able; + } + + @Override + public AccessibilityNodeInfo findTarget(AccessibilityNodeInfo n) { + AccessibilityNodeInfo node = n; + while (node != null && !mAble.isAble(node)) { + AccessibilityNodeInfo parent = node.getParent(); + if (node != n) { + node.recycle(); + } + node = parent; + } + return node; + } + } + + FindUpwardlyFilterAction(int action, String str, int type, Able able) { + super(action, str, type, new FindUpwardlyTargetFilter(able)); + } + + FindUpwardlyFilterAction(int action, Rect boundsInScreen, Able able) { + super(action, boundsInScreen, new FindUpwardlyTargetFilter(able)); + } + } + + public static class ScrollAction extends Action { + + public static final int SCROLL_FORWARD = AccessibilityNodeInfo.ACTION_SCROLL_FORWARD; + public static final int SCROLL_BACKWARD = AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; + + private int mScrollAction; + private int mTimes; + + public ScrollAction(int scrollAction) { + this(scrollAction, 1); + } + + public ScrollAction(int scrollAction, int times) { + if (scrollAction != SCROLL_BACKWARD && scrollAction != SCROLL_FORWARD) + throw new IllegalArgumentException("scrollAction illegal"); + mScrollAction = scrollAction; + mTimes = times; + } + + @Override + public boolean perform(AccessibilityNodeInfo rootNodeInfo) { + AccessibilityNodeInfo scrollableNodeInfo = findScrollableNodeInfo(rootNodeInfo); + if (scrollableNodeInfo == null) { + return false; + } else { + for (int i = 0; i < mTimes; i++) { + scrollableNodeInfo.performAction(mScrollAction); + } + return true; + } + } + + 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; + } + + } + + public static class IntentAction extends Action { + + private final Intent mIntent; + + public IntentAction(Intent intent) { + mIntent = intent; + } + + @Override + public boolean perform(AccessibilityNodeInfo rootNodeInfo) { + if (mContext == null || mContext.get() == null) + return false; + mContext.get().startActivity(mIntent); + return true; + } + + public Intent getIntent() { + return mIntent; + } + } + + + @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP) + public static class InputAction extends FindUpwardlyFilterAction { + + private static final Able EDITABLE = new Able() { + @Override + public boolean isAble(AccessibilityNodeInfo node) { + return node.isEditable(); + } + }; + private String mText; + + public InputAction(String description, String text) { + super(ACTION_SET_TEXT, description, TYPE_DESCRIPTION, EDITABLE); + mText = text; + } + + boolean performAction(AccessibilityNodeInfo nodeInfo) { + Bundle arg = new Bundle(); + arg.putCharSequence(ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, mText); + return nodeInfo.performAction(ACTION_SET_TEXT, arg); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/action/ActionPerformService.java b/app/src/main/java/com/stardust/scriptdroid/action/ActionPerformService.java new file mode 100644 index 00000000..c851a2fb --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/action/ActionPerformService.java @@ -0,0 +1,184 @@ +package com.stardust.scriptdroid.action; + +import android.accessibilityservice.AccessibilityService; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.graphics.Rect; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityNodeInfo; +import android.widget.Toast; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.stardust.scriptdroid.R; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + + +/** + * Created by Stardust on 2017/1/21. + */ + +public class ActionPerformService extends AccessibilityService { + + private static final String TAG = "SettingRunningServiceSS"; + private static ActionPerformService instance; + + public static void goToPermissionSettingIfDisabled(final Context context) { + if (!isAccessibilityServiceEnabled(context, ActionPerformService.class)) { + new MaterialDialog.Builder(context) + .content(R.string.explain_accessibility_permission) + .positiveText(R.string.text_go_to_setting) + .negativeText(R.string.text_cancel) + .onPositive((dialog, which) -> context.startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))).show(); + } + } + + public static boolean isAccessibilityServiceEnabled(Context context, Class accessibilityService) { + ComponentName expectedComponentName = new ComponentName(context, accessibilityService); + + String enabledServicesSetting = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES); + if (enabledServicesSetting == null) + return false; + + TextUtils.SimpleStringSplitter colonSplitter = new TextUtils.SimpleStringSplitter(':'); + colonSplitter.setString(enabledServicesSetting); + + while (colonSplitter.hasNext()) { + String componentNameString = colonSplitter.next(); + ComponentName enabledService = ComponentName.unflattenFromString(componentNameString); + + if (enabledService != null && enabledService.equals(expectedComponentName)) + return true; + } + + return false; + } + + public static final int STATE_WAIT = 0; + @SuppressWarnings("unchecked") + public static final List NO_ACTION = Collections.EMPTY_LIST; + + public static final List actions = new ArrayList<>(); + private static int state = STATE_WAIT; + + public static boolean assistModeEnable = true; + private AccessibilityNodeInfo mLastFocus; + + public static void setActions(Collection collection) { + synchronized (actions) { + actions.clear(); + actions.addAll(collection); + state = STATE_WAIT; + } + } + + public static ActionPerformService getInstance() { + return instance; + } + + @Override + public void onAccessibilityEvent(AccessibilityEvent event) { + Log.v(TAG, "event type=" + event.getEventType()); + AccessibilityNodeInfo nodeInfo = getRootInActiveWindow(); + performAssistance(event, event.getSource()); + Log.v(TAG, "rootInActiveWindow = " + nodeInfo); + if (nodeInfo == null) + return; + Log.v(TAG, "state = " + state); + Action action = nextAction(); + if (action == null) { + reset(); + } else if (action.perform(nodeInfo)) { + state++; + } + } + + private void performAssistance(AccessibilityEvent event, AccessibilityNodeInfo nodeInfo) { + if (!assistModeEnable || nodeInfo == null) + return; + if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_CLICKED || event.getEventType() == AccessibilityEvent.TYPE_VIEW_LONG_CLICKED) { + nodeInfo.refresh(); + Log.v(TAG, "click: " + nodeInfo); + Rect rect = new Rect(); + nodeInfo.getBoundsInScreen(rect); + String str = rect.toString().replace('-', ',').replace(" ", ""); + ClipboardManager manager = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE); + manager.setPrimaryClip(ClipData.newPlainText("", str)); + Toast.makeText(this, "id=" + nodeInfo.getViewIdResourceName() + " bounds=" + str, Toast.LENGTH_SHORT).show(); + } + } + + private int[] findAddress(AccessibilityNodeInfo nodeInfo) { + List address = new ArrayList<>(); + AccessibilityNodeInfo lastNodeInfo = null; + while (nodeInfo.getParent() != null) { + address.add(findPositionInParent(nodeInfo)); + if (lastNodeInfo != null) + lastNodeInfo.recycle(); + lastNodeInfo = nodeInfo; + nodeInfo = nodeInfo.getParent(); + } + int[] array = new int[address.size()]; + for (int i = 0; i < address.size(); i++) { + array[i] = address.get(address.size() - i - 1); + } + return array; + } + + + + private int findPositionInParent(AccessibilityNodeInfo nodeInfo) { + AccessibilityNodeInfo parent = nodeInfo.getParent(); + for (int i = 0; i < parent.getChildCount(); i++) { + AccessibilityNodeInfo child = parent.getChild(i); + if (child != null && child.equals(nodeInfo)) { + parent.recycle(); + child.recycle(); + return i; + } + if (child != null) { + child.recycle(); + } + } + parent.recycle(); + return -1; + } + + private void reset() { + state = STATE_WAIT; + synchronized (actions) { + actions.clear(); + } + } + + private Action nextAction() { + synchronized (actions) { + if (state >= actions.size()) { + return null; + } + return actions.get(state); + } + } + + @Override + public void onInterrupt() { + + } + + @Override + public void onServiceConnected() { + Log.v(TAG, "onServiceConnected"); + Action.setActionContext(this); + instance = this; + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/data/ScriptFile.java b/app/src/main/java/com/stardust/scriptdroid/data/ScriptFile.java new file mode 100644 index 00000000..ef49ddde --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/data/ScriptFile.java @@ -0,0 +1,52 @@ +package com.stardust.scriptdroid.data; + +import android.content.Context; +import android.os.Environment; + +import com.stardust.scriptdroid.action.Action; +import com.stardust.scriptdroid.action.ActionPerformService; +import com.stardust.scriptdroid.droid.Interpreter; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileReader; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class ScriptFile { + + public static final String DEFAULT_FOLDER = Environment.getExternalStorageDirectory() + "/脚本/"; + public String name; + + public String path; + + public ScriptFile(String name, String path) { + this.name = name; + this.path = path; + } + + public void run(Context context) { + Interpreter interpreter = new Interpreter(context); + + try (BufferedReader reader = new BufferedReader(new FileReader(path))) { + List actions = new ArrayList<>(); + String line = reader.readLine(); + while (line != null) { + actions.add(interpreter.interpreter(line)); + line = reader.readLine(); + } + ActionPerformService.setActions(actions); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public File toFile() { + return new File(path); + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/data/ScriptFileList.java b/app/src/main/java/com/stardust/scriptdroid/data/ScriptFileList.java new file mode 100644 index 00000000..6cc46d75 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/data/ScriptFileList.java @@ -0,0 +1,28 @@ +package com.stardust.scriptdroid.data; + +import java.io.File; + +/** + * Created by Stardust on 2017/1/23. + */ + +public abstract class ScriptFileList { + + public abstract void add(ScriptFile scriptFile); + + public abstract ScriptFile get(int i); + + public abstract void remove(int i); + + public abstract void rename(int position, String newName); + + public abstract int size(); + + public boolean deleteFromFileSystem(int i) { + File file = new File(get(i).path); + remove(i); + return file.delete(); + } + + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/data/SharedPrefScriptFileList.java b/app/src/main/java/com/stardust/scriptdroid/data/SharedPrefScriptFileList.java new file mode 100644 index 00000000..694b43eb --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/data/SharedPrefScriptFileList.java @@ -0,0 +1,86 @@ +package com.stardust.scriptdroid.data; + +import android.content.Context; +import android.content.SharedPreferences; + +import com.google.gson.Gson; +import com.google.gson.reflect.TypeToken; + +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class SharedPrefScriptFileList extends ScriptFileList { + + private static final Gson GSON = new Gson(); + + private static final String SP_KEY_SCRIPT_NAME = "script_name"; + private static final String SP_KEY_SCRIPT_PATH = "script_path"; + + private SharedPreferences mSharedPreferences; + private List mScriptPath; + private List mScriptName; + + + public SharedPrefScriptFileList(Context context) { + mSharedPreferences = context.getSharedPreferences("SharedPrefScriptFileList", Context.MODE_PRIVATE); + readFromSharedPref(); + } + + private void readFromSharedPref() { + Type type = new TypeToken>() { + }.getType(); + mScriptName = GSON.fromJson(mSharedPreferences.getString(SP_KEY_SCRIPT_NAME, ""), type); + mScriptPath = GSON.fromJson(mSharedPreferences.getString(SP_KEY_SCRIPT_PATH, ""), type); + if (mScriptName == null || mScriptPath == null || mScriptName.size() != mScriptPath.size()) { + reset(); + } + } + + private void reset() { + mScriptName = new ArrayList<>(); + mScriptPath = new ArrayList<>(); + syncWithSharedPref(); + } + + @Override + public void add(ScriptFile scriptFile) { + mScriptName.add(scriptFile.name); + mScriptPath.add(scriptFile.path); + syncWithSharedPref(); + } + + @Override + public ScriptFile get(int i) { + return new ScriptFile(mScriptName.get(i), mScriptPath.get(i)); + } + + @Override + public void remove(int i) { + mScriptName.remove(i); + mScriptPath.remove(i); + syncWithSharedPref(); + } + + @Override + public void rename(int position, String newName) { + mScriptName.set(position, newName); + syncWithSharedPref(); + } + + @Override + public int size() { + return mScriptPath.size(); + } + + protected void syncWithSharedPref() { + SharedPreferences.Editor editor = mSharedPreferences.edit(); + editor.putString(SP_KEY_SCRIPT_NAME, GSON.toJson(mScriptName)); + editor.putString(SP_KEY_SCRIPT_PATH, GSON.toJson(mScriptPath)); + editor.apply(); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java new file mode 100644 index 00000000..8b9dbfad --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/droid/Droid.java @@ -0,0 +1,29 @@ +package com.stardust.scriptdroid.droid; + +import android.content.Context; + +import com.stardust.scriptdroid.action.ActionPerformService; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class Droid { + + public static void run(Context context, File file) { + Interpreter interpreter = new Interpreter(context); + try (FileInputStream fis = new FileInputStream(file)) { + byte[] bytes = new byte[fis.available()]; + fis.read(bytes); + fis.close(); + String str = new String(bytes); + ActionPerformService.setActions(interpreter.interpreterAll(str)); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/Interpreter.java b/app/src/main/java/com/stardust/scriptdroid/droid/Interpreter.java new file mode 100644 index 00000000..09466c8f --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/droid/Interpreter.java @@ -0,0 +1,210 @@ +package com.stardust.scriptdroid.droid; + +import android.content.Context; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.graphics.Rect; +import android.os.Build; +import android.util.Pair; + +import com.stardust.scriptdroid.action.Action; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.stream.Stream; + +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_BACKWARD; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SCROLL_FORWARD; +import static android.view.accessibility.AccessibilityNodeInfo.ACTION_SELECT; + +/** + * Created by Stardust on 2017/1/21. + */ + +public class Interpreter { + + interface Filter { + boolean filter(String str); + } + + private static final Filter FILTER_STRING = new Filter() { + @Override + public boolean filter(String str) { + return str.startsWith("\"") && str.endsWith("\""); + } + }; + + + private static final Filter FILTER_ADDRESS = (str) -> str.startsWith("(") && str.endsWith(")"); + + private static final Filter FILTER_DESCRIPTION = new Filter() { + @Override + public boolean filter(String str) { + return str.startsWith("[") && str.endsWith("]"); + } + }; + + private static final Filter FILTER_ANY = new Filter() { + @Override + public boolean filter(String str) { + return true; + } + }; + + private static class StateTree { + + private List> mChildren = new LinkedList<>(); + + StateTree child(final String str, StateTree child) { + return child(new Filter() { + @Override + public boolean filter(String s) { + return str.equals(s); + } + }, child); + } + + StateTree child(Filter filter, StateTree child) { + mChildren.add(new Pair<>(filter, child)); + return this; + } + + StateLeaf match(String[] command, int startIndex) { + for (Pair child : mChildren) { + if (child.first.filter(command[startIndex])) { + return child.second.match(command, startIndex + 1); + } + } + return null; + } + + public StateTree child(String command, final int action) { + return child(command, new StateTree() + .child(FILTER_STRING, new StateLeaf() { + @Override + Action handle(String[] command) { + return Action.FindUpwardlyFilterAction.createActionByText(action, StringTool.removeDoubleQuotes(command[1])); + } + }) + .child(FILTER_DESCRIPTION, new StateLeaf() { + @Override + Action handle(String[] command) { + return Action.FindUpwardlyFilterAction.createActionByDescription(action, StringTool.removeDoubleQuotes(command[1])); + } + }) + .child(FILTER_ADDRESS, new StateLeaf() { + @Override + Action handle(String[] command) { + String[] intStrings = StringTool.removeDoubleQuotes(command[1]).split(","); + int[] bounds = Stream.of(intStrings).mapToInt(Integer::parseInt).toArray(); + Rect rect = new Rect(bounds[0], bounds[1], bounds[2], bounds[3]); + return Action.FindDownwardlyDfsFilterAction.createActionByBounds(action, rect); + } + }) + .child(FILTER_ANY, new StateLeaf() { + @Override + Action handle(String[] command) { + return Action.FindUpwardlyFilterAction.createActionById(action, StringTool.removeDoubleQuotes(command[1])); + } + })); + } + } + + private abstract static class StateLeaf extends StateTree { + + abstract Action handle(String[] command); + + StateLeaf match(String[] command, int startIndex) { + return this; + } + } + + private Context mContext; + + private StateTree stateTree = new StateTree() + .child("click", ACTION_CLICK) + .child("longclick", ACTION_LONG_CLICK) + .child("focus", ACTION_FOCUS) + .child("select", ACTION_SELECT) + .child("forward", ACTION_SCROLL_FORWARD) + .child("backward", ACTION_SCROLL_BACKWARD) + .child("scrollup", new StateLeaf() { + @Override + Action handle(String[] command) { + return new Action.ScrollAction(Action.ScrollAction.SCROLL_BACKWARD); + } + }).child("scrolldown", new StateLeaf() { + @Override + Action handle(String[] command) { + return new Action.ScrollAction(Action.ScrollAction.SCROLL_FORWARD); + } + }).child("input", new StateTree().child(FILTER_DESCRIPTION, new StateLeaf() { + @Override + Action handle(String[] command) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return new Action.InputAction(StringTool.removeDoubleQuotes(command[1]), command[2]); + } else { + return null; + } + } + })).child("open", new StateTree() + .child(FILTER_STRING, new StateLeaf() { + @Override + Action handle(String[] command) { + String appName = StringTool.removeDoubleQuotes(command[1]); + List installedApplications = mContext.getPackageManager().getInstalledApplications(PackageManager.GET_META_DATA); + for (ApplicationInfo applicationInfo : installedApplications) { + if (mContext.getPackageManager().getApplicationLabel(applicationInfo).toString().equals(appName)) { + return new Action.IntentAction(mContext.getPackageManager().getLaunchIntentForPackage(applicationInfo.packageName)); + } + } + return null; + } + }).child(FILTER_ANY, new StateLeaf() { + @Override + Action handle(String[] command) { + return new Action.IntentAction(mContext.getPackageManager().getLaunchIntentForPackage(command[1])); + } + })); + + + public Interpreter(Context context) { + mContext = context; + } + + public Action interpreterInner(String line) { + String[] words = line.split(" "); + return stateTree.match(words, 0).handle(words); + } + + public Action interpreter(String line) { + boolean independent = true; + String[] actions = line.split("\\|"); + if (actions.length == 1) { + actions = line.split("\\&"); + independent = false; + } + if (actions.length == 1) + return interpreterInner(line); + List list = new ArrayList<>(actions.length); + for (String action : actions) { + list.add(interpreterInner(action)); + } + return new Action.MultiAction(list, independent); + } + + + public List interpreterAll(String str) { + String[] lines = str.split("\n"); + List list = new ArrayList<>(lines.length); + for (String line : lines) { + list.add(interpreter(line)); + } + return list; + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/StringTool.java b/app/src/main/java/com/stardust/scriptdroid/droid/StringTool.java new file mode 100644 index 00000000..06816efe --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/droid/StringTool.java @@ -0,0 +1,10 @@ +package com.stardust.scriptdroid.droid; + +/** + * Created by Stardust on 2017/1/22. + */ +public class StringTool { + public static String removeDoubleQuotes(String str) { + return str.substring(1, str.length() - 1); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/droid/SyntaxDefinition.java b/app/src/main/java/com/stardust/scriptdroid/droid/SyntaxDefinition.java new file mode 100644 index 00000000..776012c8 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/droid/SyntaxDefinition.java @@ -0,0 +1,30 @@ +package com.stardust.scriptdroid.droid; + +/** + * Created by Stardust on 2017/1/21. + */ + +public class SyntaxDefinition { + + /** + * click 文本 + * 点击文本所在区域。e.g. click 朋友圈 + * click 编号 + * 点击编号对应区域 + * longclick 文本 + * + * longclick 编号 + * + * scrollup + * + * scrolldown + * + * scrollup 编号 [幅度] + * + * scrolldown 编号 [幅度] + * + * + * + */ + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/file/FileChooser.java b/app/src/main/java/com/stardust/scriptdroid/file/FileChooser.java new file mode 100644 index 00000000..87f2f570 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/file/FileChooser.java @@ -0,0 +1,65 @@ +package com.stardust.scriptdroid.file; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.ContentResolver; +import android.content.Intent; +import android.net.Uri; + +import java.io.FileNotFoundException; +import java.io.InputStream; + +import static android.app.Activity.RESULT_OK; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class FileChooser { + + public interface FileManagerNotFoundHandler { + void handle(ActivityNotFoundException e, String mimeType); + } + + public interface OnFileChoseListener { + void onFileChose(InputStream inputStream); + } + + private static final int FILE_CHOOSE = 1209; + private Activity mActivity; + + private OnFileChoseListener mOnFileChoseListener; + + public FileChooser(Activity activity) { + mActivity = activity; + } + + public void setOnFileChoseListener(OnFileChoseListener onFileChoseListener) { + mOnFileChoseListener = onFileChoseListener; + } + + + public void startFileManagerToChoose(String mimeType, FileManagerNotFoundHandler handler) { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType(mimeType); + intent.addCategory(Intent.CATEGORY_OPENABLE); + try { + mActivity.startActivityForResult(Intent.createChooser(intent, "选择一个文件"), FILE_CHOOSE); + } catch (ActivityNotFoundException ex) { + handler.handle(ex, mimeType); + } + } + + public void onActivityResult(int requestCode, int resultCode, Intent data) { + if (requestCode == FILE_CHOOSE && resultCode == RESULT_OK) { + Uri uri = data.getData(); + ContentResolver cr = mActivity.getContentResolver(); + try { + InputStream inputStream = cr.openInputStream(uri); + mOnFileChoseListener.onFileChose(inputStream); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java b/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java new file mode 100644 index 00000000..eb396ec5 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/file/FileUtils.java @@ -0,0 +1,79 @@ +package com.stardust.scriptdroid.file; + +import android.content.Context; +import android.database.Cursor; +import android.net.Uri; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Field; +import java.net.URISyntaxException; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class FileUtils { + + public static String getPath(Context context, Uri uri) throws URISyntaxException { + if ("content".equalsIgnoreCase(uri.getScheme())) { + String[] projection = {"_data"}; + Cursor cursor = null; + + try { + cursor = context.getContentResolver().query(uri, projection, null, null, null); + int column_index = cursor.getColumnIndexOrThrow("_data"); + if (cursor.moveToFirst()) { + return cursor.getString(column_index); + } + } catch (Exception e) { + // Eat it + } + } else if ("file".equalsIgnoreCase(uri.getScheme())) { + return uri.getPath(); + } + + return null; + } + + public static String getPath(InputStream inputStream) { + if (inputStream instanceof FileInputStream) { + FileInputStream fis = (FileInputStream) inputStream; + try { + Field field = fis.getClass().getDeclaredField("path"); + field.setAccessible(true); + return (String) field.get(fis); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + return null; + } + + public static boolean createFileIfNotExists(String path) { + ensureFolder(path); + File file = new File(path); + if (!file.exists()) { + try { + return file.createNewFile(); + } catch (IOException e) { + e.printStackTrace(); + } + } + return false; + } + + private static boolean ensureFolder(String path) { + int i = path.lastIndexOf("\\"); + if (i < 0) + i = path.lastIndexOf("/"); + if (i >= 0) { + String folder = path.substring(i); + return new File(folder).mkdirs(); + } else { + return false; + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/runningservices/RunningPackageTool.java b/app/src/main/java/com/stardust/scriptdroid/runningservices/RunningPackageTool.java new file mode 100644 index 00000000..1e58da6f --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/runningservices/RunningPackageTool.java @@ -0,0 +1,78 @@ +package com.stardust.scriptdroid.runningservices; + +import android.annotation.TargetApi; +import android.app.ActivityManager; +import android.app.usage.UsageStats; +import android.app.usage.UsageStatsManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Build; +import android.provider.Settings; + +import java.util.Collections; +import java.util.List; + +/** + * Created by Stardust on 2017/1/20. + */ + +public class RunningPackageTool { + + + public static String getRunningPackage(Context context) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + return getRunningPackageLollipop(context); + } else { + return getRunningPackageBeforeLollipop(context); + } + + } + + private static String getRunningPackageBeforeLollipop(Context context) { + ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + @SuppressWarnings("deprecation") + ComponentName runningActivity = am.getRunningTasks(1).get(0).topActivity; + return runningActivity.getPackageName(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static String getRunningPackageLollipop(Context context) { + List usageStats = getPastTwoSecondsUsageStats(context); + UsageStats latestStats = findLatestUsageStats(usageStats); + if (latestStats == null) + return null; + return latestStats.getPackageName(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static void goToUsageAccessSettings(Context context) { + Intent intent = new Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS); + context.startActivity(intent); + } + + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + private static UsageStats findLatestUsageStats(List usageStats) { + UsageStats latestStats = null; + for (UsageStats us : usageStats) { + if (latestStats == null || latestStats.getLastTimeUsed() < us.getLastTimeUsed()) { + latestStats = us; + } + } + return latestStats; + } + + + @TargetApi(Build.VERSION_CODES.LOLLIPOP_MR1) + @SuppressWarnings("unchecked") + private static List getPastTwoSecondsUsageStats(Context context) { + long ts = System.currentTimeMillis(); + UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE); + List usageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, ts - 2000, ts); + if (usageStats == null) { + usageStats = Collections.EMPTY_LIST; + } + return usageStats; + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/shortcut/Shortcut.java b/app/src/main/java/com/stardust/scriptdroid/shortcut/Shortcut.java new file mode 100644 index 00000000..937cab59 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/shortcut/Shortcut.java @@ -0,0 +1,114 @@ +package com.stardust.scriptdroid.shortcut; + +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +/** + * Created by Stardust on 2017/1/20. + */ + +public class Shortcut { + + private Context mContext; + private String mName; + private String mTargetClass; + private String mTargetPackage; + private Intent.ShortcutIconResource mIcon; + private boolean mDuplicate = false; + private Intent mLaunchIntent = new Intent(); + + public Shortcut(Context context) { + mContext = context; + mTargetPackage = mContext.getPackageName(); + } + + public Shortcut(String name, Context context) { + this(context); + mName = name; + } + + public Shortcut name(String name) { + mName = name; + return this; + } + + public Shortcut targetPackage(String targetPackage) { + mTargetPackage = targetPackage; + return this; + } + + public Shortcut targetClass(String targetClass) { + mTargetClass = targetClass; + return this; + } + + public Shortcut targetClass(Class targetClass) { + mTargetClass = targetClass.getName(); + return this; + } + + + public Shortcut icon(Intent.ShortcutIconResource icon) { + mIcon = icon; + return this; + } + + public Shortcut icon(int resId) { + mIcon = Intent.ShortcutIconResource.fromContext(mContext, resId); + return this; + } + + public Shortcut duplicate(boolean duplicate) { + mDuplicate = duplicate; + return this; + } + + public String getName() { + return mName; + } + + public Intent.ShortcutIconResource getIcon() { + return mIcon; + } + + public boolean isDuplicate() { + return mDuplicate; + } + + private String getClassName() { + return mTargetClass; + } + + private String getPackageName() { + return mTargetPackage; + } + + public Intent getCreateIntent() { + return new Intent(Intent.ACTION_CREATE_SHORTCUT) + .putExtra(Intent.EXTRA_SHORTCUT_NAME, getName()) + .putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, getIcon()) + .putExtra(Intent.EXTRA_SHORTCUT_INTENT, getLaunchIntent()) + .putExtra("duplicate", isDuplicate()) + .setAction("com.android.launcher.action.INSTALL_SHORTCUT"); + } + + public Intent getLaunchIntent() { + mLaunchIntent.setClassName(getPackageName(), getClassName()); + return mLaunchIntent; + } + + public void send() { + mContext.sendBroadcast(getCreateIntent()); + } + + public Shortcut extras(Bundle bundle) { + mLaunchIntent.putExtras(bundle); + return this; + } + + public Shortcut extras(Intent intent) { + mLaunchIntent.putExtras(intent); + return this; + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/shortcut/ShortcutHelper.java b/app/src/main/java/com/stardust/scriptdroid/shortcut/ShortcutHelper.java new file mode 100644 index 00000000..e80ba7ff --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/shortcut/ShortcutHelper.java @@ -0,0 +1,418 @@ +package com.stardust.scriptdroid.shortcut; + +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ProviderInfo; +import android.content.pm.ResolveInfo; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Build; +import android.os.Parcelable; +import android.text.TextUtils; +import android.util.Log; + +import com.stardust.scriptdroid.R; + +import java.util.ArrayList; +import java.util.List; + +public class ShortcutHelper { + public final static String TAG = ShortcutHelper.class.getSimpleName(); + public static final String ACTION_DESKTOP_LINK = "com.scu.zqc.action.SHORTCUT"; + public static final String READ_SETTINGS = "com.android.launcher.permission.READ_SETTINGS"; + public static final String ACTION_INSTALL_SHORTCUT = "com.android.launcher.action.INSTALL_SHORTCUT"; + public static final String ACTION_UNINSTALL_SHORTCUT = "com.android.launcher.action.UNINSTALL_SHORTCUT"; + public static final String MAIN_ACTIVITY = "com.scu.shortcut.MainActivity"; + public static final String APP_STORE_URL = "http://m.app.so.com/?src=browser"; + public static final String EXTRA_DUPLICATE = "duplicate"; + + public static void addShortCut(final Context context, Bitmap src) { + String appStoreShortcutName = context.getString(R.string.app_name); + if (getIsAddShortCut(context, appStoreShortcutName)) { +// uninstallShortcut(context, appStoreShortcutName); + try { + int size = (int) context.getResources().getDimension(android.R.dimen.app_icon_size); + Bitmap scaledBitmap = Bitmap.createScaledBitmap(src, size, size, true); + createFavLinkShortCut(context, appStoreShortcutName, APP_STORE_URL, scaledBitmap, false, true); + } catch (Exception e) { + } + } + } + + public static boolean uninstallShortcut(Context context, String name, Intent shortCut) { + Intent intent = new Intent(ACTION_UNINSTALL_SHORTCUT); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortCut); + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); + context.sendBroadcast(intent); + return true; + } + + public static boolean createFavLinkShortCut(Context context, String name, String url, Parcelable icon, + boolean isSendToQihooDesktop, boolean skipCheckExist) { + if (context == null || TextUtils.isEmpty(url) || TextUtils.isEmpty(name) || icon == null) { + return false; + } + // 先查询launcher页面是否已经生成其快捷方式 + if (getIsAddShortCut(context, name) && !skipCheckExist) { + return false; + } + + Intent shortcutIntent = getShortCutIntent(context, url, MAIN_ACTIVITY); + shortcutIntent.setAction(ACTION_DESKTOP_LINK); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + shortcutIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + Intent installIntent = getInstallIntent(shortcutIntent, name, icon); + + // 将创建快捷方式的intent指定到桌面应用程序包来处理. + String deskPackageName = getLauncherPackageName(context); + if (null != deskPackageName && !TextUtils.isEmpty(deskPackageName)) { + installIntent.setPackage(deskPackageName); + } + + if (isSendToQihooDesktop) { + installIntent.putExtra("from", context.getPackageName()); + } + + context.sendBroadcast(installIntent); + return true; + } + + /** + * 获取正在运行桌面包名(注:存在多个桌面时且未指定默认桌面时,该方法返回Null,使用时需处理这个情况) + */ + public static String getLauncherPackageName(Context context) { + try { + final Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + final ResolveInfo res = context.getPackageManager().resolveActivity(intent, 0); + if (res != null && res.activityInfo == null) { + // should not happen. A home is always installed, isn't it? + return null; + } + + if (res != null && ("android").equals(res.activityInfo.packageName)) { + // 有多个桌面程序存在,且未指定默认项时; + return null; + } else { + return res.activityInfo.packageName; + } + } catch (Exception e) { + + } + + return null; + } + + private static Intent getInstallIntent(Context context, Intent shortcutIntent, int name, int res) { + Intent intent = getInstallIntent(shortcutIntent, context.getString(name), null); + Parcelable icon = Intent.ShortcutIconResource.fromContext(context, res); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, icon); + return intent; + } + + private static Intent getInstallIntent(Intent shortcutIntent, String name, Parcelable iconResource) { + Intent intent = new Intent(ACTION_INSTALL_SHORTCUT); + intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, shortcutIntent); + + if (TextUtils.isEmpty(name)) { + if (Build.VERSION.SDK_INT < 11 || "SM-N9008V".equals(Build.MODEL)) { + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "添加"); + } else { + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, "\t"); + } + } else { + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, name); + } + if (iconResource != null) { + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, iconResource); + } + intent.putExtra(EXTRA_DUPLICATE, false); + return intent; + } + + //判断应用是否安装 + public static boolean isPkgInstalled(Context c, String pkgName) { + PackageManager mPm = c.getPackageManager(); + if (mPm == null) { + return false; + } + PackageInfo pkginfo = null; + try { + pkginfo = mPm.getPackageInfo(pkgName, 0); + } catch (PackageManager.NameNotFoundException e) { + //e.printStackTrace(); + } + return pkginfo == null ? false : true; + } + + public static Intent getShortCutIntent(Context context, String action, String className) { + Intent shortcutIntent = new Intent(Intent.ACTION_MAIN); + if ("NX511J".equals(Build.MODEL)) { + shortcutIntent.setPackage(context.getPackageName()); + } else { + shortcutIntent.setClassName(context, className); + } + shortcutIntent.setData(Uri.parse(action)); + shortcutIntent.putExtra("value", "test"); + return shortcutIntent; + } + + public static boolean createShortCutWithIntentInSilence(Context context, String name, Intent shortCutIntent, Parcelable icon, boolean isSendToQihooDesktop) { + if (context == null || TextUtils.isEmpty(name) || icon == null) { + return false; + } + // 先查询launcher页面是否已经生成其快捷方式 + if (getIsAddShortCut(context, name)) { + return false; + } + Intent installIntent = getInstallIntent(shortCutIntent, name, icon); + // 将创建快捷方式的intent指定到桌面应用程序包来处理. + String deskPackageName = getLauncherPackageName(context); + if (null != deskPackageName && !TextUtils.isEmpty(deskPackageName)) { + installIntent.setPackage(deskPackageName); + } + if (isSendToQihooDesktop) { + installIntent.putExtra("from", context.getPackageName()); + } + context.sendBroadcast(installIntent); + return true; + } + + public static boolean createFavLinkShortCutWithShortcutIntent(Context context, String name, String url, Parcelable icon, + boolean isSendToQihooDesktop, boolean skipCheckExist, Intent shortcutIntent) { + + if (context == null || TextUtils.isEmpty(url) || TextUtils.isEmpty(name) || icon == null) { + return false; + } + + // 先查询launcher页面是否已经生成其快捷方式 + if (getIsAddShortCut(context, name) && !skipCheckExist) { + return false; + } + + Intent installIntent = getInstallIntent(shortcutIntent, name, icon); + + // 将创建快捷方式的intent指定到桌面应用程序包来处理. + String deskPackageName = getLauncherPackageName( + context); + + if (null != deskPackageName && !TextUtils.isEmpty(deskPackageName)) { + installIntent.setPackage(deskPackageName); + } + + if (isSendToQihooDesktop) { + installIntent.putExtra("from", context.getPackageName()); + } + + context.sendBroadcast(installIntent); + return true; + } + + public static boolean getIsAddShortCut(Context context, String name) { + // 如果只是按照title来比较是否存在的话可能存在重名应用,所以还需要根据intent这个字段一起判断。 + String intentTag = "com.scu.zqc"; + Cursor c = null; + try { + final ContentResolver cr = context.getContentResolver(); + String AUTHORITY = getAuthority(context); + Log.d(TAG, "AUTHORITY = " + AUTHORITY); + + if (null == AUTHORITY) { + AUTHORITY = getSuitableAuthority(context); + } + + final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/favorites?notify=true"); + c = cr.query(CONTENT_URI, new String[]{"title", "iconResource"}, + "title=? and intent like ?", new String[]{name, "%" + intentTag + "%"}, null); + + if (null != c && c.getCount() > 0) { + Log.d(TAG, "the shortCut of" + name + "has been created!"); + return true; + } else { + if (c != null) { + c.close(); + } + // 有的手机上需要用下面的方法才能获取到快捷方式是否存在 + AUTHORITY = getAuthorityFromPermission(context, READ_SETTINGS); + final Uri CONTENT_URI_AUTHORITY = Uri.parse("content://" + AUTHORITY + + "/favorites?notify=true"); + c = cr.query(CONTENT_URI_AUTHORITY, new String[]{"title", "iconResource"}, + "title=? and intent like ?", new String[]{name, "%" + intentTag + "%"}, null); + + if (null != c && c.getCount() > 0) { + Log.d(TAG, "the shortCut of" + name + "has been created!"); + return true; + } + } + } catch (Exception e) { + Log.e(TAG, "exception: " + e.getMessage()); + return getIsAddShortCutFromPermission(context, name); + } finally { + if (c != null) { + c.close(); + } + } + + return false; + } + + private static boolean getIsAddShortCutFromPermission(Context context, String name) { + Cursor c = null; + String intentTag = context.getPackageName(); + String AUTHORITY = getAuthorityFromPermission(context, READ_SETTINGS); + try { + final ContentResolver cr = context.getContentResolver(); + final Uri CONTENT_URI_AUTHORITY = Uri.parse("content://" + AUTHORITY + + "/favorites?notify=true"); + c = cr.query(CONTENT_URI_AUTHORITY, new String[]{"title", "iconResource"}, + "title=? and intent like ?", new String[]{name, "%" + intentTag + "%"}, null); + if (null != c && c.getCount() > 0) { + Log.d(TAG, "the shortCut of" + name + "has been created!"); + return true; + } + } catch (Exception e) { + // : handle exception + Log.e(TAG, "exception: " + e.getMessage()); + } finally { + if (null != c) { + c.close(); + } + } + + return false; + } + + /** + * 通过permission来找不同机型的Authority + * + * @return + */ + public static String getAuthority(Context context) { + String authority = ".launcher.settings"; + String authority2 = ".launcher2.settings"; + String authority3 = ".launcher3.settings"; + List packs = null; + try { + packs = context.getPackageManager().getInstalledPackages( + PackageManager.GET_PROVIDERS); + + for (PackageInfo pack : packs) { + ProviderInfo[] providers = pack.providers; + + if (null != providers) { + for (ProviderInfo provider : providers) { + if (!TextUtils.isEmpty(provider.authority) && (provider.authority.contains(authority) || + provider.authority.contains(authority2) || + provider.authority.contains(authority3))) { + return provider.authority; + } + } + } + } + } catch (OutOfMemoryError e) { + e.printStackTrace(); + packs = null; + } + return null; + } + + private static String getAuthorityFromPermission(Context context, + String permission) { + if (permission == null) { + return null; + } + try { + List packs = context.getPackageManager() + .getInstalledPackages(PackageManager.GET_PROVIDERS); + + if (packs != null) { + for (PackageInfo pack : packs) { + ProviderInfo[] providers = pack.providers; + + if (providers != null) { + for (ProviderInfo provider : providers) { + if (permission.equals(provider.readPermission)) { + return provider.authority; + } + + if (permission.equals(provider.writePermission)) { + return provider.authority; + } + } + } + } + } + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } + + /** + * 获取合适的Authority访问launcher数据库
+ * 优先级:".launcher3.settings" > ".launcher2.settings" > ".launcher.settings" > ".settings" + */ + public static String getSuitableAuthority(Context context) { + String AUTHORITY = null; + String authority = ".launcher.settings"; + String authority2 = ".launcher2.settings"; + String authority3 = ".launcher3.settings"; + String authority4 = ".settings"; + + List packs = context.getPackageManager().getInstalledPackages( + PackageManager.GET_PROVIDERS); + + List authorityList = new ArrayList(); + for (PackageInfo pack : packs) { + ProviderInfo[] providers = pack.providers; + + if (null != providers) { + for (ProviderInfo provider : providers) { + if (!TextUtils.isEmpty(provider.authority) && (provider.authority.contains(authority) + || provider.authority.contains(authority2) || + provider.authority.contains(authority3))) { + authorityList.add(provider.authority); + } + } + } + } + String AUTHORITY_TEMP_1 = null; + String AUTHORITY_TEMP_2 = null; + String AUTHORITY_TEMP_3 = null; + + for (String item : authorityList) { + if (item.contains(authority3) && TextUtils.isEmpty(AUTHORITY_TEMP_3)) { + AUTHORITY_TEMP_3 = item; + } else if (item.contains(authority2) && TextUtils.isEmpty(AUTHORITY_TEMP_2)) { + AUTHORITY_TEMP_2 = item; + } else if (item.contains(authority) && TextUtils.isEmpty(AUTHORITY_TEMP_1)) { + AUTHORITY_TEMP_1 = item; + } + } + if (!TextUtils.isEmpty(AUTHORITY_TEMP_3)) { + AUTHORITY = AUTHORITY_TEMP_3;//优先适配launcher3 + } else if (!TextUtils.isEmpty(AUTHORITY_TEMP_2)) { + AUTHORITY = AUTHORITY_TEMP_2; + } else if (!TextUtils.isEmpty(AUTHORITY_TEMP_1)) { + AUTHORITY = AUTHORITY_TEMP_1; + } + if (TextUtils.isEmpty(AUTHORITY)) { + for (PackageInfo pack : packs) { + ProviderInfo[] providers = pack.providers; + + if (null != providers) { + for (ProviderInfo provider : providers) { + if (!TextUtils.isEmpty(provider.authority) && provider.authority.endsWith(authority4)) { + return provider.authority; + } + } + } + } + } + return AUTHORITY; + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/tile/TileService.java b/app/src/main/java/com/stardust/scriptdroid/tile/TileService.java new file mode 100644 index 00000000..ecb234b7 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/tile/TileService.java @@ -0,0 +1,14 @@ +package com.stardust.scriptdroid.tile; + +import com.stardust.scriptdroid.action.ActionPerformService; + +/** + * Created by Stardust on 2017/1/26. + */ + +public class TileService extends android.service.quicksettings.TileService { + + public void onClick(){ + ActionPerformService.assistModeEnable = !ActionPerformService.assistModeEnable; + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/ClassTool.java b/app/src/main/java/com/stardust/scriptdroid/tool/ClassTool.java new file mode 100644 index 00000000..b36e3619 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/tool/ClassTool.java @@ -0,0 +1,22 @@ +package com.stardust.scriptdroid.tool; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class ClassTool { + + public static void loadClass(Class c) { + try { + Class.forName(c.getName()); + } catch (ClassNotFoundException e) { + throw new RuntimeException(e); + } + } + + public static void loadClasses(Class... classes) { + for (Class c : classes) { + loadClass(c); + } + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/tool/ViewTool.java b/app/src/main/java/com/stardust/scriptdroid/tool/ViewTool.java new file mode 100644 index 00000000..972c5a52 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/tool/ViewTool.java @@ -0,0 +1,16 @@ +package com.stardust.scriptdroid.tool; + +import android.support.annotation.IdRes; +import android.view.View; + +/** + * Created by Stardust on 2017/1/24. + */ + +public class ViewTool { + + @SuppressWarnings("unchecked") + public static V $(View view, @IdRes int resId) { + return (V) view.findViewById(resId); + } +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperation.java b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperation.java new file mode 100644 index 00000000..f6bdf138 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperation.java @@ -0,0 +1,149 @@ +package com.stardust.scriptdroid.ui; + +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.support.design.widget.Snackbar; + +import com.afollestad.materialdialogs.MaterialDialog; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.ShortcutActivity; +import com.stardust.scriptdroid.data.ScriptFile; +import com.stardust.scriptdroid.data.ScriptFileList; +import com.stardust.scriptdroid.shortcut.Shortcut; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Stardust on 2017/1/23. + */ + +public abstract class ScriptFileOperation { + + private static List operationNames = new ArrayList<>(); + private static List operations = new ArrayList<>(); + + public static ScriptFileOperation getOperation(int index) { + return operations.get(index); + } + + public static List getOperationNames() { + return operationNames; + } + + public abstract void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position); + + private static void addOperation(String name, int iconResId, ScriptFileOperation operation) { + operation.mName = name; + operation.mIconResId = iconResId; + operationNames.add(name); + operations.add(operation); + } + + private String mName; + private int mIconResId; + + public String getName() { + return mName; + } + + public int getIconResId() { + return mIconResId; + } + + public static class Run extends ScriptFileOperation { + + static { + addOperation("运行", R.drawable.ic_play_green, new Run()); + } + + @Override + public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) { + Snackbar.make(recyclerView, "开始运行", Snackbar.LENGTH_SHORT).show(); + ScriptFile scriptFile = scriptFileList.get(position); + scriptFile.run(recyclerView.getContext()); + } + } + + public static class Edit extends ScriptFileOperation { + + static { + addOperation("编辑", R.drawable.ic_edit_green_48dp, new Edit()); + } + + @Override + public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) { + Context context = recyclerView.getContext(); + ScriptFile scriptFile = scriptFileList.get(position); + Uri uri = Uri.parse("file://" + scriptFile.path); + context.startActivity(new Intent(Intent.ACTION_EDIT).setDataAndType(uri, "text/plain")); + } + } + + public static class Rename extends ScriptFileOperation { + + static { + addOperation("重命名", R.drawable.ic_rename_green, new Rename()); + } + + @Override + public void operate(final ScriptListRecyclerView recyclerView, final ScriptFileList scriptFileList, final int position) { + String oldName = scriptFileList.get(position).name; + new MaterialDialog.Builder(recyclerView.getContext()).title("重命名") + .input("输入新名称", oldName, (dialog, input) -> { + scriptFileList.rename(position, input.toString()); + recyclerView.getAdapter().notifyItemChanged(position); + }).show(); + } + } + + public static class CreateShortcut extends ScriptFileOperation { + + static { + addOperation("创建桌面快捷方式", R.drawable.ic_shortcut_green, new CreateShortcut()); + } + + + @Override + public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) { + Context context = recyclerView.getContext(); + ScriptFile scriptFile = scriptFileList.get(position); + new Shortcut(context).name(scriptFile.name) + .targetClass(ShortcutActivity.class) + .icon(R.drawable.script_droid) + .extras(new Intent().putExtra("path", scriptFile.path)) + .send(); + Snackbar.make(recyclerView, "已创建", Snackbar.LENGTH_SHORT).show(); + } + } + + public static class Remove extends ScriptFileOperation { + + static { + addOperation("删除", R.drawable.ic_delete_green_48dp, new Remove()); + } + + @Override + public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) { + scriptFileList.remove(position); + recyclerView.getAdapter().notifyItemRemoved(position); + } + } + + public static class Delete extends ScriptFileOperation { + + static { + addOperation("彻底删除", R.drawable.ic_delete_forever_green_48dp, new Delete()); + } + + @Override + public void operate(ScriptListRecyclerView recyclerView, ScriptFileList scriptFileList, int position) { + boolean succeed = scriptFileList.deleteFromFileSystem(position); + Snackbar.make(recyclerView, succeed ? "已删除" : "文件删除失败", Snackbar.LENGTH_SHORT).show(); + recyclerView.getAdapter().notifyItemRemoved(position); + } + } + + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperationPopupMenu.java b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperationPopupMenu.java new file mode 100644 index 00000000..bffb9c3c --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptFileOperationPopupMenu.java @@ -0,0 +1,135 @@ +package com.stardust.scriptdroid.ui; + +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.view.WindowManager; +import android.widget.ImageView; +import android.widget.PopupWindow; +import android.widget.TextView; + +import com.stardust.scriptdroid.R; + +import static com.stardust.scriptdroid.tool.ViewTool.$; + +/** + * Created by Stardust on 2017/1/24. + */ + +public class ScriptFileOperationPopupMenu extends PopupWindow { + + + public interface OnItemClickListener { + void onClick(View view, int position); + } + + private Context mContext; + private ScriptFileOperationListRecyclerView mOperationListRecyclerView; + private OnItemClickListener mOnItemClickListener; + + private final View.OnClickListener mOnItemClickRealListener = new View.OnClickListener() { + @Override + public void onClick(View v) { + if (mOnItemClickListener != null) { + int position = mOperationListRecyclerView.getChildViewHolder(v).getAdapterPosition(); + mOnItemClickListener.onClick(v, position); + } + } + }; + + public ScriptFileOperationPopupMenu(Context context) { + super(); + mContext = context; + init(); + } + + public void setOnItemClickListener(OnItemClickListener onItemClickListener) { + mOnItemClickListener = onItemClickListener; + } + + public void show(View anchor) { + super.showAsDropDown(anchor, 0, -anchor.getWidth()); + } + + private void init() { + setWidth(WindowManager.LayoutParams.WRAP_CONTENT); + setHeight(WindowManager.LayoutParams.WRAP_CONTENT); + setOutsideTouchable(true); + setAnimationStyle(-1); + initContentView(); + } + + private void initContentView() { + View contentView = View.inflate(mContext, R.layout.script_file_operation_popup_menu_content, null); + setContentView(contentView); + mOperationListRecyclerView = $(contentView, R.id.operation_list); + mOperationListRecyclerView.setOnItemClickListener(mOnItemClickRealListener); + } + + + public static class ScriptFileOperationListRecyclerView extends RecyclerView { + + private OnClickListener mOnItemClickListener; + + public ScriptFileOperationListRecyclerView(Context context) { + super(context); + init(); + } + + public ScriptFileOperationListRecyclerView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ScriptFileOperationListRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setLayoutManager(new LinearLayoutManager(getContext())); + setAdapter(new Adapter() { + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(getContext()).inflate(R.layout.script_file_operation_popup_menu_item, parent, false); + return new ViewHolder(itemView); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + ScriptFileOperation operation = ScriptFileOperation.getOperation(position); + holder.operationName.setText(operation.getName()); + holder.icon.setImageResource(operation.getIconResId()); + } + + @Override + public int getItemCount() { + return ScriptFileOperation.getOperationNames().size(); + } + }); + } + + public void setOnItemClickListener(OnClickListener onItemClickListener) { + mOnItemClickListener = onItemClickListener; + } + + private class ViewHolder extends RecyclerView.ViewHolder { + + TextView operationName; + ImageView icon; + + ViewHolder(View itemView) { + super(itemView); + itemView.setOnClickListener(mOnItemClickListener); + operationName = $(itemView, R.id.name); + icon = $(itemView, R.id.icon); + } + } + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/ScriptListRecyclerView.java b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptListRecyclerView.java new file mode 100644 index 00000000..449ecb01 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/ScriptListRecyclerView.java @@ -0,0 +1,165 @@ +package com.stardust.scriptdroid.ui; + +import android.content.Context; +import android.os.Environment; +import android.support.annotation.Nullable; +import android.support.v7.widget.DividerItemDecoration; +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.afollestad.materialdialogs.MaterialDialog; +import com.stardust.scriptdroid.R; +import com.stardust.scriptdroid.data.ScriptFile; +import com.stardust.scriptdroid.data.ScriptFileList; +import com.stardust.scriptdroid.tool.ClassTool; + +import static com.stardust.scriptdroid.tool.ViewTool.$; + +import static com.stardust.scriptdroid.ui.ScriptFileOperation.*; + +/** + * Created by Stardust on 2017/1/23. + */ + +public class ScriptListRecyclerView extends RecyclerView { + + private ScriptFileList mScriptFileList; + + private final OnClickListener mOnItemClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + int position = getChildViewHolder(v).getAdapterPosition(); + new ScriptFileOperation.Run().operate(ScriptListRecyclerView.this, mScriptFileList, position); + } + }; + + + private final OnClickListener mOnEditIconClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + int position = getChildViewHolder((View) v.getParent()).getAdapterPosition(); + new ScriptFileOperation.Edit().operate(ScriptListRecyclerView.this, mScriptFileList, position); + } + }; + + private final OnClickListener mOnMoreIconClickListener = new OnClickListener() { + @Override + public void onClick(View v) { + mOperateFileIndex = getChildViewHolder((View) v.getParent()).getAdapterPosition(); + //showOperationDialog(position); + showOrDismissOperationPopupMenu(v); + } + + }; + + private ScriptFileOperationPopupMenu mScriptFileOperationPopupMenu; + private int mOperateFileIndex; + + + public ScriptListRecyclerView(Context context) { + super(context); + init(); + } + + + public ScriptListRecyclerView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + init(); + } + + public ScriptListRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + init(); + } + + private void init() { + setAdapter(new Adapter()); + setLayoutManager(new LinearLayoutManager(getContext())); + addItemDecoration(new DividerItemDecoration(getContext(), DividerItemDecoration.VERTICAL)); + initScriptFileOperationPopupMenu(); + } + + private void initScriptFileOperationPopupMenu() { + mScriptFileOperationPopupMenu = new ScriptFileOperationPopupMenu(getContext()); + mScriptFileOperationPopupMenu.setOnItemClickListener((view, position) -> { + ScriptFileOperation.getOperation(position).operate(ScriptListRecyclerView.this, mScriptFileList, mOperateFileIndex); + mScriptFileOperationPopupMenu.dismiss(); + }); + } + + public void setScriptFileList(ScriptFileList scriptFileList) { + mScriptFileList = scriptFileList; + getAdapter().notifyDataSetChanged(); + } + + private void showOperationDialog(final int position) { + new MaterialDialog.Builder(getContext()).items(ScriptFileOperation.getOperationNames()) + .itemsCallback((dialog, itemView, operation, text) -> ScriptFileOperation.getOperation(operation).operate(ScriptListRecyclerView.this, mScriptFileList, position)).show(); + } + + private void showOrDismissOperationPopupMenu(View v) { + if (mScriptFileOperationPopupMenu.isShowing()) { + mScriptFileOperationPopupMenu.dismiss(); + } else { + mScriptFileOperationPopupMenu.show(v); + } + } + + private class Adapter extends RecyclerView.Adapter { + + @Override + public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { + View itemView = LayoutInflater.from(getContext()).inflate(R.layout.script_list_recycler_view_item, parent, false); + return new ViewHolder(itemView); + } + + @Override + public void onBindViewHolder(ViewHolder holder, int position) { + ScriptFile scriptFile = mScriptFileList.get(position); + holder.name.setText(scriptFile.name); + holder.path.setText(trimFilePath(scriptFile.path)); + } + + private final String SD_CARD_PATH = Environment.getExternalStorageDirectory().toString(); + + private String trimFilePath(String path) { + if (path.startsWith(SD_CARD_PATH)) { + path = path.substring(SD_CARD_PATH.length()); + } + return path; + } + + @Override + public int getItemCount() { + return mScriptFileList.size(); + } + } + + private class ViewHolder extends RecyclerView.ViewHolder { + + TextView name, path; + + ViewHolder(View itemView) { + super(itemView); + name = (TextView) itemView.findViewById(R.id.name); + path = (TextView) itemView.findViewById(R.id.path); + $(itemView, R.id.edit).setOnClickListener(mOnEditIconClickListener); + $(itemView, R.id.more).setOnClickListener(mOnMoreIconClickListener); + itemView.setOnClickListener(mOnItemClickListener); + } + } + + static { + loadScriptFileOperations(); + } + + private static void loadScriptFileOperations() { + ClassTool.loadClasses(Run.class, Edit.class, Rename.class, CreateShortcut.class, Remove.class, Delete.class); + } + +} diff --git a/app/src/main/java/com/stardust/scriptdroid/ui/SlidingUpPanel.java b/app/src/main/java/com/stardust/scriptdroid/ui/SlidingUpPanel.java new file mode 100644 index 00000000..5da55754 --- /dev/null +++ b/app/src/main/java/com/stardust/scriptdroid/ui/SlidingUpPanel.java @@ -0,0 +1,108 @@ +package com.stardust.scriptdroid.ui; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.FrameLayout; + +import com.stardust.scriptdroid.R; + +/** + * Created by Stardust on 2017/1/24. + */ + +public class SlidingUpPanel extends FrameLayout { + + private static final long DEFAULT_ANIMATION_DURATION = 300; + + private Animation mSlideUpAnimation, mSlideDownAnimation; + private View mShadow; + private boolean mShowing = false; + private FrameLayout mContentContainer; + + public SlidingUpPanel(Context context) { + super(context); + init(); + } + + public SlidingUpPanel(Context context, AttributeSet attrs) { + super(context, attrs); + init(); + } + + public SlidingUpPanel(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + init(); + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + public SlidingUpPanel(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + init(); + } + + public void show() { + setVisibility(VISIBLE); + mContentContainer.startAnimation(mSlideUpAnimation); + mShowing = true; + } + + public void dismiss() { + mContentContainer.startAnimation(mSlideDownAnimation); + postDelayed(() -> setVisibility(GONE), mSlideDownAnimation.getDuration()); + mShowing = false; + } + + public void setAnimationDuration(long animationDuration) { + mSlideUpAnimation.setDuration(animationDuration); + mSlideDownAnimation.setDuration(animationDuration / 2); + } + + public boolean isShowing() { + return mShowing; + } + + private void init() { + LayoutInflater.from(getContext()).inflate(R.layout.sliding_up_panel, this, true); + initShadow(); + initAnimation(); + setVisibility(GONE); + mContentContainer = (FrameLayout) findViewById(R.id.content_container); + } + + private void initAnimation() { + mSlideUpAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_up); + mSlideDownAnimation = AnimationUtils.loadAnimation(getContext(), R.anim.slide_down); + setAnimationDuration(DEFAULT_ANIMATION_DURATION); + } + + private void initShadow() { + mShadow = findViewById(R.id.shadow); + mShadow.setOnClickListener(v -> dismiss()); + } + + @Override + public boolean onInterceptTouchEvent(MotionEvent ev) { + if (ev.getAction() == MotionEvent.ACTION_UP && isShowing()) { + dismiss(); + } + return super.onInterceptTouchEvent(ev); + } + + @Override + public void addView(View child, int index, ViewGroup.LayoutParams params) { + if (mContentContainer != null) + mContentContainer.addView(child, index, params); + else + super.addView(child, index, params); + } + + +} diff --git a/app/src/main/java/com/stardust/util/MapEntries.java b/app/src/main/java/com/stardust/util/MapEntries.java new file mode 100644 index 00000000..23c618fb --- /dev/null +++ b/app/src/main/java/com/stardust/util/MapEntries.java @@ -0,0 +1,37 @@ +package com.stardust.util; + +import java.util.Map; +import java.util.TreeMap; + +/** + * Created by Stardust on 2017/1/26. + */ + +public class MapEntries { + + private Map mMap; + + public MapEntries() { + this(new TreeMap<>()); + } + + public MapEntries(Map map) { + mMap = map; + } + + public MapEntries entry(K key, V value) { + mMap.put(key, value); + return this; + } + + public Map map() { + return mMap; + } + + public Map putIn(Map map) { + map.putAll(mMap); + return map; + } + + +} diff --git a/app/src/main/java/com/stardust/util/SparseArrayEntries.java b/app/src/main/java/com/stardust/util/SparseArrayEntries.java new file mode 100644 index 00000000..0efb3514 --- /dev/null +++ b/app/src/main/java/com/stardust/util/SparseArrayEntries.java @@ -0,0 +1,22 @@ +package com.stardust.util; + +import android.util.SparseArray; + +/** + * Created by Stardust on 2017/1/26. + */ + +public class SparseArrayEntries { + + private SparseArray mSparseArray = new SparseArray<>(); + + public SparseArrayEntries entry(int key, E value) { + mSparseArray.put(key, value); + return this; + } + + public SparseArray sparseArray() { + return mSparseArray; + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/stardust/util/function/ArrayOptional.java b/app/src/main/java/com/stardust/util/function/ArrayOptional.java new file mode 100644 index 00000000..f0d74de6 --- /dev/null +++ b/app/src/main/java/com/stardust/util/function/ArrayOptional.java @@ -0,0 +1,8 @@ +package com.stardust.util.function; + +/** + * Created by Stardust on 2017/1/24. + */ + +public class ArrayOptional { +} diff --git a/app/src/main/java/com/stardust/util/function/Domino.java b/app/src/main/java/com/stardust/util/function/Domino.java new file mode 100644 index 00000000..a19c0c16 --- /dev/null +++ b/app/src/main/java/com/stardust/util/function/Domino.java @@ -0,0 +1,8 @@ +package com.stardust.util.function; + +/** + * Created by Stardust on 2017/1/26. + */ + +public class Domino { +} diff --git a/app/src/main/java/com/stardust/util/function/FunctionTool.java b/app/src/main/java/com/stardust/util/function/FunctionTool.java new file mode 100644 index 00000000..914f3002 --- /dev/null +++ b/app/src/main/java/com/stardust/util/function/FunctionTool.java @@ -0,0 +1,15 @@ +package com.stardust.util.function; + +import android.support.annotation.RequiresApi; + +import java.util.Optional; +import java.util.function.Consumer; + +/** + * Created by Stardust on 2017/1/24. + */ + +public class FunctionTool { + + +} diff --git a/app/src/main/java/com/stardust/util/function/ListTool.java b/app/src/main/java/com/stardust/util/function/ListTool.java new file mode 100644 index 00000000..6e311d04 --- /dev/null +++ b/app/src/main/java/com/stardust/util/function/ListTool.java @@ -0,0 +1,24 @@ +package com.stardust.util.function; + +import java.util.ArrayList; +import java.util.List; + +/** + * Created by Stardust on 2017/1/26. + */ + +public class ListTool { + + @FunctionalInterface + public interface ChildSupplier { + T getChild(int i); + } + + public static List toList(ChildSupplier supplier, int size) { + ArrayList arrayList = new ArrayList(size); + for (int i = 0; i < size - 1; i++) { + arrayList.add(supplier.getChild(i)); + } + return arrayList; + } +} diff --git a/app/src/main/java/com/stardust/view/accessibility/AccessibilityNodeInfoReflect.java b/app/src/main/java/com/stardust/view/accessibility/AccessibilityNodeInfoReflect.java new file mode 100644 index 00000000..bc9cfb6a --- /dev/null +++ b/app/src/main/java/com/stardust/view/accessibility/AccessibilityNodeInfoReflect.java @@ -0,0 +1,40 @@ +package com.stardust.view.accessibility; + +import android.view.accessibility.AccessibilityNodeInfo; + +import java.lang.reflect.Field; + +/** + * Created by Stardust on 2017/1/26. + */ + +public class AccessibilityNodeInfoReflect { + + private final AccessibilityNodeInfo mAccessibilityNodeInfo; + + public AccessibilityNodeInfoReflect(AccessibilityNodeInfo accessibilityNodeInfo) { + mAccessibilityNodeInfo = accessibilityNodeInfo; + } + + public void getChildNodeIds() { + try { + Field mChildNodeIdsField = AccessibilityNodeInfo.class.getDeclaredField("mChildNodeIds"); + mChildNodeIdsField.setAccessible(true); + Object o = mChildNodeIdsField.get(mAccessibilityNodeInfo); + } catch (NoSuchFieldException | IllegalAccessException e) { + e.printStackTrace(); + } + } + + public long getSourceNodeId() { + try { + Field mSourceNodeIdField = AccessibilityNodeInfo.class.getDeclaredField("mSourceNodeId"); + mSourceNodeIdField.setAccessible(true); + return (long) mSourceNodeIdField.get(mSourceNodeIdField); + } catch (IllegalAccessException | NoSuchFieldException e) { + e.printStackTrace(); + } + return -1; + } + +} diff --git a/app/src/main/res/anim/slide_down.xml b/app/src/main/res/anim/slide_down.xml new file mode 100644 index 00000000..0ed5bbec --- /dev/null +++ b/app/src/main/res/anim/slide_down.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/anim/slide_up.xml b/app/src/main/res/anim/slide_up.xml new file mode 100644 index 00000000..3bd7fba8 --- /dev/null +++ b/app/src/main/res/anim/slide_up.xml @@ -0,0 +1,9 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_accessibility_service.png b/app/src/main/res/drawable/ic_accessibility_service.png new file mode 100644 index 00000000..21e8d3a1 Binary files /dev/null and b/app/src/main/res/drawable/ic_accessibility_service.png differ diff --git a/app/src/main/res/drawable/ic_add_black_24dp.png b/app/src/main/res/drawable/ic_add_black_24dp.png new file mode 100644 index 00000000..3cb10924 Binary files /dev/null and b/app/src/main/res/drawable/ic_add_black_24dp.png differ diff --git a/app/src/main/res/drawable/ic_add_white_48dp.png b/app/src/main/res/drawable/ic_add_white_48dp.png new file mode 100644 index 00000000..165c907d Binary files /dev/null and b/app/src/main/res/drawable/ic_add_white_48dp.png differ diff --git a/app/src/main/res/drawable/ic_create_new_folder_black_48dp.png b/app/src/main/res/drawable/ic_create_new_folder_black_48dp.png new file mode 100644 index 00000000..d28b3b21 Binary files /dev/null and b/app/src/main/res/drawable/ic_create_new_folder_black_48dp.png differ diff --git a/app/src/main/res/drawable/ic_delete_black_48dp.png b/app/src/main/res/drawable/ic_delete_black_48dp.png new file mode 100644 index 00000000..796ccd22 Binary files /dev/null and b/app/src/main/res/drawable/ic_delete_black_48dp.png differ diff --git a/app/src/main/res/drawable/ic_delete_forever_black_48dp.png b/app/src/main/res/drawable/ic_delete_forever_black_48dp.png new file mode 100644 index 00000000..a7468f27 Binary files /dev/null and b/app/src/main/res/drawable/ic_delete_forever_black_48dp.png differ diff --git a/app/src/main/res/drawable/ic_delete_forever_green_48dp.png b/app/src/main/res/drawable/ic_delete_forever_green_48dp.png new file mode 100644 index 00000000..710737e2 Binary files /dev/null and b/app/src/main/res/drawable/ic_delete_forever_green_48dp.png differ diff --git a/app/src/main/res/drawable/ic_delete_green_48dp.png b/app/src/main/res/drawable/ic_delete_green_48dp.png new file mode 100644 index 00000000..bdb1d46c Binary files /dev/null and b/app/src/main/res/drawable/ic_delete_green_48dp.png differ diff --git a/app/src/main/res/drawable/ic_edit_gray_48dp.png b/app/src/main/res/drawable/ic_edit_gray_48dp.png new file mode 100644 index 00000000..aacbcf71 Binary files /dev/null and b/app/src/main/res/drawable/ic_edit_gray_48dp.png differ diff --git a/app/src/main/res/drawable/ic_edit_green_48dp.png b/app/src/main/res/drawable/ic_edit_green_48dp.png new file mode 100644 index 00000000..8819a59b Binary files /dev/null and b/app/src/main/res/drawable/ic_edit_green_48dp.png differ diff --git a/app/src/main/res/drawable/ic_play_circle_filled_black_48dp.png b/app/src/main/res/drawable/ic_play_circle_filled_black_48dp.png new file mode 100644 index 00000000..00e83114 Binary files /dev/null and b/app/src/main/res/drawable/ic_play_circle_filled_black_48dp.png differ diff --git a/app/src/main/res/drawable/ic_play_green.png b/app/src/main/res/drawable/ic_play_green.png new file mode 100644 index 00000000..62d48972 Binary files /dev/null and b/app/src/main/res/drawable/ic_play_green.png differ diff --git a/app/src/main/res/drawable/ic_rename_green.png b/app/src/main/res/drawable/ic_rename_green.png new file mode 100644 index 00000000..72033f6b Binary files /dev/null and b/app/src/main/res/drawable/ic_rename_green.png differ diff --git a/app/src/main/res/drawable/ic_shortcut_green.png b/app/src/main/res/drawable/ic_shortcut_green.png new file mode 100644 index 00000000..fc0e2bea Binary files /dev/null and b/app/src/main/res/drawable/ic_shortcut_green.png differ diff --git a/app/src/main/res/drawable/navigation_show_more_vertical_512.png b/app/src/main/res/drawable/navigation_show_more_vertical_512.png new file mode 100644 index 00000000..0d4a3898 Binary files /dev/null and b/app/src/main/res/drawable/navigation_show_more_vertical_512.png differ diff --git a/app/src/main/res/drawable/pencil_and_paper.png b/app/src/main/res/drawable/pencil_and_paper.png new file mode 100644 index 00000000..ac57d42b Binary files /dev/null and b/app/src/main/res/drawable/pencil_and_paper.png differ diff --git a/app/src/main/res/drawable/play_blue.png b/app/src/main/res/drawable/play_blue.png new file mode 100644 index 00000000..4c830925 Binary files /dev/null and b/app/src/main/res/drawable/play_blue.png differ diff --git a/app/src/main/res/drawable/popup_menu_background.xml b/app/src/main/res/drawable/popup_menu_background.xml new file mode 100644 index 00000000..97499ebe --- /dev/null +++ b/app/src/main/res/drawable/popup_menu_background.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rename.png b/app/src/main/res/drawable/rename.png new file mode 100644 index 00000000..6002c9d3 Binary files /dev/null and b/app/src/main/res/drawable/rename.png differ diff --git a/app/src/main/res/drawable/robot_head.png b/app/src/main/res/drawable/robot_head.png new file mode 100644 index 00000000..286babd7 Binary files /dev/null and b/app/src/main/res/drawable/robot_head.png differ diff --git a/app/src/main/res/drawable/robot_head_green.png b/app/src/main/res/drawable/robot_head_green.png new file mode 100644 index 00000000..56c82ee4 Binary files /dev/null and b/app/src/main/res/drawable/robot_head_green.png differ diff --git a/app/src/main/res/drawable/robot_icon_green.png b/app/src/main/res/drawable/robot_icon_green.png new file mode 100644 index 00000000..a448ebcd Binary files /dev/null and b/app/src/main/res/drawable/robot_icon_green.png differ diff --git a/app/src/main/res/drawable/run.png b/app/src/main/res/drawable/run.png new file mode 100644 index 00000000..f9910c1d Binary files /dev/null and b/app/src/main/res/drawable/run.png differ diff --git a/app/src/main/res/drawable/script_droid.png b/app/src/main/res/drawable/script_droid.png new file mode 100644 index 00000000..c8507872 Binary files /dev/null and b/app/src/main/res/drawable/script_droid.png differ diff --git a/app/src/main/res/drawable/script_droid_100.png b/app/src/main/res/drawable/script_droid_100.png new file mode 100644 index 00000000..a075d63d Binary files /dev/null and b/app/src/main/res/drawable/script_droid_100.png differ diff --git a/app/src/main/res/drawable/script_droid_50.png b/app/src/main/res/drawable/script_droid_50.png new file mode 100644 index 00000000..0f991ebe Binary files /dev/null and b/app/src/main/res/drawable/script_droid_50.png differ diff --git a/app/src/main/res/drawable/sdcard_icon_black.png b/app/src/main/res/drawable/sdcard_icon_black.png new file mode 100644 index 00000000..3e0bf585 Binary files /dev/null and b/app/src/main/res/drawable/sdcard_icon_black.png differ diff --git a/app/src/main/res/drawable/shortcut_icon.png b/app/src/main/res/drawable/shortcut_icon.png new file mode 100644 index 00000000..6c51f31f Binary files /dev/null and b/app/src/main/res/drawable/shortcut_icon.png differ diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 00000000..0c0fa597 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/content_main.xml b/app/src/main/res/layout/content_main.xml new file mode 100644 index 00000000..f187b807 --- /dev/null +++ b/app/src/main/res/layout/content_main.xml @@ -0,0 +1,29 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/script_file_operation_popup_menu_content.xml b/app/src/main/res/layout/script_file_operation_popup_menu_content.xml new file mode 100644 index 00000000..b7dff679 --- /dev/null +++ b/app/src/main/res/layout/script_file_operation_popup_menu_content.xml @@ -0,0 +1,14 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/script_file_operation_popup_menu_item.xml b/app/src/main/res/layout/script_file_operation_popup_menu_item.xml new file mode 100644 index 00000000..db543162 --- /dev/null +++ b/app/src/main/res/layout/script_file_operation_popup_menu_item.xml @@ -0,0 +1,27 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/script_list_recycler_view_item.xml b/app/src/main/res/layout/script_list_recycler_view_item.xml new file mode 100644 index 00000000..cf5dcf14 --- /dev/null +++ b/app/src/main/res/layout/script_list_recycler_view_item.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/sliding_up_panel.xml b/app/src/main/res/layout/sliding_up_panel.xml new file mode 100644 index 00000000..efc63fb9 --- /dev/null +++ b/app/src/main/res/layout/sliding_up_panel.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/menu_main.xml b/app/src/main/res/menu/menu_main.xml new file mode 100644 index 00000000..ca351e53 --- /dev/null +++ b/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,22 @@ + + + + + + + diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 00000000..cde69bcc Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 00000000..c133a0cb Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 00000000..bfa42f0e Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 00000000..324e72cd Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 00000000..aee44e13 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml new file mode 100644 index 00000000..dbbdd40f --- /dev/null +++ b/app/src/main/res/values-v21/styles.xml @@ -0,0 +1,9 @@ + + + + diff --git a/app/src/main/res/values-w820dp/dimens.xml b/app/src/main/res/values-w820dp/dimens.xml new file mode 100644 index 00000000..63fc8164 --- /dev/null +++ b/app/src/main/res/values-w820dp/dimens.xml @@ -0,0 +1,6 @@ + + + 64dp + diff --git a/app/src/main/res/values/attrs.xml b/app/src/main/res/values/attrs.xml new file mode 100644 index 00000000..55344e51 --- /dev/null +++ b/app/src/main/res/values/attrs.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 00000000..cad427a3 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,7 @@ + + + #8ea22a + #5e6b1e + #8ea22a + #99444444 + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 00000000..812cb7be --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,6 @@ + + + 16dp + 16dp + 16dp + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 00000000..21079a73 --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,20 @@ + + 免Root脚本精灵 + 设置 + 关闭服务 + 略略略 + 新建文件 + 从文件导入 + 退出 + 服务已关闭 + 找不到文件管理器T.T + 文件创建失败 + 请输入名称 + 名称 + 去设置 + 软件需要权限才能运行 ~ 请在随后的设置中选择”脚本执行机器人\"并开启权限 + 取消 + 路径为空 + 文件不存在 + 无文件读写权限 + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 00000000..177cefc8 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,20 @@ + + + + + + + +