├── .gitignore ├── .gitmodules ├── ChangeLog.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── keystore.jks ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── github │ │ └── zhitaocai │ │ └── accessibilitydispatcher │ │ └── demo │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── assets │ │ └── targetapk-debug.apk │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── zhitaocai │ │ │ └── accessibilitydispatcher │ │ │ └── demo │ │ │ ├── activity │ │ │ └── MainActivity.java │ │ │ ├── fragment │ │ │ ├── AutoApkInstallFragment.java │ │ │ ├── AutoVpnConfigFragment.java │ │ │ ├── BaseFragment.java │ │ │ └── BasePagerAdapter.java │ │ │ ├── service │ │ │ └── AccessibilityServiceDemo.java │ │ │ └── utils │ │ │ ├── FileUtils.java │ │ │ ├── IntentUtils.java │ │ │ ├── IoUtils.java │ │ │ ├── PackageUtils.java │ │ │ └── PermissionUtils.java │ └── res │ │ ├── layout │ │ ├── activity_main.xml │ │ ├── fragment_auto_config_vpn.xml │ │ ├── fragment_auto_install.xml │ │ └── toolbar_settings.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ ├── accessibility_service.xml │ │ └── file_provider.xml │ └── test │ └── java │ └── io │ └── github │ └── zhitaocai │ └── accessibilitydispatcher │ └── demo │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── library ├── .gitignore ├── build.gradle ├── proguard-rules.pro ├── publish.gradle └── src │ ├── androidTest │ └── java │ │ └── io │ │ └── github │ │ └── zhitaocai │ │ └── accessibilitydispatcher │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── io │ │ │ └── github │ │ │ └── zhitaocai │ │ │ └── accessibilitydispatcher │ │ │ ├── AbsASHandler.java │ │ │ ├── AccessibilityDebugConfig.java │ │ │ ├── AccessibilityDispatcher.java │ │ │ ├── AccessibilityHelper.java │ │ │ ├── AccessibilityServiceUtils.java │ │ │ ├── UtilASHandler.java │ │ │ ├── ashandler │ │ │ ├── androidsettings │ │ │ │ ├── AndroidSettingsCompat.java │ │ │ │ ├── security │ │ │ │ │ ├── AbsSecuritySettingsASHandler.java │ │ │ │ │ └── unknownsources │ │ │ │ │ │ ├── android │ │ │ │ │ │ ├── AndroidUnknownSourcesASHandler444.java │ │ │ │ │ │ ├── AndroidUnknownSourcesASHandler510.java │ │ │ │ │ │ └── AndroidUnknownSourcesASHandler700.java │ │ │ │ │ │ └── fuzzy │ │ │ │ │ │ └── UnknownSourcesFuzzyASHandler.java │ │ │ │ └── vpn │ │ │ │ │ ├── AbsVpnSettingsASHandler.java │ │ │ │ │ └── android │ │ │ │ │ ├── AbsAndroidVpnSettingsASHandler.java │ │ │ │ │ └── AndroidVpnSettingsASHandler444.java │ │ │ └── apkinstall │ │ │ │ ├── AbsApkInstallHandler.java │ │ │ │ ├── android │ │ │ │ ├── AndroidApkInstallASHandler444.java │ │ │ │ ├── AndroidApkInstallASHandler500.java │ │ │ │ ├── AndroidApkInstallASHandler501.java │ │ │ │ ├── AndroidApkInstallASHandler510.java │ │ │ │ ├── AndroidApkInstallASHandler601.java │ │ │ │ └── AndroidApkInstallASHandler700.java │ │ │ │ ├── fuzzy │ │ │ │ └── FuzzyApkInstallASHandler.java │ │ │ │ └── samsung │ │ │ │ └── SamsungApkInstallASHandler500.java │ │ │ ├── businss │ │ │ ├── AbsHelper.java │ │ │ ├── IHandlerFactory.java │ │ │ ├── ITarget.java │ │ │ ├── OnCallBack.java │ │ │ ├── apkinstall │ │ │ │ ├── ApkInstallHandlerFactory.java │ │ │ │ ├── ApkInstallTarget.java │ │ │ │ ├── OnApkInstallCallBack.java │ │ │ │ └── OnApkInstallCallBackAdapter.java │ │ │ ├── security │ │ │ │ ├── OnSecurityCallBack.java │ │ │ │ ├── OnSecurityCallBackAdapter.java │ │ │ │ ├── SecurityHandlerFactory.java │ │ │ │ └── SecurityTarget.java │ │ │ └── vpn │ │ │ │ ├── OnVpnCallBack.java │ │ │ │ ├── OnVpnCallBackAdapter.java │ │ │ │ ├── VpnHandlerFactory.java │ │ │ │ └── VpnTarget.java │ │ │ ├── log │ │ │ └── DLog.java │ │ │ └── utils │ │ │ └── ClipboardManagerUtil.java │ └── res │ │ ├── values-en │ │ └── strings.xml │ │ ├── values-ja │ │ └── strings.xml │ │ ├── values-zh-rCN │ │ └── strings.xml │ │ ├── values-zh-rHK │ │ └── strings.xml │ │ ├── values-zh-rTW │ │ └── strings.xml │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── io │ └── github │ └── zhitaocai │ └── accessibilitydispatcher │ └── ExampleUnitTest.java ├── settings.gradle ├── static ├── .gitignore └── gif │ ├── auto_create_l2tp.gif │ ├── auto_create_pptp.gif │ ├── auto_install_apk_untill_finish.gif │ ├── auto_install_apk_untill_installing.gif │ ├── auto_install_apk_untill_open.gif │ ├── auto_turn_off_unknown_sources.gif │ ├── auto_turn_on_unknown_sources.gif │ ├── auto_uninstall_apk.gif │ └── auto_uninstall_apk_reject.gif └── targetapk ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src ├── androidTest └── java │ └── io │ └── github │ └── zhitaocai │ └── accessibilitydispatcher │ └── targetapk │ └── ExampleInstrumentedTest.java ├── main ├── AndroidManifest.xml ├── java │ └── io │ │ └── github │ │ └── zhitaocai │ │ └── accessibilitydispatcher │ │ └── targetapk │ │ └── MainActivity.java └── res │ ├── layout │ └── activity_main.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml └── test └── java └── io └── github └── zhitaocai └── accessibilitydispatcher └── targetapk └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /aar 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "wiki"] 2 | path = wiki 3 | url = https://github.com/zhitaocai/AccessibilityDispatcher.wiki.git 4 | branch = master 5 | -------------------------------------------------------------------------------- /ChangeLog.md: -------------------------------------------------------------------------------- 1 | # ChangeLog 2 | 3 | ## 0.4.0 4 | 5 | * 修改使用方式,改为全链式调用 6 | 7 | ## 0.3.0 8 | 9 | * 支持 **原生Android [4.4.4 - 7.0.0]** 自动安装Apk,卸载/防卸载App 10 | * 加入文字模糊搜索,在一定程度上支持其他系统完成 **自动安装Apk** 11 | 12 | ## 0.2.0 13 | 14 | * 支持 **原生Android [4.4.4 - 7.0.0]** 开/关 **允许安装位置来源的应用** 15 | * 加入文字模糊搜索,在一定程度上支持其他系统完成 **允许安装位置来源的应用** 的开/关 16 | * 优化全局自动点击搜索规则 17 | * 优先判断是否有设置目标,如果没有设置目标,则不进行节点搜索 18 | 19 | ## 0.1.0 20 | 21 | * 支持原生Android 4.4.4系统VPN自动配置 -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![](http://progressed.io/bar/15?title=completed) 2 | 3 | # 辅助功能分发器 4 | 5 | 主要实现各种功能的辅助点击,包括但不限: 6 | 7 | * [自动配置VPN教程](https://github.com/zhitaocai/AccessibilityDispatcher/wiki/%E8%87%AA%E5%8A%A8%E9%85%8D%E7%BD%AEVPN) 8 | * [自动开/关 允许安装未知来源](https://github.com/zhitaocai/AccessibilityDispatcher/wiki/%E8%87%AA%E5%8A%A8%E5%BC%80%E5%90%AF%E5%85%81%E8%AE%B8%E5%AE%89%E8%A3%85%E6%9C%AA%E7%9F%A5%E6%9D%A5%E6%BA%90) 9 | * [自动安装APK,自动(防)卸载APP](https://github.com/zhitaocai/AccessibilityDispatcher/wiki/%E8%87%AA%E5%8A%A8%E5%AE%89%E8%A3%85APK) 10 | 11 | ## 效果图 12 | 13 | ![](static/gif/auto_create_pptp.gif) ![](static/gif/auto_create_l2tp.gif) 14 | 15 | ![](static/gif/auto_turn_on_unknown_sources.gif) ![](static/gif/auto_turn_off_unknown_sources.gif) 16 | 17 | ![](static/gif/auto_install_apk_untill_open.gif) ![](static/gif/auto_uninstall_apk.gif) 18 | 19 | ## USAGE 20 | 21 | ### 下载 22 | 23 | ```gradle 24 | 25 | compile 'io.github.zhitaocai:accessibilitydispatcher:0.4.0@aar' 26 | 27 | // or 28 | // 如果你的项目本身已经集成了 support-annotations 29 | // 如果出现兼容问题,可移除本类库中本身所依赖的 support-annotations 30 | compile ('io.github.zhitaocai:accessibilitydispatcher:0.4.0@aar') { 31 | exclude group: 'com.android.support', module: 'support-annotations' 32 | } 33 | 34 | ``` 35 | 36 | ### 具体使用 37 | 38 | 请移步至 [wiki](https://github.com/zhitaocai/AccessibilityDispatcher/wiki) 39 | 40 | 41 | ### 如何获取演示程序? 42 | 43 | 1. 下载项目后在项目根目录运行: 44 | ``` 45 | ./gradlew :app:assembleRelease 46 | ``` 47 | 2. 生成的apk在 ``/static/apk`` 48 | 49 | ## License 50 | 51 | Copyright 2017 Zhitao Cai 52 | 53 | Licensed under the Apache License, Version 2.0 (the "License"); 54 | you may not use this file except in compliance with the License. 55 | You may obtain a copy of the License at 56 | 57 | http://www.apache.org/licenses/LICENSE-2.0 58 | 59 | Unless required by applicable law or agreed to in writing, software 60 | distributed under the License is distributed on an "AS IS" BASIS, 61 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 62 | See the License for the specific language governing permissions and 63 | limitations under the License. -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | 5 | compileSdkVersion 25 6 | buildToolsVersion "25.0.1" 7 | defaultConfig { 8 | applicationId "io.github.zhitaocai.accessibilitydispatcher.demo" 9 | minSdkVersion 14 10 | targetSdkVersion 25 11 | versionCode 1 12 | versionName "1.0" 13 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 14 | manifestPlaceholders = [APPLICATION_ID: applicationId] 15 | } 16 | android { 17 | lintOptions { 18 | checkReleaseBuilds false 19 | // Or, if you prefer, you can continue to check for errors in release builds, 20 | // but continue the build even when errors are found: 21 | abortOnError false 22 | } 23 | } 24 | signingConfigs { 25 | release { 26 | keyAlias '111111' 27 | keyPassword '111111' 28 | storeFile file('keystore.jks') 29 | storePassword '111111' 30 | } 31 | } 32 | buildTypes { 33 | debug { 34 | signingConfig signingConfigs.release 35 | } 36 | release { 37 | minifyEnabled true 38 | zipAlignEnabled true 39 | shrinkResources true 40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | signingConfig signingConfigs.release 42 | } 43 | } 44 | applicationVariants.all { variant -> 45 | if (variant.buildType.name == 'release') { 46 | variant.outputs.each { output -> 47 | if (output.outputFile != null && output.outputFile.name.endsWith('.apk')) { 48 | if (output.outputFile.name.contains(variant.buildType.name)) { 49 | def date = new Date().format('yyyy-MM-dd') 50 | def srcDir = new File(project.rootProject.projectDir, "static/apk") 51 | def apkFile = new File(srcDir, 52 | "AccessibilityDispatcherDemo_${variant.buildType.name}_v${variant.versionName}_${date}.apk") 53 | output.outputFile = apkFile 54 | } 55 | } 56 | } 57 | } 58 | } 59 | } 60 | 61 | dependencies { 62 | testCompile 'junit:junit:4.12' 63 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 64 | exclude group: 'com.android.support', module: 'support-annotations' 65 | }) 66 | annotationProcessor 'com.jakewharton:butterknife-compiler:8.5.1' 67 | compile 'com.jakewharton:butterknife:8.5.1' 68 | compile fileTree(include: ['*.jar'], dir: 'libs') 69 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 70 | compile 'com.android.support:appcompat-v7:25.3.0' 71 | compile 'com.android.support:support-v4:25.3.0' 72 | compile 'com.android.support:design:25.3.0' 73 | 74 | // 依赖线下project 75 | compile project(':library') 76 | 77 | // 依赖线下aar(测试用) 78 | // compile(name: 'accessibilitydispatcher-release-0.2.0', ext: 'aar') 79 | 80 | // 依赖线上版本 81 | //compile 'io.github.zhitaocai:accessibilitydispatcher:0.2.0@aar' 82 | } 83 | 84 | // 依赖线下aar时开启注释(测试用) 85 | //repositories { 86 | // flatDir { 87 | // dirs '../aar' //this way we can find the .aar file in libs folder 88 | // } 89 | //} 90 | -------------------------------------------------------------------------------- /app/keystore.jks: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/keystore.jks -------------------------------------------------------------------------------- /app/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/zhitaocai/accessibilitydispatcher/demo/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo; 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.demo", appContext.getPackageName()); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 24 | 25 | 26 | 27 | 30 | 31 | 32 | 33 | 38 | 39 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /app/src/main/assets/targetapk-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zhitaocai/AccessibilityDispatcher/b3cda66a29da205130425cfbed992e0e00e43e25/app/src/main/assets/targetapk-debug.apk -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/activity/MainActivity.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo.activity; 2 | 3 | import android.content.Intent; 4 | import android.os.Bundle; 5 | import android.provider.Settings; 6 | import android.support.design.widget.TabLayout; 7 | import android.support.v4.view.ViewPager; 8 | import android.support.v7.app.AppCompatActivity; 9 | import android.support.v7.widget.SwitchCompat; 10 | import android.support.v7.widget.Toolbar; 11 | import android.view.Window; 12 | import android.widget.Toast; 13 | 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | import java.util.Locale; 17 | 18 | import butterknife.BindView; 19 | import butterknife.ButterKnife; 20 | import butterknife.OnClick; 21 | import io.github.zhitaocai.accessibilitydispatcher.AccessibilityDispatcher; 22 | import io.github.zhitaocai.accessibilitydispatcher.AccessibilityServiceUtils; 23 | import io.github.zhitaocai.accessibilitydispatcher.demo.R; 24 | import io.github.zhitaocai.accessibilitydispatcher.demo.fragment.AutoApkInstallFragment; 25 | import io.github.zhitaocai.accessibilitydispatcher.demo.fragment.AutoVpnConfigFragment; 26 | import io.github.zhitaocai.accessibilitydispatcher.demo.fragment.BaseFragment; 27 | import io.github.zhitaocai.accessibilitydispatcher.demo.fragment.BasePagerAdapter; 28 | 29 | public class MainActivity extends AppCompatActivity { 30 | 31 | private final static int REQ_ACCESSIBILITY_SERVICE_TURN_ON = 1; 32 | 33 | @BindView(R.id.switch_open_accessibility) SwitchCompat mSwitchCompat; 34 | 35 | @BindView(R.id.toolbar) Toolbar mToolbar; 36 | 37 | @BindView(R.id.tab_layout) TabLayout mTabLayout; 38 | 39 | @BindView(R.id.viewpager) ViewPager mViewpager; 40 | 41 | private BasePagerAdapter mViewPagerAdapter; 42 | 43 | private AutoApkInstallFragment mAutoApkInstallFragment; 44 | 45 | private AutoVpnConfigFragment mAutoVpnConfigFragment; 46 | 47 | @Override 48 | protected void onCreate(Bundle savedInstanceState) { 49 | super.onCreate(savedInstanceState); 50 | supportRequestWindowFeature(Window.FEATURE_NO_TITLE); 51 | setContentView(R.layout.activity_main); 52 | ButterKnife.bind(this); 53 | 54 | initToolbar(); 55 | updateSwitchCompat(); 56 | initViewPageAndTabLayout(); 57 | 58 | // 开启调试log 59 | AccessibilityDispatcher.debugLog(true).withEventSourceLog(false).withClassNameInTag(false); 60 | 61 | } 62 | 63 | private void initToolbar() { 64 | mToolbar.setTitle("AccessibilityDispatcher"); 65 | } 66 | 67 | private void updateSwitchCompat() { 68 | boolean isAccessibilityServiceOn = AccessibilityServiceUtils.isAccessibilityServiceOn(this); 69 | Toast.makeText( 70 | this, 71 | String.format(Locale.getDefault(), "辅助功能%s", isAccessibilityServiceOn ? "已经开启" : "还没有开启"), 72 | Toast.LENGTH_SHORT 73 | ).show(); 74 | mSwitchCompat.setChecked(isAccessibilityServiceOn); 75 | } 76 | 77 | private void initViewPageAndTabLayout() { 78 | mAutoVpnConfigFragment = new AutoVpnConfigFragment(); 79 | mAutoApkInstallFragment = new AutoApkInstallFragment(); 80 | List mLists = new ArrayList<>(); 81 | mLists.add(mAutoVpnConfigFragment); 82 | mLists.add(mAutoApkInstallFragment); 83 | mViewPagerAdapter = new BasePagerAdapter(getSupportFragmentManager(), mLists); 84 | mViewpager.setAdapter(mViewPagerAdapter); 85 | mViewpager.setCurrentItem(0); 86 | mTabLayout.setupWithViewPager(mViewpager); 87 | } 88 | 89 | @Override 90 | protected void onActivityResult(int requestCode, int resultCode, Intent data) { 91 | super.onActivityResult(requestCode, resultCode, data); 92 | switch (requestCode) { 93 | case REQ_ACCESSIBILITY_SERVICE_TURN_ON: 94 | updateSwitchCompat(); 95 | break; 96 | default: 97 | break; 98 | } 99 | } 100 | 101 | @OnClick(R.id.switch_open_accessibility) 102 | protected void openAccessibility() { 103 | startActivityForResult(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), REQ_ACCESSIBILITY_SERVICE_TURN_ON); 104 | } 105 | 106 | } 107 | 108 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/fragment/AutoVpnConfigFragment.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo.fragment; 2 | 3 | import android.content.Intent; 4 | import android.os.Build; 5 | import android.os.Bundle; 6 | import android.provider.Settings; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | 11 | import butterknife.ButterKnife; 12 | import butterknife.OnClick; 13 | import butterknife.Unbinder; 14 | import io.github.zhitaocai.accessibilitydispatcher.AccessibilityHelper; 15 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.OnVpnCallBack; 16 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.OnVpnCallBackAdapter; 17 | import io.github.zhitaocai.accessibilitydispatcher.businss.vpn.VpnTarget; 18 | import io.github.zhitaocai.accessibilitydispatcher.demo.R; 19 | 20 | /** 21 | * @author zhitao 22 | * @since 2017-03-30 14:17 23 | */ 24 | public class AutoVpnConfigFragment extends BaseFragment { 25 | 26 | private final static int REQ_AUTO_CONFIG_VPN = 100; 27 | 28 | private Unbinder mUnBinder; 29 | 30 | @Override 31 | public String getFragmentTitle() { 32 | return "VPN配置"; 33 | } 34 | 35 | @Override 36 | public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 37 | View view = inflater.inflate(R.layout.fragment_auto_config_vpn, container, false); 38 | mUnBinder = ButterKnife.bind(this, view); 39 | return view; 40 | } 41 | 42 | @Override 43 | public void onDestroyView() { 44 | super.onDestroyView(); 45 | mUnBinder.unbind(); 46 | } 47 | 48 | @Override 49 | public void onActivityResult(int requestCode, int resultCode, Intent data) { 50 | super.onActivityResult(requestCode, resultCode, data); 51 | switch (requestCode) { 52 | case REQ_AUTO_CONFIG_VPN: 53 | 54 | // 不管配置是否成功,建议都取消自动配置,这样子,如果用户自己从设置中打开VPN配置的话,我们的自动点击程序就不会影响到用户的操作了 55 | //if (resultCode == RESULT_OK) { } 56 | AccessibilityHelper.vpnHelper().reset().active(); 57 | break; 58 | default: 59 | break; 60 | } 61 | } 62 | 63 | @OnClick(R.id.btn_create_l2tp) 64 | protected void createL2TP() { 65 | // 在打开VPN界面之前,设置我们需要自动操作的内容 66 | AccessibilityHelper.vpnHelper() 67 | 68 | // 创建一个新的或者更新现有的一个L2TP连接 69 | .withTargets(new VpnTarget.Builder().setVpnName("L2TP VPN Demo") 70 | .setAction(VpnTarget.ACTION_CREATE_VPN_CONFIG | 71 | VpnTarget.ACTION_UPDATE_VPN_CONFIG | 72 | VpnTarget.ACTION_INPUT_USER_CONFIG) 73 | .setVpnType(VpnTarget.VpnType.L2TP_IPSec_PSK) 74 | .setVpnServerAddr("1.2.3.4") 75 | .setL2TPSecret("L2TPSecret-test") 76 | .setIPSecIdentifier("IPSecIdentifier-test") 77 | .setIPSecPreSharedKey("IPSecPreSharedKey-test") 78 | .setDnsSearchDomain("8.8.8.8") 79 | .setDnsServers("8.8.8.8") 80 | .setForwardingRoutes("10.0.0.0/8") 81 | .setUserName("username") 82 | .setPassword("password") 83 | .setSaveAccountInfo(true) 84 | .build()) 85 | 86 | // 设置VPN回调过程中的监听Adapter(相比起listener,adapter可以实现你感兴趣的回调而不用全部实现所有接口) 87 | .withCallBacks(new OnVpnCallBackAdapter() { 88 | /** 89 | * 开始配置VPN信息时回调 90 | * 91 | * @param vpnTarget 当前在配置的VPN 92 | */ 93 | @Override 94 | public void onVpnConfigStart(VpnTarget vpnTarget) { 95 | toast("%1$tH:%1$tM:%1$tS 进入VPN配置,当前配置\n%s", System.currentTimeMillis(), vpnTarget.toString()); 96 | } 97 | }) 98 | 99 | // 设置是否自动操作 100 | .enable(true) 101 | 102 | // 将本次设置的内容加入到辅助功能服务,不然你只是在瞎逼逼 103 | .active(); 104 | 105 | startActivity2VpnSettings(); 106 | } 107 | 108 | @OnClick(R.id.btn_create_pptp) 109 | protected void createPPTP() { 110 | 111 | // 在打开VPN界面之前,设置我们需要自动操作的内容 112 | AccessibilityHelper.vpnHelper() 113 | 114 | // 创建一个新的或者更新现有的一个PPTP连接 115 | .withTargets(new VpnTarget.Builder().setVpnName("PPTP VPN Demo") 116 | .setAction(VpnTarget.ACTION_CREATE_VPN_CONFIG | 117 | VpnTarget.ACTION_UPDATE_VPN_CONFIG | 118 | VpnTarget.ACTION_INPUT_USER_CONFIG) 119 | .setVpnType(VpnTarget.VpnType.PPTP) 120 | .setVpnServerAddr("1.2.3.4") 121 | .setDnsSearchDomain("8.8.8.8") 122 | .setDnsServers("8.8.8.8") 123 | .setForwardingRoutes("10.0.0.0/8") 124 | .setUserName("username") 125 | .setPassword("password") 126 | .setSaveAccountInfo(true) 127 | .build()) 128 | 129 | // 设置VPN回调过程中的监听Listener 130 | .withCallBacks(new OnVpnCallBack() { 131 | /** 132 | * 开始配置VPN信息时回调 133 | * 134 | * @param vpnTarget 当前在配置的VPN 135 | */ 136 | @Override 137 | public void onVpnConfigStart(VpnTarget vpnTarget) { 138 | toast("%1$tH:%1$tM:%1$tS 进入VPN配置", System.currentTimeMillis()); 139 | } 140 | 141 | /** 142 | * 配置VPN信息结束时回调 143 | * 144 | * @param vpnTarget 当前在配置的VPN 145 | */ 146 | @Override 147 | public void onVpnConfigFinish(VpnTarget vpnTarget) { 148 | toast("%1$tH:%1$tM:%1$tS 完成VPN配置", System.currentTimeMillis()); 149 | } 150 | 151 | /** 152 | * 开始配置用户信息时回调 153 | * 154 | * @param vpnTarget 当前在配置的VPN 155 | */ 156 | @Override 157 | public void onUserConfigStart(VpnTarget vpnTarget) { 158 | toast("%1$tH:%1$tM:%1$tS 进入用户配置", System.currentTimeMillis()); 159 | } 160 | 161 | /** 162 | * 配置用户信息结束时回调 163 | * 164 | * @param vpnTarget 当前在配置的VPN 165 | */ 166 | @Override 167 | public void onUserConfigFinish(VpnTarget vpnTarget) { 168 | toast("%1$tH:%1$tM:%1$tS 完成用户配置", System.currentTimeMillis()); 169 | } 170 | }) 171 | 172 | // 设置是否自动操作 173 | .enable(true) 174 | 175 | // 将本次设置的内容加入到辅助功能服务,不然你只是在瞎逼逼 176 | .active(); 177 | 178 | startActivity2VpnSettings(); 179 | } 180 | 181 | private void startActivity2VpnSettings() { 182 | Intent intent; 183 | if (Build.VERSION.SDK_INT > Build.VERSION_CODES.N) { 184 | intent = new Intent(Settings.ACTION_VPN_SETTINGS); 185 | } else { 186 | intent = new Intent(); 187 | intent.setAction("android.net.vpn.SETTINGS"); 188 | } 189 | startActivityForResult(intent, REQ_AUTO_CONFIG_VPN); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/fragment/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo.fragment; 2 | 3 | import android.support.annotation.Nullable; 4 | import android.support.v4.app.Fragment; 5 | import android.widget.Toast; 6 | 7 | import java.util.Locale; 8 | 9 | /** 10 | * @author zhitao 11 | * @since 2017-03-30 15:03 12 | */ 13 | public abstract class BaseFragment extends Fragment { 14 | 15 | /** 16 | * tab 所显示的title 17 | * 18 | * @return 19 | */ 20 | @Nullable 21 | public abstract String getFragmentTitle(); 22 | 23 | protected void runOnUiThread(Runnable runnable) { 24 | getActivity().runOnUiThread(runnable); 25 | } 26 | 27 | protected void toast(final String format, final Object... args) { 28 | runOnUiThread(new Runnable() { 29 | @Override 30 | public void run() { 31 | Toast.makeText(getActivity(), String.format(Locale.getDefault(), format, args), Toast.LENGTH_SHORT).show(); 32 | } 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/fragment/BasePagerAdapter.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo.fragment; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentPagerAdapter; 6 | 7 | import java.util.List; 8 | 9 | /** 10 | * @author zhitao 11 | * @since 2017-02-02 18:38 12 | */ 13 | public class BasePagerAdapter extends FragmentPagerAdapter { 14 | 15 | private List mFragments; 16 | 17 | public BasePagerAdapter(FragmentManager fm, List fragments) { 18 | super(fm); 19 | mFragments = fragments; 20 | } 21 | 22 | @Override 23 | public Fragment getItem(int position) { 24 | return mFragments == null ? null : mFragments.get(position); 25 | } 26 | 27 | @Override 28 | public int getCount() { 29 | return mFragments == null ? 0 : mFragments.size(); 30 | } 31 | 32 | @Override 33 | public CharSequence getPageTitle(int position) { 34 | if (mFragments == null || mFragments.isEmpty()) { 35 | return "N/A"; 36 | } 37 | return mFragments.get(position).getFragmentTitle(); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/service/AccessibilityServiceDemo.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo.service; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.accessibilityservice.AccessibilityServiceInfo; 5 | import android.os.Build; 6 | import android.view.accessibility.AccessibilityEvent; 7 | 8 | import io.github.zhitaocai.accessibilitydispatcher.AccessibilityDispatcher; 9 | 10 | public class AccessibilityServiceDemo extends AccessibilityService { 11 | 12 | /** 13 | * This method is a part of the {@link AccessibilityService} lifecycle and is 14 | * called after the system has successfully bound to the service. If is 15 | * convenient to use this method for setting the {@link AccessibilityServiceInfo}. 16 | * 17 | * @see AccessibilityServiceInfo 18 | * @see #setServiceInfo(AccessibilityServiceInfo) 19 | */ 20 | @Override 21 | protected void onServiceConnected() { 22 | super.onServiceConnected(); 23 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 24 | AccessibilityDispatcher.onServiceConnected(this); 25 | } 26 | } 27 | 28 | /** 29 | * Callback for {@link AccessibilityEvent}s. 30 | * 31 | * @param event The new event. This event is owned by the caller and cannot be used after 32 | * this method returns. Services wishing to use the event after this method returns should 33 | * make a copy. 34 | */ 35 | @Override 36 | public void onAccessibilityEvent(AccessibilityEvent event) { 37 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 38 | AccessibilityDispatcher.onAccessibilityEvent(this, event); 39 | } 40 | } 41 | 42 | /** 43 | * Callback for interrupting the accessibility feedback. 44 | */ 45 | @Override 46 | public void onInterrupt() { 47 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { 48 | AccessibilityDispatcher.onInterrupt(this); 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/utils/FileUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo.utils; 2 | 3 | import android.content.Context; 4 | import android.text.TextUtils; 5 | 6 | import java.io.File; 7 | import java.io.FileInputStream; 8 | import java.io.FileOutputStream; 9 | import java.io.InputStream; 10 | import java.io.OutputStream; 11 | 12 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog; 13 | 14 | /** 15 | * 文件操作类 16 | * 17 | * @author jen 18 | */ 19 | public class FileUtils { 20 | 21 | private final static int BUFFER = 1024; 22 | 23 | /** 24 | * 改变原始文件的权限 25 | * 26 | * @param file 27 | * @param destFilePermission 28 | * 29 | * @return 30 | */ 31 | public static boolean chmod(File file, String destFilePermission) { 32 | try { 33 | 34 | if (file == null) { 35 | return false; 36 | } 37 | 38 | if (!file.exists()) { 39 | return false; 40 | } 41 | 42 | if (destFilePermission != null) { 43 | DLog.i("chmod file: %s permission is %s", file.getAbsolutePath(), destFilePermission); 44 | 45 | StringBuilder sb = new StringBuilder(100); 46 | sb.append("chmod ").append(destFilePermission).append(" ").append(file.getAbsolutePath()); 47 | String cmd = sb.toString(); 48 | Runtime.getRuntime().exec(cmd); 49 | 50 | DLog.i("chmod cmd is:[%s]", destFilePermission); 51 | return true; 52 | } 53 | } catch (Throwable e) { 54 | DLog.e(e); 55 | } 56 | return false; 57 | 58 | } 59 | 60 | /** 61 | * 移动文件,成功之后会删除原始文件 62 | * 63 | * @param srcFile 64 | * @param destFile 65 | * 66 | * @return 67 | */ 68 | public static boolean mv(File srcFile, File destFile) { 69 | try { 70 | 71 | if (srcFile == null || destFile == null) { 72 | DLog.i("move file failed: src file or dest file is null"); 73 | return false; 74 | } 75 | 76 | if (!srcFile.exists()) { 77 | DLog.i("move file failed: src file is exists == false"); 78 | return false; 79 | } 80 | 81 | if (srcFile.renameTo(destFile)) { 82 | DLog.i("move file success: srcFile.renameTo destFile"); 83 | return true; 84 | } 85 | 86 | if (cp(srcFile, destFile)) { 87 | DLog.i("move file: copy file success"); 88 | 89 | try { 90 | // 删除原始文件 91 | if (srcFile.delete()) { 92 | DLog.i("move file: delete src file success"); 93 | } else { 94 | DLog.i("move file: delete src file failed"); 95 | } 96 | } catch (Throwable e) { 97 | DLog.e(e); 98 | } 99 | return true; 100 | } 101 | } catch (Throwable e) { 102 | DLog.e(e); 103 | } 104 | 105 | return false; 106 | } 107 | 108 | /** 109 | * 复制文件 110 | * 111 | * @param srcFile 112 | * @param destFile 113 | * 114 | * @return 115 | */ 116 | public static boolean cp(File srcFile, File destFile) { 117 | 118 | FileOutputStream fos = null; 119 | FileInputStream fis = null; 120 | 121 | long startTime = System.currentTimeMillis(); 122 | long fileLen = 0; 123 | String fileNameSrc = null; 124 | String fileNameDest = null; 125 | try { 126 | if (srcFile == null) { 127 | return false; 128 | } 129 | 130 | if (!srcFile.exists()) { 131 | return false; 132 | } 133 | 134 | if (destFile == null) { 135 | return false; 136 | } 137 | try { 138 | 139 | fileLen = srcFile.length(); 140 | fileNameSrc = srcFile.getAbsolutePath(); 141 | fileNameDest = destFile.getAbsolutePath(); 142 | 143 | } catch (Throwable e) { 144 | DLog.e(e); 145 | } 146 | 147 | fis = new FileInputStream(srcFile); 148 | fos = new FileOutputStream(destFile); 149 | 150 | byte[] buff = new byte[1024]; 151 | int len = 0; 152 | 153 | while ((len = fis.read(buff)) > 0) { 154 | fos.write(buff, 0, len); 155 | } 156 | 157 | fos.flush(); 158 | fos.close(); 159 | fos = null; 160 | return true; 161 | 162 | } catch (Throwable e) { 163 | DLog.e(e); 164 | } finally { 165 | IoUtils.close(fos); 166 | IoUtils.close(fis); 167 | long nt = System.currentTimeMillis(); 168 | long span = nt - startTime; 169 | DLog.i("copy file from [%s] to [%s] , length is [%d] B , cost [%d] ms", fileNameSrc, fileNameDest, fileLen, span); 170 | } 171 | return false; 172 | } 173 | 174 | /** 175 | * 判断assets中是否存在指定的文件 176 | * 177 | * @param context 178 | * @param fileName 179 | * 180 | * @return 181 | */ 182 | public static boolean isFileExistAssets(Context context, String fileName) { 183 | try { 184 | if (context == null || TextUtils.isEmpty(fileName)) { 185 | return false; 186 | } 187 | // faster than getting the whole asset's folder as list, and check the containment 188 | InputStream is = context.getAssets().open(fileName); 189 | if (is != null) { 190 | IoUtils.close(is); 191 | return true; 192 | } else { 193 | return false; 194 | } 195 | } catch (Throwable e) { 196 | DLog.e(e); 197 | return false; 198 | } 199 | } 200 | 201 | /** 202 | * (请使用线程执行)从Assets中复制文件 203 | * 204 | * @param context 205 | * @param srcFileName 206 | * @param destFile 207 | * 208 | * @return 209 | */ 210 | public static boolean cpFromAssets(Context context, String srcFileName, File destFile) { 211 | InputStream inputStream = null; 212 | FileOutputStream outputStream = null; 213 | try { 214 | if (context == null || srcFileName == null || destFile == null) { 215 | return false; 216 | } 217 | 218 | inputStream = context.getAssets().open(srcFileName); 219 | outputStream = new FileOutputStream(destFile); 220 | int len = 0; 221 | byte[] buff = new byte[1024]; 222 | while ((len = inputStream.read(buff)) > 0) { 223 | outputStream.write(buff, 0, len); 224 | } 225 | 226 | outputStream.flush(); 227 | outputStream.close(); 228 | outputStream = null; 229 | 230 | return true; 231 | 232 | } catch (Throwable e) { 233 | DLog.e(e); 234 | } finally { 235 | IoUtils.close(outputStream); 236 | IoUtils.close(inputStream); 237 | } 238 | return false; 239 | } 240 | 241 | /** 242 | * (同步)支持删除文件或者文件夹,使用时请注意启用线程 243 | *

244 | * 使用时请注意启用线程 245 | * 246 | * @param filePath 要删除的文件或者目录 247 | * 248 | * @return true or false 249 | */ 250 | public static boolean delete(String filePath) { 251 | if (TextUtils.isEmpty(filePath)) { 252 | return false; 253 | } 254 | return delete(new File(filePath)); 255 | } 256 | 257 | /** 258 | * (同步)支持删除文件或者文件夹,使用时请注意启用线程 259 | *

260 | * 使用时请注意启用线程 261 | * 262 | * @param file 要删除的文件或者目录 263 | * 264 | * @return true or false 265 | */ 266 | public static boolean delete(File file) { 267 | try { 268 | if (file == null) { 269 | return false; 270 | } 271 | if (file.exists()) { 272 | if (file.isFile()) { 273 | boolean isSuccess = file.delete(); 274 | if (isSuccess) { 275 | DLog.i("删除成功: %s", file.getAbsolutePath()); 276 | } else { 277 | DLog.e("删除失败: %s", file.getAbsolutePath()); 278 | } 279 | return isSuccess; 280 | } else if (file.isDirectory()) { 281 | for (File f : file.listFiles()) { 282 | if (!delete(f)) { 283 | return false; 284 | } 285 | } 286 | boolean isSuccess = file.delete(); 287 | if (isSuccess) { 288 | DLog.i("删除成功: %s", file.getAbsolutePath()); 289 | } else { 290 | DLog.e("删除失败: %s", file.getAbsolutePath()); 291 | } 292 | return isSuccess; 293 | } 294 | } else { 295 | // 因为最终目的是令该文件不存在,所以如果文件一开始就不存在,那么也就意味着删除成功 296 | return true; 297 | } 298 | } catch (Throwable e) { 299 | DLog.e(e); 300 | } 301 | return false; 302 | } 303 | 304 | /** 305 | * 获取指定的路径的文件,如果没有会创建(支持自动补全所有父目录) 306 | * 307 | * @param path 文件路径 308 | */ 309 | public final static File getVaildFile(String path) { 310 | return getVaildFile(new File(path)); 311 | } 312 | 313 | /** 314 | * 获取指定的路径的文件,如果没有会创建(支持自动补全所有父目录) 315 | * 316 | * @param file 文件 317 | */ 318 | public final static File getVaildFile(File file) { 319 | try { 320 | if (file == null) { 321 | return null; 322 | } 323 | if (file.exists()) { 324 | // 如果文件存在 325 | if (file.isFile()) { 326 | return file; 327 | } 328 | if (file.isDirectory()) { 329 | return null; 330 | } 331 | } else { 332 | // 如果文件不存在,则创建 333 | 334 | // 检查是否存在父目录 335 | // 如果不存在父目录的话, 自动补全所有的根目录,然后创建 336 | if (!file.getParentFile().exists()) { 337 | 338 | DLog.w("当前文件[%s]不存在父目录[%s],将补全", file.getAbsolutePath(), file.getParent()); 339 | boolean isMkdirsSuccess = file.getParentFile().mkdirs(); 340 | 341 | if (!isMkdirsSuccess) { 342 | DLog.w("补全父目录[%s]失败", file.getParent()); 343 | return null; 344 | } else { 345 | if (!file.getParentFile().exists()) { 346 | DLog.w("补全父目录[%s]失败", file.getParent()); 347 | return null; 348 | } 349 | DLog.i("补全父目录[%s]成功", file.getParent()); 350 | } 351 | } 352 | boolean isSuccess = file.createNewFile(); 353 | if (isSuccess) { 354 | return file; 355 | } else { 356 | return null; 357 | } 358 | } 359 | } catch (Throwable e) { 360 | DLog.e(e); 361 | } 362 | return null; 363 | } 364 | 365 | @Deprecated 366 | public static boolean copyStream(InputStream inputStream, OutputStream outputStream) { 367 | try { 368 | 369 | if (inputStream == null) { 370 | return false; 371 | } 372 | 373 | if (outputStream == null) { 374 | return false; 375 | } 376 | 377 | int len = 0; 378 | 379 | } catch (Throwable e) { 380 | DLog.e(e); 381 | } 382 | return false; 383 | } 384 | 385 | } 386 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/utils/IoUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2012-2015 Jason Fang ( ifangyucun@gmail.com ) 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package io.github.zhitaocai.accessibilitydispatcher.demo.utils; 17 | 18 | import android.database.Cursor; 19 | 20 | import java.io.BufferedReader; 21 | import java.io.Closeable; 22 | import java.io.File; 23 | import java.io.FileInputStream; 24 | import java.io.FileOutputStream; 25 | import java.io.IOException; 26 | import java.io.InputStream; 27 | import java.io.InputStreamReader; 28 | import java.io.OutputStream; 29 | import java.io.RandomAccessFile; 30 | import java.io.Reader; 31 | import java.io.Writer; 32 | import java.net.ServerSocket; 33 | import java.net.Socket; 34 | 35 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog; 36 | 37 | public final class IoUtils { 38 | 39 | public static void close(Object... objs) { 40 | for (Object obj : objs) { 41 | close(obj); 42 | } 43 | } 44 | 45 | public static void closeOS(OutputStream os) { 46 | if (os != null) { 47 | try { 48 | os.close(); 49 | } catch (IOException e) { 50 | DLog.e(e); 51 | } 52 | } 53 | } 54 | 55 | public static void closeIS(InputStream is) { 56 | if (is != null) { 57 | try { 58 | is.close(); 59 | } catch (IOException e) { 60 | DLog.e(e); 61 | } 62 | } 63 | } 64 | 65 | public static void closeReader(Reader reader) { 66 | if (reader != null) { 67 | try { 68 | reader.close(); 69 | } catch (IOException e) { 70 | DLog.e(e); 71 | } 72 | } 73 | } 74 | 75 | public static void closeWriter(Writer writer) { 76 | if (writer != null) { 77 | try { 78 | writer.close(); 79 | } catch (IOException e) { 80 | DLog.e(e); 81 | } 82 | } 83 | } 84 | 85 | public static void closeFile(RandomAccessFile file) { 86 | if (file != null) { 87 | try { 88 | file.close(); 89 | } catch (IOException e) { 90 | DLog.e(e); 91 | } 92 | } 93 | } 94 | 95 | public static void closeSocket(Socket socket) { 96 | if (socket != null) { 97 | if (socket.isConnected()) { 98 | try { 99 | socket.close(); 100 | } catch (IOException e) { 101 | DLog.e(e); 102 | } 103 | } 104 | } 105 | } 106 | 107 | public static void closeServerSocket(ServerSocket socket) { 108 | if (socket != null && !socket.isClosed()) { 109 | try { 110 | socket.close(); 111 | } catch (IOException e) { 112 | DLog.e(e); 113 | } 114 | } 115 | } 116 | 117 | public static void closeProcess(Process process) { 118 | if (process != null) { 119 | process.destroy(); 120 | } 121 | } 122 | 123 | public static void closeCursor(Cursor cursor) { 124 | if (cursor != null && !cursor.isClosed()) { 125 | cursor.close(); 126 | } 127 | } 128 | 129 | public static void close(Closeable closeable) { 130 | if (closeable != null) { 131 | try { 132 | closeable.close(); 133 | } catch (IOException e) { 134 | DLog.e(e); 135 | } 136 | } 137 | } 138 | 139 | private static void close(Object obj) { 140 | if (obj == null) { 141 | return; 142 | } 143 | 144 | if (obj instanceof InputStream) { 145 | closeIS((InputStream) obj); 146 | } else if (obj instanceof OutputStream) { 147 | closeOS((OutputStream) obj); 148 | } else if (obj instanceof Writer) { 149 | closeWriter((Writer) obj); 150 | } else if (obj instanceof Reader) { 151 | closeReader((Reader) obj); 152 | } else if (obj instanceof RandomAccessFile) { 153 | closeFile((RandomAccessFile) obj); 154 | } else if (obj instanceof Socket) { 155 | closeSocket((Socket) obj); 156 | } else if (obj instanceof ServerSocket) { 157 | closeServerSocket((ServerSocket) obj); 158 | } else if (obj instanceof Process) { 159 | closeProcess((Process) obj); 160 | } else if (obj instanceof Cursor) { 161 | closeCursor((Cursor) obj); 162 | } else if (obj instanceof Closeable) { 163 | close((Closeable) obj); 164 | } else { 165 | DLog.e("不支持的关闭!"); 166 | throw new RuntimeException("不支持的关闭!"); 167 | } 168 | } 169 | 170 | public static InputStream getPhoneLogs() throws IOException, InterruptedException { 171 | ProcessBuilder builder = new ProcessBuilder("logcat", "-d"); 172 | builder.redirectErrorStream(true); 173 | Process process = builder.start(); 174 | //process.waitFor(); 175 | return process.getInputStream(); 176 | } 177 | 178 | public static void copyFile(File src, File dest) throws IOException { 179 | InputStream in = new FileInputStream(src); 180 | OutputStream out = new FileOutputStream(dest); 181 | byte[] buf = new byte[1024]; 182 | int len; 183 | while ((len = in.read(buf)) > 0) { 184 | out.write(buf, 0, len); 185 | } 186 | in.close(); 187 | out.close(); 188 | 189 | } 190 | 191 | @SuppressWarnings("TryFinallyCanBeTryWithResources") 192 | public static void writeByteArrayToFile(File file, byte[] bytes) throws IOException { 193 | FileOutputStream fout = new FileOutputStream(file); 194 | try { 195 | fout.write(bytes); 196 | } finally { 197 | fout.close(); 198 | } 199 | } 200 | 201 | /** 202 | * Get string from stream! 203 | */ 204 | public static String getStringFromStream(InputStream in) { 205 | if (in == null) { 206 | return null; 207 | } 208 | 209 | BufferedReader reader = new BufferedReader(new InputStreamReader(in), 16 * 1024); 210 | StringBuilder text = new StringBuilder(); 211 | String line; 212 | try { 213 | while ((line = reader.readLine()) != null) { 214 | text.append(line); 215 | text.append("\n"); 216 | } 217 | } catch (IOException e) { 218 | DLog.e(e); 219 | } finally { 220 | close(reader, in); 221 | } 222 | return text.toString(); 223 | } 224 | 225 | private IoUtils() {/*Do not new me*/} 226 | } 227 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/zhitaocai/accessibilitydispatcher/demo/utils/PermissionUtils.java: -------------------------------------------------------------------------------- 1 | package io.github.zhitaocai.accessibilitydispatcher.demo.utils; 2 | 3 | import android.content.Context; 4 | import android.content.pm.PackageManager; 5 | import android.os.Build; 6 | import android.provider.Settings; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | import io.github.zhitaocai.accessibilitydispatcher.log.DLog; 13 | 14 | /** 15 | * @author zhitao 16 | * @since 2017-03-30 15:59 17 | */ 18 | public class PermissionUtils { 19 | 20 | /** 21 | * 检查自身应用是否已经允许某个权限 support api 23+ @since 2015-12-09 22 | * 23 | * @param context 24 | * @param permission see {@link android.Manifest} 25 | * 26 | * @return 27 | */ 28 | public static boolean isPermissionGranted(Context context, String permission) { 29 | return isPermissionGranted(context, context.getPackageName(), permission); 30 | } 31 | 32 | /** 33 | * 检查某个应用是否已经允许某个权限 support api 23+ @since 2015-12-09 34 | * 35 | * @param context 36 | * @param pkgName 37 | * @param permission see {@link android.Manifest} 38 | * 39 | * @return 40 | */ 41 | public static boolean isPermissionGranted(Context context, String pkgName, String permission) { 42 | try { 43 | return PackageManager.PERMISSION_GRANTED == context.getPackageManager().checkPermission(permission, pkgName); 44 | } catch (Throwable e) { 45 | DLog.e(e); 46 | } 47 | return false; 48 | } 49 | 50 | /** 51 | * 获取开发者在AndroidManifest.xml文件中声明的所有权限信息,注意:仅仅是获取是否有没有在AndroidManifest.xml中配置,并不是是否已经被允许了 52 | * {@link #isPermissionGranted(Context, String, String)} 方法是不能判断到下面这种权限的存在的 53 | *

54 | 	 * 
57 | 	 * 
58 | * 因此就存在了这种一次性获取所有权限的,然后进行contains的方法来进行判断是否拥有某个权限的方法 59 | * 60 | * @param context 61 | * 62 | * @return 63 | */ 64 | public static List 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 |