├── .gitignore
├── DONATE.md
├── HOW_TO_USE.md
├── LICENSE
├── PRIVACY.md
├── README.md
├── easycontrol
├── app
│ ├── build.gradle
│ ├── proguard-rules.pro
│ ├── schemas
│ │ └── top.saymzx.easycontrol.app.helper.SQLDatabase
│ │ │ └── 1.json
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── ic_launcher_dev-playstore.png
│ │ ├── java
│ │ └── top
│ │ │ └── saymzx
│ │ │ └── easycontrol
│ │ │ └── app
│ │ │ ├── ActiveActivity.java
│ │ │ ├── AdbKeyActivity.java
│ │ │ ├── DeviceDetailActivity.java
│ │ │ ├── IpActivity.java
│ │ │ ├── MainActivity.java
│ │ │ ├── SetActivity.java
│ │ │ ├── UsbActivity.java
│ │ │ ├── adb
│ │ │ ├── Adb.java
│ │ │ ├── AdbBase64.java
│ │ │ ├── AdbChannel.java
│ │ │ ├── AdbKeyPair.java
│ │ │ ├── AdbProtocol.java
│ │ │ ├── TcpChannel.java
│ │ │ └── UsbChannel.java
│ │ │ ├── buffer
│ │ │ ├── Buffer.java
│ │ │ ├── BufferNew.java
│ │ │ └── BufferStream.java
│ │ │ ├── client
│ │ │ ├── Client.java
│ │ │ ├── decode
│ │ │ │ ├── AudioDecode.java
│ │ │ │ ├── DecodecTools.java
│ │ │ │ └── VideoDecode.java
│ │ │ ├── tools
│ │ │ │ ├── AdbTools.java
│ │ │ │ ├── ClientController.java
│ │ │ │ ├── ClientPlayer.java
│ │ │ │ ├── ClientStream.java
│ │ │ │ └── ControlPacket.java
│ │ │ └── view
│ │ │ │ ├── FullActivity.java
│ │ │ │ ├── MiniView.java
│ │ │ │ ├── MyViewForSmallView.java
│ │ │ │ └── SmallView.java
│ │ │ ├── entity
│ │ │ ├── AppData.java
│ │ │ ├── Device.java
│ │ │ ├── MyInterface.java
│ │ │ └── Setting.java
│ │ │ └── helper
│ │ │ ├── DbHelper.java
│ │ │ ├── DeviceListAdapter.java
│ │ │ ├── MyBroadcastReceiver.java
│ │ │ ├── PublicTools.java
│ │ │ └── ViewTools.java
│ │ └── res
│ │ ├── drawable
│ │ ├── ashbin.xml
│ │ ├── auto.xml
│ │ ├── background_cron.xml
│ │ ├── background_cron_stroke.xml
│ │ ├── background_round.xml
│ │ ├── bars.xml
│ │ ├── caret_left.xml
│ │ ├── chevron_down.xml
│ │ ├── chevron_left.xml
│ │ ├── chevron_right.xml
│ │ ├── dir_floder.xml
│ │ ├── editor.xml
│ │ ├── equals.xml
│ │ ├── expand.xml
│ │ ├── horizontal_rotate.xml
│ │ ├── ic_launcher_dev_foreground.xml
│ │ ├── ic_launcher_foreground.xml
│ │ ├── keyboard.xml
│ │ ├── lightbulb.xml
│ │ ├── lightbulb_off.xml
│ │ ├── link.xml
│ │ ├── main_background.webp
│ │ ├── minus.xml
│ │ ├── not_equal.xml
│ │ ├── o.xml
│ │ ├── plus.xml
│ │ ├── power.xml
│ │ ├── refresh.xml
│ │ ├── rss.xml
│ │ ├── scansion.xml
│ │ ├── share.xml
│ │ ├── square.xml
│ │ ├── un_auto.xml
│ │ ├── wifi.xml
│ │ ├── window_restore.xml
│ │ └── x.xml
│ │ ├── layout-land
│ │ ├── activity_full.xml
│ │ └── activity_main.xml
│ │ ├── layout
│ │ ├── activity_active.xml
│ │ ├── activity_adb_key.xml
│ │ ├── activity_device_detail.xml
│ │ ├── activity_full.xml
│ │ ├── activity_ip.xml
│ │ ├── activity_main.xml
│ │ ├── activity_set.xml
│ │ ├── item_devices_item.xml
│ │ ├── item_loading.xml
│ │ ├── item_request_permission.xml
│ │ ├── item_scan_address_list.xml
│ │ ├── item_set_device.xml
│ │ ├── item_spinner.xml
│ │ ├── item_spinner_item.xml
│ │ ├── item_switch.xml
│ │ ├── item_text.xml
│ │ ├── module_dialog.xml
│ │ ├── module_mini_view.xml
│ │ └── module_small_view.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ ├── ic_launcher_dev.xml
│ │ ├── ic_launcher_dev_round.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_dev.webp
│ │ ├── ic_launcher_dev_round.webp
│ │ └── ic_launcher_round.png
│ │ ├── values-en
│ │ └── strings.xml
│ │ ├── values-night
│ │ └── color.xml
│ │ ├── values
│ │ ├── color.xml
│ │ ├── ic_launcher_background.xml
│ │ ├── ic_launcher_dev_background.xml
│ │ ├── size.xml
│ │ └── strings.xml
│ │ └── xml
│ │ └── device_filter.xml
├── gradle.properties
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── local.properties
├── server
│ ├── build.gradle
│ ├── proguard-rules.pro
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── aidl
│ │ └── android
│ │ │ ├── content
│ │ │ └── IOnPrimaryClipChangedListener.aidl
│ │ │ └── view
│ │ │ └── IRotationWatcher.aidl
│ │ └── java
│ │ └── top
│ │ └── saymzx
│ │ └── easycontrol
│ │ └── server
│ │ ├── Server.java
│ │ ├── entity
│ │ ├── Device.java
│ │ ├── DisplayInfo.java
│ │ ├── Options.java
│ │ ├── Pointer.java
│ │ └── PointersState.java
│ │ ├── helper
│ │ ├── AudioCapture.java
│ │ ├── AudioEncode.java
│ │ ├── ControlPacket.java
│ │ ├── EncodecTools.java
│ │ ├── FakeContext.java
│ │ └── VideoEncode.java
│ │ └── wrappers
│ │ ├── ClipboardManager.java
│ │ ├── DisplayManager.java
│ │ ├── InputManager.java
│ │ ├── SurfaceControl.java
│ │ └── WindowManager.java
└── settings.gradle
└── pic
├── icon
├── icon.svg
└── icon.webp
├── other
├── alipay.webp
├── qq_issue.webp
├── wechat.webp
└── wechat_issue.png
├── screenshot
├── 1.jpg
├── 2.jpg
├── 3.jpg
├── 4.jpg
├── 5.jpg
├── 6.jpg
├── 7.jpg
└── 8.jpg
└── tips
├── full.webp
├── mini.webp
├── order_alipay.webp
├── order_wechat.webp
├── small.webp
└── src
├── full.drawio
├── mini.drawio
└── small.drawio
/.gitignore:
--------------------------------------------------------------------------------
1 | easycontrol/*.iml
2 | easycontrol/.gradle
3 | easycontrol/.idea
4 | easycontrol/local.properties
5 |
6 | easycontrol/app/build
7 | easycontrol/app/.gradle
8 | easycontrol/app/debug
9 | easycontrol/app/release
10 | easycontrol/app/src/main/res/raw/easycontrol_server.jar
11 |
12 | easycontrol/server/build
13 | easycontrol/server/.gradle
14 |
15 | easycontrol/app/src/main/java/top/saymzx/easycontrol/app/helper/ActiveHelper.java
16 | easycontrol/cloud
17 |
--------------------------------------------------------------------------------
/DONATE.md:
--------------------------------------------------------------------------------
1 | # 易控(Easycontrol)捐赠页面
2 |
3 | ## 说明
4 | 受限于开发成本,易控需要收费,但代码仍属于开源状态,您如果有能力,可以自行下载编译,若不像操这个心或者单纯喜欢我 ꈍ◡ꈍ,可以在下方捐赠我。
5 |
6 | ## 激活
7 | 请捐赠35元(5美元)及以上后,保存捐赠订单号,我们将在24小时内录入系统中,届时您可以通过输入订单号激活软件。
8 | * 2024.2.1-2024.2.15 期间30元
9 | 订单号位置:
10 |
11 |
12 |
13 | ## 捐赠
14 |
15 |
16 |
--------------------------------------------------------------------------------
/HOW_TO_USE.md:
--------------------------------------------------------------------------------
1 | # 易控(Easycontrol)使用说明
2 |
3 | ## 视频教程(视频版本更新较慢,请以文本说明为准)
4 | 1. [视频](https://www.bilibili.com/video/BV1V2421A7zf/)
5 |
6 | ## 准备操作
7 | 1. 被控端手机连续点击关于手机-版本号,直至提示打开开发者选项
8 | 2. 被控端手机设置中找到开发者选项
9 | - 打开“USB调试”
10 | - 打开“停用ADB授权超时功能”
11 | - 打开“USB调试(安全调试)”(MIUI设备)
12 | - 打开“USB安装”(如果有则打开)
13 | - 打开“关闭权限监控”(如果有则打开)
14 | 3. 重启被控端手机
15 |
16 | ## 软件使用
17 | 1. 简单使用-有线连接
18 | 1. 主控端安装易控,打开软件进行悬浮窗授权
19 | 2. 利用数据线将主控端与被控端连接,主控端易控界面允许易控访问设备
20 | 3. 点击主控端易控列表中第一行出现的新设备
21 | 4. 被控端授权允许主控端连接(请勾选一律允许)
22 |
23 | 2. 简单使用-无线连接
24 | 1. 主控端安装易控,打开软件进行悬浮窗授权
25 | 2. 确保主控端能够访问被控端(例如在一个wifi下面)
26 | 3. 打开被控端无线调试(并非开发者选项中的无线调试),可使用上面有线连接,随后长按设备点击“打开无线”按钮实现
27 | 4. 主控端易控界面点击右上角添加设备,在设备地址处输入被控端地址,地址格式:
28 | - IPv4:192.168.43.1:5555
29 | - IPv6:[2408:8462:2510:1e05:c39:3262:632d:1a3d]:5555
30 | - 域名:ex.com:5555
31 | 5. 点击刚添加的新设备,被控端授权允许主控端连接(请勾选一律允许)
32 |
33 | 3. 单应用投屏
34 | 1. 连接普通的镜像投屏后,打开某一个软件,点击工具栏中的向外分离的图标,将当前应用分离出来进行投屏
35 | 2. 也可以在设备设置页面,在设备的地址后面加上“#包名”,如“192.168.43.1:5555#com.baidu.BaiduMap”
36 | 3. 单应用投屏后,若在主屏幕打开对应应用,因系统不同而出现不同的情况,有可能软件会切回到主屏幕,单应用投屏的画面会卡主,也有可能会无反应
37 | 4. 单应用投屏时,如果该应用崩溃,或多次返回导致软件退出等,画面会卡在最后一帧,属于正常现象
38 |
39 | 3. 界面使用
40 | 1. 工具栏
41 | - 在小窗模式下点击上横条,可查看工具栏
42 | - 在全屏模式下点击导航栏更多按钮,可查看工具栏
43 | 2. 小窗模式
44 | - 可通过拖动横条移动小窗
45 | - 拖动右下角可更改小窗大小
46 | 3. 最小化模式
47 | - 可上下拖动
48 | - 单击返回小窗模式
49 | 4. 全屏模式
50 | - 底部为导航栏,可向被控端发送多任务、桌面、返回按键
51 | - 导航栏左边旋转按钮,可以控制被控端页面旋转,仅请求旋转,实际是否旋转看被控端当前应用是否允许
52 | - 全屏页面方向跟随手机重力方向(可在工具栏中锁定)
53 | 5. 图示版:
54 |
55 |
56 |
57 |
58 |
59 | 3. 高级使用
60 | - 在添加设备时或长按设备点击“修改”按钮,设置高级选项,可自定义编解码参数、投屏控制参数等
61 | - 设置中可设置编码参数等默认选项,添加设备时默认使用这些参数,如对单个设备进行了修改,则以该设备参数为准
62 | - 特殊地址标识符,在添加设备时可用于代表特殊地址:
63 | - 网关地址:\*gateway\*,如网关为192.168.43.1,则“\*gateway\*:5555”表示“192.168.43.1:5555”
64 | - 子网地址:\*netAddress\*,如子网为192.168.43.0/24, 则“\*netAddress\*.1:5555”表示“192.168.43.1:5555”
65 |
66 | 4. 扩展使用
67 | 易控支持在外部使用广播控制,广播地址为:"top.saymzx.easycontrol.app.CONTROL",需要向意向也就是Intent填入想要做的动作:
68 | - 启动目标设备:
69 | - action:start
70 | - uuid:设备ID
71 | - 目标设备关闭投屏:
72 | - action:close
73 | - uuid:设备ID
74 | - 目标设备变成小窗:
75 | - action:changeToSmall
76 | - uuid:设备ID
77 | - 目标设备最小化:
78 | - action:changeToMini
79 | - uuid:设备ID
80 | - 目标设备全屏:
81 | - action:changeToFull
82 | - uuid:设备ID
83 | - 目标设备按下电源键:
84 | - action:buttonPower
85 | - uuid:设备ID
86 | - 目标设备唤醒:
87 | - action:buttonWake
88 | - uuid:设备ID
89 | - 目标设备锁定:
90 | - action:buttonLock
91 | - uuid:设备ID
92 | - 目标设备打开背光:
93 | - action:buttonLight
94 | - uuid:设备ID
95 | - 目标设备关闭背光:
96 | - action:buttonLightOff
97 | - uuid:设备ID
98 | - 目标设备按下返回键:
99 | - action:buttonBack
100 | - uuid:设备ID
101 | - 目标设备按下桌面键:
102 | - action:buttonHome
103 | - uuid:设备ID
104 | - 目标设备按下最近任务键:
105 | - action:buttonSwitch
106 | - uuid:设备ID
107 | - 目标设备旋转屏幕:
108 | - action:buttonRotate
109 | - uuid:设备ID
110 | - 目标设备执行命令:
111 | - action:runShell
112 | - cmd: 命令
113 | - uuid:设备ID
114 |
115 |
--------------------------------------------------------------------------------
/PRIVACY.md:
--------------------------------------------------------------------------------
1 | # Privacy Policy
2 | mingzhixian(鸣之弦) built the EasyControl app as an Open Source app. This SERVICE is provided by mingzhixian(鸣之弦) at no cost and is intended for use as is.
3 |
4 | This page is used to inform visitors regarding my policies with the collection, use, and disclosure of Personal Information if anyone decided to use my Service.
5 |
6 | If you choose to use my Service, then you agree to the collection and use of information in relation to this policy. The Personal Information that I collect is used for providing and improving the Service. I will not use or share your information with anyone except as described in this Privacy Policy.
7 |
8 | The terms used in this Privacy Policy have the same meanings as in our Terms and Conditions, which are accessible at EasyControl unless otherwise defined in this Privacy Policy.
9 |
10 | Information Collection and Use
11 |
12 | For a better experience, while using our Service, I may require you to provide us with certain personally identifiable information, including but not limited to ip. The information that I request will be retained on your device and is not collected by me in any way.
13 |
14 | Log Data
15 |
16 | I want to inform you that whenever you use my Service, in a case of an error in the app I collect data and information on your phone called Log Data. This Log Data may include information such as your device Internet Protocol (“IP”) address, device name, operating system version, the configuration of the app when utilizing my Service, the time and date of your use of the Service, and other statistics.
17 |
18 | Cookies
19 |
20 | Cookies are files with a small amount of data that are commonly used as anonymous unique identifiers. These are sent to your browser from the websites that you visit and are stored on your device's internal memory.
21 |
22 | This Service does not use these “cookies” explicitly. However, the app may use third-party code and libraries that use “cookies” to collect information and improve their services. You have the option to either accept or refuse these cookies and know when a cookie is being sent to your device. If you choose to refuse our cookies, you may not be able to use some portions of this Service.
23 |
24 | Service Providers
25 |
26 | I may employ third-party companies and individuals due to the following reasons:
27 |
28 | To facilitate our Service;
29 | To provide the Service on our behalf;
30 | To perform Service-related services; or
31 | To assist us in analyzing how our Service is used.
32 | I want to inform users of this Service that these third parties have access to their Personal Information. The reason is to perform the tasks assigned to them on our behalf. However, they are obligated not to disclose or use the information for any other purpose.
33 |
34 | Security
35 |
36 | I value your trust in providing us your Personal Information, thus we are striving to use commercially acceptable means of protecting it. But remember that no method of transmission over the internet, or method of electronic storage is 100% secure and reliable, and I cannot guarantee its absolute security.
37 |
38 | Links to Other Sites
39 |
40 | This Service may contain links to other sites. If you click on a third-party link, you will be directed to that site. Note that these external sites are not operated by me. Therefore, I strongly advise you to review the Privacy Policy of these websites. I have no control over and assume no responsibility for the content, privacy policies, or practices of any third-party sites or services.
41 |
42 | Children’s Privacy
43 |
44 | I do not knowingly collect personally identifiable information from children. I encourage all children to never submit any personally identifiable information through the Application and/or Services. I encourage parents and legal guardians to monitor their children's Internet usage and to help enforce this Policy by instructing their children never to provide personally identifiable information through the Application and/or Services without their permission. If you have reason to believe that a child has provided personally identifiable information to us through the Application and/or Services, please contact us. You must also be at least 16 years of age to consent to the processing of your personally identifiable information in your country (in some countries we may allow your parent or guardian to do so on your behalf).
45 |
46 | Changes to This Privacy Policy
47 |
48 | I may update our Privacy Policy from time to time. Thus, you are advised to review this page periodically for any changes. I will notify you of any changes by posting the new Privacy Policy on this page.
49 |
50 | This policy is effective as of 2023-12-17
51 |
52 | Contact Us
53 |
54 | If you have any questions or suggestions about my Privacy Policy, do not hesitate to contact me at mingzhixian@outlook.com.
55 |
56 | This privacy policy page was created at privacypolicytemplate.net and modified/generated by App Privacy Policy Generator
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # 易控(Easycontrol)
2 |
3 | ## 注意
4 | Gitee和GitHub代码将保持同步,请自行选择。
5 | - [Gitee地址](https://gitee.com/mingzhixianweb/easycontrol)
6 | - [Github地址](https://github.com/mingzhixian/Easycontrol)
7 |
8 | ## 简介
9 | 本软件基于开源项目Scrcpy,对其进行了大量魔改,实现了其安卓客户端,并添加了一些功能,实现了安卓端控制安卓端。
10 |
11 | ## 功能特色
12 | - 使用简单
13 | - 支持音频传输
14 | - 多设备连接
15 | - 支持有线连接
16 | - 多设备剪切板同步
17 | - 多设备共享主控端物理键盘(需配合微信输入法或QQ输入法等输入中文)
18 | - 启动迅速
19 | - 低延迟
20 | - 支持分辨率自适应
21 | - 良好的旋转支持
22 | - 支持小窗显示与全屏显示
23 |
24 | ## 使用说明
25 | - [点击此处查看](https://gitee.com/mingzhixianweb/easycontrol/blob/master/HOW_TO_USE.md)
26 |
27 | ## 软件下载
28 | - [点击此处查看](https://gitee.com/mingzhixianweb/easycontrol/releases)
29 |
30 | ## 激活
31 | 代码是开源的,但官方打包的安装包需要激活才可使用,激活的步骤请参考[此页面](https://gitee.com/mingzhixianweb/easycontrol/blob/master/DONATE.md)
32 |
33 | ## 截图
34 |
35 |
36 |
37 |
38 |
39 |
40 | ## 构建
41 | 如果您想要自己构建,请注意以下几项
42 | - 请遵循本项目的开源协议
43 | - 我去除了官方打包加入的激活模块相关的代码文件,所以会有报错,请自行注释掉报错代码即可
44 |
45 | ## 反馈
46 | 请在Github或Gitee提出Issue,或进入易控反馈群反馈BUG或建议。
47 |
48 |
49 |
50 |
51 | ## 附加
52 | - ADB协议说明(官方的文档写的真烂,感谢cstyan大佬) [点击前往](https://github.com/cstyan/adbDocumentation)
53 | - Scrcpy官方地址 [点击前往](https://github.com/Genymobile/scrcpy)
54 | - 易控车机版(第三方用户专为车机进行了调整优化) [点击前往](https://github.com/eiyooooo/Easycontrol_For_Car)
55 |
--------------------------------------------------------------------------------
/easycontrol/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application' version '8.2.2'
3 | }
4 |
5 | android {
6 | namespace 'top.saymzx.easycontrol.app'
7 | compileSdk 34
8 |
9 | defaultConfig {
10 | applicationId "top.saymzx.easycontrol.app"
11 | minSdk 21
12 | targetSdk 34
13 | versionCode 10406
14 | versionName "1.4.6"
15 | ndk {
16 | abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
17 | }
18 | }
19 |
20 | viewBinding {
21 | enabled = true
22 | }
23 |
24 | buildTypes {
25 | debug {
26 | buildConfigField "boolean", "ENABLE_DEBUG_FEATURE", "true"
27 | manifestPlaceholders = [app_name: "@string/app_name_dev", app_icon: "@mipmap/ic_launcher_dev", app_icon_round: "@mipmap/ic_launcher_dev_round"]
28 | debuggable true
29 | }
30 | release {
31 | buildConfigField "boolean", "ENABLE_DEBUG_FEATURE", "false"
32 | manifestPlaceholders = [app_name: "@string/app_name", app_icon: "@mipmap/ic_launcher", app_icon_round: "@mipmap/ic_launcher_round"]
33 | debuggable false
34 | minifyEnabled true
35 | shrinkResources true
36 | proguardFiles 'proguard-rules.pro'
37 | }
38 | }
39 |
40 | compileOptions {
41 | sourceCompatibility JavaVersion.VERSION_1_8
42 | targetCompatibility JavaVersion.VERSION_1_8
43 | }
44 |
45 | packagingOptions {
46 | resources.excludes.add("META-INF/*")
47 | }
48 |
49 | }
50 |
51 | dependencies {
52 | }
53 |
--------------------------------------------------------------------------------
/easycontrol/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class top.saymzx.easycontrol.*
--------------------------------------------------------------------------------
/easycontrol/app/schemas/top.saymzx.easycontrol.app.helper.SQLDatabase/1.json:
--------------------------------------------------------------------------------
1 | {
2 | "formatVersion": 1,
3 | "database": {
4 | "version": 1,
5 | "identityHash": "a275d09cfe69ce7252d7e4803ae75618",
6 | "entities": [
7 | {
8 | "tableName": "Device",
9 | "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT, `address` TEXT, `videoCodec` TEXT, `maxSize` INTEGER, `maxFps` INTEGER, `maxVideoBit` INTEGER, `setResolution` INTEGER)",
10 | "fields": [
11 | {
12 | "fieldPath": "id",
13 | "columnName": "id",
14 | "affinity": "INTEGER",
15 | "notNull": false
16 | },
17 | {
18 | "fieldPath": "name",
19 | "columnName": "name",
20 | "affinity": "TEXT",
21 | "notNull": false
22 | },
23 | {
24 | "fieldPath": "address",
25 | "columnName": "address",
26 | "affinity": "TEXT",
27 | "notNull": false
28 | },
29 | {
30 | "fieldPath": "videoCodec",
31 | "columnName": "videoCodec",
32 | "affinity": "TEXT",
33 | "notNull": false
34 | },
35 | {
36 | "fieldPath": "maxSize",
37 | "columnName": "maxSize",
38 | "affinity": "INTEGER",
39 | "notNull": false
40 | },
41 | {
42 | "fieldPath": "maxFps",
43 | "columnName": "maxFps",
44 | "affinity": "INTEGER",
45 | "notNull": false
46 | },
47 | {
48 | "fieldPath": "maxVideoBit",
49 | "columnName": "maxVideoBit",
50 | "affinity": "INTEGER",
51 | "notNull": false
52 | },
53 | {
54 | "fieldPath": "setResolution",
55 | "columnName": "setResolution",
56 | "affinity": "INTEGER",
57 | "notNull": false
58 | }
59 | ],
60 | "primaryKey": {
61 | "autoGenerate": true,
62 | "columnNames": [
63 | "id"
64 | ]
65 | },
66 | "indices": [],
67 | "foreignKeys": []
68 | }
69 | ],
70 | "views": [],
71 | "setupQueries": [
72 | "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
73 | "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a275d09cfe69ce7252d7e4803ae75618')"
74 | ]
75 | }
76 | }
--------------------------------------------------------------------------------
/easycontrol/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
15 |
16 |
24 |
25 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
37 |
38 |
41 |
44 |
47 |
50 |
53 |
56 |
59 |
60 |
61 |
62 |
63 |
66 |
67 |
68 |
69 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/ic_launcher_dev-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/easycontrol/app/src/main/ic_launcher_dev-playstore.png
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/ActiveActivity.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app;
2 |
3 | import android.app.Activity;
4 | import android.app.Dialog;
5 | import android.os.Bundle;
6 | import android.util.Pair;
7 | import android.view.View;
8 | import android.view.WindowManager;
9 |
10 | import top.saymzx.easycontrol.app.databinding.ActivityActiveBinding;
11 | import top.saymzx.easycontrol.app.databinding.ItemLoadingBinding;
12 | import top.saymzx.easycontrol.app.entity.AppData;
13 | import top.saymzx.easycontrol.app.helper.ActiveHelper;
14 | import top.saymzx.easycontrol.app.helper.PublicTools;
15 | import top.saymzx.easycontrol.app.helper.ViewTools;
16 |
17 | public class ActiveActivity extends Activity {
18 |
19 | private ActivityActiveBinding activityActiveBinding;
20 |
21 | @Override
22 | protected void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | ViewTools.setStatusAndNavBar(this);
25 | ViewTools.setLocale(this);
26 | activityActiveBinding = ActivityActiveBinding.inflate(this.getLayoutInflater());
27 | setContentView(activityActiveBinding.getRoot());
28 | // 取消激活
29 | if (AppData.setting.getIsActive()) deactivate();
30 | setButtonListener();
31 | // 绘制UI
32 | drawUi();
33 | }
34 |
35 | private void drawUi() {
36 | activityActiveBinding.key.setText(AppData.setting.getActiveKey());
37 | activityActiveBinding.url.setOnClickListener(v -> PublicTools.startUrl(this, "https://gitee.com/mingzhixianweb/easycontrol/blob/master/DONATE.md"));
38 | }
39 |
40 | private void setButtonListener() {
41 | activityActiveBinding.active.setOnClickListener(v -> {
42 | String activeKey = String.valueOf(activityActiveBinding.key.getText());
43 | AppData.setting.setActiveKey(activeKey);
44 | Pair loading = ViewTools.createLoading(this);
45 | loading.second.show();
46 | new Thread(() -> {
47 | boolean isOk = ActiveHelper.active(activeKey);
48 | loading.second.cancel();
49 | AppData.uiHandler.post(() -> {
50 | if (isOk) {
51 | finish();
52 | AppData.setting.setIsActive(true);
53 | PublicTools.startUrl(this, "https://gitee.com/mingzhixianweb/easycontrol/blob/master/HOW_TO_USE.md");
54 | PublicTools.logToast("active", getString(R.string.toast_success), true);
55 | } else PublicTools.logToast("active", getString(R.string.toast_fail), true);
56 | });
57 | }).start();
58 | });
59 | }
60 |
61 | // 取消激活
62 | private void deactivate() {
63 | Pair loading = ViewTools.createLoading(this);
64 | loading.second.show();
65 | new Thread(() -> {
66 | boolean isOk = ActiveHelper.deactivate(AppData.setting.getActiveKey());
67 | loading.second.cancel();
68 | AppData.uiHandler.post(() -> {
69 | if (isOk) {
70 | AppData.setting.setIsActive(false);
71 | PublicTools.logToast("deactivate", getString(R.string.toast_success), true);
72 | } else PublicTools.logToast("deactivate", getString(R.string.toast_fail), true);
73 | });
74 | }).start();
75 | }
76 |
77 | @Override
78 | public void onBackPressed() {
79 | }
80 | }
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/AdbKeyActivity.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app;
2 |
3 | import android.app.Activity;
4 | import android.os.Bundle;
5 | import android.util.Pair;
6 | import android.widget.Toast;
7 |
8 | import java.io.File;
9 | import java.io.FileInputStream;
10 | import java.io.FileWriter;
11 | import java.io.IOException;
12 |
13 | import top.saymzx.easycontrol.app.adb.AdbKeyPair;
14 | import top.saymzx.easycontrol.app.databinding.ActivityAdbKeyBinding;
15 | import top.saymzx.easycontrol.app.entity.AppData;
16 | import top.saymzx.easycontrol.app.helper.PublicTools;
17 | import top.saymzx.easycontrol.app.helper.ViewTools;
18 |
19 | public class AdbKeyActivity extends Activity {
20 | private ActivityAdbKeyBinding activityAdbKeyBinding;
21 | private Pair adbKeyFile;
22 |
23 | @Override
24 | protected void onCreate(Bundle savedInstanceState) {
25 | super.onCreate(savedInstanceState);
26 | ViewTools.setStatusAndNavBar(this);
27 | ViewTools.setLocale(this);
28 | activityAdbKeyBinding = ActivityAdbKeyBinding.inflate(this.getLayoutInflater());
29 | setContentView(activityAdbKeyBinding.getRoot());
30 | adbKeyFile = PublicTools.getAdbKeyFile(this);
31 | readKey();
32 | activityAdbKeyBinding.backButton.setOnClickListener(v -> finish());
33 | activityAdbKeyBinding.ok.setOnClickListener(v -> writeKey());
34 | }
35 |
36 | // 读取旧的密钥公钥文件
37 | private void readKey() {
38 | try {
39 | byte[] publicKeyBytes = new byte[(int) adbKeyFile.first.length()];
40 | byte[] privateKeyBytes = new byte[(int) adbKeyFile.second.length()];
41 |
42 | try (FileInputStream stream = new FileInputStream(adbKeyFile.first)) {
43 | stream.read(publicKeyBytes);
44 | activityAdbKeyBinding.adbKeyPub.setText(new String(publicKeyBytes));
45 | }
46 | try (FileInputStream stream = new FileInputStream(adbKeyFile.second)) {
47 | stream.read(privateKeyBytes);
48 | activityAdbKeyBinding.adbKeyPri.setText(new String(privateKeyBytes));
49 | }
50 | } catch (IOException ignored) {
51 | }
52 | }
53 |
54 | // 写入新的密钥公钥文件
55 | private void writeKey() {
56 | try {
57 | try (FileWriter publicKeyWriter = new FileWriter(adbKeyFile.first)) {
58 | publicKeyWriter.write(String.valueOf(activityAdbKeyBinding.adbKeyPub.getText()));
59 | publicKeyWriter.flush();
60 | }
61 | try (FileWriter privateKeyWriter = new FileWriter(adbKeyFile.second)) {
62 | privateKeyWriter.write(String.valueOf(activityAdbKeyBinding.adbKeyPri.getText()));
63 | privateKeyWriter.flush();
64 | }
65 | AppData.keyPair = AdbKeyPair.read(adbKeyFile.first, adbKeyFile.second);
66 | Toast.makeText(this, getString(R.string.toast_success), Toast.LENGTH_SHORT).show();
67 | } catch (Exception ignored) {
68 | }
69 | }
70 | }
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/IpActivity.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app;
2 |
3 | import android.app.Activity;
4 | import android.content.ClipData;
5 | import android.content.ClipDescription;
6 | import android.os.Bundle;
7 | import android.util.Pair;
8 | import android.widget.Toast;
9 |
10 | import java.util.ArrayList;
11 |
12 | import top.saymzx.easycontrol.app.databinding.ActivityIpBinding;
13 | import top.saymzx.easycontrol.app.databinding.ItemTextBinding;
14 | import top.saymzx.easycontrol.app.entity.AppData;
15 | import top.saymzx.easycontrol.app.helper.PublicTools;
16 | import top.saymzx.easycontrol.app.helper.ViewTools;
17 |
18 | public class IpActivity extends Activity {
19 | private ActivityIpBinding activityIpBinding;
20 |
21 | @Override
22 | public void onCreate(Bundle savedInstanceState) {
23 | super.onCreate(savedInstanceState);
24 | ViewTools.setStatusAndNavBar(this);
25 | ViewTools.setLocale(this);
26 | activityIpBinding = ActivityIpBinding.inflate(this.getLayoutInflater());
27 | setContentView(activityIpBinding.getRoot());
28 | setButtonListener();
29 | // 绘制UI
30 | drawUi();
31 | }
32 |
33 | private void drawUi() {
34 | // 添加IP
35 | Pair, ArrayList> listPair = PublicTools.getLocalIp();
36 | for (String i : listPair.first) {
37 | ItemTextBinding text = ViewTools.createTextCard(this, i, () -> {
38 | AppData.clipBoard.setPrimaryClip(ClipData.newPlainText(ClipDescription.MIMETYPE_TEXT_PLAIN, i));
39 | Toast.makeText(this, getString(R.string.toast_copy), Toast.LENGTH_SHORT).show();
40 | });
41 | activityIpBinding.ipv4.addView(text.getRoot());
42 | }
43 | for (String i : listPair.second) {
44 | ItemTextBinding text = ViewTools.createTextCard(this, i, () -> {
45 | AppData.clipBoard.setPrimaryClip(ClipData.newPlainText(ClipDescription.MIMETYPE_TEXT_PLAIN, i));
46 | Toast.makeText(this, getString(R.string.toast_copy), Toast.LENGTH_SHORT).show();
47 | });
48 | activityIpBinding.ipv6.addView(text.getRoot());
49 | }
50 | }
51 |
52 | // 设置返回按钮监听
53 | private void setButtonListener() {
54 | activityIpBinding.backButton.setOnClickListener(v -> finish());
55 | }
56 |
57 | }
58 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/MainActivity.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.Activity;
5 | import android.content.ContentResolver;
6 | import android.content.Intent;
7 | import android.database.Cursor;
8 | import android.net.Uri;
9 | import android.os.Bundle;
10 | import android.provider.OpenableColumns;
11 |
12 | import java.io.IOException;
13 | import java.io.InputStream;
14 |
15 | import top.saymzx.easycontrol.app.client.Client;
16 | import top.saymzx.easycontrol.app.client.tools.AdbTools;
17 | import top.saymzx.easycontrol.app.databinding.ActivityMainBinding;
18 | import top.saymzx.easycontrol.app.entity.AppData;
19 | import top.saymzx.easycontrol.app.entity.Device;
20 | import top.saymzx.easycontrol.app.helper.DeviceListAdapter;
21 | import top.saymzx.easycontrol.app.helper.MyBroadcastReceiver;
22 | import top.saymzx.easycontrol.app.helper.ViewTools;
23 |
24 | public class MainActivity extends Activity {
25 |
26 | private ActivityMainBinding activityMainBinding;
27 | public DeviceListAdapter deviceListAdapter;
28 |
29 | // 广播
30 | private final MyBroadcastReceiver myBroadcastReceiver = new MyBroadcastReceiver();
31 |
32 | @SuppressLint("SourceLockedOrientationActivity")
33 | @Override
34 | public void onCreate(Bundle savedInstanceState) {
35 | super.onCreate(savedInstanceState);
36 | AppData.init(this);
37 | ViewTools.setStatusAndNavBar(this);
38 | ViewTools.setLocale(this);
39 | activityMainBinding = ActivityMainBinding.inflate(this.getLayoutInflater());
40 | setContentView(activityMainBinding.getRoot());
41 | // 检测激活
42 | checkActive();
43 | // 设置设备列表适配器
44 | deviceListAdapter = new DeviceListAdapter(this);
45 | activityMainBinding.devicesList.setAdapter(deviceListAdapter);
46 | myBroadcastReceiver.setDeviceListAdapter(deviceListAdapter);
47 | // 设置按钮监听
48 | setButtonListener();
49 | // 注册广播监听
50 | myBroadcastReceiver.register(this);
51 | // 重置已连接设备
52 | myBroadcastReceiver.resetUSB();
53 | // 自启动设备
54 | AppData.uiHandler.postDelayed(() -> {
55 | for (Device device : AdbTools.devicesList) if (device.connectOnStart) Client.startDevice(device);
56 | }, 2000);
57 | }
58 |
59 | @Override
60 | protected void onDestroy() {
61 | myBroadcastReceiver.unRegister(this);
62 | super.onDestroy();
63 | }
64 |
65 | // 检测激活
66 | private void checkActive() {
67 | if (!AppData.setting.getIsActive()) startActivity(new Intent(this, ActiveActivity.class));
68 | }
69 |
70 | // 设置按钮监听
71 | private void setButtonListener() {
72 | activityMainBinding.buttonAdd.setOnClickListener(v -> startActivity(new Intent(this, DeviceDetailActivity.class)));
73 | activityMainBinding.buttonSet.setOnClickListener(v -> startActivity(new Intent(this, SetActivity.class)));
74 | }
75 |
76 | @Override
77 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
78 | if (resultCode == RESULT_OK && requestCode == 1) {
79 | Uri uri = data.getData();
80 | if (uri == null) deviceListAdapter.pushFile(null, null);
81 | ;
82 | try {
83 | String fileName = "easycontrol_push_file";
84 | ContentResolver contentProvider = getContentResolver();
85 | InputStream inputStream = contentProvider.openInputStream(uri);
86 | //根据Uri查询文件名
87 | try (Cursor cursor = contentProvider.query(uri, null, null, null, null)) {
88 | if (cursor != null) {
89 | cursor.moveToFirst();
90 | int nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
91 | fileName = cursor.getString(nameIndex);
92 | }
93 | }
94 | deviceListAdapter.pushFile(inputStream, fileName);
95 | } catch (IOException ignored) {
96 | deviceListAdapter.pushFile(null, null);
97 | ;
98 | }
99 | }
100 | super.onActivityResult(requestCode, resultCode, data);
101 | }
102 |
103 | }
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/SetActivity.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app;
2 |
3 | import android.app.Activity;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.widget.Toast;
7 |
8 | import top.saymzx.easycontrol.app.databinding.ActivitySetBinding;
9 | import top.saymzx.easycontrol.app.entity.AppData;
10 | import top.saymzx.easycontrol.app.helper.PublicTools;
11 | import top.saymzx.easycontrol.app.helper.ViewTools;
12 |
13 | public class SetActivity extends Activity {
14 | private ActivitySetBinding activitySetBinding;
15 |
16 | @Override
17 | public void onCreate(Bundle savedInstanceState) {
18 | super.onCreate(savedInstanceState);
19 | ViewTools.setStatusAndNavBar(this);
20 | ViewTools.setLocale(this);
21 | activitySetBinding = ActivitySetBinding.inflate(this.getLayoutInflater());
22 | setContentView(activitySetBinding.getRoot());
23 | // 设置页面
24 | drawUi();
25 | setButtonListener();
26 | }
27 |
28 | // 设置默认值
29 | private void drawUi() {
30 | // 其他
31 | activitySetBinding.setOther.addView(ViewTools.createTextCard(this, getString(R.string.set_other_ip), () -> startActivity(new Intent(this, IpActivity.class))).getRoot());
32 | activitySetBinding.setOther.addView(ViewTools.createTextCard(this, getString(R.string.set_other_custom_key), () -> startActivity(new Intent(this, AdbKeyActivity.class))).getRoot());
33 | activitySetBinding.setOther.addView(ViewTools.createTextCard(this, getString(R.string.set_other_reset_key), () -> {
34 | AppData.keyPair = PublicTools.reGenerateAdbKeyPair();
35 | Toast.makeText(this, getString(R.string.toast_success), Toast.LENGTH_SHORT).show();
36 | }).getRoot());
37 | activitySetBinding.setOther.addView(ViewTools.createTextCard(this, getString(R.string.set_other_locale), () -> {
38 | AppData.setting.setLocale(AppData.setting.getLocale().equals("en") ? "zh" : "en");
39 | Toast.makeText(this, getString(R.string.toast_change_locale), Toast.LENGTH_SHORT).show();
40 | }).getRoot());
41 | // 关于
42 | activitySetBinding.setAbout.addView(ViewTools.createTextCard(this, getString(R.string.set_about_active), () -> startActivity(new Intent(this, ActiveActivity.class))).getRoot());
43 | activitySetBinding.setAbout.addView(ViewTools.createTextCard(this, getString(R.string.set_about_website), () -> PublicTools.startUrl(this, "https://gitee.com/mingzhixianweb/easycontrol")).getRoot());
44 | activitySetBinding.setAbout.addView(ViewTools.createTextCard(this, getString(R.string.set_about_privacy), () -> PublicTools.startUrl(this, "https://gitee.com/mingzhixianweb/easycontrol/blob/master/PRIVACY.md")).getRoot());
45 | activitySetBinding.setAbout.addView(ViewTools.createTextCard(this, getString(R.string.set_about_version) + BuildConfig.VERSION_NAME, () -> PublicTools.startUrl(this, "https://gitee.com/mingzhixianweb/easycontrol/releases/latest")).getRoot());
46 | }
47 |
48 | // 设置按钮监听
49 | private void setButtonListener() {
50 | activitySetBinding.backButton.setOnClickListener(v -> finish());
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/UsbActivity.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app;
2 |
3 | import android.app.Activity;
4 | import android.content.Context;
5 | import android.content.Intent;
6 | import android.content.SharedPreferences;
7 | import android.os.Bundle;
8 |
9 | import top.saymzx.easycontrol.app.entity.AppData;
10 | import top.saymzx.easycontrol.app.helper.MyBroadcastReceiver;
11 |
12 | public class UsbActivity extends Activity {
13 |
14 | @Override
15 | public void onCreate(Bundle savedInstanceState) {
16 | super.onCreate(savedInstanceState);
17 | SharedPreferences sharedPreferences = this.getSharedPreferences("setting", Context.MODE_PRIVATE);
18 | if (sharedPreferences.getBoolean("isActive", false)) {
19 | Intent intent = new Intent();
20 | intent.setAction(MyBroadcastReceiver.ACTION_UPDATE_USB);
21 | sendBroadcast(intent);
22 | if (AppData.mainActivity == null) startActivity(new Intent(this, MainActivity.class));
23 | }
24 | finish();
25 | }
26 | }
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/adb/AdbBase64.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.adb;
2 |
3 | public interface AdbBase64 {
4 | String encodeToString(byte[] data);
5 |
6 | byte[] decode(byte[] data);
7 | }
8 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/adb/AdbChannel.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.adb;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | public interface AdbChannel {
7 | void write(ByteBuffer data) throws IOException, InterruptedException;
8 |
9 | void flush() throws IOException;
10 |
11 | ByteBuffer read(int size) throws IOException, InterruptedException;
12 |
13 | void close();
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/adb/AdbProtocol.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.adb;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 | import java.nio.ByteOrder;
6 | import java.nio.charset.StandardCharsets;
7 |
8 | public class AdbProtocol {
9 | public static final int ADB_HEADER_LENGTH = 24;
10 |
11 | public static final int AUTH_TYPE_TOKEN = 1;
12 | public static final int AUTH_TYPE_SIGNATURE = 2;
13 | public static final int AUTH_TYPE_RSA_PUBLIC = 3;
14 |
15 | public static final int CMD_AUTH = 0x48545541;
16 | public static final int CMD_CNXN = 0x4e584e43;
17 | public static final int CMD_OPEN = 0x4e45504f;
18 | public static final int CMD_OKAY = 0x59414b4f;
19 | public static final int CMD_CLSE = 0x45534c43;
20 | public static final int CMD_WRTE = 0x45545257;
21 |
22 | public static final int CONNECT_VERSION = 0x01000000;
23 | // 最大数据大小一般为1024*1024,此处设置为15*1024,是因为有些设备USB仅支持最大16*1024,所以如果ADB使用了过大的数据,会导致USB无法传输,丢失数据,所以限制ADB协议最大为15k
24 | // 旧版本的adb服务端硬编码maxdata=4096,若设备实在太老,可尝试将此处修改为4096
25 | public static final int CONNECT_MAXDATA = 15 * 1024;
26 |
27 | public static final byte[] CONNECT_PAYLOAD = "host::\0".getBytes();
28 |
29 | public static ByteBuffer generateConnect() {
30 | return generateMessage(CMD_CNXN, CONNECT_VERSION, CONNECT_MAXDATA, CONNECT_PAYLOAD);
31 | }
32 |
33 | public static ByteBuffer generateAuth(int type, byte[] data) {
34 | return generateMessage(CMD_AUTH, type, 0, data);
35 | }
36 |
37 | public static ByteBuffer generateOpen(int localId, String dest) {
38 | ByteBuffer bbuf = ByteBuffer.allocate(dest.length() + 1);
39 | bbuf.put(dest.getBytes(StandardCharsets.UTF_8));
40 | bbuf.put((byte) 0);
41 | return generateMessage(CMD_OPEN, localId, 0, bbuf.array());
42 | }
43 |
44 | public static ByteBuffer generateWrite(int localId, int remoteId, byte[] data) {
45 | return generateMessage(CMD_WRTE, localId, remoteId, data);
46 | }
47 |
48 | public static ByteBuffer generateClose(int localId, int remoteId) {
49 | return generateMessage(CMD_CLSE, localId, remoteId, null);
50 | }
51 |
52 | public static ByteBuffer generateOkay(int localId, int remoteId) {
53 | return generateMessage(CMD_OKAY, localId, remoteId, null);
54 | }
55 |
56 | private static ByteBuffer generateMessage(int cmd, int arg0, int arg1, byte[] payload) {
57 |
58 | int size = payload == null ? ADB_HEADER_LENGTH : (ADB_HEADER_LENGTH + payload.length);
59 | ByteBuffer buffer = ByteBuffer.allocate(size).order(ByteOrder.LITTLE_ENDIAN);
60 |
61 | buffer.putInt(cmd);
62 | buffer.putInt(arg0);
63 | buffer.putInt(arg1);
64 |
65 | if (payload == null) {
66 | buffer.putInt(0);
67 | buffer.putInt(0);
68 | } else {
69 | buffer.putInt(payload.length);
70 | buffer.putInt(payloadChecksum(payload));
71 | }
72 |
73 | buffer.putInt(~cmd);
74 | if (payload != null) buffer.put(payload);
75 | buffer.flip();
76 |
77 | return buffer;
78 | }
79 |
80 | public static ByteBuffer generateSyncHeader(String id, int arg) {
81 | ByteBuffer tmpBuffer = ByteBuffer.allocate(8);
82 | tmpBuffer.order(ByteOrder.LITTLE_ENDIAN);
83 | tmpBuffer.clear();
84 | tmpBuffer.put(id.getBytes(StandardCharsets.UTF_8));
85 | tmpBuffer.putInt(arg);
86 | tmpBuffer.flip();
87 | return tmpBuffer;
88 | }
89 |
90 | private static int payloadChecksum(byte[] payload) {
91 | int checksum = 0;
92 | for (byte b : payload) checksum += (b & 0xFF);
93 | return checksum;
94 | }
95 |
96 | final static class AdbMessage {
97 | public int command;
98 | public int arg0;
99 | public int arg1;
100 | public int payloadLength;
101 | public ByteBuffer payload = null;
102 |
103 | public static AdbMessage parseAdbMessage(AdbChannel channel) throws IOException, InterruptedException {
104 | AdbMessage msg = new AdbMessage();
105 | ByteBuffer buffer = channel.read(ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
106 |
107 | msg.command = buffer.getInt();
108 | msg.arg0 = buffer.getInt();
109 | msg.arg1 = buffer.getInt();
110 | msg.payloadLength = buffer.getInt();
111 | // msg.checksum = buffer.getInt();
112 | // msg.magic = buffer.getInt();
113 | if (msg.payloadLength > 0) msg.payload = channel.read(msg.payloadLength);
114 |
115 | return msg;
116 | }
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/adb/TcpChannel.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.adb;
2 |
3 | import java.io.IOException;
4 | import java.io.InputStream;
5 | import java.io.OutputStream;
6 | import java.net.Socket;
7 | import java.nio.ByteBuffer;
8 |
9 | public class TcpChannel implements AdbChannel {
10 | private final Socket socket;
11 | private final InputStream inputStream;
12 | private final OutputStream outputStream;
13 |
14 | public TcpChannel(String host, int port) throws IOException {
15 | socket = new Socket(host, port);
16 | inputStream = socket.getInputStream();
17 | outputStream = socket.getOutputStream();
18 | }
19 |
20 | @Override
21 | public void write(ByteBuffer data) throws IOException {
22 | outputStream.write(data.array());
23 | }
24 |
25 | @Override
26 | public void flush() throws IOException {
27 | outputStream.flush();
28 | }
29 |
30 | @Override
31 | public ByteBuffer read(int size) throws IOException {
32 | byte[] buffer = new byte[size];
33 | int bytesRead = 0;
34 | while (bytesRead < size) {
35 | int read = inputStream.read(buffer, bytesRead, size - bytesRead);
36 | if (read == -1) break;
37 | bytesRead += read;
38 | }
39 | return ByteBuffer.wrap(buffer);
40 | }
41 |
42 | @Override
43 | public void close() {
44 | try {
45 | outputStream.close();
46 | inputStream.close();
47 | socket.close();
48 | } catch (Exception ignored) {
49 | }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/adb/UsbChannel.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本页大量借鉴学习了开源ADB库:https://github.com/wuxudong/flashbot/blob/master/adblib/src/main/java/com/cgutman/adblib/UsbChannel.java,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.app.adb;
5 |
6 | import android.hardware.usb.UsbConstants;
7 | import android.hardware.usb.UsbDevice;
8 | import android.hardware.usb.UsbDeviceConnection;
9 | import android.hardware.usb.UsbEndpoint;
10 | import android.hardware.usb.UsbInterface;
11 | import android.hardware.usb.UsbRequest;
12 |
13 | import java.io.IOException;
14 | import java.nio.ByteBuffer;
15 | import java.nio.ByteOrder;
16 | import java.util.LinkedList;
17 |
18 | import top.saymzx.easycontrol.app.buffer.BufferNew;
19 | import top.saymzx.easycontrol.app.entity.AppData;
20 |
21 | public class UsbChannel implements AdbChannel {
22 |
23 | private final UsbDeviceConnection usbConnection;
24 | private UsbInterface usbInterface = null;
25 | private UsbEndpoint endpointIn = null;
26 | private UsbEndpoint endpointOut = null;
27 | private final BufferNew sourceBuffer = new BufferNew();
28 | private final Thread readBackgroundThread = new Thread(this::readBackground);
29 | private final LinkedList mInRequestPool = new LinkedList<>();
30 |
31 | public UsbChannel(UsbDevice usbDevice) throws IOException {
32 | // 连接USB设备
33 | if (AppData.usbManager == null) throw new IOException("not have usbManager");
34 | usbConnection = AppData.usbManager.openDevice(usbDevice);
35 | if (usbConnection == null) return;
36 | // 查找ADB的接口
37 | for (int i = 0; i < usbDevice.getInterfaceCount(); i++) {
38 | UsbInterface tmpUsbInterface = usbDevice.getInterface(i);
39 | if ((tmpUsbInterface.getInterfaceClass() == UsbConstants.USB_CLASS_VENDOR_SPEC) && (tmpUsbInterface.getInterfaceSubclass() == 66) && (tmpUsbInterface.getInterfaceProtocol() == 1)) {
40 | usbInterface = tmpUsbInterface;
41 | break;
42 | }
43 | }
44 | if (usbInterface == null) return;
45 | // 宣告独占接口
46 | if (usbConnection.claimInterface(usbInterface, true)) {
47 | // 查找输入输出端点
48 | for (int i = 0; i < usbInterface.getEndpointCount(); i++) {
49 | UsbEndpoint endpoint = usbInterface.getEndpoint(i);
50 | if (endpoint.getType() == UsbConstants.USB_ENDPOINT_XFER_BULK) {
51 | if (endpoint.getDirection() == UsbConstants.USB_DIR_OUT) endpointOut = endpoint;
52 | else if (endpoint.getDirection() == UsbConstants.USB_DIR_IN) endpointIn = endpoint;
53 | if (endpointIn != null && endpointOut != null) {
54 | readBackgroundThread.start();
55 | return;
56 | }
57 | }
58 | }
59 | }
60 | throw new IOException("有线连接错误");
61 | }
62 |
63 | @Override
64 | public void write(ByteBuffer data) throws IOException {
65 | // 此处感谢群友:○_○ 的帮助,ADB通过USB连接时必须头部和载荷分开发送,否则会导致ADB连接重置(官方的实现真差劲,明明可以顺序读取的)
66 | while (data.remaining() > 0) {
67 | // 读取头部
68 | byte[] header = new byte[AdbProtocol.ADB_HEADER_LENGTH];
69 | data.get(header);
70 | usbConnection.bulkTransfer(endpointOut, header, header.length, 1000);
71 | // 读取载荷
72 | int payloadLength = ByteBuffer.wrap(header).order(ByteOrder.LITTLE_ENDIAN).getInt(12);
73 | if (payloadLength > 0) {
74 | byte[] payload = new byte[payloadLength];
75 | data.get(payload);
76 | usbConnection.bulkTransfer(endpointOut, payload, payload.length, 1000);
77 | }
78 | }
79 | }
80 |
81 | @Override
82 | public ByteBuffer read(int size) throws InterruptedException, IOException {
83 | return sourceBuffer.read(size);
84 | }
85 |
86 | private void readBackground() {
87 | try {
88 | while (!Thread.interrupted()) {
89 | // 读取头部
90 | ByteBuffer header = readRequest(AdbProtocol.ADB_HEADER_LENGTH).order(ByteOrder.LITTLE_ENDIAN);
91 | if (header.remaining() < AdbProtocol.ADB_HEADER_LENGTH) throw new IOException("read error");
92 | sourceBuffer.write(header);
93 | // 读取载荷
94 | int payloadLength = header.getInt(12);
95 | if (payloadLength > 0) {
96 | ByteBuffer payload = readRequest(payloadLength);
97 | sourceBuffer.write(payload);
98 | }
99 | }
100 | } catch (IOException ignored) {
101 | sourceBuffer.close();
102 | }
103 | }
104 |
105 | private ByteBuffer readRequest(int len) throws IOException {
106 | // 获取Request
107 | UsbRequest request;
108 | if (mInRequestPool.isEmpty()) {
109 | request = new UsbRequest();
110 | request.initialize(usbConnection, endpointIn);
111 | } else request = mInRequestPool.removeFirst();
112 | ByteBuffer data = ByteBuffer.allocate(len);
113 | request.setClientData(data);
114 | // 加入异步请求
115 | if (!request.queue(data, len)) throw new IOException("fail to queue read UsbRequest");
116 | // 等待请求回应
117 | while (true) {
118 | UsbRequest wait = usbConnection.requestWait();
119 | if (wait == null) throw new IOException("Connection.requestWait return null");
120 | if (wait.getEndpoint() == endpointIn) {
121 | ByteBuffer clientData = (ByteBuffer) wait.getClientData();
122 | mInRequestPool.add(request);
123 | if (clientData == data) {
124 | data.flip();
125 | return data;
126 | }
127 | }
128 | }
129 | }
130 |
131 | @Override
132 | public void flush() {
133 | }
134 |
135 | @Override
136 | public void close() {
137 | readBackgroundThread.interrupt();
138 | try {
139 | // 强制让adb执行错误,从而断开重连USB
140 | usbConnection.bulkTransfer(endpointOut, new byte[100], 100, 100);
141 | } catch (Exception ignored) {
142 | }
143 | try {
144 | usbConnection.releaseInterface(usbInterface);
145 | usbConnection.close();
146 | } catch (Exception ignored) {
147 | }
148 | }
149 |
150 | }
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/buffer/Buffer.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.buffer;
2 |
3 | import java.io.IOException;
4 |
5 | //本缓冲区为环形缓冲区,请自行设置最佳大小,数据写入会覆盖旧数据,若未读取就写入会造成未知后果
6 | public class Buffer {
7 | private final int capacity;
8 | private final byte[] buffer;
9 | private int head = 0;
10 | private int tail = 0;
11 |
12 | private final Object writeLock = new Object();
13 | private final Object readLock = new Object();
14 |
15 | public Buffer(int capacity) {
16 | this.capacity = capacity;
17 | this.buffer = new byte[capacity];
18 | }
19 |
20 | public void write(byte[] data) {
21 | synchronized (writeLock) {
22 | int remainingBytes = capacity - tail;
23 | if (data.length < remainingBytes) {
24 | // 无需环回
25 | System.arraycopy(data, 0, buffer, tail, data.length);
26 | tail += data.length;
27 | } else {
28 | // 需环回
29 | System.arraycopy(data, 0, buffer, tail, remainingBytes);
30 | tail = data.length - remainingBytes;
31 | System.arraycopy(data, remainingBytes, buffer, 0, tail);
32 | }
33 | synchronized (buffer) {
34 | buffer.notify();
35 | }
36 | }
37 | }
38 |
39 | public byte[] read(int size) throws InterruptedException, IOException {
40 | require(size);
41 | byte[] data = new byte[size];
42 | synchronized (readLock) {
43 | int remainingBytes = capacity - head;
44 | if (size < remainingBytes) {
45 | // 无需环回
46 | System.arraycopy(buffer, head, data, 0, size);
47 | head += size;
48 | } else {
49 | // 需环回
50 | System.arraycopy(buffer, head, data, 0, remainingBytes);
51 | head = size - remainingBytes;
52 | System.arraycopy(buffer, 0, data, remainingBytes, head);
53 | }
54 | }
55 | return data;
56 | }
57 |
58 | private void require(long byteCount) throws InterruptedException {
59 | while (true) {
60 | if (getSize() >= byteCount) {
61 | break;
62 | } else {
63 | synchronized (buffer) {
64 | buffer.wait();
65 | }
66 | }
67 | }
68 | }
69 |
70 | public int getSize() {
71 | if (tail >= head) return tail - head;
72 | else return capacity - head + tail;
73 | }
74 |
75 | }
76 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/buffer/BufferNew.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.buffer;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 | import java.util.concurrent.LinkedBlockingDeque;
6 |
7 | public class BufferNew {
8 | private boolean isClosed = false;
9 | private final LinkedBlockingDeque dataQueue = new LinkedBlockingDeque<>();
10 |
11 | public void write(ByteBuffer data) {
12 | dataQueue.offerLast(data);
13 | }
14 |
15 | public synchronized ByteBuffer read(int len) throws InterruptedException, IOException {
16 | if (len < 0 || isClosed) throw new IOException("BufferNew error");
17 | ByteBuffer data = ByteBuffer.allocate(len);
18 | int bytesToRead = len;
19 | while (bytesToRead > 0) {
20 | ByteBuffer tmpData = dataQueue.takeFirst();
21 | if (isClosed) throw new IOException("BufferNew error");
22 | int remaining = tmpData.remaining();
23 | if (remaining <= bytesToRead) {
24 | data.put(tmpData);
25 | bytesToRead -= remaining;
26 | } else {
27 | int oldLimit = tmpData.limit();
28 | tmpData.limit(tmpData.position() + bytesToRead);
29 | data.put(tmpData);
30 | tmpData.limit(oldLimit);
31 | dataQueue.offerFirst(tmpData);
32 | bytesToRead = 0;
33 | }
34 | }
35 | data.flip();
36 | return data;
37 | }
38 |
39 | public synchronized ByteBuffer readNext() throws InterruptedException, IOException {
40 | if (isClosed) throw new IOException("BufferNew error");
41 | ByteBuffer byteBuffer = dataQueue.takeFirst();
42 | if (isClosed) throw new IOException("BufferNew error");
43 | return byteBuffer;
44 | }
45 |
46 | public ByteBuffer readByteArrayBeforeClose() {
47 | ByteBuffer byteBuffer = ByteBuffer.allocate(Math.max(getSize(), 1));
48 | for (ByteBuffer tmpBuffer : dataQueue) byteBuffer.put(tmpBuffer);
49 | return byteBuffer;
50 | }
51 |
52 | public boolean isEmpty() {
53 | return dataQueue.isEmpty();
54 | }
55 |
56 | public int getSize() {
57 | int size = 0;
58 | for (ByteBuffer byteBuffer : dataQueue) size += byteBuffer.remaining();
59 | return size;
60 | }
61 |
62 | public void close() {
63 | if (isClosed) return;
64 | isClosed = true;
65 | dataQueue.offer(ByteBuffer.allocate(1));
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/buffer/BufferStream.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.buffer;
2 |
3 | import java.io.IOException;
4 | import java.nio.ByteBuffer;
5 |
6 | public class BufferStream {
7 | private boolean isClosed = false;
8 | private boolean canWrite;
9 | private final boolean canMultipleSend;
10 |
11 | private final BufferNew source = new BufferNew();
12 | private final BufferNew sink = new BufferNew();
13 | private final UnderlySocketFunction underlySocketFunction;
14 |
15 | // canWrite的设立,是为了兼容某些底层连接不能随时发送,例如adb协议规定需等待对方回复确认后才可以开始下一次发送,因此使用canWrite限制发送
16 | // canMultipleSend的设立,是为了兼容某些上层应用需逐次发送的场景,即上层的一次写入对应底层的一次写入,不会将上层多次写入合并在一起写入底层连接
17 | public BufferStream(boolean canWrite, boolean canMultipleSend, UnderlySocketFunction underlySocketFunction) throws Exception {
18 | this.canWrite = canWrite;
19 | this.canMultipleSend = canMultipleSend;
20 | this.underlySocketFunction = underlySocketFunction;
21 | underlySocketFunction.connect(this);
22 | }
23 |
24 | public BufferStream(boolean canWrite, UnderlySocketFunction underlySocketFunction) throws Exception {
25 | this(canWrite, true, underlySocketFunction);
26 | }
27 |
28 | public BufferStream(UnderlySocketFunction underlySocketFunction) throws Exception {
29 | this(true, true, underlySocketFunction);
30 | }
31 |
32 | public void pushSource(ByteBuffer byteBuffer) {
33 | if (byteBuffer != null) source.write(byteBuffer);
34 | }
35 |
36 | public byte readByte() throws InterruptedException, IOException {
37 | return readByteArray(1).get();
38 | }
39 |
40 | public short readShort() throws InterruptedException, IOException {
41 | return readByteArray(2).getShort();
42 | }
43 |
44 | public int readInt() throws InterruptedException, IOException {
45 | return readByteArray(4).getInt();
46 | }
47 |
48 | public long readLong() throws InterruptedException, IOException {
49 | return readByteArray(8).getLong();
50 | }
51 |
52 | public ByteBuffer readAllBytes() throws InterruptedException, IOException {
53 | return readByteArray(getSize());
54 | }
55 |
56 | public ByteBuffer readByteArray(int size) throws InterruptedException, IOException {
57 | if (isClosed) throw new IOException("connection is closed");
58 | return source.read(size);
59 | }
60 |
61 | public ByteBuffer readByteArrayBeforeClose() {
62 | return source.readByteArrayBeforeClose();
63 | }
64 |
65 | public void write(ByteBuffer byteBuffer) throws Exception {
66 | if (isClosed) throw new IOException("connection is closed");
67 | sink.write(byteBuffer);
68 | pollSink();
69 | }
70 |
71 | public void setCanWrite(boolean canWrite) throws Exception {
72 | if (isClosed) return;
73 | this.canWrite = canWrite;
74 | if (canWrite) pollSink();
75 | }
76 |
77 | private synchronized void pollSink() throws Exception {
78 | if (canWrite && !sink.isEmpty()) underlySocketFunction.write(this, canMultipleSend ? sink.read(sink.getSize()) : sink.readNext());
79 | }
80 |
81 | public boolean isEmpty() {
82 | return source.isEmpty();
83 | }
84 |
85 | public int getSize() {
86 | return source.getSize();
87 | }
88 |
89 | public boolean isClosed() {
90 | return isClosed;
91 | }
92 |
93 | public void flush() throws Exception {
94 | underlySocketFunction.flush(this);
95 | }
96 |
97 | public void close() {
98 | if (isClosed) return;
99 | isClosed = true;
100 | source.close();
101 | sink.close();
102 | try {
103 | underlySocketFunction.close(this);
104 | } catch (Exception ignored) {
105 | }
106 | }
107 |
108 | public interface UnderlySocketFunction {
109 | void connect(BufferStream bufferStream) throws Exception;
110 |
111 | void write(BufferStream bufferStream, ByteBuffer buffer) throws Exception;
112 |
113 | void flush(BufferStream bufferStream) throws Exception;
114 |
115 | void close(BufferStream bufferStream) throws Exception;
116 | }
117 | }
118 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/Client.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client;
2 |
3 | import android.app.Dialog;
4 | import android.util.Pair;
5 |
6 | import java.nio.ByteBuffer;
7 | import java.util.HashMap;
8 | import java.util.Objects;
9 |
10 | import top.saymzx.easycontrol.app.client.tools.AdbTools;
11 | import top.saymzx.easycontrol.app.client.tools.ClientController;
12 | import top.saymzx.easycontrol.app.client.tools.ClientPlayer;
13 | import top.saymzx.easycontrol.app.client.tools.ClientStream;
14 | import top.saymzx.easycontrol.app.client.tools.ControlPacket;
15 | import top.saymzx.easycontrol.app.databinding.ItemLoadingBinding;
16 | import top.saymzx.easycontrol.app.entity.AppData;
17 | import top.saymzx.easycontrol.app.entity.Device;
18 | import top.saymzx.easycontrol.app.helper.ViewTools;
19 |
20 | public class Client {
21 | private static final HashMap allClient = new HashMap<>();
22 | private boolean isClosed = false;
23 |
24 | // 组件
25 | private ClientStream clientStream = null;
26 | private ClientController clientController = null;
27 | private ClientPlayer clientPlayer = null;
28 | private Device device;
29 |
30 | public Client(Device device) {
31 | if (allClient.containsKey(device.uuid)) return;
32 | this.device = device;
33 | Pair loading = ViewTools.createLoading(AppData.mainActivity);
34 | loading.second.show();
35 | // 连接
36 | clientStream = new ClientStream(device, bool -> {
37 | if (bool) {
38 | allClient.put(device.uuid, this);
39 | // 控制器、播放器
40 | clientController = new ClientController(device, clientStream, () -> clientPlayer = new ClientPlayer(device.uuid, clientStream));
41 | // 临时设备
42 | boolean isTempDevice = device.isTempDevice();
43 | // 启动界面
44 | clientController.handleAction(device.changeToFullOnConnect ? "changeToFull" : "changeToSmall", null, 0);
45 | // 运行启动时操作
46 | if (device.customResolutionOnConnect) clientController.handleAction("writeByteBuffer", ControlPacket.createChangeResolutionEvent(device.customResolutionWidth, device.customResolutionHeight), 0);
47 | if (!isTempDevice && device.wakeOnConnect) clientController.handleAction("buttonWake", null, 0);
48 | if (!isTempDevice && device.lightOffOnConnect) clientController.handleAction("buttonLightOff", null, 2000);
49 | }
50 | if (loading.second.isShowing()) loading.second.cancel();
51 | });
52 | }
53 |
54 | public static void startDevice(Device device) {
55 | if (device == null) return;
56 | new Client(device);
57 | }
58 |
59 | public static Device getDevice(String uuid) {
60 | Client client = allClient.get(uuid);
61 | if (client == null) return null;
62 | return client.device;
63 | }
64 |
65 | public static ClientController getClientController(String uuid) {
66 | Client client = allClient.get(uuid);
67 | if (client == null) return null;
68 | return client.clientController;
69 | }
70 |
71 | public static void sendAction(String uuid, String action, ByteBuffer byteBuffer, int delay) {
72 | if (action == null || uuid == null) return;
73 | if (action.equals("start")) {
74 | for (Device device : AdbTools.devicesList) if (Objects.equals(device.uuid, uuid)) startDevice(device);
75 | } else {
76 | Client client = allClient.get(uuid);
77 | if (client == null) return;
78 | if (action.equals("close")) {
79 | client.close(byteBuffer);
80 | } else {
81 | if (client.clientController == null) return;
82 | client.clientController.handleAction(action, byteBuffer, delay);
83 | }
84 | }
85 | }
86 |
87 | private void close(ByteBuffer byteBuffer) {
88 | if (isClosed) return;
89 | isClosed = true;
90 | // 临时设备
91 | boolean isTempDevice = device.isTempDevice();
92 | // 运行断开时操作
93 | if (!isTempDevice && device.lockOnClose) clientController.handleAction("buttonLock", null, 0);
94 | else if (!isTempDevice && device.lightOnClose) clientController.handleAction("buttonLight", null, 0);
95 | // 关闭组件
96 | if (clientPlayer != null) clientPlayer.close();
97 | if (clientController != null) clientController.close();
98 | if (clientStream != null) clientStream.close();
99 | // 更新数据库
100 | if (!isTempDevice) AppData.dbHelper.update(device);
101 | allClient.remove(device.uuid);
102 | // 如果设置了自动重连
103 | if (byteBuffer != null && device.reconnectOnClose) startDevice(device);
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/decode/DecodecTools.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client.decode;
2 |
3 | import android.media.MediaCodecInfo;
4 | import android.media.MediaCodecList;
5 | import android.media.MediaFormat;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Objects;
9 |
10 | public class DecodecTools {
11 | private static ArrayList hevcDecodecList = null;
12 | private static ArrayList avcDecodecList = null;
13 | private static ArrayList opusDecodecList = null;
14 | private static Boolean isSupportOpus = null;
15 | private static Boolean isSupportH265 = null;
16 |
17 | // 获取解码器列表
18 | private static void getDecodecList() {
19 | MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
20 | hevcDecodecList = new ArrayList<>();
21 | avcDecodecList = new ArrayList<>();
22 | opusDecodecList = new ArrayList<>();
23 | for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) {
24 | if (!mediaCodecInfo.isEncoder()) {
25 | String codecName = mediaCodecInfo.getName();
26 | for (String supportType : mediaCodecInfo.getSupportedTypes()) {
27 | if (Objects.equals(supportType, MediaFormat.MIMETYPE_AUDIO_OPUS)) opusDecodecList.add(codecName);
28 | else {
29 | // 视频解码器要求硬件实现
30 | if (!codecName.startsWith("OMX.google") && !codecName.startsWith("c2.android")) {
31 | if (Objects.equals(supportType, MediaFormat.MIMETYPE_VIDEO_HEVC)) hevcDecodecList.add(codecName);
32 | else if (Objects.equals(supportType, MediaFormat.MIMETYPE_VIDEO_AVC)) avcDecodecList.add(codecName);
33 | }
34 | }
35 | }
36 | }
37 | }
38 | }
39 |
40 | // 获取解码器是否支持
41 | public static boolean isSupportOpus() {
42 | if (isSupportOpus != null) return isSupportOpus;
43 | if (opusDecodecList == null) getDecodecList();
44 | isSupportOpus = opusDecodecList.size() > 0;
45 | return isSupportOpus;
46 | }
47 |
48 | public static boolean isSupportH265() {
49 | if (isSupportH265 != null) return isSupportH265;
50 | if (hevcDecodecList == null) getDecodecList();
51 | isSupportH265 = hevcDecodecList.size() > 0;
52 | return isSupportH265;
53 | }
54 |
55 | // 获取视频最优解码器
56 | public static String getVideoDecoder(boolean h265) {
57 | if (hevcDecodecList == null || avcDecodecList == null) getDecodecList();
58 | ArrayList allHardNormalDecodec = h265 ? hevcDecodecList : avcDecodecList;
59 | ArrayList allHardLowLatencyDecodec = new ArrayList<>();
60 | for (String codecName : allHardNormalDecodec) if (codecName.contains("low_latency")) allHardLowLatencyDecodec.add(codecName);
61 | // 存在低延迟解码器
62 | if (allHardLowLatencyDecodec.size() > 0) return getC2Decodec(allHardLowLatencyDecodec);
63 | // 选择正常解码器
64 | if (allHardNormalDecodec.size() > 0) return getC2Decodec(allHardNormalDecodec);
65 | return "";
66 | }
67 |
68 | // 优选C2解码器
69 | private static String getC2Decodec(ArrayList allHardDecodec) {
70 | for (String codecName : allHardDecodec) if (codecName.contains("c2")) return codecName;
71 | return allHardDecodec.get(0);
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/decode/VideoDecode.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client.decode;
2 |
3 | import android.media.MediaCodec;
4 | import android.media.MediaFormat;
5 | import android.os.Build;
6 | import android.os.Handler;
7 | import android.util.Pair;
8 | import android.view.Surface;
9 |
10 | import androidx.annotation.NonNull;
11 |
12 | import java.io.IOException;
13 | import java.nio.ByteBuffer;
14 | import java.util.Objects;
15 | import java.util.concurrent.LinkedBlockingQueue;
16 |
17 | public class VideoDecode {
18 | private MediaCodec decodec;
19 | private final MediaCodec.Callback callback = new MediaCodec.Callback() {
20 | @Override
21 | public void onInputBufferAvailable(@NonNull MediaCodec mediaCodec, int inIndex) {
22 | intputBufferQueue.offer(inIndex);
23 | }
24 |
25 | @Override
26 | public void onOutputBufferAvailable(@NonNull MediaCodec mediaCodec, int outIndex, @NonNull MediaCodec.BufferInfo bufferInfo) {
27 | try {
28 | mediaCodec.releaseOutputBuffer(outIndex, bufferInfo.presentationTimeUs);
29 | } catch (IllegalStateException ignored) {
30 | }
31 | }
32 |
33 | @Override
34 | public void onError(@NonNull MediaCodec mediaCodec, @NonNull MediaCodec.CodecException e) {
35 | }
36 |
37 | @Override
38 | public void onOutputFormatChanged(@NonNull MediaCodec mediaCodec, @NonNull MediaFormat format) {
39 | }
40 | };
41 |
42 | public VideoDecode(Pair videoSize, Surface surface, ByteBuffer csd0, ByteBuffer csd1, Handler playHandler) throws IOException, InterruptedException {
43 | setVideoDecodec(videoSize, surface, csd0, csd1, playHandler);
44 | }
45 |
46 | public void release() {
47 | try {
48 | decodec.stop();
49 | decodec.release();
50 | } catch (Exception ignored) {
51 | }
52 | }
53 |
54 | private final LinkedBlockingQueue intputBufferQueue = new LinkedBlockingQueue<>();
55 |
56 | public void decodeIn(ByteBuffer data) throws InterruptedException {
57 | try {
58 | long pts = data.getLong();
59 | int inIndex = intputBufferQueue.take();
60 | decodec.getInputBuffer(inIndex).put(data);
61 | decodec.queueInputBuffer(inIndex, 0, data.capacity() - 8, pts, 0);
62 | } catch (IllegalStateException ignored) {
63 | }
64 | }
65 |
66 | // 创建Codec
67 | private void setVideoDecodec(Pair videoSize, Surface surface, ByteBuffer csd0, ByteBuffer csd1, Handler playHandler) throws IOException, InterruptedException {
68 | boolean useH265 = csd1 == null;
69 | // 创建解码器
70 | String codecMime = useH265 ? MediaFormat.MIMETYPE_VIDEO_HEVC : MediaFormat.MIMETYPE_VIDEO_AVC;
71 | try {
72 | String codecName = DecodecTools.getVideoDecoder(useH265);
73 | if (Objects.equals(codecName, "")) decodec = MediaCodec.createDecoderByType(codecMime);
74 | else decodec = MediaCodec.createByCodecName(codecName);
75 | } catch (Exception ignord) {
76 | decodec = MediaCodec.createDecoderByType(codecMime);
77 | }
78 | MediaFormat decodecFormat = MediaFormat.createVideoFormat(codecMime, videoSize.first, videoSize.second);
79 | // 获取视频标识头
80 | csd0.position(8);
81 | decodecFormat.setByteBuffer("csd-0", csd0);
82 | if (!useH265) {
83 | csd1.position(8);
84 | decodecFormat.setByteBuffer("csd-1", csd1);
85 | }
86 | // 异步解码
87 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && playHandler != null) {
88 | decodec.setCallback(callback, playHandler);
89 | } else decodec.setCallback(callback);
90 | // 配置解码器
91 | decodec.configure(decodecFormat, surface, null, 0);
92 | // 启动解码器
93 | decodec.start();
94 | // 解析首帧,解决开始黑屏问题
95 | csd0.position(0);
96 | decodeIn(csd0);
97 | if (!useH265) {
98 | csd1.position(0);
99 | decodeIn(csd1);
100 | }
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/tools/AdbTools.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client.tools;
2 |
3 | import android.hardware.usb.UsbDevice;
4 |
5 | import java.io.InputStream;
6 | import java.util.ArrayList;
7 | import java.util.HashMap;
8 | import java.util.UUID;
9 | import java.util.regex.Pattern;
10 |
11 | import top.saymzx.easycontrol.app.adb.Adb;
12 | import top.saymzx.easycontrol.app.entity.AppData;
13 | import top.saymzx.easycontrol.app.entity.Device;
14 | import top.saymzx.easycontrol.app.entity.MyInterface;
15 | import top.saymzx.easycontrol.app.helper.PublicTools;
16 |
17 | public class AdbTools {
18 | private static final HashMap allAdbConnect = new HashMap<>();
19 | public static final ArrayList devicesList = new ArrayList<>();
20 | public static final HashMap usbDevicesList = new HashMap<>();
21 |
22 | // 连接ADB
23 | public static Adb connectADB(Device device) throws Exception {
24 | String addressId = device.isLinkDevice() ? device.uuid : device.address + ":" + device.adbPort;
25 | Adb adb = allAdbConnect.get(addressId);
26 | if (adb == null || adb.isClosed()) {
27 | if (device.isLinkDevice()) adb = new Adb(usbDevicesList.get(addressId), AppData.keyPair);
28 | else adb = new Adb(PublicTools.getIp(device.address), device.adbPort, AppData.keyPair);
29 | allAdbConnect.put(addressId, adb);
30 | }
31 | return adb;
32 | }
33 |
34 | public static void runOnceCmd(Device device, String cmd, MyInterface.MyFunctionBoolean handle) {
35 | new Thread(() -> {
36 | try {
37 | Adb adb = connectADB(device);
38 | adb.runAdbCmd(cmd);
39 | handle.run(true);
40 | } catch (Exception ignored) {
41 | handle.run(false);
42 | }
43 | }).start();
44 | }
45 |
46 | public static void restartOnTcpip(Device device, MyInterface.MyFunctionBoolean handle) {
47 | new Thread(() -> {
48 | try {
49 | Adb adb = connectADB(device);
50 | String output = adb.restartOnTcpip(5555);
51 | handle.run(output.contains("restarting"));
52 | } catch (Exception ignored) {
53 | handle.run(false);
54 | }
55 | }).start();
56 | }
57 |
58 | public static void pushFile(Device device, InputStream file, String fileName, MyInterface.MyFunctionInt handleProcess) {
59 | new Thread(() -> {
60 | try {
61 | String tempFileName = fileName;
62 | Adb adb = connectADB(device);
63 | // 因为糟糕的ADB,如果使用中文名的话,会崩溃,所以此处使用随机名词
64 | if (!Pattern.compile("^[a-zA-Z0-9\\(\\)\\-\\_\\[\\]\\.]+$").matcher(tempFileName).matches()) {
65 | int dotIndex = tempFileName.lastIndexOf(".");
66 | tempFileName = UUID.randomUUID() + (dotIndex == -1 ? "" : tempFileName.substring(dotIndex));
67 | }
68 | adb.pushFile(file, "/sdcard/Download/Easycontrol/" + tempFileName, handleProcess);
69 | } catch (Exception ignored) {
70 | handleProcess.run(-1);
71 | }
72 | }).start();
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/tools/ClientPlayer.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client.tools;
2 |
3 | import android.os.Build;
4 | import android.os.Handler;
5 | import android.os.HandlerThread;
6 | import android.util.Pair;
7 | import android.view.Surface;
8 |
9 | import java.nio.ByteBuffer;
10 |
11 | import top.saymzx.easycontrol.app.client.Client;
12 | import top.saymzx.easycontrol.app.client.decode.AudioDecode;
13 | import top.saymzx.easycontrol.app.client.decode.VideoDecode;
14 | import top.saymzx.easycontrol.app.helper.PublicTools;
15 |
16 | public class ClientPlayer {
17 | private boolean isClose = false;
18 | private final ClientController clientController;
19 | private final ClientStream clientStream;
20 | private final Thread mainStreamInThread = new Thread(this::mainStreamIn);
21 | private final Thread videoStreamInThread = new Thread(this::videoStreamIn);
22 | private Handler playHandler = null;
23 | private final HandlerThread playHandlerThread = new HandlerThread("easycontrol_play", Thread.MAX_PRIORITY);
24 | private static final int AUDIO_EVENT = 1;
25 | private static final int CLIPBOARD_EVENT = 2;
26 | private static final int CHANGE_SIZE_EVENT = 3;
27 |
28 | public ClientPlayer(String uuid, ClientStream clientStream) {
29 | clientController = Client.getClientController(uuid);
30 | this.clientStream = clientStream;
31 | if (clientController == null) return;
32 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
33 | playHandlerThread.start();
34 | playHandler = new Handler(playHandlerThread.getLooper());
35 | }
36 | mainStreamInThread.start();
37 | videoStreamInThread.start();
38 | }
39 |
40 | private void mainStreamIn() {
41 | AudioDecode audioDecode = null;
42 | boolean useOpus = true;
43 | try {
44 | if (clientStream.readByteFromMain() == 1) useOpus = clientStream.readByteFromMain() == 1;
45 | // 循环处理报文
46 | while (!Thread.interrupted()) {
47 | switch (clientStream.readByteFromMain()) {
48 | case AUDIO_EVENT:
49 | ByteBuffer audioFrame = clientStream.readFrameFromMain();
50 | if (audioDecode != null) audioDecode.decodeIn(audioFrame);
51 | else audioDecode = new AudioDecode(useOpus, audioFrame, playHandler);
52 | break;
53 | case CLIPBOARD_EVENT:
54 | clientController.handleAction("setClipBoard", clientStream.readByteArrayFromMain(clientStream.readIntFromMain()), 0);
55 | break;
56 | case CHANGE_SIZE_EVENT:
57 | clientController.handleAction("updateVideoSize", clientStream.readByteArrayFromMain(8), 0);
58 | break;
59 | }
60 | }
61 | } catch (InterruptedException ignored) {
62 | } catch (Exception e) {
63 | PublicTools.logToast("player", e.toString(), false);
64 | } finally {
65 | if (audioDecode != null) audioDecode.release();
66 | }
67 | }
68 |
69 | private void videoStreamIn() {
70 | VideoDecode videoDecode = null;
71 | try {
72 | boolean useH265 = clientStream.readByteFromVideo() == 1;
73 | Pair videoSize = new Pair<>(clientStream.readIntFromVideo(), clientStream.readIntFromVideo());
74 | Surface surface = new Surface(clientController.getTextureView().getSurfaceTexture());
75 | ByteBuffer csd0 = clientStream.readFrameFromVideo();
76 | ByteBuffer csd1 = useH265 ? null : clientStream.readFrameFromVideo();
77 | videoDecode = new VideoDecode(videoSize, surface, csd0, csd1, playHandler);
78 | while (!Thread.interrupted()) videoDecode.decodeIn(clientStream.readFrameFromVideo());
79 | } catch (Exception ignored) {
80 | } finally {
81 | if (videoDecode != null) videoDecode.release();
82 | }
83 | }
84 |
85 | public void close() {
86 | if (isClose) return;
87 | isClose = true;
88 | mainStreamInThread.interrupt();
89 | videoStreamInThread.interrupt();
90 | playHandlerThread.interrupt();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/tools/ControlPacket.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client.tools;
2 |
3 | import android.view.MotionEvent;
4 |
5 | import java.nio.ByteBuffer;
6 | import java.nio.charset.StandardCharsets;
7 |
8 | public final class ControlPacket {
9 |
10 | // 触摸事件
11 | public static ByteBuffer createTouchEvent(int action, int p, float x, float y, int offsetTime) {
12 | if (x < 0 || x > 1 || y < 0 || y > 1) {
13 | // 超出范围则改为抬起事件
14 | if (x < 0) x = 0;
15 | if (x > 1) x = 1;
16 | if (y < 0) y = 0;
17 | if (y > 1) y = 1;
18 | action = MotionEvent.ACTION_UP;
19 | }
20 | ByteBuffer byteBuffer = ByteBuffer.allocate(15);
21 | // 触摸事件
22 | byteBuffer.put((byte) 1);
23 | // 触摸类型
24 | byteBuffer.put((byte) action);
25 | // pointerId
26 | byteBuffer.put((byte) p);
27 | // 坐标位置
28 | byteBuffer.putFloat(x);
29 | byteBuffer.putFloat(y);
30 | // 时间偏移
31 | byteBuffer.putInt(offsetTime);
32 | byteBuffer.flip();
33 | return byteBuffer;
34 | }
35 |
36 | // 按键事件
37 | public static ByteBuffer createKeyEvent(int key, int meta) {
38 | ByteBuffer byteBuffer = ByteBuffer.allocate(9);
39 | // 输入事件
40 | byteBuffer.put((byte) 2);
41 | // 按键类型
42 | byteBuffer.putInt(key);
43 | byteBuffer.putInt(meta);
44 | byteBuffer.flip();
45 | return byteBuffer;
46 | }
47 |
48 | // 剪切板事件
49 | public static ByteBuffer createClipboardEvent(String text) {
50 | byte[] tmpTextByte = text.getBytes(StandardCharsets.UTF_8);
51 | if (tmpTextByte.length == 0 || tmpTextByte.length > 5000) return null;
52 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 + tmpTextByte.length);
53 | byteBuffer.put((byte) 3);
54 | byteBuffer.putInt(tmpTextByte.length);
55 | byteBuffer.put(tmpTextByte);
56 | byteBuffer.flip();
57 | return byteBuffer;
58 | }
59 |
60 | // 心跳包
61 | public static ByteBuffer createKeepAlive() {
62 | return ByteBuffer.wrap(new byte[]{4});
63 | }
64 |
65 | // 修改分辨率事件
66 | public static ByteBuffer createChangeResolutionEvent(float newSize) {
67 | ByteBuffer byteBuffer = ByteBuffer.allocate(5);
68 | byteBuffer.put((byte) 5);
69 | byteBuffer.putFloat(newSize);
70 | byteBuffer.flip();
71 | return byteBuffer;
72 | }
73 |
74 | // 修改分辨率事件
75 | public static ByteBuffer createChangeResolutionEvent(int width, int height) {
76 | ByteBuffer byteBuffer = ByteBuffer.allocate(9);
77 | byteBuffer.put((byte) 9);
78 | byteBuffer.putInt(width);
79 | byteBuffer.putInt(height);
80 | byteBuffer.flip();
81 | return byteBuffer;
82 | }
83 |
84 | // 旋转请求事件
85 | public static ByteBuffer createRotateEvent() {
86 | return ByteBuffer.wrap(new byte[]{6});
87 | }
88 |
89 | // 背光控制事件
90 | public static ByteBuffer createLightEvent(int mode) {
91 | return ByteBuffer.wrap(new byte[]{7, (byte) mode});
92 | }
93 |
94 | // 电源键事件
95 | public static ByteBuffer createPowerEvent(int mode) {
96 | ByteBuffer byteBuffer = ByteBuffer.allocate(5);
97 | byteBuffer.put((byte) 8);
98 | byteBuffer.putInt(mode);
99 | byteBuffer.flip();
100 | return byteBuffer;
101 | }
102 |
103 | }
104 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/view/MiniView.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client.view;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.graphics.PixelFormat;
5 | import android.os.Build;
6 | import android.view.Gravity;
7 | import android.view.LayoutInflater;
8 | import android.view.MotionEvent;
9 | import android.view.WindowManager;
10 |
11 | import java.nio.ByteBuffer;
12 | import java.util.concurrent.atomic.AtomicInteger;
13 |
14 | import top.saymzx.easycontrol.app.client.Client;
15 | import top.saymzx.easycontrol.app.client.tools.ClientController;
16 | import top.saymzx.easycontrol.app.databinding.ModuleMiniViewBinding;
17 | import top.saymzx.easycontrol.app.entity.AppData;
18 | import top.saymzx.easycontrol.app.entity.Device;
19 | import top.saymzx.easycontrol.app.helper.PublicTools;
20 | import top.saymzx.easycontrol.app.helper.ViewTools;
21 |
22 | public class MiniView {
23 |
24 | private final Device device;
25 | private ClientController clientController;
26 | private Thread timeoutListenerThread;
27 | private long lastTouchTIme = 0;
28 |
29 | // 迷你悬浮窗
30 | private final ModuleMiniViewBinding miniView = ModuleMiniViewBinding.inflate(LayoutInflater.from(AppData.applicationContext));
31 | private final WindowManager.LayoutParams miniViewParams = new WindowManager.LayoutParams(
32 | WindowManager.LayoutParams.WRAP_CONTENT,
33 | WindowManager.LayoutParams.WRAP_CONTENT,
34 | Build.VERSION.SDK_INT >= Build.VERSION_CODES.O ? WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY : WindowManager.LayoutParams.TYPE_PHONE,
35 | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON,
36 | PixelFormat.TRANSLUCENT
37 | );
38 |
39 | public MiniView(String uuid) {
40 | device = Client.getDevice(uuid);
41 | clientController = Client.getClientController(uuid);
42 | if (device == null || clientController == null) return;
43 | miniViewParams.gravity = Gravity.START | Gravity.TOP;
44 | miniViewParams.x = 0;
45 | // 设置监听控制
46 | setBarListener();
47 | setButtonListener();
48 | }
49 |
50 | public void show(ByteBuffer byteBuffer) {
51 | if (device == null || clientController == null) return;
52 | miniViewParams.y = device.miniY;
53 | // 显示
54 | ViewTools.viewAnim(miniView.getRoot(), true, PublicTools.dp2px(-40f), 0, (isStart -> {
55 | if (isStart) AppData.windowManager.addView(miniView.getRoot(), miniViewParams);
56 | }));
57 | // 超时检测
58 | if (device.miniTimeoutOnRunning && byteBuffer != null) {
59 | lastTouchTIme = System.currentTimeMillis();
60 | timeoutListenerThread = new Thread(() -> timeoutListener(new String(byteBuffer.array())));
61 | timeoutListenerThread.start();
62 | }
63 | }
64 |
65 | public void hide() {
66 | if (device == null || clientController == null) return;
67 | try {
68 | AppData.windowManager.removeView(miniView.getRoot());
69 | if (timeoutListenerThread != null) timeoutListenerThread.interrupt();
70 | } catch (Exception ignored) {
71 | }
72 | }
73 |
74 | // 超时监听
75 | private void timeoutListener(String timeoutAction) {
76 | try {
77 | long now;
78 | while (!Thread.interrupted()) {
79 | Thread.sleep(2);
80 | now = System.currentTimeMillis();
81 | if (now - lastTouchTIme > 5000) {
82 | clientController.handleAction( timeoutAction, null, 0);
83 | return;
84 | }
85 | }
86 | } catch (Exception ignored) {
87 | }
88 | }
89 |
90 | // 设置监听控制
91 | @SuppressLint("ClickableViewAccessibility")
92 | private void setBarListener() {
93 | AtomicInteger yy = new AtomicInteger();
94 | AtomicInteger oldYy = new AtomicInteger();
95 | miniView.getRoot().setOnTouchListener((v, event) -> {
96 | switch (event.getActionMasked()) {
97 | case MotionEvent.ACTION_OUTSIDE:
98 | lastTouchTIme = System.currentTimeMillis();
99 | break;
100 | case MotionEvent.ACTION_DOWN: {
101 | yy.set((int) event.getRawY());
102 | oldYy.set(miniViewParams.y);
103 | break;
104 | }
105 | case MotionEvent.ACTION_MOVE: {
106 | miniViewParams.y = oldYy.get() + (int) event.getRawY() - yy.get();
107 | device.miniY = miniViewParams.y;
108 | AppData.windowManager.updateViewLayout(miniView.getRoot(), miniViewParams);
109 | break;
110 | }
111 | }
112 | return true;
113 | });
114 | }
115 |
116 | // 设置按钮监听
117 | private void setButtonListener() {
118 | miniView.buttonSmall.setOnClickListener(v -> clientController.handleAction( "changeToSmall", null, 0));
119 | miniView.buttonFull.setOnClickListener(v -> clientController.handleAction( "changeToFull", null, 0));
120 | }
121 |
122 | }
123 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/client/view/MyViewForSmallView.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.client.view;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.MotionEvent;
6 | import android.widget.FrameLayout;
7 |
8 | import androidx.annotation.NonNull;
9 | import androidx.annotation.Nullable;
10 |
11 | public class MyViewForSmallView extends FrameLayout {
12 |
13 | private MyFunctionMotionEvent onTouchHandle;
14 |
15 | public MyViewForSmallView(@NonNull Context context) {
16 | super(context);
17 | }
18 |
19 | public MyViewForSmallView(@NonNull Context context, @Nullable AttributeSet attrs) {
20 | super(context, attrs);
21 | }
22 |
23 | public MyViewForSmallView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
24 | super(context, attrs, defStyleAttr);
25 | }
26 |
27 | public MyViewForSmallView(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
28 | super(context, attrs, defStyleAttr, defStyleRes);
29 | }
30 |
31 | @Override
32 | public boolean dispatchTouchEvent(MotionEvent ev) {
33 | if (onTouchHandle != null) onTouchHandle.run(ev);
34 | return super.dispatchTouchEvent(ev);
35 | }
36 |
37 | public void setOnTouchHandle(MyFunctionMotionEvent handle) {
38 | onTouchHandle = handle;
39 | }
40 |
41 | public interface MyFunctionMotionEvent {
42 | void run(MotionEvent event);
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/entity/AppData.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.entity;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.content.ClipboardManager;
5 | import android.content.Context;
6 | import android.hardware.SensorManager;
7 | import android.hardware.usb.UsbManager;
8 | import android.net.wifi.WifiManager;
9 | import android.os.Handler;
10 | import android.view.WindowManager;
11 |
12 | import top.saymzx.easycontrol.app.MainActivity;
13 | import top.saymzx.easycontrol.app.adb.AdbKeyPair;
14 | import top.saymzx.easycontrol.app.helper.DbHelper;
15 | import top.saymzx.easycontrol.app.helper.PublicTools;
16 |
17 | public class AppData {
18 | @SuppressLint("StaticFieldLeak")
19 | public static Context applicationContext;
20 | public static MainActivity mainActivity;
21 | public static Handler uiHandler;
22 |
23 | // 数据库工具库
24 | public static DbHelper dbHelper;
25 |
26 | // 密钥文件
27 | public static AdbKeyPair keyPair;
28 |
29 | // 系统服务
30 | public static ClipboardManager clipBoard;
31 | public static WifiManager wifiManager;
32 | public static UsbManager usbManager;
33 | public static WindowManager windowManager;
34 | public static SensorManager sensorManager;
35 |
36 | // 设置值
37 | public static Setting setting;
38 |
39 | public static void init(MainActivity m) {
40 | mainActivity = m;
41 | applicationContext = m.getApplicationContext();
42 | uiHandler = new android.os.Handler(m.getMainLooper());
43 | dbHelper = new DbHelper(applicationContext);
44 | clipBoard = (ClipboardManager) applicationContext.getSystemService(Context.CLIPBOARD_SERVICE);
45 | wifiManager = (WifiManager) applicationContext.getSystemService(Context.WIFI_SERVICE);
46 | usbManager = (UsbManager) applicationContext.getSystemService(Context.USB_SERVICE);
47 | windowManager = (WindowManager) applicationContext.getSystemService(Context.WINDOW_SERVICE);
48 | sensorManager = (SensorManager) applicationContext.getSystemService(Context.SENSOR_SERVICE);
49 | setting = new Setting(applicationContext.getSharedPreferences("setting", Context.MODE_PRIVATE));
50 | // 读取密钥
51 | keyPair = PublicTools.readAdbKeyPair();
52 | }
53 |
54 | }
55 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/entity/Device.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.entity;
2 |
3 | import java.util.Objects;
4 |
5 | public class Device {
6 | public static final int TYPE_NETWORK = 1;
7 | public static final int TYPE_LINK = 2;
8 | public final String uuid;
9 | public final int type;
10 | public String name;
11 | public String address = "";
12 | public String startApp = "";
13 | public int adbPort = 5555;
14 | public int serverPort = 25166;
15 | public boolean listenClip=true;
16 | public boolean isAudio = true;
17 | public int maxSize = 1600;
18 | public int maxFps = 60;
19 | public int maxVideoBit = 4;
20 | public boolean useH265 = true;
21 | public boolean connectOnStart = false;
22 | public boolean customResolutionOnConnect = false;
23 | public boolean wakeOnConnect = true;
24 | public boolean lightOffOnConnect = false;
25 | public boolean showNavBarOnConnect = true;
26 | public boolean changeToFullOnConnect = false;
27 | public boolean keepWakeOnRunning = true;
28 | public boolean changeResolutionOnRunning = false;
29 | public boolean smallToMiniOnRunning = false;
30 | public boolean fullToMiniOnRunning = true;
31 | public boolean miniTimeoutOnRunning = false;
32 | public boolean lockOnClose = true;
33 | public boolean lightOnClose = false;
34 | public boolean reconnectOnClose = false;
35 | public int customResolutionWidth = 1080;
36 | public int customResolutionHeight = 2400;
37 | public int smallX = 200;
38 | public int smallY = 200;
39 | public int smallLength = 800;
40 | public int miniY = 200;
41 |
42 | public Device(String uuid, int type) {
43 | this.uuid = uuid;
44 | this.type = type;
45 | this.name = uuid;
46 | }
47 |
48 | public boolean isNetworkDevice() {
49 | return type == TYPE_NETWORK;
50 | }
51 |
52 | public boolean isLinkDevice() {
53 | return type == TYPE_LINK;
54 | }
55 |
56 | public boolean isTempDevice() {
57 | return Objects.equals(name, "----");
58 | }
59 |
60 | public Device clone(String uuid) {
61 | Device newDevice = new Device(uuid, type);
62 | newDevice.name = name;
63 | newDevice.address = address;
64 | newDevice.startApp = startApp;
65 | newDevice.adbPort = adbPort;
66 | newDevice.serverPort = serverPort;
67 | newDevice.listenClip = listenClip;
68 | newDevice.isAudio = isAudio;
69 | newDevice.maxSize = maxSize;
70 | newDevice.maxFps = maxFps;
71 | newDevice.maxVideoBit = maxVideoBit;
72 | newDevice.useH265 = useH265;
73 | newDevice.connectOnStart = connectOnStart;
74 | newDevice.customResolutionOnConnect = customResolutionOnConnect;
75 | newDevice.wakeOnConnect = wakeOnConnect;
76 | newDevice.lightOffOnConnect = lightOffOnConnect;
77 | newDevice.showNavBarOnConnect = showNavBarOnConnect;
78 | newDevice.changeToFullOnConnect = changeToFullOnConnect;
79 | newDevice.keepWakeOnRunning = keepWakeOnRunning;
80 | newDevice.changeResolutionOnRunning = changeResolutionOnRunning;
81 | newDevice.smallToMiniOnRunning = smallToMiniOnRunning;
82 | newDevice.fullToMiniOnRunning = fullToMiniOnRunning;
83 | newDevice.miniTimeoutOnRunning = miniTimeoutOnRunning;
84 | newDevice.lockOnClose = lockOnClose;
85 | newDevice.lightOnClose = lightOnClose;
86 | newDevice.reconnectOnClose = reconnectOnClose;
87 |
88 | newDevice.customResolutionWidth = customResolutionWidth;
89 | newDevice.customResolutionHeight = customResolutionHeight;
90 | newDevice.smallX = smallX;
91 | newDevice.smallY = smallY;
92 | newDevice.smallLength = smallLength;
93 | newDevice.miniY = miniY;
94 | return newDevice;
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/entity/MyInterface.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.entity;
2 |
3 | public final class MyInterface {
4 | public interface MyFunction {
5 | void run();
6 | }
7 |
8 | public interface MyFunctionBoolean {
9 | void run(Boolean bool);
10 | }
11 |
12 | public interface MyFunctionString {
13 | void run(String str);
14 | }
15 |
16 | public interface MyFunctionInt {
17 | void run(int value);
18 | }
19 |
20 | public interface MyFunctionBytes {
21 | void run(byte[] buffer);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/entity/Setting.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.entity;
2 |
3 | import android.content.SharedPreferences;
4 |
5 | import java.util.UUID;
6 |
7 | public final class Setting {
8 | private final SharedPreferences sharedPreferences;
9 |
10 | private final SharedPreferences.Editor editor;
11 |
12 | public boolean getIsActive() {
13 | return sharedPreferences.getBoolean("isActive", false);
14 | }
15 |
16 | public void setIsActive(boolean value) {
17 | editor.putBoolean("isActive", value);
18 | editor.apply();
19 | }
20 |
21 | public String getActiveKey() {
22 | return sharedPreferences.getString("activeKey", "");
23 | }
24 |
25 | public void setActiveKey(String value) {
26 | editor.putString("activeKey", value);
27 | editor.apply();
28 | }
29 |
30 | public String getLocale() {
31 | return sharedPreferences.getString("locale", "");
32 | }
33 |
34 | public void setLocale(String value) {
35 | editor.putString("locale", value);
36 | editor.apply();
37 | }
38 |
39 | public boolean getAutoRotate() {
40 | return sharedPreferences.getBoolean("autoRotate", true);
41 | }
42 |
43 | public void setAutoRotate(boolean value) {
44 | editor.putBoolean("autoRotate", value);
45 | editor.apply();
46 | }
47 |
48 | public String getLocalUUID() {
49 | if (!sharedPreferences.contains("UUID")) {
50 | editor.putString("UUID", UUID.randomUUID().toString());
51 | editor.apply();
52 | }
53 | return sharedPreferences.getString("UUID", "");
54 | }
55 |
56 | public Setting(SharedPreferences sharedPreferences) {
57 | this.sharedPreferences = sharedPreferences;
58 | this.editor = sharedPreferences.edit();
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/java/top/saymzx/easycontrol/app/helper/MyBroadcastReceiver.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.app.helper;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.app.PendingIntent;
5 | import android.content.BroadcastReceiver;
6 | import android.content.Context;
7 | import android.content.Intent;
8 | import android.content.IntentFilter;
9 | import android.hardware.usb.UsbDevice;
10 | import android.hardware.usb.UsbManager;
11 | import android.os.Build;
12 | import android.util.Log;
13 |
14 | import java.io.IOException;
15 | import java.nio.ByteBuffer;
16 | import java.util.Map;
17 |
18 | import top.saymzx.easycontrol.app.adb.UsbChannel;
19 | import top.saymzx.easycontrol.app.client.Client;
20 | import top.saymzx.easycontrol.app.client.tools.AdbTools;
21 | import top.saymzx.easycontrol.app.entity.AppData;
22 | import top.saymzx.easycontrol.app.entity.Device;
23 |
24 | public class MyBroadcastReceiver extends BroadcastReceiver {
25 |
26 | public static final String ACTION_UPDATE_USB = "top.saymzx.easycontrol.app.UPDATE_USB";
27 | private static final String ACTION_USB_PERMISSION = "top.saymzx.easycontrol.app.USB_PERMISSION";
28 | public static final String ACTION_UPDATE_DEVICE_LIST = "top.saymzx.easycontrol.app.UPDATE_DEVICE_LIST";
29 | public static final String ACTION_CONTROL = "top.saymzx.easycontrol.app.CONTROL";
30 | private static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF";
31 |
32 | private DeviceListAdapter deviceListAdapter;
33 |
34 | // 注册广播
35 | @SuppressLint("UnspecifiedRegisterReceiverFlag")
36 | public void register(Context context) {
37 | IntentFilter filter = new IntentFilter();
38 | filter.addAction(UsbManager.ACTION_USB_DEVICE_ATTACHED);
39 | filter.addAction(UsbManager.ACTION_USB_DEVICE_DETACHED);
40 | filter.addAction(ACTION_USB_PERMISSION);
41 | filter.addAction(ACTION_UPDATE_USB);
42 | filter.addAction(ACTION_UPDATE_DEVICE_LIST);
43 | filter.addAction(ACTION_CONTROL);
44 | filter.addAction(ACTION_SCREEN_OFF);
45 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) context.registerReceiver(this, filter, Context.RECEIVER_EXPORTED);
46 | else context.registerReceiver(this, filter);
47 | }
48 |
49 | public void unRegister(Context context) {
50 | context.unregisterReceiver(this);
51 | }
52 |
53 | @Override
54 | public void onReceive(Context context, Intent intent) {
55 | String action = intent.getAction();
56 | if (UsbManager.ACTION_USB_DEVICE_ATTACHED.equals(action)) onConnectUsb(context, intent);
57 | else if (ACTION_UPDATE_USB.equals(action) || ACTION_USB_PERMISSION.equals(action) || UsbManager.ACTION_USB_DEVICE_DETACHED.equals(action)) updateUSB();
58 | else if (ACTION_SCREEN_OFF.equals(action)) handleScreenOff();
59 | else if (ACTION_UPDATE_DEVICE_LIST.equals(action)) deviceListAdapter.update();
60 | else if (ACTION_CONTROL.equals(action)) handleControl(intent);
61 | }
62 |
63 |
64 | public void setDeviceListAdapter(DeviceListAdapter deviceListAdapter) {
65 | this.deviceListAdapter = deviceListAdapter;
66 | }
67 |
68 | private void handleScreenOff() {
69 | for (Device device : AdbTools.devicesList) Client.sendAction(device.uuid, "close", null, 0);
70 | }
71 |
72 | private void handleControl(Intent intent) {
73 | String action = intent.getStringExtra("action");
74 | String uuid = intent.getStringExtra("uuid");
75 | if (action == null || uuid == null) return;
76 | if (action.equals("runShell")) {
77 | String cmd = intent.getStringExtra("cmd");
78 | if (cmd == null) return;
79 | Client.sendAction(uuid, action, ByteBuffer.wrap(cmd.getBytes()), 0);
80 | } else Client.sendAction(uuid, action, null, 0);
81 | }
82 |
83 | // 请求USB设备权限
84 | private void onConnectUsb(Context context, Intent intent) {
85 | UsbDevice usbDevice = intent.getParcelableExtra(UsbManager.EXTRA_DEVICE);
86 | if (usbDevice == null || AppData.usbManager == null) return;
87 | @SuppressLint("MutableImplicitPendingIntent") PendingIntent permissionIntent = PendingIntent.getBroadcast(context, 0, new Intent(ACTION_USB_PERMISSION), PendingIntent.FLAG_MUTABLE);
88 | AppData.usbManager.requestPermission(usbDevice, permissionIntent);
89 | }
90 |
91 | public synchronized void updateUSB() {
92 | if (AppData.usbManager == null) return;
93 | AdbTools.usbDevicesList.clear();
94 | for (Map.Entry entry : AppData.usbManager.getDeviceList().entrySet()) {
95 | UsbDevice usbDevice = entry.getValue();
96 | if (AppData.usbManager.hasPermission(usbDevice)) {
97 | // 有线设备使用序列号作为唯一标识符
98 | String uuid = usbDevice.getSerialNumber();
99 | // 若没有该设备,则新建设备
100 | Device device = AppData.dbHelper.getByUUID(uuid);
101 | if (device == null) {
102 | device = new Device(uuid, Device.TYPE_LINK);
103 | device.address = uuid;
104 | AppData.dbHelper.insert(device);
105 | }
106 | AdbTools.usbDevicesList.put(uuid, usbDevice);
107 | }
108 | }
109 | deviceListAdapter.update();
110 | }
111 |
112 | public synchronized void resetUSB() {
113 | if (AppData.usbManager == null) return;
114 | for (Map.Entry entry : AppData.usbManager.getDeviceList().entrySet()) {
115 | UsbDevice usbDevice = entry.getValue();
116 | if (AppData.usbManager.hasPermission(usbDevice)) {
117 | try {
118 | new UsbChannel(usbDevice).close();
119 | } catch (IOException ignored) {
120 | }
121 | }
122 | }
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/ashbin.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/auto.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/background_cron.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/background_cron_stroke.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
8 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/background_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/bars.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/caret_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/chevron_down.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/chevron_left.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/chevron_right.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/dir_floder.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/editor.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/equals.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/expand.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/horizontal_rotate.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/ic_launcher_dev_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
20 |
24 |
27 |
31 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
15 |
19 |
22 |
26 |
29 |
30 |
31 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/keyboard.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/lightbulb.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/lightbulb_off.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
13 |
14 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/link.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/main_background.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/easycontrol/app/src/main/res/drawable/main_background.webp
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/minus.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/not_equal.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/o.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/plus.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/power.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/refresh.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/rss.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/scansion.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/share.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
13 |
14 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/square.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
14 |
18 |
22 |
23 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/un_auto.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
16 |
17 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/wifi.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
15 |
18 |
19 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/window_restore.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/drawable/x.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout-land/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
13 |
14 |
22 |
23 |
32 |
33 |
38 |
39 |
46 |
47 |
54 |
55 |
56 |
63 |
64 |
65 |
66 |
78 |
79 |
80 |
84 |
85 |
93 |
94 |
95 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/activity_active.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
13 |
14 |
22 |
23 |
33 |
34 |
43 |
44 |
52 |
53 |
58 |
59 |
60 |
61 |
62 |
71 |
72 |
78 |
79 |
87 |
88 |
89 |
96 |
97 |
109 |
110 |
111 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/activity_adb_key.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
21 |
22 |
28 |
29 |
38 |
39 |
46 |
47 |
54 |
55 |
56 |
66 |
67 |
74 |
75 |
82 |
83 |
84 |
85 |
98 |
99 |
100 |
101 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/activity_ip.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
21 |
22 |
28 |
29 |
38 |
39 |
46 |
47 |
53 |
54 |
55 |
56 |
67 |
68 |
75 |
76 |
82 |
83 |
84 |
85 |
86 |
87 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
17 |
18 |
22 |
23 |
32 |
33 |
38 |
39 |
46 |
47 |
54 |
55 |
56 |
63 |
64 |
65 |
66 |
75 |
76 |
77 |
78 |
90 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/activity_set.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
17 |
18 |
21 |
22 |
28 |
29 |
39 |
40 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_devices_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
22 |
23 |
29 |
30 |
31 |
38 |
39 |
50 |
51 |
61 |
62 |
63 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_loading.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_request_permission.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
14 |
15 |
25 |
26 |
37 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_scan_address_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
20 |
21 |
26 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_spinner.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
25 |
26 |
33 |
34 |
35 |
44 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_spinner_item.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_switch.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
21 |
22 |
30 |
31 |
32 |
42 |
43 |
44 |
53 |
54 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/item_text.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
16 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/module_dialog.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/layout/module_mini_view.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
13 |
14 |
22 |
23 |
33 |
34 |
38 |
39 |
46 |
47 |
48 |
58 |
59 |
60 |
61 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-anydpi-v26/ic_launcher_dev.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-anydpi-v26/ic_launcher_dev_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher_dev.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher_dev.webp
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher_dev_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher_dev_round.webp
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/easycontrol/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/values-night/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #222325
5 | #DEDEDE
6 | #A7A7A7
7 | #3D3D3D
8 | #FFFFFF
9 | #8B8B8B
10 |
11 |
12 | #FFFFFF
13 | #414141
14 | #ca5357
15 | #ded8d8
16 |
17 |
18 | #828282
19 |
20 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/values/color.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | #F3F4F6
5 | #242424
6 | #636564
7 | #FFFFFF
8 | #000000
9 | #696969
10 |
11 |
12 | #2E2E2E
13 | #E0E0E0
14 | #ca5357
15 | #ded8d8
16 |
17 |
18 | #828282
19 |
20 |
21 | #EBF7FF
22 | #0D94FD
23 | #FDF0EF
24 | #EE4F4B
25 | #FFEFEF
26 | #DC0115
27 | #FFF2E9
28 | #FE871E
29 | #E7FFF2
30 | #03CE5E
31 | #EEEBFE
32 | #8561FD
33 | #EAF4FE
34 | #39A2FD
35 | #EAF0FE
36 | #3A70FC
37 | #ECF9FF
38 | #10BEF1
39 |
40 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/values/ic_launcher_dev_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/values/size.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 14dp
4 | 50dp
5 |
6 | 26sp
7 | 22sp
8 | 18sp
9 | 14sp
10 |
--------------------------------------------------------------------------------
/easycontrol/app/src/main/res/xml/device_filter.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/easycontrol/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
24 | android.defaults.buildfeatures.buildconfig=true
25 | android.nonFinalResIds=false
--------------------------------------------------------------------------------
/easycontrol/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/easycontrol/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/easycontrol/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Aug 17 12:15:21 CST 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.2-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/easycontrol/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/easycontrol/local.properties:
--------------------------------------------------------------------------------
1 | ## This file must *NOT* be checked into Version Control Systems,
2 | # as it contains information specific to your local configuration.
3 | #
4 | # Location of the SDK. This is only used by Gradle.
5 | # For customization when using a Version Control System, please read the
6 | # header note.
7 | #Sun Jan 07 22:04:08 CST 2024
8 | sdk.dir=C\:\\Users\\mzx\\AppData\\Local\\Android\\Sdk
9 |
--------------------------------------------------------------------------------
/easycontrol/server/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application' version '8.2.2'
3 | }
4 |
5 | android {
6 | namespace 'top.saymzx.easycontrol.server'
7 | compileSdk 34
8 |
9 | defaultConfig {
10 | minSdk 21
11 | targetSdk 34
12 | applicationId 'top.saymzx.easycontrol.server'
13 | versionCode 20000
14 | versionName '2.0.0'
15 | }
16 |
17 | buildTypes {
18 | release {
19 | ndk {
20 | abiFilters "arm64-v8a", "armeabi-v7a", "x86", "x86_64"
21 | }
22 | debuggable false
23 | minifyEnabled true
24 | shrinkResources true
25 | proguardFiles 'proguard-rules.pro'
26 | }
27 | }
28 |
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 |
34 | task copyRelease(type: Copy) {
35 | dependsOn 'assembleRelease'
36 | from file('build/outputs/apk/release/server-release-unsigned.apk')
37 | into file('../app/src/main/res/raw/')
38 | rename('server-release-unsigned.apk', 'easycontrol_server.jar')
39 | }
40 |
41 | task copyDebug(type: Copy) {
42 | dependsOn 'assembleDebug'
43 | from file("build/outputs/apk/debug/server-debug.apk")
44 | into file('../app/src/main/res/raw/')
45 | rename('server-debug.apk', 'easycontrol_server.jar')
46 | }
47 |
48 | buildFeatures {
49 | aidl true
50 | }
51 | }
52 |
53 | dependencies {
54 | }
--------------------------------------------------------------------------------
/easycontrol/server/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 | -keep class top.saymzx.easycontrol.server.Server {
23 | main(java.lang.String[]);
24 | }
25 | -keep class android.content.IOnPrimaryClipChangedListener{*;}
26 | -keep class android.view.IRotationWatcher{*;}
27 | -keep class top.saymzx.easycontrol.server.helper.FakeContext{*;}
--------------------------------------------------------------------------------
/easycontrol/server/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/aidl/android/content/IOnPrimaryClipChangedListener.aidl:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) 2008, The Android Open Source Project
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 |
17 | package android.content;
18 |
19 | /**
20 | * {@hide}
21 | */
22 | oneway interface IOnPrimaryClipChangedListener {
23 | void dispatchPrimaryClipChanged();
24 | }
25 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/aidl/android/view/IRotationWatcher.aidl:
--------------------------------------------------------------------------------
1 | /* //device/java/android/android/hardware/ISensorListener.aidl
2 | **
3 | ** Copyright 2008, The Android Open Source Project
4 | **
5 | ** Licensed under the Apache License, Version 2.0 (the "License");
6 | ** you may not use this file except in compliance with the License.
7 | ** You may obtain a copy of the License at
8 | **
9 | ** http://www.apache.org/licenses/LICENSE-2.0
10 | **
11 | ** Unless required by applicable law or agreed to in writing, software
12 | ** distributed under the License is distributed on an "AS IS" BASIS,
13 | ** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | ** See the License for the specific language governing permissions and
15 | ** limitations under the License.
16 | */
17 |
18 | package android.view;
19 |
20 | /**
21 | * {@hide}
22 | */
23 | interface IRotationWatcher {
24 | oneway void onRotationChanged(int rotation);
25 | }
26 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/entity/DisplayInfo.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.entity;
5 |
6 | import android.util.Pair;
7 |
8 | public final class DisplayInfo {
9 | public final int displayId;
10 | public final int width;
11 | public final int height;
12 | public final int rotation;
13 | public final int layerStack;
14 | public final int density;
15 |
16 | public DisplayInfo(int displayId, int width,int height, int rotation,int density, int layerStack) {
17 | this.displayId = displayId;
18 | this.width=width;
19 | this.height=height;
20 | this.rotation = rotation;
21 | this.layerStack = layerStack;
22 | this.density = density;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/entity/Options.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.entity;
5 |
6 | public final class Options {
7 | public static int serverPort=25166;
8 | public static boolean listenerClip=true;
9 | public static boolean isAudio = true;
10 | public static int maxSize = 1600;
11 | public static int maxVideoBit = 4000000;
12 | public static int maxFps = 60;
13 | public static boolean keepAwake = true;
14 | public static boolean supportH265 = true;
15 | public static boolean supportOpus = true;
16 | public static String startApp = "";
17 |
18 | public static void parse(String... args) {
19 | for (String arg : args) {
20 | int equalIndex = arg.indexOf('=');
21 | if (equalIndex == -1) throw new IllegalArgumentException("参数格式错误");
22 | String key = arg.substring(0, equalIndex);
23 | String value = arg.substring(equalIndex + 1);
24 | switch (key) {
25 | case "serverPort":
26 | serverPort = Integer.parseInt(value);
27 | break;
28 | case "listenerClip":
29 | listenerClip = Integer.parseInt(value) == 1;
30 | break;
31 | case "isAudio":
32 | isAudio = Integer.parseInt(value) == 1;
33 | break;
34 | case "maxSize":
35 | maxSize = Integer.parseInt(value);
36 | break;
37 | case "maxFps":
38 | maxFps = Integer.parseInt(value);
39 | break;
40 | case "maxVideoBit":
41 | maxVideoBit = Integer.parseInt(value) * 1000000;
42 | break;
43 | case "keepAwake":
44 | keepAwake = Integer.parseInt(value) == 1;
45 | break;
46 | case "supportH265":
47 | supportH265 = Integer.parseInt(value) == 1;
48 | break;
49 | case "supportOpus":
50 | supportOpus = Integer.parseInt(value) == 1;
51 | break;
52 | case "startApp":
53 | startApp = value;
54 | break;
55 | }
56 | }
57 | }
58 | }
59 |
60 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/entity/Pointer.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.entity;
5 |
6 | public final class Pointer {
7 |
8 | public int id;
9 |
10 | public float x;
11 |
12 | public float y;
13 |
14 | public long downTime;
15 |
16 | public Pointer(int id, long downTime) {
17 | this.id = id;
18 | this.downTime = downTime;
19 | }
20 |
21 | }
22 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/entity/PointersState.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.entity;
5 |
6 | import android.view.MotionEvent;
7 |
8 | import java.util.concurrent.ConcurrentHashMap;
9 |
10 | public final class PointersState {
11 |
12 | private final int MAX_POINTERS = 10;
13 |
14 | private final ConcurrentHashMap pointers = new ConcurrentHashMap<>();
15 | public final MotionEvent.PointerProperties[] pointerProperties = new MotionEvent.PointerProperties[MAX_POINTERS];
16 | public final MotionEvent.PointerCoords[] pointerCoords = new MotionEvent.PointerCoords[MAX_POINTERS];
17 |
18 | public PointersState() {
19 | // 初始化指针
20 | for (int i = 0; i < MAX_POINTERS; ++i) {
21 | MotionEvent.PointerProperties props = new MotionEvent.PointerProperties();
22 | props.toolType = MotionEvent.TOOL_TYPE_FINGER;
23 | pointerProperties[i] = props;
24 |
25 | MotionEvent.PointerCoords coords = new MotionEvent.PointerCoords();
26 | coords.orientation = 0;
27 | coords.size = 0.01f;
28 | coords.pressure = 1f;
29 | pointerCoords[i] = coords;
30 | }
31 | }
32 |
33 | public Pointer newPointer(int pointerId, long now) {
34 | for (int i = 0; i < MAX_POINTERS; i++) {
35 | if (isLocalIdAvailable(i)) {
36 | Pointer pointer = new Pointer(i, now);
37 | pointers.put(pointerId, pointer);
38 | return pointer;
39 | }
40 | }
41 | return null;
42 | }
43 |
44 | public Pointer get(int pointerId) {
45 | return pointers.get(pointerId);
46 | }
47 |
48 | private boolean isLocalIdAvailable(int localId) {
49 | for (Pointer value : pointers.values()) if (value.id == localId) return false;
50 | return true;
51 | }
52 |
53 | public void remove(int pointerId) {
54 | pointers.remove(pointerId);
55 | }
56 |
57 | public int update() {
58 | int i = 0;
59 | for (Pointer value : pointers.values()) {
60 | pointerProperties[i].id = value.id;
61 | pointerCoords[i].x = value.x;
62 | pointerCoords[i].y = value.y;
63 | i++;
64 | }
65 | return i;
66 | }
67 |
68 | }
69 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/helper/AudioEncode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.helper;
5 |
6 | import android.media.AudioRecord;
7 | import android.media.MediaCodec;
8 | import android.media.MediaCodecInfo;
9 | import android.media.MediaFormat;
10 | import android.os.Build;
11 |
12 | import java.io.IOException;
13 | import java.nio.ByteBuffer;
14 |
15 | import top.saymzx.easycontrol.server.Server;
16 | import top.saymzx.easycontrol.server.entity.Options;
17 |
18 | public final class AudioEncode {
19 | private static MediaCodec encedec;
20 | private static AudioRecord audioCapture;
21 | private static boolean useOpus;
22 |
23 | public static boolean init() throws IOException {
24 | useOpus = Options.supportOpus && EncodecTools.isSupportOpus();
25 | byte[] bytes = new byte[]{0};
26 | try {
27 | // 从安卓12开始支持音频
28 | if (!Options.isAudio || Build.VERSION.SDK_INT < Build.VERSION_CODES.S) throw new Exception("版本低");
29 | setAudioEncodec();
30 | encedec.start();
31 | audioCapture = AudioCapture.init();
32 | } catch (Exception ignored) {
33 | Server.writeMain(ByteBuffer.wrap(bytes));
34 | return false;
35 | }
36 | bytes[0] = 1;
37 | Server.writeMain(ByteBuffer.wrap(bytes));
38 | bytes[0] = (byte) (useOpus ? 1 : 0);
39 | Server.writeMain(ByteBuffer.wrap(bytes));
40 | return true;
41 | }
42 |
43 | private static void setAudioEncodec() throws IOException {
44 | String codecMime = useOpus ? MediaFormat.MIMETYPE_AUDIO_OPUS : MediaFormat.MIMETYPE_AUDIO_AAC;
45 | encedec = MediaCodec.createEncoderByType(codecMime);
46 | MediaFormat encodecFormat = MediaFormat.createAudioFormat(codecMime, AudioCapture.SAMPLE_RATE, AudioCapture.CHANNELS);
47 | encodecFormat.setInteger(MediaFormat.KEY_BIT_RATE, 96000);
48 | encodecFormat.setInteger(MediaFormat.KEY_MAX_INPUT_SIZE, AudioCapture.AUDIO_PACKET_SIZE);
49 | if (!useOpus) encodecFormat.setInteger(MediaFormat.KEY_AAC_PROFILE, MediaCodecInfo.CodecProfileLevel.AACObjectLC);
50 | encedec.configure(encodecFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
51 | }
52 |
53 | public static void encodeIn() {
54 | try {
55 | int inIndex;
56 | do inIndex = encedec.dequeueInputBuffer(-1); while (inIndex < 0);
57 | ByteBuffer buffer = encedec.getInputBuffer(inIndex);
58 | if (buffer == null) return;
59 | int size = Math.min(buffer.remaining(), AudioCapture.AUDIO_PACKET_SIZE);
60 | audioCapture.read(buffer, size);
61 | encedec.queueInputBuffer(inIndex, 0, size, 0, 0);
62 | } catch (IllegalStateException ignored) {
63 | }
64 | }
65 |
66 | private static final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
67 |
68 | public static void encodeOut() throws IOException {
69 | try {
70 | // 找到已完成的输出缓冲区
71 | int outIndex;
72 | do outIndex = encedec.dequeueOutputBuffer(bufferInfo, -1); while (outIndex < 0);
73 | ByteBuffer buffer = encedec.getOutputBuffer(outIndex);
74 | if (buffer == null) return;
75 | if (useOpus) {
76 | if ((bufferInfo.flags & MediaCodec.BUFFER_FLAG_CODEC_CONFIG) != 0) {
77 | buffer.getLong();
78 | int size = (int) buffer.getLong();
79 | buffer.limit(buffer.position() + size);
80 | }
81 | // 当无声音时不发送
82 | if (buffer.remaining() < 5) {
83 | encedec.releaseOutputBuffer(outIndex, false);
84 | return;
85 | }
86 | }
87 | ControlPacket.sendAudioEvent(buffer);
88 | encedec.releaseOutputBuffer(outIndex, false);
89 | } catch (IllegalStateException ignored) {
90 | }
91 | }
92 |
93 | public static void release() {
94 | try {
95 | encedec.stop();
96 | encedec.release();
97 | audioCapture.stop();
98 | audioCapture.release();
99 | } catch (Exception ignored) {
100 | }
101 | }
102 | }
103 |
104 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/helper/ControlPacket.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.helper;
5 |
6 | import android.system.ErrnoException;
7 |
8 | import java.io.IOException;
9 | import java.nio.ByteBuffer;
10 | import java.nio.charset.StandardCharsets;
11 |
12 | import top.saymzx.easycontrol.server.Server;
13 | import top.saymzx.easycontrol.server.entity.Device;
14 |
15 | public final class ControlPacket {
16 |
17 | public static void sendVideoEvent(long pts, ByteBuffer data) throws IOException {
18 | int size = data.remaining() + 8;
19 | ByteBuffer byteBuffer = ByteBuffer.allocate(4 + size);
20 | byteBuffer.putInt(size);
21 | byteBuffer.putLong(pts);
22 | byteBuffer.put(data);
23 | byteBuffer.flip();
24 | Server.writeVideo(byteBuffer);
25 | }
26 |
27 | public static void sendAudioEvent(ByteBuffer data) throws IOException {
28 | int size = data.remaining();
29 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 + size);
30 | byteBuffer.put((byte) 1);
31 | byteBuffer.putInt(size);
32 | byteBuffer.put(data);
33 | byteBuffer.flip();
34 | Server.writeMain(byteBuffer);
35 | }
36 |
37 | public static void sendClipboardEvent(String newClipboardText) {
38 | byte[] tmpTextByte = newClipboardText.getBytes(StandardCharsets.UTF_8);
39 | if (tmpTextByte.length == 0 || tmpTextByte.length > 5000) return;
40 | ByteBuffer byteBuffer = ByteBuffer.allocate(5 + tmpTextByte.length);
41 | byteBuffer.put((byte) 2);
42 | byteBuffer.putInt(tmpTextByte.length);
43 | byteBuffer.put(tmpTextByte);
44 | byteBuffer.flip();
45 | try {
46 | Server.writeMain(byteBuffer);
47 | } catch (IOException e) {
48 | Server.errorClose(e);
49 | }
50 | }
51 |
52 | public static void sendVideoSizeEvent() throws IOException {
53 | ByteBuffer byteBuffer = ByteBuffer.allocate(9);
54 | byteBuffer.put((byte) 3);
55 | byteBuffer.putInt(Device.videoSize.first);
56 | byteBuffer.putInt(Device.videoSize.second);
57 | byteBuffer.flip();
58 | Server.writeMain(byteBuffer);
59 | }
60 |
61 | public static void handleTouchEvent() throws IOException {
62 | int action = Server.mainInputStream.readByte();
63 | int pointerId = Server.mainInputStream.readByte();
64 | float x = Server.mainInputStream.readFloat();
65 | float y = Server.mainInputStream.readFloat();
66 | int offsetTime = Server.mainInputStream.readInt();
67 | Device.touchEvent(action, x, y, pointerId, offsetTime);
68 | }
69 |
70 | public static void handleKeyEvent() throws IOException {
71 | int keyCode = Server.mainInputStream.readInt();
72 | int meta = Server.mainInputStream.readInt();
73 | Device.keyEvent(keyCode, meta);
74 | }
75 |
76 | public static void handleClipboardEvent() throws IOException {
77 | int size = Server.mainInputStream.readInt();
78 | byte[] textBytes = new byte[size];
79 | Server.mainInputStream.readFully(textBytes);
80 | String text = new String(textBytes, StandardCharsets.UTF_8);
81 | Device.setClipboardText(text);
82 | }
83 |
84 | }
85 |
86 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/helper/EncodecTools.java:
--------------------------------------------------------------------------------
1 | package top.saymzx.easycontrol.server.helper;
2 |
3 | import android.media.MediaCodecInfo;
4 | import android.media.MediaCodecList;
5 | import android.media.MediaFormat;
6 |
7 | import java.util.ArrayList;
8 | import java.util.Objects;
9 |
10 | public class EncodecTools {
11 | private static ArrayList hevcEncodecList = null;
12 | private static ArrayList opusEncodecList = null;
13 |
14 | // 获取解码器列表
15 | private static void getEncodecList() {
16 | MediaCodecList mediaCodecList = new MediaCodecList(MediaCodecList.REGULAR_CODECS);
17 | hevcEncodecList = new ArrayList<>();
18 | opusEncodecList = new ArrayList<>();
19 | for (MediaCodecInfo mediaCodecInfo : mediaCodecList.getCodecInfos()) {
20 | if (mediaCodecInfo.isEncoder()) {
21 | String codecName = mediaCodecInfo.getName();
22 | if (codecName.toLowerCase().contains("opus")) opusEncodecList.add(codecName);
23 | // 要求硬件实现
24 | if (!codecName.startsWith("OMX.google") && !codecName.startsWith("c2.android")) {
25 | for (String supportType : mediaCodecInfo.getSupportedTypes()) {
26 | if (Objects.equals(supportType, MediaFormat.MIMETYPE_VIDEO_HEVC)) hevcEncodecList.add(codecName);
27 | }
28 | }
29 | }
30 | }
31 | }
32 |
33 | // 获取解码器是否支持
34 | public static boolean isSupportOpus() {
35 | if (opusEncodecList == null) getEncodecList();
36 | return opusEncodecList.size() > 0;
37 | }
38 |
39 | public static boolean isSupportH265() {
40 | if (hevcEncodecList == null) getEncodecList();
41 | return hevcEncodecList.size() > 0;
42 | }
43 |
44 | }
45 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/helper/FakeContext.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.helper;
5 |
6 | import android.annotation.TargetApi;
7 | import android.content.AttributionSource;
8 | import android.content.MutableContextWrapper;
9 | import android.os.Build;
10 | import android.os.Process;
11 |
12 | public final class FakeContext extends MutableContextWrapper {
13 |
14 | public static final String PACKAGE_NAME = "com.android.shell";
15 | public static final int ROOT_UID = 0; // Like android.os.Process.ROOT_UID, but before API 29
16 |
17 | private static final FakeContext INSTANCE = new FakeContext();
18 |
19 | public static FakeContext get() {
20 | return INSTANCE;
21 | }
22 |
23 | private FakeContext() {
24 | super(null);
25 | }
26 |
27 | @Override
28 | public String getPackageName() {
29 | return PACKAGE_NAME;
30 | }
31 |
32 | @Override
33 | public String getOpPackageName() {
34 | return PACKAGE_NAME;
35 | }
36 |
37 | @TargetApi(Build.VERSION_CODES.S)
38 | @Override
39 | public AttributionSource getAttributionSource() {
40 | AttributionSource.Builder builder = new AttributionSource.Builder(Process.SHELL_UID);
41 | builder.setPackageName(PACKAGE_NAME);
42 | return builder.build();
43 | }
44 |
45 | // @Override to be added on SDK upgrade for Android 14
46 | @SuppressWarnings("unused")
47 | public int getDeviceId() {
48 | return 0;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/helper/VideoEncode.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.helper;
5 |
6 | import android.graphics.Rect;
7 | import android.media.MediaCodec;
8 | import android.media.MediaCodecInfo;
9 | import android.media.MediaFormat;
10 | import android.os.Build;
11 | import android.os.IBinder;
12 | import android.system.ErrnoException;
13 | import android.view.Surface;
14 |
15 | import java.io.IOException;
16 | import java.lang.reflect.InvocationTargetException;
17 | import java.nio.ByteBuffer;
18 |
19 | import top.saymzx.easycontrol.server.Server;
20 | import top.saymzx.easycontrol.server.entity.Device;
21 | import top.saymzx.easycontrol.server.entity.Options;
22 | import top.saymzx.easycontrol.server.wrappers.SurfaceControl;
23 |
24 | public final class VideoEncode {
25 | private static MediaCodec encedec;
26 | private static MediaFormat encodecFormat;
27 | public static boolean isHasChangeConfig = false;
28 | private static boolean useH265;
29 |
30 | private static IBinder display;
31 |
32 | public static void init() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException, ErrnoException {
33 | useH265 = Options.supportH265 && EncodecTools.isSupportH265();
34 | ByteBuffer byteBuffer = ByteBuffer.allocate(9);
35 | byteBuffer.put((byte) (useH265 ? 1 : 0));
36 | byteBuffer.putInt(Device.videoSize.first);
37 | byteBuffer.putInt(Device.videoSize.second);
38 | byteBuffer.flip();
39 | Server.writeVideo(byteBuffer);
40 | // 创建显示器
41 | display = SurfaceControl.createDisplay("easycontrol", Build.VERSION.SDK_INT < Build.VERSION_CODES.R || (Build.VERSION.SDK_INT == Build.VERSION_CODES.R && !"S".equals(Build.VERSION.CODENAME)));
42 | // 创建Codec
43 | createEncodecFormat();
44 | startEncode();
45 | }
46 |
47 | private static void createEncodecFormat() throws IOException {
48 | String codecMime = useH265 ? MediaFormat.MIMETYPE_VIDEO_HEVC : MediaFormat.MIMETYPE_VIDEO_AVC;
49 | encedec = MediaCodec.createEncoderByType(codecMime);
50 | encodecFormat = new MediaFormat();
51 | encodecFormat.setString(MediaFormat.KEY_MIME, codecMime);
52 | encodecFormat.setInteger(MediaFormat.KEY_BIT_RATE, Options.maxVideoBit);
53 | encodecFormat.setInteger(MediaFormat.KEY_FRAME_RATE, Options.maxFps);
54 | encodecFormat.setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 10);
55 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) encodecFormat.setInteger(MediaFormat.KEY_INTRA_REFRESH_PERIOD, Options.maxFps * 3);
56 | encodecFormat.setFloat("max-fps-to-encoder", Options.maxFps);
57 | encodecFormat.setLong(MediaFormat.KEY_REPEAT_PREVIOUS_FRAME_AFTER, 50_000);
58 | encodecFormat.setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatSurface);
59 | }
60 |
61 | // 初始化编码器
62 | private static Surface surface;
63 |
64 | public static void startEncode() throws InvocationTargetException, NoSuchMethodException, IllegalAccessException, IOException, ErrnoException {
65 | ControlPacket.sendVideoSizeEvent();
66 | encodecFormat.setInteger(MediaFormat.KEY_WIDTH, Device.videoSize.first);
67 | encodecFormat.setInteger(MediaFormat.KEY_HEIGHT, Device.videoSize.second);
68 | encedec.configure(encodecFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE);
69 | // 绑定Display和Surface
70 | surface = encedec.createInputSurface();
71 | setDisplaySurface(display, surface);
72 | // 启动编码
73 | encedec.start();
74 | }
75 |
76 | public static void stopEncode() {
77 | encedec.stop();
78 | encedec.reset();
79 | surface.release();
80 | }
81 |
82 | private static void setDisplaySurface(IBinder display, Surface surface) throws InvocationTargetException, NoSuchMethodException, IllegalAccessException {
83 | SurfaceControl.openTransaction();
84 | try {
85 | SurfaceControl.setDisplaySurface(display, surface);
86 | SurfaceControl.setDisplayProjection(display, 0, new Rect(0, 0, Device.displayInfo.width, Device.displayInfo.height), new Rect(0, 0, Device.videoSize.first, Device.videoSize.second));
87 | SurfaceControl.setDisplayLayerStack(display, Device.displayInfo.layerStack);
88 | } finally {
89 | SurfaceControl.closeTransaction();
90 | }
91 | }
92 |
93 | private static final MediaCodec.BufferInfo bufferInfo = new MediaCodec.BufferInfo();
94 |
95 | public static void encodeOut() throws IOException {
96 | try {
97 | // 找到已完成的输出缓冲区
98 | int outIndex;
99 | do outIndex = encedec.dequeueOutputBuffer(bufferInfo, -1); while (outIndex < 0);
100 | ByteBuffer buffer = encedec.getOutputBuffer(outIndex);
101 | if (buffer == null) return;
102 | ControlPacket.sendVideoEvent(bufferInfo.presentationTimeUs, buffer);
103 | encedec.releaseOutputBuffer(outIndex, false);
104 | } catch (IllegalStateException ignored) {
105 | }
106 | }
107 |
108 | public static void release() {
109 | try {
110 | stopEncode();
111 | encedec.release();
112 | SurfaceControl.destroyDisplay(display);
113 | } catch (Exception ignored) {
114 | }
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/wrappers/DisplayManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.wrappers;
5 |
6 | import android.content.Context;
7 | import android.hardware.display.VirtualDisplay;
8 | import android.media.MediaCodec;
9 | import android.os.Build;
10 | import android.view.Display;
11 | import android.view.Surface;
12 |
13 | import java.util.Objects;
14 | import java.util.regex.Matcher;
15 | import java.util.regex.Pattern;
16 |
17 | import top.saymzx.easycontrol.server.entity.Device;
18 | import top.saymzx.easycontrol.server.entity.DisplayInfo;
19 | import top.saymzx.easycontrol.server.helper.FakeContext;
20 |
21 | public final class DisplayManager {
22 | private static Object manager;
23 |
24 | private static final int VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL = 1 << 8;
25 | private static final int VIRTUAL_DISPLAY_FLAG_SHOULD_SHOW_SYSTEM_DECORATIONS = 1 << 9;
26 | private static final int VIRTUAL_DISPLAY_FLAG_TRUSTED = 1 << 10;
27 | private static final int VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP = 1 << 11;
28 | private static final int VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED = 1 << 12;
29 |
30 | public static void init(Object m) {
31 | manager = m;
32 | }
33 |
34 | private static DisplayInfo getDisplayInfoFromDumpsysDisplay(int displayId) {
35 | try {
36 | String dumpsysDisplayOutput = Device.execReadOutput("dumpsys display");
37 | Matcher m = Pattern.compile("mOverrideDisplayInfo=DisplayInfo.*?, displayId " + displayId + ".*?, real ([0-9]+) x ([0-9]+).*?, rotation ([0-9]+).*?, density ([0-9]+).*?, layerStack ([0-9]+)").matcher(dumpsysDisplayOutput);
38 | if (!m.find()) return null;
39 | int width = Integer.parseInt(Objects.requireNonNull(m.group(1)));
40 | int height = Integer.parseInt(Objects.requireNonNull(m.group(2)));
41 | int rotation = Integer.parseInt(Objects.requireNonNull(m.group(3)));
42 | int density = Integer.parseInt(Objects.requireNonNull(m.group(4)));
43 | int layerStack = Integer.parseInt(Objects.requireNonNull(m.group(5)));
44 | return new DisplayInfo(displayId, width, height, rotation, density, layerStack);
45 | } catch (Exception e) {
46 | return null;
47 | }
48 | }
49 |
50 | public static DisplayInfo getDisplayInfo(int displayId) {
51 | try {
52 | Object displayInfo = manager.getClass().getMethod("getDisplayInfo", int.class).invoke(manager, displayId);
53 | // fallback when displayInfo is null
54 | if (displayInfo == null) return getDisplayInfoFromDumpsysDisplay(displayId);
55 | Class> cls = displayInfo.getClass();
56 | // width and height already take the rotation into account
57 | int width = cls.getDeclaredField("logicalWidth").getInt(displayInfo);
58 | int height = cls.getDeclaredField("logicalHeight").getInt(displayInfo);
59 | int rotation = cls.getDeclaredField("rotation").getInt(displayInfo);
60 | int layerStack = cls.getDeclaredField("layerStack").getInt(displayInfo);
61 | int density = cls.getDeclaredField("logicalDensityDpi").getInt(displayInfo);
62 | return new DisplayInfo(displayId, width, height, rotation, density, layerStack);
63 | } catch (Exception e) {
64 | throw new AssertionError(e);
65 | }
66 | }
67 |
68 | // 此处大量借鉴了 群友 @○_○ 所编写的易控车机版本相应功能
69 | public static VirtualDisplay createVirtualDisplay() throws Exception {
70 | DisplayInfo realDisplayinfo = getDisplayInfo(Display.DEFAULT_DISPLAY);
71 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) {
72 | throw new Exception("Virtual display is not supported before Android 11");
73 | }
74 |
75 | int flags = android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC | android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | VIRTUAL_DISPLAY_FLAG_DESTROY_CONTENT_ON_REMOVAL | android.hardware.display.DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION;
76 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) flags |= VIRTUAL_DISPLAY_FLAG_TRUSTED | VIRTUAL_DISPLAY_FLAG_OWN_DISPLAY_GROUP | VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
77 |
78 | Surface surface = MediaCodec.createPersistentInputSurface();
79 | android.hardware.display.DisplayManager displayManager = android.hardware.display.DisplayManager.class.getDeclaredConstructor(Context.class).newInstance(FakeContext.get());
80 | return displayManager.createVirtualDisplay("easycontrol", realDisplayinfo.width, realDisplayinfo.height, realDisplayinfo.density, surface, flags);
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/wrappers/InputManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.wrappers;
5 |
6 | import android.view.InputEvent;
7 |
8 | import java.lang.reflect.InvocationTargetException;
9 | import java.lang.reflect.Method;
10 |
11 | public final class InputManager {
12 |
13 | public static final int INJECT_INPUT_EVENT_MODE_ASYNC = 0;
14 |
15 | private static Object manager;
16 | private static Method injectInputEventMethod;
17 | private static Method setDisplayIdMethod;
18 |
19 | public static void init(Object m) throws NoSuchMethodException {
20 | manager = m;
21 | injectInputEventMethod = manager.getClass().getMethod("injectInputEvent", InputEvent.class, int.class);
22 | try {
23 | setDisplayIdMethod = InputEvent.class.getMethod("setDisplayId", int.class);
24 | } catch (Exception ignored) {
25 | }
26 | }
27 |
28 | public static void setDisplayId(InputEvent inputEvent, int displayId) throws InvocationTargetException, IllegalAccessException {
29 | if (setDisplayIdMethod == null) return;
30 | setDisplayIdMethod.invoke(inputEvent, displayId);
31 | }
32 |
33 | public static void injectInputEvent(InputEvent inputEvent, int mode) throws InvocationTargetException, IllegalAccessException {
34 | injectInputEventMethod.invoke(manager, inputEvent, mode);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/easycontrol/server/src/main/java/top/saymzx/easycontrol/server/wrappers/WindowManager.java:
--------------------------------------------------------------------------------
1 | /*
2 | * 本项目大量借鉴学习了开源投屏软件:Scrcpy,在此对该项目表示感谢
3 | */
4 | package top.saymzx.easycontrol.server.wrappers;
5 |
6 | import android.os.IInterface;
7 | import android.view.IRotationWatcher;
8 |
9 | import java.lang.reflect.Method;
10 |
11 | public final class WindowManager {
12 | private static IInterface manager;
13 | private static Class> CLASS;
14 | private static Method freezeRotationMethod = null;
15 | private static Method freezeDisplayRotationMethod = null;
16 | private static Method isRotationFrozenMethod = null;
17 | private static Method isDisplayRotationFrozenMethod = null;
18 | private static Method thawRotationMethod = null;
19 | private static Method thawDisplayRotationMethod = null;
20 |
21 | public static void init(IInterface m) {
22 | manager = m;
23 | CLASS = manager.getClass();
24 | try {
25 | freezeRotationMethod = manager.getClass().getMethod("freezeRotation", int.class);
26 | freezeDisplayRotationMethod = manager.getClass().getMethod("freezeDisplayRotation", int.class, int.class);
27 | } catch (Exception ignored) {
28 | }
29 | try {
30 | isRotationFrozenMethod = manager.getClass().getMethod("isRotationFrozen");
31 | isDisplayRotationFrozenMethod = manager.getClass().getMethod("isDisplayRotationFrozen", int.class);
32 | } catch (Exception ignored) {
33 | }
34 | try {
35 | thawRotationMethod = manager.getClass().getMethod("thawRotation");
36 | thawDisplayRotationMethod = manager.getClass().getMethod("thawDisplayRotation", int.class);
37 | } catch (Exception ignored) {
38 | }
39 | }
40 |
41 | public static void freezeRotation(int displayId, int rotation) {
42 | try {
43 | if (freezeDisplayRotationMethod != null) freezeDisplayRotationMethod.invoke(manager, displayId, rotation);
44 | else if (freezeRotationMethod != null) freezeRotationMethod.invoke(manager, rotation);
45 | } catch (Exception ignored) {
46 | }
47 | }
48 |
49 | public static boolean isRotationFrozen(int displayId) {
50 | try {
51 | if (isDisplayRotationFrozenMethod != null) return (boolean) isDisplayRotationFrozenMethod.invoke(manager, displayId);
52 | else if (isRotationFrozenMethod != null) return (boolean) isRotationFrozenMethod.invoke(manager);
53 | return false;
54 | } catch (Exception ignored) {
55 | return false;
56 | }
57 | }
58 |
59 | public static void thawRotation(int displayId) {
60 | try {
61 | if (thawDisplayRotationMethod != null) thawDisplayRotationMethod.invoke(manager, displayId);
62 | else if (thawRotationMethod != null) thawRotationMethod.invoke(manager);
63 | } catch (Exception ignored) {
64 | }
65 | }
66 |
67 | public static void registerRotationWatcher(IRotationWatcher rotationWatcher, int displayId) {
68 | try {
69 | try {
70 | CLASS.getMethod("watchRotation", IRotationWatcher.class, int.class).invoke(manager, rotationWatcher, displayId);
71 | } catch (NoSuchMethodException e) {
72 | CLASS.getMethod("watchRotation", IRotationWatcher.class).invoke(manager, rotationWatcher);
73 | }
74 | } catch (Exception ignored) {
75 | }
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/easycontrol/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | mavenCentral()
5 | gradlePluginPortal()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Easycontrol"
16 | include ':app'
17 | include ':server'
18 | include ':cloud'
19 |
--------------------------------------------------------------------------------
/pic/icon/icon.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 | Layer 1
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/pic/icon/icon.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/icon/icon.webp
--------------------------------------------------------------------------------
/pic/other/alipay.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/other/alipay.webp
--------------------------------------------------------------------------------
/pic/other/qq_issue.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/other/qq_issue.webp
--------------------------------------------------------------------------------
/pic/other/wechat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/other/wechat.webp
--------------------------------------------------------------------------------
/pic/other/wechat_issue.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/other/wechat_issue.png
--------------------------------------------------------------------------------
/pic/screenshot/1.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/1.jpg
--------------------------------------------------------------------------------
/pic/screenshot/2.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/2.jpg
--------------------------------------------------------------------------------
/pic/screenshot/3.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/3.jpg
--------------------------------------------------------------------------------
/pic/screenshot/4.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/4.jpg
--------------------------------------------------------------------------------
/pic/screenshot/5.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/5.jpg
--------------------------------------------------------------------------------
/pic/screenshot/6.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/6.jpg
--------------------------------------------------------------------------------
/pic/screenshot/7.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/7.jpg
--------------------------------------------------------------------------------
/pic/screenshot/8.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/screenshot/8.jpg
--------------------------------------------------------------------------------
/pic/tips/full.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/tips/full.webp
--------------------------------------------------------------------------------
/pic/tips/mini.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/tips/mini.webp
--------------------------------------------------------------------------------
/pic/tips/order_alipay.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/tips/order_alipay.webp
--------------------------------------------------------------------------------
/pic/tips/order_wechat.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/tips/order_wechat.webp
--------------------------------------------------------------------------------
/pic/tips/small.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/eiyooooo/Easycontrol/c355de4dd26992d00ac32505e3e56fdb3d514f94/pic/tips/small.webp
--------------------------------------------------------------------------------