initial commit
1
app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
43
app/build.gradle
Normal file
@@ -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'
|
||||
}
|
||||
17
app/proguard-rules.pro
vendored
Normal file
@@ -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 *;
|
||||
#}
|
||||
@@ -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 <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@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());
|
||||
}
|
||||
}
|
||||
76
app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.stardust.scriptdroid">
|
||||
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="com.android.launcher.permission.INSTALL_SHORTCUT"/>
|
||||
<uses-permission android:name="com.android.launcher.permission.UNINSTALL_SHORTCUT"/>
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@drawable/script_droid"
|
||||
android:label="@string/app_name"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@style/AppTheme">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<activity
|
||||
android:name=".ShortcutActivity"
|
||||
android:label="脚本执行机器人"
|
||||
android:theme="@style/AppTheme.NoActionBar">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="com.example.Project.Action"/>
|
||||
<category android:name="android.intent.category.DEFAULT"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<service
|
||||
android:name=".action.ActionPerformService"
|
||||
android:enabled="true"
|
||||
android:exported="true"
|
||||
android:label="脚本执行机器人"
|
||||
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
|
||||
<intent-filter>
|
||||
<action android:name="android.accessibilityservice.AccessibilityService"/>
|
||||
</intent-filter>
|
||||
<meta-data
|
||||
android:name="android.accessibilityservice"
|
||||
android:resource="@xml/accessibility_service_config"/>
|
||||
|
||||
</service>
|
||||
|
||||
<service
|
||||
android:name="com.stardust.scriptdroid.tile.TileService"
|
||||
android:icon="@drawable/ic_accessibility_service"
|
||||
android:label="脚本辅助"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE">
|
||||
<intent-filter>
|
||||
<action
|
||||
android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<provider
|
||||
android:name="android.support.v4.content.FileProvider"
|
||||
android:authorities="com.stardust.scriptdroid.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/provider_paths"/>
|
||||
</provider>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
32
app/src/main/java/com/stardust/scriptdroid/BaseActivity.java
Normal file
@@ -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 extends View> 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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
162
app/src/main/java/com/stardust/scriptdroid/MainActivity.java
Normal file
@@ -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<Integer, Runnable> mOptionActionMap = new MapEntries<Integer, Runnable>()
|
||||
.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);
|
||||
}
|
||||
}
|
||||
@@ -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<Tile> mTiles = new LinkedList<>();
|
||||
|
||||
Domino then(Tile next) {
|
||||
mTiles.add(next);
|
||||
return this;
|
||||
}
|
||||
|
||||
void fall() {
|
||||
for (Tile tile : mTiles) {
|
||||
if (!tile.fall())
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
466
app/src/main/java/com/stardust/scriptdroid/action/Action.java
Normal file
@@ -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<Context> 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<Action> mActions;
|
||||
private boolean mDependent;
|
||||
|
||||
public MultiAction(List<Action> 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<AccessibilityNodeInfo> 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<AccessibilityNodeInfo> 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<AccessibilityNodeInfo> findEditText(AccessibilityNodeInfo rootNodeInfo) {
|
||||
if (rootNodeInfo == null) {
|
||||
return Collections.EMPTY_LIST;
|
||||
}
|
||||
if (rootNodeInfo.isEditable()) {
|
||||
return Collections.singletonList(rootNodeInfo);
|
||||
}
|
||||
List<AccessibilityNodeInfo> 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<AccessibilityNodeInfo> 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<AccessibilityNodeInfo> 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<AccessibilityNodeInfo> 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<Able> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Action> NO_ACTION = Collections.EMPTY_LIST;
|
||||
|
||||
public static final List<Action> actions = new ArrayList<>();
|
||||
private static int state = STATE_WAIT;
|
||||
|
||||
public static boolean assistModeEnable = true;
|
||||
private AccessibilityNodeInfo mLastFocus;
|
||||
|
||||
public static void setActions(Collection<Action> 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<Integer> 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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<Action> 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<String> mScriptPath;
|
||||
private List<String> mScriptName;
|
||||
|
||||
|
||||
public SharedPrefScriptFileList(Context context) {
|
||||
mSharedPreferences = context.getSharedPreferences("SharedPrefScriptFileList", Context.MODE_PRIVATE);
|
||||
readFromSharedPref();
|
||||
}
|
||||
|
||||
private void readFromSharedPref() {
|
||||
Type type = new TypeToken<List<String>>() {
|
||||
}.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();
|
||||
}
|
||||
}
|
||||
29
app/src/main/java/com/stardust/scriptdroid/droid/Droid.java
Normal file
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<Pair<Filter, StateTree>> 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<Filter, StateTree> 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<ApplicationInfo> 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<Action> list = new ArrayList<>(actions.length);
|
||||
for (String action : actions) {
|
||||
list.add(interpreterInner(action));
|
||||
}
|
||||
return new Action.MultiAction(list, independent);
|
||||
}
|
||||
|
||||
|
||||
public List<Action> interpreterAll(String str) {
|
||||
String[] lines = str.split("\n");
|
||||
List<Action> list = new ArrayList<>(lines.length);
|
||||
for (String line : lines) {
|
||||
list.add(interpreter(line));
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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 编号 [幅度]
|
||||
*
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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> 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) {
|
||||
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<UsageStats> getPastTwoSecondsUsageStats(Context context) {
|
||||
long ts = System.currentTimeMillis();
|
||||
UsageStatsManager usageStatsManager = (UsageStatsManager) context.getSystemService(Context.USAGE_STATS_SERVICE);
|
||||
List<UsageStats> usageStats = usageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_BEST, ts - 2000, ts);
|
||||
if (usageStats == null) {
|
||||
usageStats = Collections.EMPTY_LIST;
|
||||
}
|
||||
return usageStats;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<PackageInfo> 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<PackageInfo> 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数据库<br/>
|
||||
* 优先级:".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<PackageInfo> packs = context.getPackageManager().getInstalledPackages(
|
||||
PackageManager.GET_PROVIDERS);
|
||||
|
||||
List<String> authorityList = new ArrayList<String>();
|
||||
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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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 extends View> V $(View view, @IdRes int resId) {
|
||||
return (V) view.findViewById(resId);
|
||||
}
|
||||
}
|
||||
@@ -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<String> operationNames = new ArrayList<>();
|
||||
private static List<ScriptFileOperation> operations = new ArrayList<>();
|
||||
|
||||
public static ScriptFileOperation getOperation(int index) {
|
||||
return operations.get(index);
|
||||
}
|
||||
|
||||
public static List<String> 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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<ViewHolder>() {
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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<ViewHolder> {
|
||||
|
||||
@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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
37
app/src/main/java/com/stardust/util/MapEntries.java
Normal file
@@ -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<K, V> {
|
||||
|
||||
private Map<K, V> mMap;
|
||||
|
||||
public MapEntries() {
|
||||
this(new TreeMap<>());
|
||||
}
|
||||
|
||||
public MapEntries(Map<K, V> map) {
|
||||
mMap = map;
|
||||
}
|
||||
|
||||
public MapEntries<K, V> entry(K key, V value) {
|
||||
mMap.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Map<K, V> map() {
|
||||
return mMap;
|
||||
}
|
||||
|
||||
public Map<K, V> putIn(Map<K, V> map) {
|
||||
map.putAll(mMap);
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
22
app/src/main/java/com/stardust/util/SparseArrayEntries.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.stardust.util;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/1/26.
|
||||
*/
|
||||
|
||||
public class SparseArrayEntries<E> {
|
||||
|
||||
private SparseArray<E> mSparseArray = new SparseArray<>();
|
||||
|
||||
public SparseArrayEntries<E> entry(int key, E value) {
|
||||
mSparseArray.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
public SparseArray<E> sparseArray() {
|
||||
return mSparseArray;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package com.stardust.util.function;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/1/24.
|
||||
*/
|
||||
|
||||
public class ArrayOptional {
|
||||
}
|
||||
8
app/src/main/java/com/stardust/util/function/Domino.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package com.stardust.util.function;
|
||||
|
||||
/**
|
||||
* Created by Stardust on 2017/1/26.
|
||||
*/
|
||||
|
||||
public class Domino {
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
}
|
||||
24
app/src/main/java/com/stardust/util/function/ListTool.java
Normal file
@@ -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> {
|
||||
T getChild(int i);
|
||||
}
|
||||
|
||||
public static <T> List<T> toList(ChildSupplier<T> supplier, int size) {
|
||||
ArrayList<T> arrayList = new ArrayList<T>(size);
|
||||
for (int i = 0; i < size - 1; i++) {
|
||||
arrayList.add(supplier.getChild(i));
|
||||
}
|
||||
return arrayList;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
9
app/src/main/res/anim/slide_down.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="0%"
|
||||
android:toXDelta="0%"
|
||||
android:toYDelta="100%"/>
|
||||
</set>
|
||||
9
app/src/main/res/anim/slide_up.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<set xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<translate
|
||||
android:fromXDelta="0%"
|
||||
android:fromYDelta="100%"
|
||||
android:toXDelta="0%"
|
||||
android:toYDelta="0%"/>
|
||||
</set>
|
||||
BIN
app/src/main/res/drawable/ic_accessibility_service.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
app/src/main/res/drawable/ic_add_black_24dp.png
Normal file
|
After Width: | Height: | Size: 119 B |
BIN
app/src/main/res/drawable/ic_add_white_48dp.png
Normal file
|
After Width: | Height: | Size: 116 B |
BIN
app/src/main/res/drawable/ic_create_new_folder_black_48dp.png
Normal file
|
After Width: | Height: | Size: 191 B |
BIN
app/src/main/res/drawable/ic_delete_black_48dp.png
Normal file
|
After Width: | Height: | Size: 148 B |
BIN
app/src/main/res/drawable/ic_delete_forever_black_48dp.png
Normal file
|
After Width: | Height: | Size: 227 B |
BIN
app/src/main/res/drawable/ic_delete_forever_green_48dp.png
Normal file
|
After Width: | Height: | Size: 965 B |
BIN
app/src/main/res/drawable/ic_delete_green_48dp.png
Normal file
|
After Width: | Height: | Size: 546 B |
BIN
app/src/main/res/drawable/ic_edit_gray_48dp.png
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
app/src/main/res/drawable/ic_edit_green_48dp.png
Normal file
|
After Width: | Height: | Size: 767 B |
BIN
app/src/main/res/drawable/ic_play_circle_filled_black_48dp.png
Normal file
|
After Width: | Height: | Size: 444 B |
BIN
app/src/main/res/drawable/ic_play_green.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
app/src/main/res/drawable/ic_rename_green.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
app/src/main/res/drawable/ic_shortcut_green.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
app/src/main/res/drawable/navigation_show_more_vertical_512.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
app/src/main/res/drawable/pencil_and_paper.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
app/src/main/res/drawable/play_blue.png
Normal file
|
After Width: | Height: | Size: 4.4 KiB |
6
app/src/main/res/drawable/popup_menu_background.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="#42000000"/>
|
||||
<corners android:radius="5dp"/>
|
||||
</shape>
|
||||
BIN
app/src/main/res/drawable/rename.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/drawable/robot_head.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
app/src/main/res/drawable/robot_head_green.png
Normal file
|
After Width: | Height: | Size: 32 KiB |
BIN
app/src/main/res/drawable/robot_icon_green.png
Normal file
|
After Width: | Height: | Size: 27 KiB |
BIN
app/src/main/res/drawable/run.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
app/src/main/res/drawable/script_droid.png
Normal file
|
After Width: | Height: | Size: 16 KiB |
BIN
app/src/main/res/drawable/script_droid_100.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
app/src/main/res/drawable/script_droid_50.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable/sdcard_icon_black.png
Normal file
|
After Width: | Height: | Size: 1.9 KiB |
BIN
app/src/main/res/drawable/shortcut_icon.png
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
97
app/src/main/res/layout/activity_main.xml
Normal file
@@ -0,0 +1,97 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<android.support.design.widget.CoordinatorLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true"
|
||||
tools:context="com.stardust.scriptdroid.MainActivity">
|
||||
|
||||
<android.support.design.widget.AppBarLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:theme="@style/AppTheme.AppBarOverlay">
|
||||
|
||||
<android.support.v7.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:title="@string/app_name"
|
||||
app:popupTheme="@style/AppTheme.PopupOverlay"/>
|
||||
|
||||
</android.support.design.widget.AppBarLayout>
|
||||
|
||||
<include layout="@layout/content_main"/>
|
||||
|
||||
<com.stardust.scriptdroid.ui.SlidingUpPanel
|
||||
android:id="@+id/bottom_menu"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="160dp"
|
||||
android:background="@android:color/white"
|
||||
android:baselineAligned="false"
|
||||
android:gravity="center"
|
||||
android:orientation="horizontal">
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/create_new_file"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="600dp"
|
||||
android:layout_height="60dp"
|
||||
android:src="@drawable/ic_create_new_folder_black_48dp"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/text_new_file"
|
||||
android:textColor="@android:color/secondary_text_light"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/import_from_file"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="60dp"
|
||||
android:layout_height="60dp"
|
||||
android:padding="10dp"
|
||||
android:src="@drawable/sdcard_icon_black"/>
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:text="@string/text_import_from_file"
|
||||
android:textColor="@android:color/secondary_text_light"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</com.stardust.scriptdroid.ui.SlidingUpPanel>
|
||||
|
||||
</android.support.design.widget.CoordinatorLayout>
|
||||
29
app/src/main/res/layout/content_main.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/content_main"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"
|
||||
tools:context="com.stardust.scriptdroid.MainActivity"
|
||||
tools:showIn="@layout/activity_main">
|
||||
|
||||
|
||||
<com.stardust.scriptdroid.ui.ScriptListRecyclerView
|
||||
android:id="@+id/script_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<android.support.design.widget.FloatingActionButton
|
||||
android:id="@+id/fab"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_margin="@dimen/fab_margin"
|
||||
app:srcCompat="@drawable/ic_add_white_48dp"/>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@android:drawable/dialog_holo_light_frame"
|
||||
android:orientation="vertical">
|
||||
|
||||
<view
|
||||
android:id="@+id/operation_list"
|
||||
class="com.stardust.scriptdroid.ui.ScriptFileOperationPopupMenu$ScriptFileOperationListRecyclerView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"/>
|
||||
</LinearLayout>
|
||||
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="52dp"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:orientation="horizontal"
|
||||
android:padding="16dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@string/app_name"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_toRightOf="@id/icon"
|
||||
android:textColor="@android:color/secondary_text_light"
|
||||
android:textSize="16sp"/>
|
||||
|
||||
</RelativeLayout>
|
||||
88
app/src/main/res/layout/script_list_recycler_view_item.xml
Normal file
@@ -0,0 +1,88 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:foreground="?android:selectableItemBackground"
|
||||
android:orientation="vertical"
|
||||
android:paddingBottom="12dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="12dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/robot_icon_green"/>
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_marginLeft="56dp"
|
||||
android:layout_marginRight="80dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/name"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="@android:color/primary_text_light"
|
||||
android:textSize="18sp"
|
||||
tools:text="正在运行的服务"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/path"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textColor="#777777"
|
||||
android:textSize="14sp"
|
||||
tools:text="/storage/emulated/0/脚本/正在运行的服务.txt"/>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/edit"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_marginRight="42dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:src="@drawable/ic_edit_gray_48dp"/>
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/more"
|
||||
android:layout_width="42dp"
|
||||
android:layout_height="42dp"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:background="?attr/selectableItemBackgroundBorderless">
|
||||
|
||||
<ImageView
|
||||
android:layout_width="20dp"
|
||||
android:layout_height="20dp"
|
||||
android:layout_gravity="center"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:src="@drawable/navigation_show_more_vertical_512"/>
|
||||
</FrameLayout>
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
24
app/src/main/res/layout/sliding_up_panel.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<View
|
||||
android:id="@+id/shadow"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@color/sliding_up_panel_shadow_color"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/content_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentBottom="true"/>
|
||||
|
||||
</RelativeLayout>
|
||||
</FrameLayout>
|
||||
22
app/src/main/res/menu/menu_main.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
tools:context="com.stardust.scriptdroid.MainActivity">
|
||||
<item
|
||||
android:id="@+id/action_settings"
|
||||
android:orderInCategory="100"
|
||||
android:title="@string/action_settings"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_disable_service"
|
||||
android:orderInCategory="200"
|
||||
android:title="@string/action_disable_service"
|
||||
app:showAsAction="never"/>
|
||||
|
||||
<item
|
||||
android:id="@+id/action_exit"
|
||||
android:orderInCategory="300"
|
||||
android:title="@string/action_exit"
|
||||
app:showAsAction="never"/>
|
||||
</menu>
|
||||
BIN
app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 3.3 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 4.7 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 7.5 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
9
app/src/main/res/values-v21/styles.xml
Normal file
@@ -0,0 +1,9 @@
|
||||
<resources>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:statusBarColor">@android:color/transparent</item>
|
||||
</style>
|
||||
</resources>
|
||||
6
app/src/main/res/values-w820dp/dimens.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Example customization of dimensions originally defined in res/values/dimens.xml
|
||||
(such as screen margins) for screens with more than 820dp of available width. This
|
||||
would include 7" and 10" devices in landscape (~960dp and ~1280dp respectively). -->
|
||||
<dimen name="activity_horizontal_margin">64dp</dimen>
|
||||
</resources>
|
||||
3
app/src/main/res/values/attrs.xml
Normal file
@@ -0,0 +1,3 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
</resources>
|
||||
7
app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="colorPrimary">#8ea22a</color>
|
||||
<color name="colorPrimaryDark">#5e6b1e</color>
|
||||
<color name="colorAccent">#8ea22a</color>
|
||||
<color name="sliding_up_panel_shadow_color">#99444444</color>
|
||||
</resources>
|
||||
6
app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,6 @@
|
||||
<resources>
|
||||
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||
<dimen name="fab_margin">16dp</dimen>
|
||||
</resources>
|
||||
20
app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<resources>
|
||||
<string name="app_name">免Root脚本精灵</string>
|
||||
<string name="action_settings">设置</string>
|
||||
<string name="action_disable_service">关闭服务</string>
|
||||
<string name="text_accesibility_service_description">略略略</string>
|
||||
<string name="text_new_file">新建文件</string>
|
||||
<string name="text_import_from_file">从文件导入</string>
|
||||
<string name="action_exit">退出</string>
|
||||
<string name="text_service_disabled">服务已关闭</string>
|
||||
<string name="text_file_manager_not_found">找不到文件管理器T.T</string>
|
||||
<string name="text_file_create_fail">文件创建失败</string>
|
||||
<string name="text_please_input_name">请输入名称</string>
|
||||
<string name="text_name">名称</string>
|
||||
<string name="text_go_to_setting">去设置</string>
|
||||
<string name="explain_accessibility_permission">软件需要权限才能运行 ~ 请在随后的设置中选择”脚本执行机器人\"并开启权限</string>
|
||||
<string name="text_cancel">取消</string>
|
||||
<string name="text_path_is_empty">路径为空</string>
|
||||
<string name="text_file_not_exists">文件不存在</string>
|
||||
<string name="text_no_file_rw_permission">无文件读写权限</string>
|
||||
</resources>
|
||||
20
app/src/main/res/values/styles.xml
Normal file
@@ -0,0 +1,20 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.AppBarOverlay" parent="ThemeOverlay.AppCompat.Dark.ActionBar"/>
|
||||
|
||||
<style name="AppTheme.PopupOverlay" parent="ThemeOverlay.AppCompat.Light"/>
|
||||
|
||||
</resources>
|
||||
8
app/src/main/res/xml/accessibility_service_config.xml
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:accessibilityEventTypes="typeAllMask"
|
||||
android:accessibilityFeedbackType="feedbackGeneric"
|
||||
android:accessibilityFlags="flagIncludeNotImportantViews|flagReportViewIds|flagRetrieveInteractiveWindows"
|
||||
android:canRetrieveWindowContent="true"
|
||||
android:description="@string/text_accesibility_service_description"
|
||||
android:notificationTimeout="100"/>
|
||||
4
app/src/main/res/xml/provider_paths.xml
Normal file
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="external_files" path="."/>
|
||||
</paths>
|
||||
@@ -0,0 +1,19 @@
|
||||
package com.stardust.scriptdroid;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.Stack;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
@Test
|
||||
public void testStack() throws Exception {
|
||||
|
||||
|
||||
}
|
||||
}
|
||||