getAllPermissionsDeclarateInAndroidManifest(Context context, String pkgName) {
65 | try {
66 | return Arrays.asList(context.getPackageManager()
67 | .getPackageInfo(pkgName, PackageManager.GET_PERMISSIONS).requestedPermissions);
68 | } catch (PackageManager.NameNotFoundException e) {
69 | DLog.e(e);
70 | }
71 | return Collections.emptyList();
72 | }
73 |
74 | /**
75 | * 是否开启了 未知来源
76 | *
77 | * @param context
78 | *
79 | * @return
80 | */
81 | public static boolean isOpenUnknownSources(Context context) {
82 | boolean isOpenUnknownSources = false;
83 | try {
84 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 &&
85 | Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
86 | isOpenUnknownSources =
87 | Settings.Secure.getInt(context.getContentResolver(), Settings.Global.INSTALL_NON_MARKET_APPS) == 1;
88 | } else {
89 | isOpenUnknownSources =
90 | Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.INSTALL_NON_MARKET_APPS) == 1;
91 | }
92 | } catch (Throwable e) {
93 | DLog.e(e);
94 | }
95 | return isOpenUnknownSources;
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
17 |
18 |
24 |
25 |
35 |
36 |
48 |
49 |
50 |
51 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_auto_config_vpn.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
21 |
22 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_auto_install.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
21 |
22 |
29 |
30 |
37 |
38 |
45 |
46 |
53 |
54 |
61 |
62 |
69 |
70 |
71 |
72 |
73 |
74 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/toolbar_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | AccessibilityDispatcher
3 |
4 | 这是一个演示的辅助功能服务
5 | \n\t* 自动配置VPN
6 | \n\t* 自动安装应用
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
12 |
13 |
18 |
19 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/accessibility_service.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/file_provider.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/test/java/io/github/zhitaocai/accessibilitydispatcher/demo/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.demo;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 |
14 | @Test
15 | public void addition_isCorrect() throws Exception {
16 | assertEquals(4, 2 + 2);
17 | }
18 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 | buildscript {
3 | repositories {
4 | jcenter()
5 | }
6 | dependencies {
7 | classpath 'com.android.tools.build:gradle:2.3.1'
8 |
9 | //用于上传Maven生成的文件到Bintray
10 | classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.3'
11 |
12 | //用于打包Maven所需文件
13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
14 |
15 | // NOTE: Do not place your application dependencies here; they belong
16 | // in the individual module build.gradle files
17 | }
18 | }
19 |
20 | allprojects {
21 | repositories {
22 | jcenter()
23 | mavenCentral()
24 | // for test
25 | // maven {
26 | // url 'https://dl.bintray.com/zhitaocai/maven'
27 | // }
28 | }
29 | }
30 |
31 | task clean(type: Delete) {
32 | delete rootProject.buildDir
33 | }
34 |
35 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed Mar 22 17:19:05 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/library/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 |
3 | ext {
4 | releaseNoteFile = 'ChangeLog.md' // 相对路径,填仓库下该文件地址即可
5 | libraryName = 'AccessibilityDispatcher' // bintray项目名字以及aar的名字,确定好了就不要在修改了
6 | libLabels = ['android', 'accessibility'] // bintray项目的标签
7 |
8 | //compile 'publishedGroupId:artifact:libraryVersionName'
9 |
10 | publishedGroupId = 'io.github.zhitaocai' // 项目组ID(e.g 包名为io.github.zhitaocai.toastcompat, 那么这里就是io.github.zhitaocai)
11 | artifact = 'accessibilitydispatcher' // 项目ID (e.g 包名为io.github.zhitaocai.toastcompat, 那么这里就是toastcompat)
12 | libraryVersionName = '0.4.0' // 对应的versionname
13 | libraryVersionCode = 4
14 |
15 | libDesc = 'an library for using accessibility service more convenient' // 项目描述
16 | libraryVersionDesc = libDesc // 当前版本下的项目描述
17 | repoTag = libraryVersionName // 当前版本下所对应的仓库的tag
18 | gitRepo = 'zhitaocai/AccessibilityDispatcher' // 仓库短地址
19 | siteUrl = 'https://github.com/zhitaocai/AccessibilityDispatcher'
20 | gitUrl = 'https://github.com/zhitaocai/AccessibilityDispatcher.git'
21 | issueUrl = 'https://github.com/zhitaocai/AccessibilityDispatcher/issues'
22 |
23 | isPackageResources = true // 是否打包资源文件进去,是的话,就需要打包R.class
24 | }
25 |
26 | android {
27 | compileSdkVersion 25
28 | buildToolsVersion "25.0.1"
29 |
30 | defaultConfig {
31 | minSdkVersion 14
32 | targetSdkVersion 25
33 | versionCode libraryVersionCode
34 | versionName libraryVersionName
35 |
36 | consumerProguardFiles 'proguard-rules.pro'
37 |
38 | //testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
39 | }
40 | buildTypes {
41 | release {
42 | minifyEnabled false
43 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
44 | }
45 | }
46 |
47 | compileOptions {
48 | encoding "UTF-8"
49 | sourceCompatibility JavaVersion.VERSION_1_7
50 | targetCompatibility JavaVersion.VERSION_1_7
51 | }
52 |
53 | libraryVariants.all { variant ->
54 | // 修改aar的名字和输出路径
55 | variant.outputs.each { output ->
56 | def outputFile = output.outputFile
57 | if (outputFile != null && outputFile.name.endsWith('.aar')) {
58 | def fileName = "${artifact}-${variant.buildType.name}-${libraryVersionName}.aar"
59 | // output.outputFile = new File(outputFile.parent, fileName)
60 | output.outputFile = new File(project.rootProject.getProjectDir().getAbsolutePath() + "/aar/" + fileName)
61 | }
62 | }
63 | // if (variant.name == 'release') {
64 | // task("generate${variant.name.capitalize()}Javadoc", type: Javadoc) {
65 | // // title = ''
66 | // // description = ''
67 | // source = variant.javaCompile.source
68 | // classpath = files(variant.javaCompile.classpath.files, project.android.getBootClasspath())
69 | // options {
70 | // encoding "UTF-8"
71 | // links "http://docs.oracle.com/javase/7/docs/api/"
72 | // linksOffline "http://d.android.com/reference", "${android.sdkDirectory}/docs/reference"
73 | // failOnError false
74 | // }
75 | // exclude '**/BuildConfig.java'
76 | // exclude '**/R.java'
77 | // }
78 | // task("javadoc${variant.name.capitalize()}Jar", type: Jar, dependsOn: "generate${variant.name.capitalize()}Javadoc") {
79 | // classifier = 'javadoc'
80 | // from tasks.getByName("generate${variant.name.capitalize()}Javadoc").destinationDir
81 | // }
82 | //
83 | // task androidReleaseJar(type: Jar, dependsOn: assembleRelease) {
84 | // from "$buildDir/intermediates/classes/release/"
85 | // exclude '**/BuildConfig.class'
86 | // exclude '**/R.class'
87 | // exclude '**/R$*.class'
88 | // classifier 'sources11'
89 | // baseName project.ext.artifact
90 | // }
91 | //
92 | // artifacts {
93 | // archives tasks.getByName("androidReleaseJar")
94 | // //archives tasks.getByName("javadoc${variant.name.capitalize()}Jar")
95 | // }
96 | // }
97 | }
98 | }
99 |
100 | //configurations {
101 | // javadocDeps
102 | //}
103 |
104 | dependencies {
105 | // compile fileTree(dir: 'libs', include: ['*.jar'])
106 | // androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
107 | // exclude group: 'com.android.support', module: 'support-annotations'
108 | // })
109 | // compile 'com.android.support:appcompat-v7:25.3.0'
110 | // testCompile 'junit:junit:4.12'
111 | // compile 'com.android.support:support-annotations:25.3.0'
112 |
113 | compile 'com.android.support:support-annotations:25.3.0'
114 |
115 | //avadocDeps 'com.android.support:support-annotations:25.3.0'
116 | }
117 |
118 | apply from: 'publish.gradle'
119 |
120 |
--------------------------------------------------------------------------------
/library/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/caizhitao/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/library/src/androidTest/java/io/github/zhitaocai/accessibilitydispatcher/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | //package io.github.zhitaocai.accessibilitydispatcher;
2 | //
3 | //import android.content.Context;
4 | //import android.support.test.InstrumentationRegistry;
5 | //import android.support.test.runner.AndroidJUnit4;
6 | //
7 | //import org.junit.Test;
8 | //import org.junit.runner.RunWith;
9 | //
10 | //import static org.junit.Assert.*;
11 | //
12 | ///**
13 | // * Instrumentation test, which will execute on an Android device.
14 | // *
15 | // * @see Testing documentation
16 | // */
17 | //@RunWith(AndroidJUnit4.class)
18 | //public class ExampleInstrumentedTest {
19 | //
20 | // @Test
21 | // public void useAppContext() throws Exception {
22 | // // Context of the app under test.
23 | // Context appContext = InstrumentationRegistry.getTargetContext();
24 | //
25 | // assertEquals("io.github.zhitaocai.accessibilitydispatcher.test", appContext.getPackageName());
26 | // }
27 | //}
28 |
--------------------------------------------------------------------------------
/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
5 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/AbsASHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.view.accessibility.AccessibilityEvent;
5 |
6 | import java.util.ArrayList;
7 |
8 | import io.github.zhitaocai.accessibilitydispatcher.businss.ITarget;
9 | import io.github.zhitaocai.accessibilitydispatcher.businss.OnCallBack;
10 |
11 | /**
12 | * 辅助功能抽象类,提供各种节点操作,查找方法,子类继承本类并实现还没有实现的接口即可
13 | *
14 | * ps:
15 | *
16 | * AS 为AccessibilityService 的缩写
17 | *
18 | * @author zhitao
19 | * @since 2016-08-04 14:57
20 | */
21 | public abstract class AbsASHandler extends UtilASHandler {
22 |
23 | private boolean mIsEnable = true;
24 |
25 | private ArrayList mCallBacks;
26 |
27 | private ArrayList mTargets;
28 |
29 | /**
30 | * 设置是否开启本辅助功能
31 | *
32 | * @param enable
33 | */
34 | void setEnable(boolean enable) {
35 | mIsEnable = enable;
36 | }
37 |
38 | /**
39 | * 当前是否允许本辅助功能开启
40 | */
41 | boolean isEnable() {
42 | return mIsEnable;
43 | }
44 |
45 | protected ArrayList getCallBacks() {
46 | if (mCallBacks == null) {
47 | mCallBacks = new ArrayList<>();
48 | }
49 | return mCallBacks;
50 | }
51 |
52 | protected void setCallBacks(ArrayList callBacks) {
53 | mCallBacks = callBacks;
54 | }
55 |
56 | protected ArrayList getTargets() {
57 | if (mTargets == null) {
58 | mTargets = new ArrayList<>();
59 | }
60 | return mTargets;
61 | }
62 |
63 | protected void setTargets(ArrayList targets) {
64 | mTargets = targets;
65 | }
66 |
67 | void handlerOnServiceConnected() {
68 | if (isEnable()) {
69 | onServiceConnected();
70 | }
71 | }
72 |
73 | void handlerOnInterrupt() {
74 | if (isEnable()) {
75 | onInterrupt();
76 | }
77 | }
78 |
79 | void handlerOnAccessibilityEvent(AccessibilityEvent event) {
80 | if (isEnable()) {
81 | onAccessibilityEvent(event);
82 | }
83 | }
84 |
85 | protected abstract void onServiceConnected();
86 |
87 | protected abstract void onInterrupt();
88 |
89 | protected abstract void onAccessibilityEvent(AccessibilityEvent event);
90 |
91 | @Override
92 | public int hashCode() {
93 | return getSupportPkgName().hashCode();
94 | }
95 |
96 | @Override
97 | public boolean equals(Object obj) {
98 | return obj != null && hashCode() == obj.hashCode();
99 | }
100 |
101 | /**
102 | * 具体实现类的辅助功能所针对的应用包名
103 | *
104 | * e.g.
105 | *
106 | * 如果需要改动系统设置,那么这里的包名可能为 com.android.settings 或者 其他第三方系统所对应的包名
107 | *
108 | * @return 具体实现类的辅助功能所针对的应用包名
109 | *
110 | * @see #isUsingPkgName2TrackEvent()
111 | */
112 | @NonNull
113 | protected abstract String getSupportPkgName();
114 |
115 | /**
116 | * 是否根据包名去定位事件,并交由指定包名的自动点击类处理
117 | *
118 | * 如果能确定包名的话,当然是最好
119 | * 但是如果不能确定包名的话,那么这里就要返回false
120 | *
121 | * e.g.
122 | *
123 | * 安装apk的系统应用包名在不同系统上都不太相同,暂时我们可能采取文字搜索来定位 "下一步" "安装" 等等的按钮来点击
124 | * (毕竟即便包名不一样,但是给用户显示的界面一般都有"下一步" "安装"这些按钮文字),如果我们采取了这种根据文字搜索的方法来处理的话
125 | * 那么我们就不需要包名来定位了,所以这种时候就需要返回false了
126 | *
127 | * @return 返回结果
128 | *
129 | * - true : 是
130 | * - false : 否
131 | *
132 | *
133 | * @see #getSupportPkgName()
134 | */
135 | protected boolean isUsingPkgName2TrackEvent() {
136 | return true;
137 | }
138 |
139 | }
140 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/AccessibilityDebugConfig.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher;
2 |
3 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
4 |
5 | /**
6 | * @author zhitao
7 | * @since 2017-03-22 22:15
8 | */
9 | public final class AccessibilityDebugConfig {
10 |
11 | private boolean mIsShowDebugLog;
12 |
13 | private boolean mIsShowEventSourceLog;
14 |
15 | private boolean mIsShowClassNameInTag;
16 |
17 | AccessibilityDebugConfig() {
18 | super();
19 | mIsShowDebugLog = false;
20 | mIsShowEventSourceLog = false;
21 | mIsShowClassNameInTag = false;
22 | }
23 |
24 | boolean isShowClassNameInTag() {
25 | return mIsShowClassNameInTag;
26 | }
27 |
28 | /**
29 | * 调试的Log显示时,是否显示log调用时所在的类名
30 | *
31 | * @param showClassNameInTag 是否显示
32 | *
33 | * @return 本对象
34 | */
35 | public AccessibilityDebugConfig withClassNameInTag(boolean showClassNameInTag) {
36 | mIsShowClassNameInTag = showClassNameInTag;
37 | DLog.setIsShowClassNameInTag(mIsShowClassNameInTag);
38 | return this;
39 | }
40 |
41 | boolean isShowEventSourceLog() {
42 | return mIsShowEventSourceLog;
43 | }
44 |
45 | /**
46 | * 调试的Log显示时,是否显示时间源输出
47 | *
48 | * @param isShowEventSourceLog 是否显示
49 | *
50 | * @return 本对象
51 | */
52 | public AccessibilityDebugConfig withEventSourceLog(boolean isShowEventSourceLog) {
53 | mIsShowEventSourceLog = isShowEventSourceLog;
54 | return this;
55 | }
56 |
57 | boolean isShowDebugLog() {
58 | return mIsShowDebugLog;
59 | }
60 |
61 | AccessibilityDebugConfig debugLog(boolean isShowDebugLog) {
62 | mIsShowDebugLog = isShowDebugLog;
63 | DLog.setIsDebug(isShowDebugLog);
64 | return this;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/AccessibilityDispatcher.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher;
2 |
3 | import android.accessibilityservice.AccessibilityService;
4 | import android.annotation.TargetApi;
5 | import android.os.Build;
6 | import android.os.Handler;
7 | import android.os.Looper;
8 | import android.util.SparseArray;
9 | import android.view.accessibility.AccessibilityEvent;
10 |
11 | import java.lang.reflect.Field;
12 | import java.lang.reflect.Modifier;
13 | import java.util.ArrayList;
14 | import java.util.concurrent.ExecutorService;
15 | import java.util.concurrent.Executors;
16 |
17 | import io.github.zhitaocai.accessibilitydispatcher.businss.AbsHelper;
18 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
19 |
20 | /**
21 | * 辅助功能事件分发器
22 | *
23 | * @author zhitao
24 | * @since 2016-08-03 17:27
25 | */
26 | public final class AccessibilityDispatcher {
27 |
28 | /**
29 | * 子业务实例同步锁
30 | */
31 | private final static String SYNC = "sync";
32 |
33 | /**
34 | * 支持的子业务
35 | *
36 | * - key : 业务唯一标识
37 | * - value : 执行该业务所用到的自动点击类,理论上应该为1个,但是这里弄成列表是为了能兼容处理多个,比如需要自动安装应用,被类库支持原生系统,那么我在miui
38 | * 上运行可能会出问题,这个时候,我尝试使用好几个自动点击处理类,一个为原生Android
39 | * 系统的处理类,一个为根据文字模糊搜索的点击处理类,毕竟文字模糊搜索的处理兼容性更好
40 | * (尽管准确度可能存在问题,但是这里我们出现一个明显不适配的场合了,所以用上这种兼容性处理就比较好了)
41 | *
42 | */
43 | private static SparseArray> sAbsASHandlers;
44 |
45 | /**
46 | * 耗时任务运行的线程池
47 | */
48 | private static ExecutorService sSingleThreadExecutor;
49 |
50 | /**
51 | * UIhandler
52 | */
53 | private static Handler sUIHandler;
54 |
55 | /**
56 | * 配置实例
57 | */
58 | private static AccessibilityDebugConfig sConfig = new AccessibilityDebugConfig();
59 |
60 | /**
61 | * 是否开启调试的log
62 | *
63 | * @param isDebug
64 | * - {@code true}: 开启调试模式,会有log输出
65 | * - {@code false}: 关闭调试模式,没有log输出(默认)
66 | *
67 | *
68 | * @return 调试配置
69 | */
70 | public static AccessibilityDebugConfig debugLog(boolean isDebug) {
71 | sConfig.debugLog(isDebug);
72 | return sConfig;
73 | }
74 |
75 | /**
76 | * 所有的节点查找或者其他耗时操作都统一用这个服务来执行
77 | *
78 | * 你应该需要知道
79 | *
80 | * - AccessibilityService中本质还是一个Service,在里面执行查找节点之类等的耗时操作是可能导致UI线程产生卡顿感
81 | * - 实际上,我们用辅助功能产生的事件是一件一件来的,如果我们要自动点击某些事件,那么其实在我们还没有点击前,基本是不会产生新的事件(不考虑计时器之类的界面)
82 | *
83 | * 基于上面两点,我们就可以用一个单线程池来,来针对每个新的事件做判断而不会导致逻辑混淆
84 | */
85 | static ExecutorService getSingleThreadExecutor() {
86 | if (sSingleThreadExecutor == null) {
87 | sSingleThreadExecutor = Executors.newSingleThreadExecutor();
88 | }
89 | return sSingleThreadExecutor;
90 | }
91 |
92 | static Handler getUIHandler() {
93 | if (sUIHandler == null) {
94 | sUIHandler = new Handler(Looper.getMainLooper());
95 | }
96 | return sUIHandler;
97 | }
98 |
99 | static SparseArray> getAbsASHandlers() {
100 | if (sAbsASHandlers == null) {
101 | sAbsASHandlers = new SparseArray<>();
102 | }
103 | return sAbsASHandlers;
104 | }
105 |
106 | /**
107 | * 更新辅助功能支持的列表以及其他各种属性
108 | *
109 | * @param helper 实际对应的业务
110 | */
111 | public static void updateHelper(AbsHelper helper) {
112 | if (helper == null) {
113 | return;
114 | }
115 | synchronized (SYNC) {
116 | // 先看看当前支持的列表中是否有
117 | ArrayList handlers = getAbsASHandlers().get(helper.hashCode());
118 | if (handlers == null || handlers.isEmpty()) {
119 | // 如果列表没有的话,检查是否需要开启,如果不需要开启的话,那么也就没必要创建对象
120 | if (!helper.isEnable()) {
121 | return;
122 | }
123 | handlers = helper.getHandlerFactory().initHandlers();
124 | }
125 | for (AbsASHandler handler : handlers) {
126 | handler.setEnable(helper.isEnable());
127 | handler.setCallBacks(helper.getCallBacks());
128 | handler.setTargets(helper.getTargets());
129 | }
130 | sAbsASHandlers.put(helper.hashCode(), handlers);
131 | return;
132 | }
133 | }
134 |
135 | @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
136 | public static void onServiceConnected(AccessibilityService service) {
137 | DLog.setTag("accessibility");
138 | synchronized (SYNC) {
139 | try {
140 | if (sConfig.isShowDebugLog()) {
141 | DLog.i(">>>>>>>>>>>>>>>>>> 辅助功能服务连接 >>>>>>>>>>>>>>>>>>");
142 | }
143 | if (sAbsASHandlers == null || sAbsASHandlers.size() == 0) {
144 | return;
145 | }
146 | for (int i = 0; i < sAbsASHandlers.size(); i++) {
147 | ArrayList handlers = sAbsASHandlers.valueAt(i);
148 | if (handlers == null || handlers.isEmpty()) {
149 | continue;
150 | }
151 | for (AbsASHandler handler : handlers) {
152 | // 如果没有目标就不进行事件分发的的操作了
153 | if (handler.getTargets() == null || handler.getTargets().isEmpty()) {
154 | continue;
155 | }
156 | handler.setAccessibilityService(service);
157 | handler.handlerOnServiceConnected();
158 | }
159 | }
160 | } catch (Throwable e) {
161 | DLog.e(e);
162 | }
163 | }
164 | }
165 |
166 | public static void onInterrupt(AccessibilityService service) {
167 | synchronized (SYNC) {
168 | try {
169 | if (sConfig.isShowDebugLog()) {
170 | DLog.i(">>>>>>>>>>>>>>>>>> 辅助功能服务被中断 >>>>>>>>>>>>>>>>>>");
171 | }
172 | if (sAbsASHandlers == null || sAbsASHandlers.size() == 0) {
173 | return;
174 | }
175 | for (int i = 0; i < sAbsASHandlers.size(); i++) {
176 | ArrayList handlers = sAbsASHandlers.valueAt(i);
177 | if (handlers == null || handlers.isEmpty()) {
178 | continue;
179 | }
180 | for (AbsASHandler handler : handlers) {
181 | // 如果没有目标就不进行事件分发的的操作了
182 | if (handler.getTargets() == null || handler.getTargets().isEmpty()) {
183 | continue;
184 | }
185 | handler.setAccessibilityService(service);
186 | handler.handlerOnInterrupt();
187 | }
188 | }
189 | } catch (Throwable e) {
190 | DLog.e(e);
191 | }
192 | }
193 | }
194 |
195 | public static void onAccessibilityEvent(AccessibilityService service, AccessibilityEvent event) {
196 | synchronized (SYNC) {
197 | try {
198 | if (sConfig.isShowDebugLog()) {
199 | DLog.i(
200 | ">>>>>>>>>>>>>>>>>> 触发新事件 >>>>>>>>>>>>>>>>>>\n" +
201 | "> 事件来源包名: %s\n> 事件源类名: %s\n> 事件类型描述: %s\n> 事件类型: %d\n> 事件源: %s",
202 | event.getPackageName(),
203 | event.getClassName(),
204 | getEventTypeNameByTypeId(event.getEventType()),
205 | event.getEventType(),
206 | sConfig.isShowEventSourceLog() ? event.getSource() : "暂时不输出"
207 | );
208 | }
209 | if (sAbsASHandlers == null || sAbsASHandlers.size() == 0) {
210 | return;
211 | }
212 | for (int i = 0; i < sAbsASHandlers.size(); i++) {
213 | ArrayList handlers = sAbsASHandlers.valueAt(i);
214 | if (handlers == null || handlers.isEmpty()) {
215 | continue;
216 | }
217 | for (AbsASHandler handler : handlers) {
218 |
219 | // 如果没有目标就不进行事件分发的的操作了
220 | if (handler.getTargets() == null || handler.getTargets().isEmpty()) {
221 | continue;
222 | }
223 |
224 | if (handler.isUsingPkgName2TrackEvent()) {
225 | if (!event.getPackageName().equals(handler.getSupportPkgName())) {
226 | continue;
227 | }
228 | }
229 |
230 | handler.setAccessibilityService(service);
231 | handler.handlerOnAccessibilityEvent(event);
232 | }
233 | }
234 | } catch (Throwable e) {
235 | DLog.e(e);
236 | }
237 | }
238 | }
239 |
240 | /**
241 | * 主要用来输出log
242 | */
243 | private static String getEventTypeNameByTypeId(int typeId) {
244 | if (sEventNameArray == null) {
245 | sEventNameArray = new SparseArray<>();
246 | Field[] fields = AccessibilityEvent.class.getDeclaredFields();
247 | for (Field field : fields) {
248 | if (Modifier.isPublic(field.getModifiers()) && Modifier.isStatic(field.getModifiers()) &&
249 | Modifier.isFinal(field.getModifiers()) && field.getName().startsWith("TYPE_")) {
250 | try {
251 | sEventNameArray.put(field.getInt(AccessibilityEvent.class), field.getName());
252 | } catch (Exception e) {
253 | DLog.i(e);
254 | }
255 | }
256 | }
257 | }
258 | return sEventNameArray.get(typeId);
259 | }
260 |
261 | /**
262 | * 主要用来输出log
263 | *
264 | * key : eventType
265 | * value : eventTypeDesc
266 | */
267 | private static SparseArray sEventNameArray;
268 |
269 | }
270 |
271 |
272 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/AccessibilityHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher;
2 |
3 | import android.content.Intent;
4 | import android.provider.Settings;
5 | import android.util.SparseArray;
6 |
7 | import io.github.zhitaocai.accessibilitydispatcher.businss.AbsHelper;
8 | import io.github.zhitaocai.accessibilitydispatcher.businss.IHandlerFactory;
9 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.ApkInstallHandlerFactory;
10 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.ApkInstallTarget;
11 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.OnApkInstallCallBack;
12 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.OnSecurityCallBack;
13 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.SecurityHandlerFactory;
14 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.SecurityTarget;
15 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.OnVpnCallBack;
16 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.VpnHandlerFactory;
17 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.VpnTarget;
18 |
19 | /**
20 | * @author zhitao
21 | * @since 2017-04-21 10:31
22 | */
23 | public class AccessibilityHelper {
24 |
25 | private static final SparseArray sAbsHelperSparseArray = new SparseArray<>();
26 |
27 | private final static String IDENTIFY_VPN = "android.net.vpn.SETTINGS";
28 |
29 | private final static String IDENTIFY_SECURITY = Settings.ACTION_SECURITY_SETTINGS;
30 |
31 | private final static String IDENTIFY_APK_INSTALL = Intent.ACTION_INSTALL_PACKAGE;
32 |
33 | /**
34 | * 创建一个VPN自动配置的辅助功能助手
35 | *
36 | * @return 一个VPN自动配置的辅助功能助手对象
37 | */
38 | public static AbsHelper vpnHelper() {
39 | synchronized (sAbsHelperSparseArray) {
40 | AbsHelper absHelper = sAbsHelperSparseArray.get(IDENTIFY_VPN.hashCode());
41 | if (absHelper == null) {
42 | absHelper = new AbsHelper("android.net.vpn.SETTINGS") {
43 |
44 | /**
45 | * @return 创建一个默认的工厂对象,如果没有调用 {@link #withHandlerFactory(IHandlerFactory)} 方法设置工厂,那么就会使用这个方法创建的默认工厂
46 | */
47 | @Override
48 | protected VpnHandlerFactory newDefaultHandlerFactory() {
49 | return new VpnHandlerFactory();
50 | }
51 | };
52 | sAbsHelperSparseArray.put(IDENTIFY_VPN.hashCode(), absHelper);
53 | }
54 | return absHelper;
55 | }
56 | }
57 |
58 | /**
59 | * 创建一个系统设置安全界面的自动点击辅助功能助手
60 | *
61 | * @return 一个系统设置安全界面的自动点击辅助功能助手对象
62 | */
63 | public static AbsHelper securityHelper() {
64 | synchronized (sAbsHelperSparseArray) {
65 |
66 | AbsHelper absHelper = sAbsHelperSparseArray.get(IDENTIFY_SECURITY.hashCode());
67 | if (absHelper == null) {
68 | absHelper = new AbsHelper(IDENTIFY_SECURITY) {
69 |
70 | /**
71 | * @return 创建一个默认的工厂对象,如果没有调用 {@link #withHandlerFactory(IHandlerFactory)} 方法设置工厂,那么就会使用这个方法创建的默认工厂
72 | */
73 | @Override
74 | protected SecurityHandlerFactory newDefaultHandlerFactory() {
75 | return new SecurityHandlerFactory();
76 | }
77 | };
78 | }
79 | return absHelper;
80 | }
81 | }
82 |
83 | /**
84 | * 创建一个APK安装/APP卸载的自动点击辅助功能助手
85 | *
86 | * @return 一个APK安装/APP卸载的自动点击辅助功能助手
87 | */
88 | public static AbsHelper apkInstallHelper() {
89 | synchronized (sAbsHelperSparseArray) {
90 | AbsHelper absHelper = sAbsHelperSparseArray.get(IDENTIFY_APK_INSTALL.hashCode());
91 | if (absHelper == null) {
92 | absHelper =
93 | new AbsHelper(IDENTIFY_APK_INSTALL) {
94 |
95 | /**
96 | * @return 创建一个默认的工厂对象,如果没有调用 {@link #withHandlerFactory(IHandlerFactory)} 方法设置工厂,那么就会使用这个方法创建的默认工厂
97 | */
98 | @Override
99 | protected ApkInstallHandlerFactory newDefaultHandlerFactory() {
100 | return new ApkInstallHandlerFactory();
101 | }
102 | };
103 | }
104 | return absHelper;
105 | }
106 | }
107 |
108 | }
109 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/AccessibilityServiceUtils.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher;
2 |
3 | import android.content.ComponentName;
4 | import android.content.Context;
5 | import android.content.pm.PackageManager;
6 | import android.provider.Settings;
7 | import android.support.annotation.NonNull;
8 |
9 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
10 |
11 | /**
12 | * @author zhitao
13 | * @since 2016-08-03 17:47
14 | */
15 | public class AccessibilityServiceUtils {
16 |
17 | /**
18 | * 触发系统rebind通知监听服务
19 | *
20 | * @param context 上下文
21 | * @param serviceClass 辅助功能服务的类
22 | */
23 | public static void toggleAccessibilityService(Context context, Class serviceClass) {
24 | PackageManager pm = context.getPackageManager();
25 | pm.setComponentEnabledSetting(
26 | new ComponentName(context, serviceClass),
27 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED,
28 | PackageManager.DONT_KILL_APP
29 | );
30 | pm.setComponentEnabledSetting(
31 | new ComponentName(context, serviceClass),
32 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED,
33 | PackageManager.DONT_KILL_APP
34 | );
35 | }
36 |
37 | /**
38 | * 判断当前应用的辅助功能服务是否开启
39 | *
40 | * @param context 上下文
41 | *
42 | * @return 是否开启
43 | */
44 | public static boolean isAccessibilityServiceOn(@NonNull Context context) {
45 | return isAccessibilityServiceOn(context, context.getPackageName());
46 | }
47 |
48 | /**
49 | * 判断指定的应用的辅助功能是否开启
50 | *
51 | * @param context 上下文
52 | * @param pkgName 要检查的服务所对应的app包名
53 | *
54 | * @return 是否开启
55 | */
56 | public static boolean isAccessibilityServiceOn(@NonNull Context context, @NonNull String pkgName) {
57 | int accessibilityEnabled = 0;
58 | try {
59 | accessibilityEnabled = Settings.Secure.getInt(context.getContentResolver(), Settings.Secure.ACCESSIBILITY_ENABLED);
60 | } catch (Exception e) {
61 | DLog.e(e);
62 | }
63 |
64 | if (accessibilityEnabled == 1) {
65 | String services =
66 | Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
67 | if (services != null) {
68 | return services.toLowerCase().contains(pkgName.toLowerCase());
69 | }
70 | }
71 | return false;
72 | }
73 |
74 | // /**
75 | // * 判断指定的应用的辅助功能是否开启,不能判断自身的辅助功能服务是否开启
76 | // *
77 | // * @param context
78 | // * @param name 应用包名?应用服务名?
79 | // *
80 | // * @return
81 | // */
82 | // @Deprecated
83 | // public static boolean isAccessibilityServiceOn(@NonNull Context context, @NonNull String name) {
84 | // AccessibilityManager am = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
85 | // List serviceInfos =
86 | // am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
87 | // List installedAccessibilityServiceList = am.getInstalledAccessibilityServiceList();
88 | // for (AccessibilityServiceInfo info : installedAccessibilityServiceList) {
89 | // if (name.equals(info.getId())) {
90 | // return true;
91 | // }
92 | // }
93 | // return false;
94 | // }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/AndroidSettingsCompat.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings;
2 |
3 | /**
4 | * @author zhitao
5 | * @since 2017-03-23 17:35
6 | */
7 | public class AndroidSettingsCompat {
8 |
9 | public final static String ANDROID_PKGNAME = "com.android.settings";
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/security/AbsSecuritySettingsASHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security;
2 |
3 | import java.util.List;
4 |
5 | import io.github.zhitaocai.accessibilitydispatcher.AbsASHandler;
6 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.AndroidSettingsCompat;
7 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.OnSecurityCallBack;
8 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.SecurityTarget;
9 |
10 | /**
11 | * @author zhitao
12 | * @since 2017-03-30 11:55
13 | */
14 | public abstract class AbsSecuritySettingsASHandler extends AbsASHandler {
15 |
16 | /**
17 | * 具体实现类的辅助功能所针对的应用包名
18 | *
19 | * e.g.
20 | *
21 | * 如果需要改动系统设置,那么这里的包名可能为 com.android.settings 或者 其他第三方系统所对应的包名
22 | *
23 | * @return 具体实现类的辅助功能所针对的应用包名
24 | *
25 | * @see #isUsingPkgName2TrackEvent()
26 | */
27 | @Override
28 | protected String getSupportPkgName() {
29 | return AndroidSettingsCompat.ANDROID_PKGNAME;
30 | }
31 |
32 | protected void handleInSecurityPage() {
33 | if (isInSecurityPage()) {
34 | runLogicInSecurityPage();
35 | }
36 | }
37 |
38 | protected void handleInUnknownSourcesTurnOnConfirmDialog() {
39 | if (isInUnknownSourcesTurnOnConfirmDialog()) {
40 | runLogicInUnknownSourcesTurnOnConfirmDialog();
41 | }
42 | }
43 |
44 | protected void handleScrollInSecurityPage() {
45 | if (isInSecurityPage()) {
46 | scrollInSecurityPage();
47 | }
48 | }
49 |
50 | protected void callBackOnUnknownSourceItemClick() {
51 | List list = getCallBacks();
52 | if (list == null || list.isEmpty()) {
53 | return;
54 | }
55 | for (final OnSecurityCallBack callBack : list) {
56 | getUIHandler().post(new Runnable() {
57 | @Override
58 | public void run() {
59 | callBack.onUnknownSourceItemClick();
60 | }
61 | });
62 | }
63 | }
64 |
65 | protected void callBackOnUnknownSourceDialogConfirm() {
66 | List list = getCallBacks();
67 | if (list == null || list.isEmpty()) {
68 | return;
69 | }
70 | for (final OnSecurityCallBack callBack : list) {
71 | getUIHandler().post(new Runnable() {
72 | @Override
73 | public void run() {
74 | callBack.onUnknownSourceDialogConfirm();
75 | }
76 | });
77 | }
78 | }
79 |
80 | /**
81 | * 是否在安全设置页面中
82 | *
83 | * @return 是否在安全设置页面中
84 | */
85 | protected abstract boolean isInSecurityPage();
86 |
87 | /**
88 | * 处理界面在安全设置页面中的逻辑
89 | */
90 | protected abstract void runLogicInSecurityPage();
91 |
92 | /**
93 | * 目标可能在下面,所以需要先滑动listview
94 | */
95 | protected abstract void scrollInSecurityPage();
96 |
97 | /**
98 | * 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
99 | *
100 | * @return 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
101 | */
102 | protected abstract boolean isInUnknownSourcesTurnOnConfirmDialog();
103 |
104 | /**
105 | * 处理界面在点击允许安装未知来源的开关之后弹出来的确认对话框页面的逻辑
106 | */
107 | protected abstract void runLogicInUnknownSourcesTurnOnConfirmDialog();
108 | }
109 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/security/unknownsources/android/AndroidUnknownSourcesASHandler444.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.unknownsources.android;
2 |
3 | import android.os.Build;
4 | import android.support.annotation.RequiresApi;
5 | import android.view.accessibility.AccessibilityEvent;
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 |
8 | import java.util.List;
9 |
10 | import io.github.zhitaocai.accessibilitydispatcher.R;
11 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.AbsSecuritySettingsASHandler;
12 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.SecurityTarget;
13 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
14 |
15 | /**
16 | * 支持原生Android 4.4.4系统
17 | *
18 | * @author zhitao
19 | * @since 2017-03-30 15:19
20 | */
21 | public class AndroidUnknownSourcesASHandler444 extends AbsSecuritySettingsASHandler {
22 |
23 | @Override
24 | protected void onServiceConnected() {
25 |
26 | }
27 |
28 | @Override
29 | protected void onInterrupt() {
30 |
31 | }
32 |
33 | @Override
34 | public void onAccessibilityEvent(AccessibilityEvent event) {
35 | switch (event.getEventType()) {
36 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
37 | handleInSecurityPage();
38 | break;
39 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
40 | switch (event.getClassName().toString()) {
41 | // 通过Intent方式进入安全设置界面
42 | case "com.android.settings.Settings$SecuritySettingsActivity":
43 | handleInSecurityPage();
44 | handleScrollInSecurityPage();
45 | break;
46 | // 二次确认对话框
47 | case "android.app.AlertDialog":
48 | handleInUnknownSourcesTurnOnConfirmDialog();
49 | break;
50 | default:
51 | break;
52 | }
53 | break;
54 | case AccessibilityEvent.TYPE_VIEW_SCROLLED:
55 | switch (event.getClassName().toString()) {
56 | case "android.widget.ListView":
57 | handleInSecurityPage();
58 | handleScrollInSecurityPage();
59 | break;
60 | default:
61 | break;
62 | }
63 |
64 | break;
65 | default:
66 | break;
67 | }
68 | }
69 |
70 | /**
71 | * 是否在安全设置页面中
72 | *
73 | * @return 是否在安全设置页面中
74 | */
75 | @Override
76 | protected boolean isInSecurityPage() {
77 | return isNodeExistInRootActiveWindowByViewIds(
78 | "android:id/action_bar_title",
79 | "android:id/prefs_frame",
80 | "android:id/prefs",
81 | "android:id/list",
82 | "android:id/title"
83 | );
84 | }
85 |
86 | /**
87 | * 处理界面在安全设置页面中的逻辑
88 | */
89 | @Override
90 | protected void runLogicInSecurityPage() {
91 | // 找到ListView
92 | List listViewNodeInfos = getNodeByViewIdFromRootInActiveWindow("android:id/list");
93 | if (listViewNodeInfos == null || listViewNodeInfos.isEmpty()) {
94 | return;
95 | }
96 | AccessibilityNodeInfo listViewNodeInfo = listViewNodeInfos.get(0);
97 | if (listViewNodeInfo == null) {
98 | return;
99 | }
100 |
101 | for (SecurityTarget target : getTargets()) {
102 | if (!target.isValid()) {
103 | continue;
104 | }
105 |
106 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) ||
107 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
108 |
109 | // 遍历ListView的item, 找到"未知来源" item
110 | for (int i = 0; i < listViewNodeInfo.getChildCount(); i++) {
111 | DLog.i("=============查找第%d个item=============", i);
112 | // 找到item
113 | AccessibilityNodeInfo itemInfo = listViewNodeInfo.getChild(i);
114 | if (itemInfo == null) {
115 | continue;
116 | }
117 |
118 | // 找到item中的标题
119 | List titleInfos = getNodeByViewIdFromNode(itemInfo, "android:id/title");
120 | if (titleInfos == null || titleInfos.isEmpty()) {
121 | continue;
122 | }
123 | AccessibilityNodeInfo titleInfo = titleInfos.get(0);
124 | if (titleInfo == null) {
125 | continue;
126 | }
127 | String title = titleInfo.getText().toString();
128 |
129 | // 找到item中的checkbox
130 | List checkBoxInfos = getNodeByViewIdFromNode(itemInfo, "android:id/checkbox");
131 | if (checkBoxInfos == null || checkBoxInfos.isEmpty()) {
132 | continue;
133 | }
134 | AccessibilityNodeInfo checkBoxInfo = checkBoxInfos.get(0);
135 | if (checkBoxInfo == null) {
136 | continue;
137 | }
138 |
139 | String unknownSourceStr = getAccessibilityService().getResources()
140 | .getString(R.string
141 | .accessibility_dispatcher_settings_security_unknown_source);
142 | if (unknownSourceStr.equals(title)) {
143 | // 找到了就判断
144 | // 1. 如果需要开启,但是还没有开启就点击
145 | // 2. 或者需要关闭,但是还没有关闭就点击
146 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0 &&
147 | !checkBoxInfo.isChecked()) ||
148 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0 &&
149 | checkBoxInfo.isChecked())) {
150 | // CheckboxNode不一定能点击,但是item一般都设置能点击,所以用item的node来点击
151 | if (itemInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
152 | callBackOnUnknownSourceItemClick();
153 | }
154 | } else {
155 | goBack();
156 | }
157 | }
158 | }
159 | }
160 | }
161 |
162 | }
163 |
164 | /**
165 | * 目标可能在下面,所以需要先滑动listview
166 | */
167 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
168 | @Override
169 | protected void scrollInSecurityPage() {
170 | // 找到ListView
171 | List listViewNodeInfos = getNodeByViewIdFromRootInActiveWindow("android:id/list");
172 | if (listViewNodeInfos == null || listViewNodeInfos.isEmpty()) {
173 | return;
174 | }
175 | AccessibilityNodeInfo listViewNodeInfo = listViewNodeInfos.get(0);
176 | if (listViewNodeInfo == null) {
177 | return;
178 | }
179 | listViewNodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
180 | }
181 |
182 | /**
183 | * 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
184 | *
185 | * @return 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
186 | */
187 | @Override
188 | protected boolean isInUnknownSourcesTurnOnConfirmDialog() {
189 | return isNodeExistInRootActiveWindowByViewIds(
190 | "android:id/content",
191 | "android:id/parentPanel",
192 | "android:id/contentPanel",
193 | "android:id/message",
194 | "android:id/buttonPanel",
195 | "android:id/button2",
196 | "android:id/button1"
197 | );
198 | }
199 |
200 | /**
201 | * 处理界面在点击允许安装未知来源的开关之后弹出来的确认对话框页面的逻辑
202 | */
203 | @Override
204 | protected void runLogicInUnknownSourcesTurnOnConfirmDialog() {
205 | for (SecurityTarget target : getTargets()) {
206 | if (!target.isValid()) {
207 | continue;
208 | }
209 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) ||
210 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
211 | if (performClickByViewIdFromRootActiveWindow("android:id/button1")) {
212 | callBackOnUnknownSourceDialogConfirm();
213 | }
214 | }
215 | }
216 | }
217 | }
218 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/security/unknownsources/android/AndroidUnknownSourcesASHandler510.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.unknownsources.android;
2 |
3 | import android.view.accessibility.AccessibilityEvent;
4 | import android.view.accessibility.AccessibilityNodeInfo;
5 |
6 | import java.util.List;
7 |
8 | import io.github.zhitaocai.accessibilitydispatcher.R;
9 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.AbsSecuritySettingsASHandler;
10 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.SecurityTarget;
11 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
12 |
13 | /**
14 | * 支持原生Android 5.1.0系统
15 | *
16 | * @author zhitao
17 | * @since 2017-03-30 15:23
18 | */
19 | public class AndroidUnknownSourcesASHandler510 extends AbsSecuritySettingsASHandler {
20 |
21 | @Override
22 | protected void onServiceConnected() {
23 |
24 | }
25 |
26 | @Override
27 | protected void onInterrupt() {
28 |
29 | }
30 |
31 | @Override
32 | public void onAccessibilityEvent(AccessibilityEvent event) {
33 | switch (event.getEventType()) {
34 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
35 | handleInSecurityPage();
36 | break;
37 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
38 | switch (event.getClassName().toString()) {
39 | // 通过Intent方式进入安全设置界面
40 | case "com.android.settings.Settings$SecuritySettingsActivity":
41 | handleInSecurityPage();
42 | handleScrollInSecurityPage();
43 | break;
44 | // 二次确认对话框
45 | case "android.app.AlertDialog":
46 | handleInUnknownSourcesTurnOnConfirmDialog();
47 | break;
48 | default:
49 | break;
50 | }
51 | break;
52 | case AccessibilityEvent.TYPE_VIEW_SCROLLED:
53 | switch (event.getClassName().toString()) {
54 | case "android.widget.ListView":
55 | handleInSecurityPage();
56 | handleScrollInSecurityPage();
57 | break;
58 | default:
59 | break;
60 | }
61 |
62 | break;
63 | default:
64 | break;
65 | }
66 | }
67 |
68 | /**
69 | * 是否在安全设置页面中
70 | *
71 | * @return 是否在安全设置页面中
72 | */
73 | @Override
74 | protected boolean isInSecurityPage() {
75 | return isNodeExistInRootActiveWindowByViewIds(
76 | "android:id/action_bar",
77 | "com.android.settings:id/main_content",
78 | "com.android.settings:id/container_material",
79 | "android:id/list",
80 | "android:id/title"
81 | );
82 | }
83 |
84 | /**
85 | * 处理界面在安全设置页面中的逻辑
86 | */
87 | @Override
88 | protected void runLogicInSecurityPage() {
89 | // 找到ListView
90 | List listViewNodeInfos = getNodeByViewIdFromRootInActiveWindow("android:id/list");
91 | if (listViewNodeInfos == null || listViewNodeInfos.isEmpty()) {
92 | return;
93 | }
94 | AccessibilityNodeInfo listViewNodeInfo = listViewNodeInfos.get(0);
95 | if (listViewNodeInfo == null) {
96 | return;
97 | }
98 |
99 | for (SecurityTarget target : getTargets()) {
100 | if (!target.isValid()) {
101 | continue;
102 | }
103 |
104 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) ||
105 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
106 |
107 | // 遍历ListView的item, 找到"未知来源" item
108 | for (int i = 0; i < listViewNodeInfo.getChildCount(); i++) {
109 | DLog.i("=============查找第%d个item=============", i);
110 | // 找到item
111 | AccessibilityNodeInfo itemInfo = listViewNodeInfo.getChild(i);
112 | if (itemInfo == null) {
113 | continue;
114 | }
115 |
116 | // 找到item中的标题
117 | List titleInfos = getNodeByViewIdFromNode(itemInfo, "android:id/title");
118 | if (titleInfos == null || titleInfos.isEmpty()) {
119 | continue;
120 | }
121 | AccessibilityNodeInfo titleInfo = titleInfos.get(0);
122 | if (titleInfo == null) {
123 | continue;
124 | }
125 | String title = titleInfo.getText().toString();
126 | DLog.i("title: %s", title);
127 |
128 | // 找到item中的switchWidget
129 | List switchInfos = getNodeByViewIdFromNode(itemInfo, "android:id/switchWidget");
130 | if (switchInfos == null || switchInfos.isEmpty()) {
131 | continue;
132 | }
133 | AccessibilityNodeInfo switchInfo = switchInfos.get(0);
134 | if (switchInfo == null) {
135 | continue;
136 | }
137 |
138 | String unknownSourceStr = getAccessibilityService().getResources()
139 | .getString(R.string
140 | .accessibility_dispatcher_settings_security_unknown_source);
141 | if (unknownSourceStr.equals(title)) {
142 | // 找到了就判断
143 | // 1. 如果需要开启,但是还没有开启就点击
144 | // 2. 或者需要关闭,但是还没有关闭就点击
145 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0 &&
146 | !switchInfo.isChecked()) ||
147 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0 &&
148 | switchInfo.isChecked())) {
149 | // CheckboxNode不一定能点击,但是item一般都设置能点击,所以用item的node来点击
150 | if (itemInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
151 | callBackOnUnknownSourceItemClick();
152 | }
153 | } else {
154 | goBack();
155 | }
156 | }
157 | }
158 | }
159 | }
160 | }
161 |
162 | /**
163 | * 目标可能在下面,所以需要先滑动listview
164 | */
165 | @Override
166 | protected void scrollInSecurityPage() {
167 | // 找到ListView
168 | List listViewNodeInfos = getNodeByViewIdFromRootInActiveWindow("android:id/list");
169 | if (listViewNodeInfos == null || listViewNodeInfos.isEmpty()) {
170 | return;
171 | }
172 | AccessibilityNodeInfo listViewNodeInfo = listViewNodeInfos.get(0);
173 | if (listViewNodeInfo == null) {
174 | return;
175 | }
176 | listViewNodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
177 | }
178 |
179 | /**
180 | * 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
181 | *
182 | * @return 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
183 | */
184 | @Override
185 | protected boolean isInUnknownSourcesTurnOnConfirmDialog() {
186 | return isNodeExistInRootActiveWindowByViewIds(
187 | "android:id/content",
188 | "android:id/parentPanel",
189 | "android:id/contentPanel",
190 | "android:id/message",
191 | "android:id/buttonPanel",
192 | "android:id/button2",
193 | "android:id/button1"
194 | );
195 | }
196 |
197 | /**
198 | * 处理界面在点击允许安装未知来源的开关之后弹出来的确认对话框页面的逻辑
199 | */
200 | @Override
201 | protected void runLogicInUnknownSourcesTurnOnConfirmDialog() {
202 | for (SecurityTarget target : getTargets()) {
203 | if (!target.isValid()) {
204 | continue;
205 | }
206 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) ||
207 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
208 | if (performClickByViewIdFromRootActiveWindow("android:id/button1")) {
209 | callBackOnUnknownSourceDialogConfirm();
210 | }
211 | }
212 | }
213 | }
214 | }
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/security/unknownsources/android/AndroidUnknownSourcesASHandler700.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.unknownsources.android;
2 |
3 | import android.view.accessibility.AccessibilityEvent;
4 | import android.view.accessibility.AccessibilityNodeInfo;
5 |
6 | import java.util.List;
7 |
8 | import io.github.zhitaocai.accessibilitydispatcher.R;
9 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.AbsSecuritySettingsASHandler;
10 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.SecurityTarget;
11 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
12 |
13 | /**
14 | * 支持原生Android 7.0.0系统
15 | *
16 | * @author zhitao
17 | * @since 2017-03-30 15:23
18 | */
19 | public class AndroidUnknownSourcesASHandler700 extends AbsSecuritySettingsASHandler {
20 |
21 | @Override
22 | protected void onServiceConnected() {
23 |
24 | }
25 |
26 | @Override
27 | protected void onInterrupt() {
28 |
29 | }
30 |
31 | @Override
32 | public void onAccessibilityEvent(AccessibilityEvent event) {
33 | switch (event.getEventType()) {
34 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
35 | handleInSecurityPage();
36 | break;
37 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
38 | switch (event.getClassName().toString()) {
39 | // 通过设置界面进入
40 | case "com.android.settings.SubSettings":
41 |
42 | // 通过Intent方式进入安全设置界面
43 | case "com.android.settings.Settings$SecuritySettingsActivity":
44 | handleInSecurityPage();
45 | handleScrollInSecurityPage();
46 | break;
47 | // 二次确认对话框
48 | case "android.app.AlertDialog":
49 | handleInUnknownSourcesTurnOnConfirmDialog();
50 | break;
51 | default:
52 | break;
53 | }
54 | break;
55 | case AccessibilityEvent.TYPE_VIEW_SCROLLED:
56 | switch (event.getClassName().toString()) {
57 | case "android.support.v7.widget.RecyclerView":
58 | handleInSecurityPage();
59 | handleScrollInSecurityPage();
60 | break;
61 | default:
62 | break;
63 | }
64 |
65 | break;
66 | default:
67 | break;
68 | }
69 | }
70 |
71 | /**
72 | * 是否在安全设置页面中
73 | *
74 | * @return 是否在安全设置页面中
75 | */
76 | @Override
77 | protected boolean isInSecurityPage() {
78 | return isNodeExistInRootActiveWindowByViewIds(
79 | "com.android.settings:id/action_bar",
80 | "com.android.settings:id/main_content",
81 | "com.android.settings:id/container_material",
82 | "com.android.settings:id/list",
83 | "android:id/title"
84 | );
85 | }
86 |
87 | /**
88 | * 处理界面在安全设置页面中的逻辑
89 | */
90 | @Override
91 | protected void runLogicInSecurityPage() {
92 |
93 | // 找到ListView
94 | List listViewNodeInfos = getNodeByViewIdFromRootInActiveWindow("com.android.settings:id/list");
95 | if (listViewNodeInfos == null || listViewNodeInfos.isEmpty()) {
96 | return;
97 | }
98 | AccessibilityNodeInfo listViewNodeInfo = listViewNodeInfos.get(0);
99 | if (listViewNodeInfo == null) {
100 | return;
101 | }
102 |
103 | for (SecurityTarget target : getTargets()) {
104 | if (!target.isValid()) {
105 | continue;
106 | }
107 |
108 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) ||
109 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
110 |
111 | // 遍历ListView的item, 找到"未知来源" item
112 | for (int i = 0; i < listViewNodeInfo.getChildCount(); i++) {
113 | DLog.i("=============查找第%d个item=============", i);
114 | // 找到item
115 | AccessibilityNodeInfo itemInfo = listViewNodeInfo.getChild(i);
116 | if (itemInfo == null) {
117 | continue;
118 | }
119 |
120 | // 找到item中的标题
121 | List titleInfos = getNodeByViewIdFromNode(itemInfo, "android:id/title");
122 | if (titleInfos == null || titleInfos.isEmpty()) {
123 | continue;
124 | }
125 | AccessibilityNodeInfo titleInfo = titleInfos.get(0);
126 | if (titleInfo == null) {
127 | continue;
128 | }
129 | String title = titleInfo.getText().toString();
130 | DLog.i("title: %s", title);
131 |
132 | // 找到item中的switchWidget
133 | List switchInfos = getNodeByViewIdFromNode(itemInfo, "android:id/switch_widget");
134 | if (switchInfos == null || switchInfos.isEmpty()) {
135 | continue;
136 | }
137 | AccessibilityNodeInfo switchInfo = switchInfos.get(0);
138 | if (switchInfo == null) {
139 | continue;
140 | }
141 | String unknownSourceStr = getAccessibilityService().getResources()
142 | .getString(R.string
143 | .accessibility_dispatcher_settings_security_unknown_source);
144 | if (unknownSourceStr.equals(title)) {
145 | // 找到了就判断
146 | // 1. 如果需要开启,但是还没有开启就点击
147 | // 2. 或者需要关闭,但是还没有关闭就点击
148 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0 &&
149 | !switchInfo.isChecked()) ||
150 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0 &&
151 | switchInfo.isChecked())) {
152 | // CheckboxNode不一定能点击,但是item一般都设置能点击,所以用item的node来点击
153 | if (itemInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
154 | callBackOnUnknownSourceItemClick();
155 | }
156 | } else {
157 | goBack();
158 | }
159 | }
160 | }
161 | }
162 | }
163 | }
164 |
165 | /**
166 | * 目标可能在下面,所以需要先滑动listview
167 | */
168 | @Override
169 | protected void scrollInSecurityPage() {
170 | // 找到ListView
171 | List listViewNodeInfos = getNodeByViewIdFromRootInActiveWindow("com.android.settings:id/list");
172 | if (listViewNodeInfos == null || listViewNodeInfos.isEmpty()) {
173 | return;
174 | }
175 | AccessibilityNodeInfo listViewNodeInfo = listViewNodeInfos.get(0);
176 | if (listViewNodeInfo == null) {
177 | return;
178 | }
179 | listViewNodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
180 | }
181 |
182 | /**
183 | * 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
184 | *
185 | * @return 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
186 | */
187 | @Override
188 | protected boolean isInUnknownSourcesTurnOnConfirmDialog() {
189 | return isNodeExistInRootActiveWindowByViewIds(
190 | "android:id/content",
191 | "android:id/parentPanel",
192 | "android:id/contentPanel",
193 | "android:id/message",
194 | "android:id/buttonPanel",
195 | "android:id/button2",
196 | "android:id/button1"
197 | );
198 | }
199 |
200 | /**
201 | * 处理界面在点击允许安装未知来源的开关之后弹出来的确认对话框页面的逻辑
202 | */
203 | @Override
204 | protected void runLogicInUnknownSourcesTurnOnConfirmDialog() {
205 | for (SecurityTarget target : getTargets()) {
206 | if (!target.isValid()) {
207 | continue;
208 | }
209 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) ||
210 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
211 | if (performClickByViewIdFromRootActiveWindow("android:id/button1")) {
212 | callBackOnUnknownSourceDialogConfirm();
213 | }
214 | }
215 | }
216 | }
217 | }
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/security/unknownsources/fuzzy/UnknownSourcesFuzzyASHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.unknownsources.fuzzy;
2 |
3 | import android.os.Build;
4 | import android.support.annotation.RequiresApi;
5 | import android.view.accessibility.AccessibilityEvent;
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 | import android.widget.CheckBox;
8 | import android.widget.ListView;
9 | import android.widget.Switch;
10 |
11 | import java.util.List;
12 |
13 | import io.github.zhitaocai.accessibilitydispatcher.R;
14 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.AbsSecuritySettingsASHandler;
15 | import io.github.zhitaocai.accessibilitydispatcher.businss.security.SecurityTarget;
16 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
17 |
18 | /**
19 | * 根据文字定位的自动处理
20 | *
21 | * @author zhitao
22 | * @since 2017-03-30 15:25
23 | */
24 | @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
25 | public class UnknownSourcesFuzzyASHandler extends AbsSecuritySettingsASHandler {
26 |
27 | /**
28 | * 标记当前是否已经点击了Checkbox
29 | *
30 | * 因为这是根据文字定位,所以事件分发并没有根据具体的类来处理,见 {@link #onAccessibilityEvent(AccessibilityEvent)}
31 | * 因此,同一个类型的事件,会连续执行几个逻辑
32 | *
33 | * 在这里,因为我们点击checkbox之后,应该是有个对话框弹出来的,但是如果我们不做处理,那么对话框在完全弹出来之前,可能又会在调用一次 {@link #handleInSecurityPage()}
34 | * 而这个时候的调用就会触发到返回,导致设置失败
35 | *
36 | * 基于以上分析,这里就列一个状态值来记录是否已经点击了checkbox
37 | */
38 | private boolean mIsClickCheckBox = false;
39 |
40 | /**
41 | * 标记当前是否已经点击了Checkbox
42 | *
43 | * 因为这是根据文字定位,所以事件分发并没有根据具体的类来处理,见 {@link #onAccessibilityEvent(AccessibilityEvent)}
44 | * 因此,同一个类型的事件,会连续执行几个逻辑
45 | *
46 | * 在这里,因为我们点击checkbox之后,应该是有个对话框弹出来的,但是如果我们不做处理,那么对话框在完全弹出来之前,可能又会在调用一次 {@link #handleInSecurityPage()}
47 | * 而这个时候的调用就会触发到返回,导致设置失败
48 | *
49 | * 基于以上分析,这里就列一个状态值来记录是否已经点击了对话框中的确认按钮
50 | */
51 | private boolean mIsClickConfirm = false;
52 |
53 | @Override
54 | public void onServiceConnected() {
55 |
56 | }
57 |
58 | @Override
59 | public void onInterrupt() {
60 |
61 | }
62 |
63 | @Override
64 | public void onAccessibilityEvent(AccessibilityEvent event) {
65 | switch (event.getEventType()) {
66 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
67 | handleInSecurityPage();
68 | break;
69 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
70 | handleInSecurityPage();
71 | handleInUnknownSourcesTurnOnConfirmDialog();
72 | handleScrollInSecurityPage();
73 | break;
74 | case AccessibilityEvent.TYPE_VIEW_SCROLLED:
75 | handleInSecurityPage();
76 | handleScrollInSecurityPage();
77 | break;
78 | default:
79 | break;
80 | }
81 | }
82 |
83 | /**
84 | * 是否在安全设置页面中
85 | *
86 | * @return 是否在安全设置页面中
87 | */
88 | @Override
89 | protected boolean isInSecurityPage() {
90 | // 因为是模糊搜索,所以这里直接返回true,不进行页面精确判断
91 | return true;
92 | }
93 |
94 | /**
95 | * 处理界面在安全设置页面中的逻辑
96 | */
97 | @Override
98 | protected void runLogicInSecurityPage() {
99 |
100 | for (SecurityTarget target : getTargets()) {
101 | if (!target.isValid()) {
102 | continue;
103 | }
104 |
105 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) ||
106 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
107 |
108 | // 如果已经点击了checkbox 但是还没有点击到对话框中的确认按钮,那么就暂时不处理,等点击了才进行
109 | if ((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0) {
110 | if (mIsClickCheckBox && !mIsClickConfirm) {
111 | continue;
112 | }
113 | }
114 |
115 | // 根据文字模糊搜索 未知来源,找到item的话就点击
116 | String unknownSourceStr = getAccessibilityService().getResources()
117 | .getString(R.string
118 | .accessibility_dispatcher_settings_security_unknown_source);
119 | List unknownSourceNodes = getNodeByTextFromRootInActiveWindow(unknownSourceStr);
120 | if (unknownSourceNodes == null || unknownSourceNodes.isEmpty()) {
121 | return;
122 | }
123 | for (AccessibilityNodeInfo unknownSourceNode : unknownSourceNodes) {
124 | if (unknownSourceNode == null || unknownSourceNode.getText() == null) {
125 | continue;
126 | }
127 | DLog.i("* text: %s", unknownSourceNode.getText().toString());
128 | if (!unknownSourceNode.getText().toString().equals(unknownSourceStr)) {
129 | continue;
130 | }
131 | DLog.i("找到允许安装位置来源的View:%s", unknownSourceNode.getText().toString());
132 |
133 | AccessibilityNodeInfo itemNode = unknownSourceNode.getParent();
134 | AccessibilityNodeInfo checkboxNode = null;
135 | // 定义20次,防止死循环,应该没有布局找了20次之后还没有尽的吧 - -!
136 | int count = 0;
137 | while (itemNode != null && count++ < 20) {
138 | checkboxNode = getNodeByClass(itemNode, CheckBox.class, Switch.class);
139 | if (checkboxNode == null) {
140 | itemNode = itemNode.getParent();
141 | continue;
142 | }
143 | break;
144 | }
145 | if (checkboxNode == null) {
146 | break;
147 | }
148 | DLog.i("当前Checkbox是否已经选中: %b", checkboxNode.isChecked());
149 |
150 | // 找到了就判断
151 | // 1. 如果需要开启,但是还没有开启就点击
152 | // 2. 或者需要关闭,但是还没有关闭就点击
153 | if (((target.getAction() & SecurityTarget.ACTION_TURN_ON_UNKNOWNSOURCES) != 0 && !checkboxNode.isChecked()
154 | ) ||
155 | ((target.getAction() & SecurityTarget.ACTION_TURN_OFF_UNKNOWNSOURCES) != 0 && checkboxNode.isChecked()
156 | )) {
157 | // CheckboxNode不一定能点击,但是item一般都设置能点击,所以用item的node来点击
158 | if (itemNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
159 | mIsClickCheckBox = true;
160 | callBackOnUnknownSourceItemClick();
161 | }
162 | } else {
163 | goBack();
164 | }
165 | break;
166 | }
167 | return;
168 | }
169 | }
170 | }
171 |
172 | /**
173 | * 目标可能在下面,所以需要先滑动listview
174 | */
175 | @Override
176 | protected void scrollInSecurityPage() {
177 | // 因为不同系统版本有的用ListView或者用RecyclerView,所以我们一次找两个
178 | // 找到ListView 或者 RecyclerView 然后滑动他
179 | AccessibilityNodeInfo listNodeInfo =
180 | getNodeByClassName(ListView.class.getName(), "android.support.v7.widget.RecyclerView");
181 | if (listNodeInfo == null) {
182 | return;
183 | }
184 | listNodeInfo.performAction(AccessibilityNodeInfo.ACTION_SCROLL_FORWARD);
185 | }
186 |
187 | /**
188 | * 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
189 | *
190 | * @return 是否在点击允许安装未知来源的开关之后弹出来的确认对话框
191 | */
192 | @Override
193 | protected boolean isInUnknownSourcesTurnOnConfirmDialog() {
194 | // 因为是模糊搜索,所以这里直接返回true,不进行页面精确判断
195 | return true;
196 | }
197 |
198 | /**
199 | * 处理界面在点击允许安装未知来源的开关之后弹出来的确认对话框页面的逻辑
200 | */
201 | @Override
202 | protected void runLogicInUnknownSourcesTurnOnConfirmDialog() {
203 |
204 | // 根据文字模糊搜索 确定,找到item的话就点击
205 | String confirmStr = getAccessibilityService().getResources()
206 | .getString(R.string
207 | .accessibility_dispatcher_settings_security_unknown_source_confirm);
208 | List confirmNodes = getNodeByTextFromRootInActiveWindow(confirmStr);
209 | if (confirmNodes == null || confirmNodes.isEmpty()) {
210 | return;
211 | }
212 | for (AccessibilityNodeInfo confirmNode : confirmNodes) {
213 | if (confirmNode == null || confirmNode.getText() == null) {
214 | continue;
215 | }
216 | DLog.i("* text: %s", confirmNode.getText().toString());
217 | if (!confirmNode.getText().toString().equals(confirmStr)) {
218 | continue;
219 | }
220 | DLog.i("找到对话框 确定 的View:%s", confirmNode.getText().toString());
221 |
222 | // 根据文字定位到node之后还不能直接点击,因为有些系统是不能点击的,这里要做个循环,如果当前node不能点击就找到能点击的父node
223 | AccessibilityNodeInfo clickNode = confirmNode;
224 | // 定义20次,防止死循环,应该没有布局找了20次之后还没有尽的吧 - -!
225 | int count = 0;
226 | boolean isClickable = clickNode.isClickable();
227 | while (!isClickable && count++ < 20) {
228 | clickNode = clickNode.getParent();
229 | if (clickNode == null) {
230 | break;
231 | }
232 | isClickable = clickNode.isClickable();
233 | }
234 |
235 | if (clickNode != null && isClickable) {
236 | if (clickNode.performAction(AccessibilityNodeInfo.ACTION_CLICK)) {
237 | mIsClickConfirm = true;
238 | callBackOnUnknownSourceDialogConfirm();
239 | }
240 | }
241 | break;
242 | }
243 | }
244 | }
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/vpn/AbsVpnSettingsASHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.vpn;
2 |
3 | import io.github.zhitaocai.accessibilitydispatcher.AbsASHandler;
4 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.AndroidSettingsCompat;
5 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.OnVpnCallBack;
6 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.VpnTarget;
7 |
8 | /**
9 | * @author zhitao
10 | * @since 2017-03-23 10:59
11 | */
12 | public abstract class AbsVpnSettingsASHandler extends AbsASHandler {
13 |
14 | /**
15 | * 具体实现类的辅助功能所针对的应用包名
16 | *
17 | * e.g.
18 | *
19 | * 如果需要改动系统设置,那么这里的包名可能为 com.android.settings 或者 其他第三方系统所对应的包名
20 | *
21 | * @return 具体实现类的辅助功能所针对的应用包名
22 | *
23 | * @see #isUsingPkgName2TrackEvent()
24 | */
25 | @Override
26 | protected String getSupportPkgName() {
27 | return AndroidSettingsCompat.ANDROID_PKGNAME;
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/androidsettings/vpn/android/AbsAndroidVpnSettingsASHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.vpn.android;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashMap;
5 | import java.util.List;
6 |
7 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.vpn.AbsVpnSettingsASHandler;
8 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.OnVpnCallBack;
9 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.VpnTarget;
10 |
11 | /**
12 | * 原生系统4.4.4配置VPN大概就差不多4个页面
13 | *
14 | * @author zhitao
15 | * @since 2017-03-23 17:38
16 | */
17 | abstract class AbsAndroidVpnSettingsASHandler extends AbsVpnSettingsASHandler {
18 |
19 | /**
20 | * 是否在VPN列表页
21 | *
22 | * @return true or false
23 | */
24 | protected abstract boolean isInVpnListPage();
25 |
26 | /**
27 | * 执行在VPN列表页的逻辑
28 | */
29 | protected abstract void runLogicInVpnListPage();
30 |
31 | /**
32 | * 是否在VPN配置的对话框中
33 | *
34 | * @return true or false
35 | */
36 | protected abstract boolean isInVpnConfigDialog();
37 |
38 | /**
39 | * 执行在VPN配置的对话框中的逻辑
40 | */
41 | protected abstract void runLogicInVpnConfigDialog();
42 |
43 | /**
44 | * 是否在VPN配置的对话框中选择VPN类型的Spinner中
45 | *
46 | * @return true or false
47 | */
48 | protected abstract boolean isInVpnConfigDialogSpinnerWindow();
49 |
50 | /**
51 | * 执行在VPN配置的对话框中选择VPN类型的Spinner中的逻辑
52 | */
53 | protected abstract void runLogicInVpnConfigDialogSpinnerWindow();
54 |
55 | /**
56 | * 是否在用户配置的对话框中
57 | *
58 | * @return true or false
59 | */
60 | protected abstract boolean isInUserConfigDialog();
61 |
62 | /**
63 | * 执行在用户配置的对话框中的逻辑
64 | */
65 | protected abstract void runLogicInUserConfigDialog();
66 |
67 | /**
68 | * 是否在VPN列表页,长按了某个VPN配置之后,弹出来的对话框中
69 | *
70 | * @return true or false
71 | */
72 | protected abstract boolean isInVpnProfileEditDialog();
73 |
74 | /**
75 | * 执行在VPN列表页,长按了某个VPN配置之后,弹出来的对话框中的逻辑
76 | */
77 | protected abstract void runLogicInVpnProfileEditDialog();
78 |
79 | /**
80 | * 滑动列表页的VPN的ListView
81 | */
82 | protected abstract void scrollListViewInVpnListPage();
83 |
84 | /**
85 | * 滑动VPN列表页的ListView,寻找是否已经存在指定的VPN
86 | */
87 | protected void handleScrollListPage() {
88 | if (isInVpnListPage()) {
89 | scrollListViewInVpnListPage();
90 | }
91 | }
92 |
93 | /**
94 | * 处理在VPN列表页时的逻辑
95 | */
96 | protected void handleInVpnListPage() {
97 | if (isInVpnListPage()) {
98 | runLogicInVpnListPage();
99 | }
100 | }
101 |
102 | /**
103 | * 处理在VPN配置对话框的逻辑
104 | */
105 | protected void handleInVpnConfigDialog() {
106 | if (isInVpnConfigDialog()) {
107 | runLogicInVpnConfigDialog();
108 | }
109 | }
110 |
111 | /**
112 | * 处理在VPN配置的对话框中选择VPN类型的Spinner中的逻辑
113 | */
114 | protected void handleInVpnConfigDialogSpinnerWindow() {
115 | if (isInVpnConfigDialogSpinnerWindow()) {
116 | runLogicInVpnConfigDialogSpinnerWindow();
117 | }
118 | }
119 |
120 | /**
121 | * 处理在用户配置对话框的逻辑
122 | */
123 | protected void handleInUserConfigDialog() {
124 | if (isInUserConfigDialog()) {
125 | runLogicInUserConfigDialog();
126 | }
127 | }
128 |
129 | /**
130 | * 处理在VPN列表页,长按了某个VPN配置之后,弹出来的对话框中的逻辑
131 | */
132 | protected void handleInVpnProfileEditDialog() {
133 | if (isInVpnProfileEditDialog()) {
134 | runLogicInVpnProfileEditDialog();
135 | }
136 | }
137 |
138 | protected void callBackEnterVpnConfigDialog(final VpnTarget vpnTarget) {
139 | List list = getCallBacks();
140 | if (list == null || list.isEmpty()) {
141 | return;
142 | }
143 | for (final OnVpnCallBack callBack : list) {
144 | getUIHandler().post(new Runnable() {
145 | @Override
146 | public void run() {
147 | callBack.onVpnConfigStart(vpnTarget);
148 | }
149 | });
150 | }
151 | }
152 |
153 | protected void callBackExitVpnConfigDialog(final VpnTarget vpnTarget) {
154 | List list = getCallBacks();
155 | if (list == null || list.isEmpty()) {
156 | return;
157 | }
158 | for (final OnVpnCallBack callBack : list) {
159 | getUIHandler().post(new Runnable() {
160 | @Override
161 | public void run() {
162 | callBack.onVpnConfigFinish(vpnTarget);
163 | }
164 | });
165 | }
166 | }
167 |
168 | protected void callBackEnterUserConfigDialog(final VpnTarget vpnTarget) {
169 | List list = getCallBacks();
170 | if (list == null || list.isEmpty()) {
171 | return;
172 | }
173 | for (final OnVpnCallBack callBack : list) {
174 | getUIHandler().post(new Runnable() {
175 | @Override
176 | public void run() {
177 | callBack.onUserConfigStart(vpnTarget);
178 | }
179 | });
180 | }
181 | }
182 |
183 | protected void callBackExitUserConfigDialog(final VpnTarget vpnTarget) {
184 | List list = getCallBacks();
185 | if (list == null || list.isEmpty()) {
186 | return;
187 | }
188 | for (final OnVpnCallBack callBack : list) {
189 | getUIHandler().post(new Runnable() {
190 | @Override
191 | public void run() {
192 | callBack.onUserConfigFinish(vpnTarget);
193 | }
194 | });
195 | }
196 | }
197 |
198 | /**
199 | * 记录目标要配置的VPN信息当前已经完成到什么步骤了
200 | */
201 | private HashMap mVpnTargetCacheHashMap;
202 |
203 | /**
204 | * 点击了创建VPN的按钮
205 | */
206 | protected final static int STEP_VPN_CONFIG_START_2_CREATE_OR_UPDATE = 1;
207 |
208 | /**
209 | * 点击了选择VPN类型
210 | */
211 | protected final static int STEP_VPN_CONFIG_HAS_CHOOSE_VPN_TYPE = 2;
212 |
213 | /**
214 | * 在VPN配置窗口中已经完成了文字方面的信息输入
215 | */
216 | protected final static int STEP_VPN_CONFIG_FINISH_INPUT = 4;
217 |
218 | /**
219 | * 点击了目标VPN进行输入用户配置信息
220 | */
221 | protected final static int STEP_USER_CONFIG_START_INPUT = 8;
222 |
223 | /**
224 | * 在用户信息配置窗口中已经完成了文字方面的信息输入
225 | */
226 | protected final static int STEP_USER_CONFIG_FINISH_INPUT = 16;
227 |
228 | @Override
229 | protected void setTargets(ArrayList targets) {
230 | super.setTargets(targets);
231 | if (targets == null) {
232 | return;
233 | }
234 | mVpnTargetCacheHashMap = new HashMap<>();
235 | for (VpnTarget vpnTarget : targets) {
236 | if (vpnTarget == null || !vpnTarget.isValid()) {
237 | continue;
238 | }
239 | mVpnTargetCacheHashMap.put(vpnTarget, 0);
240 | }
241 | }
242 |
243 | /**
244 | * 获取缓存VPN目标信息当前进行到什么步骤的Hashmap
245 | *
246 | * @return 缓存的目标的步骤
247 | */
248 | protected HashMap getVpnTargetCacheHashMap() {
249 | return mVpnTargetCacheHashMap;
250 | }
251 | }
252 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/AbsApkInstallHandler.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall;
2 |
3 | import java.util.List;
4 |
5 | import io.github.zhitaocai.accessibilitydispatcher.AbsASHandler;
6 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.ApkInstallTarget;
7 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.OnApkInstallCallBack;
8 |
9 | /**
10 | * @author zhitao
11 | * @since 2017-04-01 09:58
12 | */
13 | public abstract class AbsApkInstallHandler extends AbsASHandler {
14 |
15 | /**
16 | * @return 是否在apk安装界面
17 | */
18 | protected abstract boolean isInApkInstallPage();
19 |
20 | /**
21 | * 执行在apk安装界面的逻辑
22 | */
23 | protected abstract void runLogicInApkInstallPage();
24 |
25 | /**
26 | * @return 是否在apk安装中界面
27 | */
28 | protected abstract boolean isInApkInstallingPage();
29 |
30 | /**
31 | * 执行在apk安装中界面的逻辑
32 | */
33 | protected abstract void runLogicInApkInstallingPage();
34 |
35 | /**
36 | * @return 是否在apk安装成功界面
37 | */
38 | protected abstract boolean isInApkInstallSuccessPage();
39 |
40 | /**
41 | * 执行在apk安装成功界面的逻辑
42 | */
43 | protected abstract void runLogicInApkInstallSuccessPage();
44 |
45 | /**
46 | * @return 是否在apk卸载界面
47 | */
48 | protected abstract boolean isInAppUninstallPage();
49 |
50 | /**
51 | * 执行在apk卸载界面的逻辑
52 | */
53 | protected abstract void runLogicInAppUninstallPage();
54 |
55 | protected void handleApkInstall() {
56 | if (isInApkInstallPage()) {
57 | runLogicInApkInstallPage();
58 | }
59 | }
60 |
61 | protected void handleApkInstalling() {
62 | if (isInApkInstallingPage()) {
63 | runLogicInApkInstallingPage();
64 | }
65 | }
66 |
67 | protected void handleApkInstallSuccess() {
68 | if (isInApkInstallSuccessPage()) {
69 | runLogicInApkInstallSuccessPage();
70 | }
71 | }
72 |
73 | protected void handleAppUninstall() {
74 | if (isInAppUninstallPage()) {
75 | runLogicInAppUninstallPage();
76 | }
77 | }
78 |
79 | protected void callbackApkInstallBtnClick(final ApkInstallTarget target) {
80 | List list = getCallBacks();
81 | if (list == null || list.isEmpty()) {
82 | return;
83 | }
84 | for (final OnApkInstallCallBack callBack : list) {
85 | getUIHandler().post(new Runnable() {
86 | @Override
87 | public void run() {
88 | callBack.onApkInstallBtnClick(target);
89 | }
90 | });
91 | }
92 | }
93 |
94 | protected void callbackApkInstalling(final ApkInstallTarget target) {
95 | List list = getCallBacks();
96 | if (list == null || list.isEmpty()) {
97 | return;
98 | }
99 | for (final OnApkInstallCallBack callBack : list) {
100 | getUIHandler().post(new Runnable() {
101 | @Override
102 | public void run() {
103 | callBack.onApkInstalling(target);
104 | }
105 | });
106 | }
107 | }
108 |
109 | protected void callbackApkInstallFinish(final ApkInstallTarget target) {
110 | List list = getCallBacks();
111 | if (list == null || list.isEmpty()) {
112 | return;
113 | }
114 | for (final OnApkInstallCallBack callBack : list) {
115 | getUIHandler().post(new Runnable() {
116 | @Override
117 | public void run() {
118 | callBack.onApkInstallFinish(target);
119 | }
120 | });
121 | }
122 | }
123 |
124 | protected void callbackApkInstallLaunch(final ApkInstallTarget target) {
125 | List list = getCallBacks();
126 | if (list == null || list.isEmpty()) {
127 | return;
128 | }
129 | for (final OnApkInstallCallBack callBack : list) {
130 | getUIHandler().post(new Runnable() {
131 | @Override
132 | public void run() {
133 | callBack.onApkInstallLaunch(target);
134 | }
135 | });
136 | }
137 | }
138 |
139 | protected void callbackAppUninstallConfirm(final ApkInstallTarget target) {
140 | List list = getCallBacks();
141 | if (list == null || list.isEmpty()) {
142 | return;
143 | }
144 | for (final OnApkInstallCallBack callBack : list) {
145 | getUIHandler().post(new Runnable() {
146 | @Override
147 | public void run() {
148 | callBack.onApkUninstallConfirm(target);
149 | }
150 | });
151 | }
152 | }
153 |
154 | protected void callbackAppUninstallCancel(final ApkInstallTarget target) {
155 | List list = getCallBacks();
156 | if (list == null || list.isEmpty()) {
157 | return;
158 | }
159 | for (final OnApkInstallCallBack callBack : list) {
160 | getUIHandler().post(new Runnable() {
161 | @Override
162 | public void run() {
163 | callBack.onApkUninstallCancel(target);
164 | }
165 | });
166 | }
167 | }
168 |
169 | }
170 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/android/AndroidApkInstallASHandler444.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android;
2 |
3 | import android.view.accessibility.AccessibilityEvent;
4 |
5 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.AbsApkInstallHandler;
6 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.ApkInstallTarget;
7 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
8 |
9 | /**
10 | * @author zhitao
11 | * @since 2017-04-01 10:35
12 | */
13 | public class AndroidApkInstallASHandler444 extends AbsApkInstallHandler {
14 |
15 | /**
16 | * 具体实现类的辅助功能所针对的应用包名
17 | *
18 | * e.g.
19 | *
20 | * 如果需要改动系统设置,那么这里的包名可能为 com.android.settings 或者 其他第三方系统所对应的包名
21 | *
22 | * @return 具体实现类的辅助功能所针对的应用包名
23 | *
24 | * @see #isUsingPkgName2TrackEvent()
25 | */
26 | @Override
27 | protected String getSupportPkgName() {
28 | return "com.android.packageinstaller";
29 | }
30 |
31 | @Override
32 | protected void onServiceConnected() {
33 |
34 | }
35 |
36 | @Override
37 | protected void onInterrupt() {
38 |
39 | }
40 |
41 | @Override
42 | protected void onAccessibilityEvent(AccessibilityEvent event) {
43 | switch (event.getEventType()) {
44 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
45 | handleApkInstallSuccess();
46 | break;
47 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
48 | switch (event.getClassName().toString()) {
49 | // 安装界面
50 | case "com.android.packageinstaller.PackageInstallerActivity":
51 | handleApkInstall();
52 | break;
53 | // 安装中界面,同时也是安装成功的界面,但是安装成功的界面不是这个事件触发的
54 | case "com.android.packageinstaller.InstallAppProgress":
55 | handleApkInstalling();
56 | break;
57 | // 卸载界面
58 | case "com.android.packageinstaller.UninstallerActivity":
59 | handleAppUninstall();
60 | break;
61 | default:
62 | break;
63 | }
64 | break;
65 | case AccessibilityEvent.TYPE_VIEW_SCROLLED:
66 | switch (event.getClassName().toString()) {
67 | case "android.widget.ScrollView":
68 | // 安装界面点击下一步之后会触发这里,要不断点击,直到开始安装
69 | handleApkInstall();
70 | break;
71 | default:
72 | break;
73 | }
74 | break;
75 | default:
76 | break;
77 | }
78 | }
79 |
80 | /**
81 | * @return 是否在apk安装界面
82 | */
83 | @Override
84 | protected boolean isInApkInstallPage() {
85 | return isNodeExistInRootActiveWindowByViewIds(
86 | "com.android.packageinstaller:id/app_icon",
87 | "com.android.packageinstaller:id/app_name",
88 | "com.android.packageinstaller:id/install_confirm_panel",
89 | "com.android.packageinstaller:id/cancel_button",
90 | "com.android.packageinstaller:id/ok_button"
91 | );
92 | }
93 |
94 | /**
95 | * 执行在apk安装界面的逻辑
96 | */
97 | @Override
98 | protected void runLogicInApkInstallPage() {
99 | String appName = getTextByViewIdFromRootActiveWindow("com.android.packageinstaller:id/app_name");
100 | if (appName == null) {
101 | return;
102 | }
103 |
104 | DLog.i("进入应用[%s]的安装界面", appName);
105 |
106 | for (ApkInstallTarget target : getTargets()) {
107 | if (!target.isValid()) {
108 | continue;
109 | }
110 |
111 | if (!target.getAppName().equals(appName)) {
112 | continue;
113 | }
114 |
115 | if ((target.getAction() & ApkInstallTarget.ACTION_AUTO_INSTALL) != 0) {
116 | if (performClickByViewIdFromRootActiveWindow("com.android.packageinstaller:id/ok_button")) {
117 | callbackApkInstallBtnClick(target);
118 | }
119 | }
120 | break;
121 | }
122 | }
123 |
124 | /**
125 | * @return 是否在apk安装中界面
126 | */
127 | @Override
128 | protected boolean isInApkInstallingPage() {
129 | return isNodeExistInRootActiveWindowByViewIds(
130 | "com.android.packageinstaller:id/app_icon",
131 | "com.android.packageinstaller:id/app_name",
132 | "com.android.packageinstaller:id/center_text",
133 | "com.android.packageinstaller:id/progress_bar"
134 | );
135 | }
136 |
137 | /**
138 | * 执行在apk安装中界面的逻辑
139 | */
140 | @Override
141 | protected void runLogicInApkInstallingPage() {
142 | String appName = getTextByViewIdFromRootActiveWindow("com.android.packageinstaller:id/app_name");
143 | if (appName == null) {
144 | return;
145 | }
146 | DLog.i("应用[%s]安装中", appName);
147 |
148 | for (ApkInstallTarget target : getTargets()) {
149 | if (!target.isValid()) {
150 | continue;
151 | }
152 |
153 | if (!target.getAppName().equals(appName)) {
154 | continue;
155 | }
156 |
157 | if ((target.getAction() & ApkInstallTarget.ACTION_WAIT_INSTALLING) != 0) {
158 | callbackApkInstalling(target);
159 | } else {
160 | goBack();
161 | }
162 | break;
163 | }
164 | }
165 |
166 | /**
167 | * @return 是否在apk安装成功界面
168 | */
169 | @Override
170 | protected boolean isInApkInstallSuccessPage() {
171 | return isNodeExistInRootActiveWindowByViewIds(
172 | "com.android.packageinstaller:id/app_icon",
173 | "com.android.packageinstaller:id/app_name",
174 | "com.android.packageinstaller:id/center_text",
175 | "com.android.packageinstaller:id/launch_button",
176 | "com.android.packageinstaller:id/done_button"
177 | );
178 | }
179 |
180 | /**
181 | * 执行在apk安装成功界面的逻辑
182 | */
183 | @Override
184 | protected void runLogicInApkInstallSuccessPage() {
185 | String appName = getTextByViewIdFromRootActiveWindow("com.android.packageinstaller:id/app_name");
186 | if (appName == null) {
187 | return;
188 | }
189 | DLog.i("应用[%s]安装成功", appName);
190 |
191 | for (ApkInstallTarget target : getTargets()) {
192 | if (!target.isValid()) {
193 | continue;
194 | }
195 |
196 | if (!target.getAppName().equals(appName)) {
197 | continue;
198 | }
199 |
200 | if ((target.getAction() & ApkInstallTarget.ACTION_CLICK_FINISH) != 0) {
201 | if (performClickByViewIdFromRootActiveWindow("com.android.packageinstaller:id/done_button")) {
202 | callbackApkInstallFinish(target);
203 | }
204 | } else if ((target.getAction() & ApkInstallTarget.ACTION_CLICK_OPEN) != 0) {
205 | if (performClickByViewIdFromRootActiveWindow("com.android.packageinstaller:id/launch_button")) {
206 | callbackApkInstallLaunch(target);
207 | }
208 | }
209 | break;
210 | }
211 | }
212 |
213 | /**
214 | * @return 是否在apk卸载界面
215 | */
216 | @Override
217 | protected boolean isInAppUninstallPage() {
218 | return isNodeExistInRootActiveWindowByViewIds(
219 | "com.android.packageinstaller:id/app_icon",
220 | "com.android.packageinstaller:id/app_name",
221 | "com.android.packageinstaller:id/uninstall_confirm",
222 | "com.android.packageinstaller:id/ok_button",
223 | "com.android.packageinstaller:id/cancel_button"
224 | );
225 | }
226 |
227 | /**
228 | * 执行在apk卸载界面的逻辑
229 | */
230 | @Override
231 | protected void runLogicInAppUninstallPage() {
232 | String appName = getTextByViewIdFromRootActiveWindow("com.android.packageinstaller:id/app_name");
233 | if (appName == null) {
234 | return;
235 | }
236 | DLog.i("进入应用[%s]的卸载界面", appName);
237 |
238 | for (ApkInstallTarget target : getTargets()) {
239 | if (!target.isValid()) {
240 | continue;
241 | }
242 |
243 | if (!target.getAppName().equals(appName)) {
244 | continue;
245 | }
246 |
247 | if ((target.getAction() & ApkInstallTarget.ACTION_AUTO_DELETE) != 0) {
248 | if (performClickByViewIdFromRootActiveWindow("com.android.packageinstaller:id/ok_button")) {
249 | callbackAppUninstallConfirm(target);
250 | }
251 | } else if ((target.getAction() & ApkInstallTarget.ACTION_CAN_NOT_DELETE) != 0) {
252 | if (performClickByViewIdFromRootActiveWindow("com.android.packageinstaller:id/cancel_button")) {
253 | callbackAppUninstallCancel(target);
254 | }
255 | }
256 | break;
257 | }
258 | }
259 |
260 | }
261 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/android/AndroidApkInstallASHandler500.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android;
2 |
3 | import android.view.accessibility.AccessibilityEvent;
4 |
5 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.ApkInstallTarget;
6 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
7 |
8 | /**
9 | * 继承Android 444 版本是因为大部分逻辑和id相同,所以就直接继承,然后只重写不同部分
10 | *
11 | * @author zhitao
12 | * @since 2017-04-05 10:41
13 | */
14 | public class AndroidApkInstallASHandler500 extends AndroidApkInstallASHandler444 {
15 |
16 | @Override
17 | public void onAccessibilityEvent(AccessibilityEvent event) {
18 | switch (event.getEventType()) {
19 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
20 | handleApkInstallSuccess();
21 | break;
22 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
23 | switch (event.getClassName().toString()) {
24 | // 安装界面
25 | case "com.android.packageinstaller.PackageInstallerActivity":
26 | handleApkInstall();
27 | break;
28 | // 安装中界面,同时也是安装成功的界面,但是安装成功的界面不是这个事件触发的
29 | case "com.android.packageinstaller.InstallAppProgress":
30 | handleApkInstalling();
31 | break;
32 | // 卸载界面
33 | case "android.app.AlertDialog":
34 | handleAppUninstall();
35 | break;
36 | default:
37 | break;
38 | }
39 | break;
40 | case AccessibilityEvent.TYPE_VIEW_SCROLLED:
41 | switch (event.getClassName().toString()) {
42 | case "android.widget.ScrollView":
43 | // 安装界面点击下一步之后会触发这里,要不断点击,直到开始安装
44 | handleApkInstall();
45 | break;
46 | default:
47 | break;
48 | }
49 | break;
50 | default:
51 | break;
52 | }
53 | }
54 |
55 | /**
56 | * @return 是否在apk卸载界面
57 | */
58 | @Override
59 | protected boolean isInAppUninstallPage() {
60 | return isNodeExistInRootActiveWindowByViewIds(
61 | "android:id/content",
62 | "android:id/parentPanel",
63 | "android:id/topPanel",
64 | "android:id/title_template",
65 | "android:id/alertTitle",
66 | "android:id/contentPanel",
67 | "android:id/scrollView",
68 | "android:id/message",
69 | "android:id/buttonPanel",
70 | "android:id/button2",
71 | "android:id/button1"
72 | );
73 | }
74 |
75 | /**
76 | * 执行在apk卸载界面的逻辑
77 | */
78 | @Override
79 | protected void runLogicInAppUninstallPage() {
80 | String appName = getTextByViewIdFromRootActiveWindow("android:id/alertTitle");
81 | if (appName == null) {
82 | return;
83 | }
84 | DLog.i("进入应用[%s]的卸载界面", appName);
85 |
86 | for (ApkInstallTarget target : getTargets()) {
87 | if (!target.isValid()) {
88 | continue;
89 | }
90 |
91 | if (!target.getAppName().equals(appName)) {
92 | continue;
93 | }
94 |
95 | if ((target.getAction() & ApkInstallTarget.ACTION_AUTO_DELETE) != 0) {
96 | if (performClickByViewIdFromRootActiveWindow("android:id/button1")) {
97 | callbackAppUninstallConfirm(target);
98 | }
99 | } else if ((target.getAction() & ApkInstallTarget.ACTION_CAN_NOT_DELETE) != 0) {
100 | if (performClickByViewIdFromRootActiveWindow("android:id/button2")) {
101 | callbackAppUninstallCancel(target);
102 | }
103 | }
104 | break;
105 | }
106 | }
107 | }
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/android/AndroidApkInstallASHandler501.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android;
2 |
3 | import io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.ApkInstallTarget;
4 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
5 |
6 | /**
7 | * 继承Android 500 版本是因为大部分逻辑和id相同,所以就直接继承,然后只重写不同部分
8 | *
9 | * @author zhitao
10 | * @since 2017-04-05 10:47
11 | */
12 | public class AndroidApkInstallASHandler501 extends AndroidApkInstallASHandler500 {
13 |
14 | /**
15 | * 执行在apk卸载界面的逻辑
16 | */
17 | @Override
18 | protected void runLogicInAppUninstallPage() {
19 | // 三星5.0.1系统上的卸载界面的标题不是app名字,我们需要从正文信息中获取文字判断是否包含我们要卸载的app的名字
20 | String message = getTextByViewIdFromRootActiveWindow("android:id/message");
21 | if (message == null) {
22 | return;
23 | }
24 |
25 | DLog.i("进入应用[%s]的卸载界面", message);
26 |
27 | for (ApkInstallTarget target : getTargets()) {
28 | if (!target.isValid()) {
29 | continue;
30 | }
31 |
32 | if (!message.contains(target.getAppName())) {
33 | continue;
34 | }
35 |
36 | if ((target.getAction() & ApkInstallTarget.ACTION_AUTO_DELETE) != 0) {
37 | if (performClickByViewIdFromRootActiveWindow("android:id/button1")) {
38 | callbackAppUninstallConfirm(target);
39 | }
40 | } else if ((target.getAction() & ApkInstallTarget.ACTION_CAN_NOT_DELETE) != 0) {
41 | if (performClickByViewIdFromRootActiveWindow("android:id/button2")) {
42 | callbackAppUninstallCancel(target);
43 | }
44 | }
45 | break;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/android/AndroidApkInstallASHandler510.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android;
2 |
3 | /**
4 | * 继承Android 500 版本是因为大部分逻辑和id相同,所以就直接继承,然后只重写不同部分
5 | *
6 | * @author zhitao
7 | * @since 2017-04-05 10:54
8 | */
9 | public class AndroidApkInstallASHandler510 extends AndroidApkInstallASHandler500 {
10 |
11 | }
12 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/android/AndroidApkInstallASHandler601.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android;
2 |
3 | /**
4 | * @author zhitao
5 | * @since 2017-04-05 10:56
6 | */
7 | public class AndroidApkInstallASHandler601 extends AndroidApkInstallASHandler500 {
8 |
9 | /**
10 | * 具体实现类的辅助功能所针对的应用包名
11 | *
12 | * e.g.
13 | *
14 | * 如果需要改动系统设置,那么这里的包名可能为 com.android.settings 或者 其他第三方系统所对应的包名
15 | *
16 | * @return 具体实现类的辅助功能所针对的应用包名
17 | *
18 | * @see #isUsingPkgName2TrackEvent()
19 | */
20 | @Override
21 | protected String getSupportPkgName() {
22 | return "com.google.android.packageinstaller";
23 | }
24 |
25 | /**
26 | * @return 是否在apk安装界面
27 | */
28 | @Override
29 | protected boolean isInApkInstallPage() {
30 | return isNodeExistInRootActiveWindowByViewIds(
31 | "com.android.packageinstaller:id/app_icon",
32 | "com.android.packageinstaller:id/app_name",
33 | "com.android.packageinstaller:id/cancel_button",
34 | "com.android.packageinstaller:id/ok_button"
35 | );
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/android/AndroidApkInstallASHandler700.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android;
2 |
3 | import android.view.accessibility.AccessibilityEvent;
4 |
5 | /**
6 | * @author zhitao
7 | * @since 2017-04-05 11:00
8 | */
9 | public class AndroidApkInstallASHandler700 extends AndroidApkInstallASHandler500 {
10 |
11 | @Override
12 | public void onAccessibilityEvent(AccessibilityEvent event) {
13 | switch (event.getEventType()) {
14 | case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
15 | handleApkInstallSuccess();
16 | break;
17 | case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
18 | switch (event.getClassName().toString()) {
19 | // 安装界面
20 | case "com.android.packageinstaller.PackageInstallerActivity":
21 | handleApkInstall();
22 | break;
23 | // 安装中界面,同时也是安装成功的界面,但是安装成功的界面不是这个事件触发的
24 | case "com.android.packageinstaller.InstallAppProgress":
25 | handleApkInstalling();
26 | break;
27 | // 卸载界面
28 | case "com.android.packageinstaller.UninstallerActivity":
29 | handleAppUninstall();
30 | break;
31 | default:
32 | break;
33 | }
34 | break;
35 | case AccessibilityEvent.TYPE_VIEW_SCROLLED:
36 | switch (event.getClassName().toString()) {
37 | case "android.widget.ScrollView":
38 | // 安装界面点击下一步之后会触发这里,要不断点击,直到开始安装
39 | handleApkInstall();
40 | break;
41 | default:
42 | break;
43 | }
44 | break;
45 | default:
46 | break;
47 | }
48 | }
49 |
50 | }
51 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/ashandler/apkinstall/samsung/SamsungApkInstallASHandler500.java:
--------------------------------------------------------------------------------
1 | //package io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.samsung;
2 | //
3 | //import android.view.accessibility.AccessibilityEvent;
4 | //
5 | //import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.AbsApkInstallHandler;
6 | //
7 | //import static android.content.Intent.ACTION_DELETE;
8 | //import static io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall.ApkInstallTarget.ACTION_CAN_NOT_DELETE;
9 | //
10 | ///**
11 | // * @author zhitao
12 | // * @since 2017-04-01 09:58
13 | // */
14 | //public class SamsungApkInstallASHandler500 extends AbsApkInstallHandler{
15 | //
16 | // /**
17 | // * 具体实现类的辅助功能所针对的应用包名
18 | // *
19 | // * e.g.
20 | // *
21 | // * 如果需要改动系统设置,那么这里的包名可能为 com.android.settings 或者 其他第三方系统所对应的包名
22 | // *
23 | // * @return 具体实现类的辅助功能所针对的应用包名
24 | // *
25 | // * @see #isUsingPkgName2TrackEvent()
26 | // */
27 | // @Override
28 | // protected String getSupportPkgName() {
29 | // return "com.sec.android.app.launcher";
30 | // }
31 | //
32 | // @Override
33 | // protected void onServiceConnected() {
34 | //
35 | // }
36 | //
37 | // @Override
38 | // protected void onInterrupt() {
39 | //
40 | // }
41 | //
42 | // @Override
43 | // protected void onAccessibilityEvent(AccessibilityEvent event) {
44 | // switch (event.getEventType()) {
45 | // case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
46 | // switch (event.getClassName().toString()) {
47 | // // 卸载界面
48 | // case "android.app.AlertDialog":
49 | // handleAppUninstall();
50 | // break;
51 | // default:
52 | // break;
53 | // }
54 | // break;
55 | // default:
56 | // break;
57 | // }
58 | // }
59 | //
60 | // /**
61 | // * @return 是否在apk安装界面
62 | // */
63 | // @Override
64 | // protected boolean isInApkInstallPage() {
65 | // return false;
66 | // }
67 | //
68 | // /**
69 | // * 执行在apk安装界面的逻辑
70 | // */
71 | // @Override
72 | // protected void runLogicInApkInstallPage() {
73 | //
74 | // }
75 | //
76 | // /**
77 | // * @return 是否在apk安装中界面
78 | // */
79 | // @Override
80 | // protected boolean isInApkInstallingPage() {
81 | // return false;
82 | // }
83 | //
84 | // /**
85 | // * 执行在apk安装中界面的逻辑
86 | // */
87 | // @Override
88 | // protected void runLogicInApkInstallingPage() {
89 | //
90 | // }
91 | //
92 | // /**
93 | // * @return 是否在apk安装成功界面
94 | // */
95 | // @Override
96 | // protected boolean isInApkInstallSuccessPage() {
97 | // return false;
98 | // }
99 | //
100 | // /**
101 | // * 执行在apk安装成功界面的逻辑
102 | // */
103 | // @Override
104 | // protected void runLogicInApkInstallSuccessPage() {
105 | //
106 | // }
107 | //
108 | // /**
109 | // * @return 是否在apk卸载界面
110 | // */
111 | // @Override
112 | // protected boolean isInAppUninstallPage() {
113 | // return isNodeExistInRootActiveWindowByViewIds(
114 | // "android:id/content",
115 | // "android:id/parentPanel",
116 | // "android:id/topPanel",
117 | // "android:id/title_template",
118 | // "android:id/alertTitle",
119 | // "android:id/contentPanel",
120 | // "android:id/scrollView",
121 | // "android:id/message",
122 | // "android:id/buttonPanel",
123 | // "android:id/button2",
124 | // "android:id/button1"
125 | // );
126 | // }
127 | //
128 | // /**
129 | // * 执行在apk卸载界面的逻辑
130 | // */
131 | // @Override
132 | // protected void runLogicInAppUninstallPage() {
133 | // // 三星5.0.1系统上的卸载界面的标题不是app名字
134 | // String message = getTextByViewIdFromRootActiveWindow("android:id/message");
135 | // if (message == null) {
136 | // return;
137 | // }
138 | // if (mApkInstallTargetApps == null) {
139 | // return;
140 | // }
141 | // for (ApkInstallTargetApp app : mApkInstallTargetApps) {
142 | // if (message.contains(app.appName)) {
143 | // if ((app.action & ACTION_DELETE) != 0) {
144 | // if (performClickByViewIdFromRootActiveWindow("android:id/button1")) {
145 | // callbackAppUninstallConfirm(app.appName);
146 | // }
147 | // continue;
148 | // }
149 | //
150 | // if ((app.action & ACTION_CAN_NOT_DELETE) != 0) {
151 | // if (performClickByViewIdFromRootActiveWindow("android:id/button2")) {
152 | // callbackAppUninstallCancel(app.appName);
153 | // }
154 | // continue;
155 | // }
156 | // }
157 | // }
158 | // }
159 | //}
160 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/AbsHelper.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss;
2 |
3 | import android.support.annotation.NonNull;
4 | import android.support.annotation.Nullable;
5 |
6 | import java.util.ArrayList;
7 |
8 | import io.github.zhitaocai.accessibilitydispatcher.AccessibilityDispatcher;
9 |
10 | /**
11 | * @author zhitao
12 | * @since 2017-03-23 11:10
13 | */
14 | public abstract class AbsHelper {
15 |
16 | private boolean mIsEnable;
17 |
18 | @Nullable private ArrayList mCallBacks;
19 |
20 | @Nullable private ArrayList mTargets;
21 |
22 | @Nullable private H mHandlerFactory;
23 |
24 | @NonNull private String mIdentify;
25 |
26 | /**
27 | * @param identify 助手的唯一标识
28 | */
29 | protected AbsHelper(@NonNull String identify) {
30 | mIdentify = identify;
31 | }
32 |
33 | @Override
34 | public int hashCode() {
35 | return mIdentify.hashCode();
36 | }
37 |
38 | @Override
39 | public boolean equals(Object obj) {
40 | return obj != null && hashCode() == obj.hashCode();
41 | }
42 |
43 | /**
44 | * 重置所有参数
45 | *
46 | * @return this
47 | */
48 | public AbsHelper reset() {
49 | mIsEnable = false;
50 | mTargets = null;
51 | mCallBacks = null;
52 | return this;
53 | }
54 |
55 | /**
56 | * @return 创建一个默认的工厂对象,如果没有调用 {@link #withHandlerFactory(IHandlerFactory)} 方法设置工厂,那么就会使用这个方法创建的默认工厂
57 | */
58 | protected abstract H newDefaultHandlerFactory();
59 |
60 | /**
61 | * 创建需要的业务对象的工厂
62 | *
63 | * @return 具体业务创建的工厂,工厂依据一定的规则来生成对应的辅助业务
64 | */
65 | public H getHandlerFactory() {
66 | return mHandlerFactory;
67 | }
68 |
69 | /**
70 | * 初始化生成对应辅助点击的逻辑工具类的工厂
71 | *
72 | * 不如在魅族手机上,工厂需要生成适配Flyme系统的自动点击工具类,而不需要其他系统的自动点击类
73 | *
74 | * @param handlerFactory 具体业务创建的工厂
75 | *
76 | * @return this
77 | */
78 | public AbsHelper withHandlerFactory(H handlerFactory) {
79 | mHandlerFactory = handlerFactory;
80 | return this;
81 | }
82 |
83 | /**
84 | * 是否激活这个业务
85 | *
86 | * @param enable 是否激活
87 | *
88 | * @return this
89 | */
90 | public AbsHelper enable(boolean enable) {
91 | mIsEnable = enable;
92 | return this;
93 | }
94 |
95 | /**
96 | * @return 当前是否激活这个业务
97 | */
98 | public boolean isEnable() {
99 | return mIsEnable;
100 | }
101 |
102 | /**
103 | * @return 获取回调监听器列表
104 | */
105 | public ArrayList getCallBacks() {
106 | return mCallBacks;
107 | }
108 |
109 | /**
110 | * 添加回调监听器(请记得在适当的位置调用 {@link #removeCallBacks(OnCallBack[])} 释放监听)
111 | *
112 | * e.g.
113 | *
114 | * 如果你需要知道在安装界面中,是否点击了 "下一步" 或者 "安装" 时,那么可以通过设置回调监听器知道,以此来做一点额外的逻辑,比如统计点击了多少次下一步之类的
115 | *
116 | * @param callBacks 监听器
117 | *
118 | * @return this
119 | *
120 | * @see #removeCallBacks(OnCallBack[])
121 | */
122 | public AbsHelper withCallBacks(C... callBacks) {
123 | if (callBacks == null || callBacks.length == 0) {
124 | return this;
125 | }
126 | if (mCallBacks == null) {
127 | mCallBacks = new ArrayList<>();
128 | }
129 | for (C callBack : callBacks) {
130 | if (!mCallBacks.contains(callBack)) {
131 | mCallBacks.add(callBack);
132 | }
133 | }
134 | return this;
135 | }
136 |
137 | /**
138 | * 移除回调监听器
139 | *
140 | * 你应该在适当的实际移除监听器,比如自动安装业务完毕之后应该移除监听器
141 | *
142 | * @param callBacks 监听器
143 | *
144 | * @return this
145 | */
146 | public AbsHelper removeCallBacks(C... callBacks) {
147 | if (callBacks == null || callBacks.length == 0) {
148 | return this;
149 | }
150 | if (mCallBacks == null || mCallBacks.isEmpty()) {
151 | return this;
152 | }
153 | for (C callBack : callBacks) {
154 | mCallBacks.remove(callBack);
155 | }
156 | return this;
157 | }
158 |
159 | /**
160 | * @return 获取当前的业务目标
161 | */
162 | public ArrayList getTargets() {
163 | return mTargets;
164 | }
165 |
166 | /**
167 | * 添加业务目标
168 | *
169 | * 在不同业务中,"目标" 这个定义可能不同,但是大同小异
170 | *
171 | * e.g.
172 | *
173 | * 1. 比如在安装/卸载业务中
174 | * 目标可能为 自动安装某个应用 或者 不能卸载某个应用 (应用名 : 对应的action[自动安装,还是防卸载等])
175 | * 2. 比如在安全设置界面业务中
176 | * 目标可能为 开启位置来源
177 | *
178 | *
179 | * @param targets 业务目标
180 | *
181 | * @return this
182 | */
183 | public AbsHelper withTargets(T... targets) {
184 | if (targets == null || targets.length == 0) {
185 | return this;
186 | }
187 | if (mTargets == null) {
188 | mTargets = new ArrayList<>();
189 | }
190 | for (T target : targets) {
191 | if (!mTargets.contains(target)) {
192 | mTargets.add(target);
193 | }
194 | }
195 | return this;
196 | }
197 |
198 | /**
199 | * 移除指定的业务目标
200 | *
201 | * 比如当你已经完成了一个应用的自动安装操作,那么应该及时从自动安装业务中移除这个目标及其行为,防止多开业务的时候出现一些问题
202 | *
203 | * 在不同业务中,"目标" 这个定义可能不同,但是大同小异
204 | *
205 | * e.g.
206 | *
207 | * 1. 比如在安装/卸载业务中
208 | * 目标可能为 自动安装某个应用 或者 不能卸载某个应用 (应用名 : 对应的action[自动安装,还是防卸载等])
209 | * 2. 比如在安全设置界面业务中
210 | * 目标可能为 开启位置来源
211 | *
212 | *
213 | * @param targets 业务目标
214 | *
215 | * @return this
216 | */
217 | public AbsHelper removeTargets(T... targets) {
218 | if (targets == null || targets.length == 0) {
219 | return this;
220 | }
221 | if (mTargets == null || mTargets.isEmpty()) {
222 | return this;
223 | }
224 | for (T target : targets) {
225 | mTargets.remove(target);
226 | }
227 | return this;
228 | }
229 |
230 | /**
231 | * 将配置加入到 {@link io.github.zhitaocai.accessibilitydispatcher.AccessibilityDispatcher} 中
232 | *
233 | * 注意:配置完毕之后,必须要调用这个方法,才会真的令辅助功能服务生效,不然你只是在瞎逼逼 ^_^
234 | */
235 | public void active() {
236 | if (mHandlerFactory == null) {
237 | mHandlerFactory = newDefaultHandlerFactory();
238 | }
239 | AccessibilityDispatcher.updateHelper(this);
240 | }
241 |
242 | }
243 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/IHandlerFactory.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss;
2 |
3 | import java.util.ArrayList;
4 |
5 | import io.github.zhitaocai.accessibilitydispatcher.AbsASHandler;
6 |
7 | /**
8 | * 自动点击业务类工厂
9 | *
10 | * @author zhitao
11 | * @since 2017-03-23 12:00
12 | */
13 | public interface IHandlerFactory {
14 |
15 | /**
16 | * 根据机型 系统版本 系统类型等来创建具体的自动点击业务类
17 | *
18 | * @return 返回业务对象列表
19 | */
20 | ArrayList initHandlers();
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/ITarget.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss;
2 |
3 | /**
4 | * @author zhitao
5 | * @since 2017-03-23 11:15
6 | */
7 | public interface ITarget {
8 |
9 | /**
10 | * 建立一些规则来判断每个传入来的目标是否有效
11 | *
12 | * @return 目标是否有效
13 | */
14 | boolean isValid();
15 |
16 | }
17 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/OnCallBack.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss;
2 |
3 | /**
4 | * @author zhitao
5 | * @since 2017-03-23 11:16
6 | */
7 | public interface OnCallBack {
8 |
9 | }
10 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/apkinstall/ApkInstallHandlerFactory.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall;
2 |
3 | import android.os.Build;
4 |
5 | import java.util.ArrayList;
6 |
7 | import io.github.zhitaocai.accessibilitydispatcher.AbsASHandler;
8 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android.AndroidApkInstallASHandler444;
9 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android.AndroidApkInstallASHandler500;
10 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android.AndroidApkInstallASHandler501;
11 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android.AndroidApkInstallASHandler510;
12 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android.AndroidApkInstallASHandler601;
13 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.apkinstall.android.AndroidApkInstallASHandler700;
14 | import io.github.zhitaocai.accessibilitydispatcher.businss.IHandlerFactory;
15 |
16 | /**
17 | * @author zhitao
18 | * @since 2017-04-01 09:47
19 | */
20 | public class ApkInstallHandlerFactory implements IHandlerFactory {
21 |
22 | /**
23 | * 根据机型 系统版本 系统类型等来创建具体的自动点击业务类
24 | *
25 | * @return 返回业务对象列表
26 | */
27 | @Override
28 | public ArrayList initHandlers() {
29 | ArrayList handlers = new ArrayList<>();
30 | // handlers.add(new SamsungApkInstallASHandler501());
31 | // handlers.add(new FuzzyApkInstallASHandler());
32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
33 | handlers.add(new AndroidApkInstallASHandler700());
34 | return handlers;
35 | }
36 | if ("6.0.1".equals(Build.VERSION.RELEASE)) {
37 | handlers.add(new AndroidApkInstallASHandler601());
38 | return handlers;
39 | }
40 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
41 | handlers.add(new AndroidApkInstallASHandler510());
42 | return handlers;
43 | }
44 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
45 | if ("5.0.1".equals(Build.VERSION.RELEASE)) {
46 | handlers.add(new AndroidApkInstallASHandler501());
47 | return handlers;
48 | }
49 | handlers.add(new AndroidApkInstallASHandler500());
50 | return handlers;
51 | }
52 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
53 | handlers.add(new AndroidApkInstallASHandler444());
54 | return handlers;
55 | }
56 | handlers.add(new AndroidApkInstallASHandler444());
57 | return handlers;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/apkinstall/ApkInstallTarget.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall;
2 |
3 | import android.text.TextUtils;
4 |
5 | import io.github.zhitaocai.accessibilitydispatcher.businss.ITarget;
6 |
7 | /**
8 | * @author zhitao
9 | * @since 2017-04-01 09:35
10 | */
11 | public class ApkInstallTarget implements ITarget {
12 |
13 | /**
14 | * 自动点击安装
15 | */
16 | public final static int ACTION_AUTO_INSTALL = 1;
17 |
18 | /**
19 | * 是否等待安装完成
20 | *
21 | * - 如果存在此ACTION,那么点击安装之后就会等待安装完成
22 | * - 如果不存在此ACTION,那么点击安装之后就会自动返回
23 | *
24 | */
25 | public final static int ACTION_WAIT_INSTALLING = 2;
26 |
27 | /**
28 | * 安装完成时的界面,点击完成
29 | */
30 | public final static int ACTION_CLICK_FINISH = 4;
31 |
32 | /**
33 | * 安装完成时的界面,点击打开
34 | */
35 | public final static int ACTION_CLICK_OPEN = 8;
36 |
37 | /**
38 | * 自动卸载
39 | */
40 | public final static int ACTION_AUTO_DELETE = 16;
41 |
42 | /**
43 | * 不能卸载
44 | */
45 | public final static int ACTION_CAN_NOT_DELETE = 32;
46 |
47 | /**
48 | * 安装的应用名字(不是包名是因为界面上不会显示包名)
49 | */
50 | public String mAppName;
51 |
52 | /**
53 | * ACTION
54 | */
55 | public int action;
56 |
57 | private ApkInstallTarget() {
58 | super();
59 | }
60 |
61 | public String getAppName() {
62 | return mAppName;
63 | }
64 |
65 | public void setAppName(String appName) {
66 | mAppName = appName;
67 | }
68 |
69 | public int getAction() {
70 | return action;
71 | }
72 |
73 | public void setAction(int action) {
74 | this.action = action;
75 | }
76 |
77 | /**
78 | * 建立一些规则来判断每个传入来的目标是否有效
79 | *
80 | * @return 目标是否有效
81 | */
82 | @Override
83 | public boolean isValid() {
84 | if (TextUtils.isEmpty(getAppName())) {
85 | return false;
86 | }
87 | if (getAction() < 0) {
88 | return false;
89 | }
90 | return true;
91 | }
92 |
93 | public static class Builder {
94 |
95 | private ApkInstallTarget mApkInstallTarget = new ApkInstallTarget();
96 |
97 | public Builder setAppName(String appName) {
98 | mApkInstallTarget.setAppName(appName);
99 | return this;
100 | }
101 |
102 | public Builder setAction(int action) {
103 | mApkInstallTarget.setAction(action);
104 | return this;
105 | }
106 |
107 | public ApkInstallTarget build() {
108 | return mApkInstallTarget;
109 | }
110 | }
111 |
112 | }
113 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/apkinstall/OnApkInstallCallBack.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall;
2 |
3 | import io.github.zhitaocai.accessibilitydispatcher.businss.OnCallBack;
4 |
5 | /**
6 | * @author zhitao
7 | * @since 2017-04-01 09:46
8 | */
9 | public interface OnApkInstallCallBack extends OnCallBack {
10 |
11 | /**
12 | * 点击了apk安装界面中的"下一步"或者"安装"按钮
13 | *
14 | * @param target 目标应用
15 | */
16 | void onApkInstallBtnClick(ApkInstallTarget target);
17 |
18 | /**
19 | * 进入安装中的界面
20 | *
21 | * @param target 目标应用
22 | */
23 | void onApkInstalling(ApkInstallTarget target);
24 |
25 | /**
26 | * 点击了apk安装成功界面中的"完成"按钮
27 | *
28 | * @param target 目标应用
29 | */
30 | void onApkInstallFinish(ApkInstallTarget target);
31 |
32 | /**
33 | * 点击了apk安装成功界面中的"打开"按钮
34 | *
35 | * @param target 目标应用
36 | */
37 | void onApkInstallLaunch(ApkInstallTarget target);
38 |
39 | /**
40 | * 点击了apk卸载界面中"确认"按钮
41 | *
42 | * @param target 目标应用
43 | */
44 | void onApkUninstallConfirm(ApkInstallTarget target);
45 |
46 | /**
47 | * 点击了apk卸载界面中"取消"按钮
48 | *
49 | * @param target 目标应用
50 | */
51 | void onApkUninstallCancel(ApkInstallTarget target);
52 |
53 | }
54 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/apkinstall/OnApkInstallCallBackAdapter.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.apkinstall;
2 |
3 | /**
4 | * @author zhitao
5 | * @since 2017-04-05 10:10
6 | */
7 | public class OnApkInstallCallBackAdapter implements OnApkInstallCallBack {
8 |
9 | /**
10 | * 点击了apk安装界面中的"下一步"或者"安装"按钮
11 | *
12 | * @param target 目标应用
13 | */
14 | @Override
15 | public void onApkInstallBtnClick(ApkInstallTarget target) {
16 |
17 | }
18 |
19 | /**
20 | * 进入安装中的界面
21 | *
22 | * @param target 目标应用
23 | */
24 | @Override
25 | public void onApkInstalling(ApkInstallTarget target) {
26 |
27 | }
28 |
29 | /**
30 | * 点击了apk安装成功界面中的"完成"按钮
31 | *
32 | * @param target 目标应用
33 | */
34 | @Override
35 | public void onApkInstallFinish(ApkInstallTarget target) {
36 |
37 | }
38 |
39 | /**
40 | * 点击了apk安装成功界面中的"打开"按钮
41 | *
42 | * @param target 目标应用
43 | */
44 | @Override
45 | public void onApkInstallLaunch(ApkInstallTarget target) {
46 |
47 | }
48 |
49 | /**
50 | * 点击了apk卸载界面中"确认"按钮
51 | *
52 | * @param target 目标应用
53 | */
54 | @Override
55 | public void onApkUninstallConfirm(ApkInstallTarget target) {
56 |
57 | }
58 |
59 | /**
60 | * 点击了apk卸载界面中"取消"按钮
61 | *
62 | * @param target 目标应用
63 | */
64 | @Override
65 | public void onApkUninstallCancel(ApkInstallTarget target) {
66 |
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/security/OnSecurityCallBack.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.security;
2 |
3 | import io.github.zhitaocai.accessibilitydispatcher.businss.OnCallBack;
4 |
5 | /**
6 | * @author zhitao
7 | * @since 2017-03-30 11:47
8 | */
9 | public interface OnSecurityCallBack extends OnCallBack {
10 |
11 | /**
12 | * 点击了 未知来源 所在的item时的回调
13 | */
14 | void onUnknownSourceItemClick();
15 |
16 | /**
17 | * 点击了 未知来源 所在的item时
18 | *
19 | * - 如果为开启 未知来源 ,会有对话框弹出,这里为点击了对话框中的确认按钮时的回调
20 | * - 如果为关闭 未知来源 ,则没有对话框弹出,所以也不会有这个回调
21 | *
22 | */
23 | void onUnknownSourceDialogConfirm();
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/security/OnSecurityCallBackAdapter.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.security;
2 |
3 | /**
4 | * @author zhitao
5 | * @since 2017-03-30 18:47
6 | */
7 | public class OnSecurityCallBackAdapter implements OnSecurityCallBack {
8 |
9 | /**
10 | * 点击了 未知来源 所在的item时的回调
11 | */
12 | @Override
13 | public void onUnknownSourceItemClick() {
14 |
15 | }
16 |
17 | /**
18 | * 点击了 未知来源 所在的item时
19 | *
20 | * - 如果为开启 未知来源 ,会有对话框弹出,这里为点击了对话框中的确认按钮时的回调
21 | * - 如果为关闭 未知来源 ,则没有对话框弹出,所以也不会有这个回调
22 | *
23 | */
24 | @Override
25 | public void onUnknownSourceDialogConfirm() {
26 |
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/security/SecurityHandlerFactory.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.security;
2 |
3 | import android.os.Build;
4 |
5 | import java.util.ArrayList;
6 |
7 | import io.github.zhitaocai.accessibilitydispatcher.AbsASHandler;
8 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.unknownsources.android
9 | .AndroidUnknownSourcesASHandler444;
10 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.unknownsources.android
11 | .AndroidUnknownSourcesASHandler510;
12 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.security.unknownsources.android
13 | .AndroidUnknownSourcesASHandler700;
14 | import io.github.zhitaocai.accessibilitydispatcher.businss.IHandlerFactory;
15 |
16 | /**
17 | * @author zhitao
18 | * @since 2017-03-30 11:51
19 | */
20 | public class SecurityHandlerFactory implements IHandlerFactory {
21 |
22 | /**
23 | * 根据机型 系统版本 系统类型等来创建具体的自动点击业务类
24 | *
25 | * @return 返回业务对象列表
26 | */
27 | @Override
28 | public ArrayList initHandlers() {
29 | ArrayList handlers = new ArrayList<>();
30 |
31 | // 先加入文字搜索的处理,成功率难说,但是这种方案可能兼容更多的版本
32 | //handlers.add(new UnknownSourcesFuzzyASHandler());
33 |
34 | // 后续再加入更加精准的处理,成功率提高了,但是适配的版本比较局限
35 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
36 | handlers.add(new AndroidUnknownSourcesASHandler700());
37 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
38 | handlers.add(new AndroidUnknownSourcesASHandler510());
39 | } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
40 | handlers.add(new AndroidUnknownSourcesASHandler444());
41 | }
42 | return handlers;
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/security/SecurityTarget.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.security;
2 |
3 | import io.github.zhitaocai.accessibilitydispatcher.businss.ITarget;
4 |
5 | /**
6 | * @author zhitao
7 | * @since 2017-03-30 11:48
8 | */
9 | public class SecurityTarget implements ITarget {
10 |
11 | /**
12 | * 打开允许安装位置来源的开关
13 | */
14 | public final static int ACTION_TURN_ON_UNKNOWNSOURCES = 1;
15 |
16 | /**
17 | * 关闭允许安装位置来源的开关
18 | */
19 | public final static int ACTION_TURN_OFF_UNKNOWNSOURCES = 2;
20 |
21 | private int mAction;
22 |
23 | private SecurityTarget() {
24 | super();
25 | }
26 |
27 | public int getAction() {
28 | return mAction;
29 | }
30 |
31 | public void setAction(int action) {
32 | mAction = action;
33 | }
34 |
35 | @Override
36 | public String toString() {
37 | final StringBuilder sb = new StringBuilder("SecurityTarget{");
38 | sb.append("\n mAction=").append(mAction);
39 | sb.append("\n}");
40 | return sb.toString();
41 | }
42 |
43 | /**
44 | * 建立一些规则来判断每个传入来的目标是否有效
45 | *
46 | * @return 目标是否有效
47 | */
48 | @Override
49 | public boolean isValid() {
50 | if (getAction() < 0) {
51 | return false;
52 | }
53 | // 不能同时设置 开启和关闭
54 | if (((getAction() & ACTION_TURN_ON_UNKNOWNSOURCES) != 0) && ((getAction() & ACTION_TURN_OFF_UNKNOWNSOURCES) != 0)) {
55 | return false;
56 | }
57 | return true;
58 | }
59 |
60 | public static class Builder {
61 |
62 | private SecurityTarget mSecurityTarget = new SecurityTarget();
63 |
64 | public Builder setAction(int action) {
65 | mSecurityTarget.setAction(action);
66 | return this;
67 | }
68 |
69 | public SecurityTarget build() {
70 | return mSecurityTarget;
71 | }
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/vpn/OnVpnCallBack.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.vpn;
2 |
3 | import io.github.zhitaocai.accessibilitydispatcher.businss.OnCallBack;
4 |
5 | /**
6 | * 配置VPN过程中的回调信息
7 | *
8 | * TODO 加入超时限制,以及加入是否成功配置完毕的回调参数
9 | *
10 | * @author zhitao
11 | * @since 2017-03-23 11:26
12 | */
13 | public interface OnVpnCallBack extends OnCallBack {
14 |
15 | /**
16 | * 开始配置VPN信息时回调
17 | *
18 | * @param vpnTarget 当前在配置的VPN
19 | */
20 | void onVpnConfigStart(VpnTarget vpnTarget);
21 |
22 | /**
23 | * 配置VPN信息结束时回调
24 | *
25 | * @param vpnTarget 当前在配置的VPN
26 | */
27 | void onVpnConfigFinish(VpnTarget vpnTarget);
28 |
29 | /**
30 | * 开始配置用户信息时回调
31 | *
32 | * @param vpnTarget 当前在配置的VPN
33 | */
34 | void onUserConfigStart(VpnTarget vpnTarget);
35 |
36 | /**
37 | * 配置用户信息结束时回调
38 | *
39 | * @param vpnTarget 当前在配置的VPN
40 | */
41 | void onUserConfigFinish(VpnTarget vpnTarget);
42 | }
43 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/vpn/OnVpnCallBackAdapter.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.vpn;
2 |
3 | /**
4 | * @author zhitao
5 | * @since 2017-03-23 16:51
6 | */
7 | public class OnVpnCallBackAdapter implements OnVpnCallBack {
8 |
9 | /**
10 | * 开始配置VPN信息时回调
11 | *
12 | * @param vpnTarget 当前在配置的VPN
13 | */
14 | @Override
15 | public void onVpnConfigStart(VpnTarget vpnTarget) {
16 |
17 | }
18 |
19 | /**
20 | * 配置VPN信息结束时回调
21 | *
22 | * @param vpnTarget 当前在配置的VPN
23 | */
24 | @Override
25 | public void onVpnConfigFinish(VpnTarget vpnTarget) {
26 |
27 | }
28 |
29 | /**
30 | * 开始配置用户信息时回调
31 | *
32 | * @param vpnTarget 当前在配置的VPN
33 | */
34 | @Override
35 | public void onUserConfigStart(VpnTarget vpnTarget) {
36 |
37 | }
38 |
39 | /**
40 | * 配置用户信息结束时回调
41 | *
42 | * @param vpnTarget 当前在配置的VPN
43 | */
44 | @Override
45 | public void onUserConfigFinish(VpnTarget vpnTarget) {
46 |
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/businss/vpn/VpnHandlerFactory.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.businss.vpn;
2 |
3 | import java.util.ArrayList;
4 |
5 | import io.github.zhitaocai.accessibilitydispatcher.AbsASHandler;
6 | import io.github.zhitaocai.accessibilitydispatcher.ashandler.androidsettings.vpn.android.AndroidVpnSettingsASHandler444;
7 | import io.github.zhitaocai.accessibilitydispatcher.businss.IHandlerFactory;
8 |
9 | /**
10 | * @author zhitao
11 | * @since 2017-03-23 14:18
12 | */
13 | public class VpnHandlerFactory implements IHandlerFactory {
14 |
15 | /**
16 | * 根据机型 系统版本 系统类型等来创建具体的自动点击业务类
17 | *
18 | * @return 返回业务对象列表
19 | */
20 | @Override
21 | public ArrayList initHandlers() {
22 | ArrayList handlers = new ArrayList<>();
23 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
24 | // handlers.add(new AndroidAccessibilityPageASHandler510());
25 | // return handlers;
26 | // }
27 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
28 | // handlers.add(new AndroidAccessibilityPageASHandler444());
29 | // return handlers;
30 | // }
31 | handlers.add(new AndroidVpnSettingsASHandler444());
32 | return handlers;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/log/DLog.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.log;
2 |
3 | import android.text.TextUtils;
4 | import android.util.Log;
5 |
6 | import java.util.Locale;
7 |
8 | /**
9 | * @author zhitao
10 | * @since 2016-05-16 19:26
11 | */
12 | public class DLog {
13 |
14 | private static String sTag = "dlog";
15 |
16 | private static boolean sIsDebug = false;
17 |
18 | private static boolean sIsShowClassNameInTag = false;
19 |
20 | public static String getTag() {
21 | return sTag;
22 | }
23 |
24 | public static void setTag(String tag) {
25 | sTag = tag;
26 | }
27 |
28 | public static boolean isDebug() {
29 | return sIsDebug;
30 | }
31 |
32 | public static void setIsDebug(boolean isDebug) {
33 | sIsDebug = isDebug;
34 | }
35 |
36 | public static boolean isShowClassNameInTag() {
37 | return sIsShowClassNameInTag;
38 | }
39 |
40 | public static void setIsShowClassNameInTag(boolean isShowClassNameInTag) {
41 | sIsShowClassNameInTag = isShowClassNameInTag;
42 | }
43 |
44 | // INFO
45 | public static void i(String format, Object... args) {
46 | log(Log.INFO, null, format, args);
47 | }
48 |
49 | public static void i(Throwable throwable) {
50 | log(Log.INFO, throwable, null);
51 | }
52 |
53 | public static void i(Throwable throwable, String format, Object... args) {
54 | log(Log.INFO, throwable, format, args);
55 | }
56 |
57 | // ERROR
58 | public static void e(String format, Object... args) {
59 | log(Log.ERROR, null, format, args);
60 | }
61 |
62 | public static void e(Throwable throwable) {
63 | log(Log.ERROR, throwable, null);
64 | }
65 |
66 | public static void e(Throwable throwable, String format, Object... args) {
67 | log(Log.ERROR, throwable, format, args);
68 | }
69 |
70 | // DEBUG
71 | public static void d(String format, Object... args) {
72 | log(Log.DEBUG, null, format, args);
73 | }
74 |
75 | public static void d(Throwable throwable) {
76 | log(Log.DEBUG, throwable, null);
77 | }
78 |
79 | public static void d(Throwable throwable, String format, Object... args) {
80 | log(Log.DEBUG, throwable, format, args);
81 | }
82 |
83 | // WARN
84 | public static void w(String format, Object... args) {
85 | log(Log.WARN, null, format, args);
86 | }
87 |
88 | public static void w(Throwable throwable) {
89 | log(Log.WARN, throwable, null);
90 | }
91 |
92 | public static void w(Throwable throwable, String format, Object... args) {
93 | log(Log.WARN, throwable, format, args);
94 | }
95 |
96 | // VERBOSE
97 | public static void v(String format, Object... args) {
98 | log(Log.VERBOSE, null, format, args);
99 | }
100 |
101 | public static void v(Throwable throwable) {
102 | log(Log.VERBOSE, throwable, null);
103 | }
104 |
105 | public static void v(Throwable throwable, String format, Object... args) {
106 | log(Log.VERBOSE, throwable, format, args);
107 | }
108 |
109 | /**
110 | * 输出log
111 | *
112 | * @param level Log级别 {@link Log#DEBUG}之类
113 | * @param throwable 异常信息
114 | * @param format 格式化的输出
115 | * @param args 输出参数
116 | */
117 | private static void log(int level, Throwable throwable, String format, Object... args) {
118 | if (!isDebug()) {
119 | return;
120 | }
121 | try {
122 | String msg = "";
123 | if (!TextUtils.isEmpty(format)) {
124 | msg = String.format(Locale.getDefault(), format, args);
125 | }
126 |
127 | String tag = getTag();
128 |
129 | if (isShowClassNameInTag()) {
130 | StackTraceElement[] elements = Thread.currentThread().getStackTrace();
131 | String classPackageName = elements[4].getClassName();
132 | tag += "_" + classPackageName.substring(classPackageName.lastIndexOf(".") + 1);
133 | }
134 |
135 | if (throwable == null) {
136 | switch (level) {
137 | case Log.DEBUG:
138 | Log.d(tag, msg);
139 | break;
140 | case Log.INFO:
141 | Log.i(tag, msg);
142 | break;
143 | case Log.WARN:
144 | Log.w(tag, msg);
145 | break;
146 | case Log.ERROR:
147 | Log.e(tag, msg);
148 | break;
149 | default:
150 | Log.v(tag, msg);
151 | break;
152 | }
153 | } else {
154 | switch (level) {
155 | case Log.DEBUG:
156 | Log.d(tag, msg, throwable);
157 | break;
158 | case Log.INFO:
159 | Log.i(tag, msg, throwable);
160 | break;
161 | case Log.WARN:
162 | Log.w(tag, msg, throwable);
163 | break;
164 | case Log.ERROR:
165 | Log.e(tag, msg, throwable);
166 | break;
167 | default:
168 | Log.v(tag, msg, throwable);
169 | break;
170 | }
171 | }
172 | } catch (Throwable e) {
173 | e.printStackTrace();
174 | }
175 | }
176 |
177 | }
178 |
--------------------------------------------------------------------------------
/library/src/main/java/io/github/zhitaocai/accessibilitydispatcher/utils/ClipboardManagerUtil.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.utils;
2 |
3 | import android.content.ClipData;
4 | import android.content.ClipDescription;
5 | import android.content.ClipboardManager;
6 | import android.content.Context;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.Nullable;
9 |
10 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog;
11 |
12 | /**
13 | * 剪切板使用(暂时只支持文字剪切)
14 | *
15 | * @author zhitaocai edit on 2014-7-15
16 | */
17 | public class ClipboardManagerUtil {
18 |
19 | /**
20 | * 保存文字到剪切板中
21 | *
22 | * @param context 上下文
23 | * @param str 要保存的文字
24 | *
25 | * @return 是否保存成功
26 | */
27 | public static boolean setText(@NonNull Context context, String str) {
28 | Context appliactionContext = context.getApplicationContext();
29 | // if (Build.VERSION.SDK_INT >= 11) {
30 | try {
31 | ClipboardManager clipManager = (ClipboardManager) appliactionContext.getSystemService(Context.CLIPBOARD_SERVICE);
32 | ClipData clip = ClipData.newPlainText("simple text", str);
33 | clipManager.setPrimaryClip(clip);
34 | return true;
35 | } catch (Exception e) {
36 | DLog.e(e);
37 | }
38 | return false;
39 | // } else {
40 | // try {
41 | // android.text.ClipboardManager clipManager =
42 | // (android.text.ClipboardManager) appliactionContext.getSystemService(Context.CLIPBOARD_SERVICE);
43 | // clipManager.setText(str);
44 | // return true;
45 | // } catch (Exception e) {
46 | // DLog.e(e);
47 | // }
48 | // return false;
49 | // }
50 | }
51 |
52 | /**
53 | * 获取剪切版中的文字,如果有的话
54 | *
55 | * @param context 上下文
56 | *
57 | * @return 获取到的文字
58 | */
59 | @Nullable
60 | public static String getText(@NonNull Context context) {
61 | Context appliactionContext = context.getApplicationContext();
62 |
63 | // 如果当前设备的android-sdk 版本号小于11
64 | // if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
65 | try {
66 | ClipboardManager clipManager = (ClipboardManager) appliactionContext.getSystemService(Context.CLIPBOARD_SERVICE);
67 | if (clipManager.hasPrimaryClip()) {
68 | // 如果剪切版中的是文字
69 | if (clipManager.getPrimaryClipDescription().hasMimeType(ClipDescription.MIMETYPE_TEXT_PLAIN)) {
70 | StringBuilder sb = new StringBuilder();
71 | ClipData clipData = clipManager.getPrimaryClip();
72 | for (int i = 0; i < clipData.getItemCount(); ++i) {
73 | sb.append(clipData.getItemAt(i).getText());
74 |
75 | // ClipData.Item item = clipData.getItemAt(i);
76 | // CharSequence str = item.coerceToText(SettingsActivity.this);
77 | // resultString += str;
78 | }
79 | return sb.toString();
80 | }
81 | }
82 | } catch (Exception e) {
83 | DLog.e(e);
84 | }
85 | return null;
86 |
87 | // } else {
88 | // try {
89 | // android.text.ClipboardManager clipManager =
90 | // (android.text.ClipboardManager) appliactionContext.getSystemService(Context.CLIPBOARD_SERVICE);
91 | // if (clipManager.hasText()) {
92 | // return clipManager.getText().toString();
93 | // }
94 | // } catch (Exception e) {
95 | // DLog.e(e);
96 | // }
97 | // return null;
98 | // }
99 | }
100 | }
--------------------------------------------------------------------------------
/library/src/main/res/values-en/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Unknown sources
3 | OK
4 | Next
5 | Install
6 | Done
7 | Open
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/values-ja/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 提供元不明のアプリ
3 | OK
4 | 次へ
5 | インストール
6 | 完了
7 | 開く
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/values-zh-rCN/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 未知来源
3 | 确定
4 | 下一步
5 | 安装
6 | 完成
7 | 打开
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/values-zh-rHK/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 不明的來源
3 | 確定
4 | 下一步
5 | 安裝
6 | 完成
7 | 開啟
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/values-zh-rTW/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 不明的來源
3 | 確定
4 | 下一步
5 | 安裝
6 | 完成
7 | 開啟
8 |
9 |
--------------------------------------------------------------------------------
/library/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | 未知来源
3 | 确定
4 | 下一步
5 | 安装
6 | 完成
7 | 打开
8 |
9 |
--------------------------------------------------------------------------------
/library/src/test/java/io/github/zhitaocai/accessibilitydispatcher/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | //package io.github.zhitaocai.accessibilitydispatcher;
2 | //
3 | //import org.junit.Test;
4 | //
5 | //import static junit.framework.Assert.assertEquals;
6 | //import static org.junit.Assert.*;
7 | //
8 | ///**
9 | // * Example local unit test, which will execute on the development machine (host).
10 | // *
11 | // * @see Testing documentation
12 | // */
13 | //public class ExampleUnitTest {
14 | //
15 | // @Test
16 | // public void addition_isCorrect() throws Exception {
17 | // assertEquals(4, 2 + 2);
18 | // }
19 | //}
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':targetapk'
2 | include ':library'
3 |
4 | // https://github.com/dcendents/android-maven-gradle-plugin
5 | // 上传到Bintray的时候,artifactId是在这里改的,而group 和 version 是在build.gradle中的
6 | // 实际发现其实改不了,在publish.gradle 中定义project.archivesBaseName即可改成功
7 | //
8 | //rootProject.name = 'accessibilitydispatcher'
--------------------------------------------------------------------------------
/static/.gitignore:
--------------------------------------------------------------------------------
1 | /apk
2 | /icons
3 | icons.zip
--------------------------------------------------------------------------------
/static/gif/auto_create_l2tp.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_create_l2tp.gif
--------------------------------------------------------------------------------
/static/gif/auto_create_pptp.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_create_pptp.gif
--------------------------------------------------------------------------------
/static/gif/auto_install_apk_untill_finish.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_install_apk_untill_finish.gif
--------------------------------------------------------------------------------
/static/gif/auto_install_apk_untill_installing.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_install_apk_untill_installing.gif
--------------------------------------------------------------------------------
/static/gif/auto_install_apk_untill_open.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_install_apk_untill_open.gif
--------------------------------------------------------------------------------
/static/gif/auto_turn_off_unknown_sources.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_turn_off_unknown_sources.gif
--------------------------------------------------------------------------------
/static/gif/auto_turn_on_unknown_sources.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_turn_on_unknown_sources.gif
--------------------------------------------------------------------------------
/static/gif/auto_uninstall_apk.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_uninstall_apk.gif
--------------------------------------------------------------------------------
/static/gif/auto_uninstall_apk_reject.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/static/gif/auto_uninstall_apk_reject.gif
--------------------------------------------------------------------------------
/targetapk/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/targetapk/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 25
5 | buildToolsVersion "25.0.1"
6 |
7 | defaultConfig {
8 | applicationId "io.github.zhitaocai.accessibilitydispatcher.targetapk"
9 | minSdkVersion 14
10 | targetSdkVersion 25
11 | versionCode 1
12 | versionName "1.0"
13 |
14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
15 |
16 | }
17 | buildTypes {
18 | release {
19 | minifyEnabled false
20 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
21 | }
22 | }
23 | }
24 |
25 | dependencies {
26 | compile fileTree(dir: 'libs', include: ['*.jar'])
27 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
28 | exclude group: 'com.android.support', module: 'support-annotations'
29 | })
30 | compile 'com.android.support:appcompat-v7:25.3.1'
31 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
32 | testCompile 'junit:junit:4.12'
33 | }
34 |
--------------------------------------------------------------------------------
/targetapk/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/caizhitao/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/targetapk/src/androidTest/java/io/github/zhitaocai/accessibilitydispatcher/targetapk/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.targetapk;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 |
20 | @Test
21 | public void useAppContext() throws Exception {
22 | // Context of the app under test.
23 | Context appContext = InstrumentationRegistry.getTargetContext();
24 |
25 | assertEquals("io.github.zhitaocai.accessibilitydispatcher.targetapk", appContext.getPackageName());
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/targetapk/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/targetapk/src/main/java/io/github/zhitaocai/accessibilitydispatcher/targetapk/MainActivity.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.targetapk;
2 |
3 | import android.support.v7.app.AppCompatActivity;
4 | import android.os.Bundle;
5 |
6 | public class MainActivity extends AppCompatActivity {
7 |
8 | @Override
9 | protected void onCreate(Bundle savedInstanceState) {
10 | super.onCreate(savedInstanceState);
11 | setContentView(R.layout.activity_main);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/targetapk/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/targetapk/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/targetapk/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/targetapk/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TargetApk
3 |
4 |
--------------------------------------------------------------------------------
/targetapk/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/targetapk/src/test/java/io/github/zhitaocai/accessibilitydispatcher/targetapk/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package io.github.zhitaocai.accessibilitydispatcher.targetapk;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 |
14 | @Test
15 | public void addition_isCorrect() throws Exception {
16 | assertEquals(4, 2 + 2);
17 | }
18 | }
--------------------------------------------------------------------------------