├── .gitignore
├── .idea
├── compiler.xml
├── copyright
│ └── profiles_settings.xml
├── gradle.xml
├── inspectionProfiles
│ ├── Project_Default.xml
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
├── runConfigurations.xml
└── vcs.xml
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── INTRODUCTION.md
├── LICENSE
├── README.md
├── README
├── Desktop.png
├── HomeKit.sketch
├── IMG_5182.JPG
├── IMG_5185.JPG
└── MiHomePlus.png
├── app
├── .gitignore
├── app-release.apk
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── example
│ │ └── qoli
│ │ └── myapplication
│ │ └── ExampleInstrumentedTest.java
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── example
│ │ │ └── qoli
│ │ │ └── myapplication
│ │ │ ├── AppSetting.java
│ │ │ ├── Help.java
│ │ │ ├── MainActivity.java
│ │ │ └── MyAccessibility.java
│ └── res
│ │ ├── drawable-v21
│ │ ├── ic_menu_camera.xml
│ │ ├── ic_menu_gallery.xml
│ │ ├── ic_menu_manage.xml
│ │ ├── ic_menu_send.xml
│ │ ├── ic_menu_share.xml
│ │ └── ic_menu_slideshow.xml
│ │ ├── drawable
│ │ └── side_nav_bar.xml
│ │ ├── layout
│ │ ├── activity_app_setting.xml
│ │ ├── activity_help.xml
│ │ ├── activity_main.xml
│ │ └── content_help.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_round.png
│ │ └── screen.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ └── accessibility.xml
│ └── test
│ └── java
│ └── com
│ └── example
│ └── qoli
│ └── myapplication
│ ├── ExampleUnitTest.java
│ ├── MainActivityTest.java
│ └── SettingTest.java
├── build.gradle
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/workspace.xml
5 | /.idea/libraries
6 | .DS_Store
7 | /build
8 | /captures
9 | .externalNativeBuild
10 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
--------------------------------------------------------------------------------
/.idea/copyright/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
17 |
18 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation.
6 |
7 | ## Our Standards
8 |
9 | Examples of behavior that contributes to creating a positive environment include:
10 |
11 | * Using welcoming and inclusive language
12 | * Being respectful of differing viewpoints and experiences
13 | * Gracefully accepting constructive criticism
14 | * Focusing on what is best for the community
15 | * Showing empathy towards other community members
16 |
17 | Examples of unacceptable behavior by participants include:
18 |
19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances
20 | * Trolling, insulting/derogatory comments, and personal or political attacks
21 | * Public or private harassment
22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission
23 | * Other conduct which could reasonably be considered inappropriate in a professional setting
24 |
25 | ## Our Responsibilities
26 |
27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior.
28 |
29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful.
30 |
31 | ## Scope
32 |
33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers.
34 |
35 | ## Enforcement
36 |
37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at llqoli@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately.
38 |
39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership.
40 |
41 | ## Attribution
42 |
43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version]
44 |
45 | [homepage]: http://contributor-covenant.org
46 | [version]: http://contributor-covenant.org/version/1/4/
47 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | CONTRIBUTING.md
2 |
--------------------------------------------------------------------------------
/INTRODUCTION.md:
--------------------------------------------------------------------------------
1 | # 為你的 iOS 家庭搭建最強的 Homebridge 支援﹣MiHomePlus 介紹
2 |
3 | MiHomePlus 是我為 iOS 家庭編寫的 Android App,主要的作用就是作為 iOS 家庭的操作代理。
4 |
5 | MiHomePlus 的**工作原理**是這樣的:
6 | 1. MiHomePlus 調用 Android 「無障礙」特性,監視和控制米家 App。
7 | 2. 當無障礙功能觸發「TYPE_WINDOW_CONTENT_CHANGED」事件時候,與另一項目 MiPlusServer 通信,把監視的設備狀態同步給 MiPlusServer。
8 | 3. MIPlusServer 從 Homebridge 收取到操作通知時候,基於 Socket.io 通知 MiHomePlus 操作米家 App 去切換設備狀態。
9 | 4. 在 Homebridge 基於 Switcheroo 插件提供的操作接口。
10 | 5. MiPlusServer 是 Web 接口。
11 |
12 | ## 關聯項目
13 |
14 | ##### MiHomePlus
15 | https://github.com/qoli/MiHomePlus
16 |
17 | ##### MiPlusServer
18 | https://github.com/qoli/MiPlusServer
19 |
20 | ## 準備設備
21 |
22 | 1. Pi 一枚。
23 | 2. 閒置 Android 手機一個。
24 |
25 | Pi 我用了 NanoPi,59 元那個 256MB 的版本就足夠了。然而 Android 手機最低要求是 4.2.1 版本的,基於 API 19,因為我是基於這個版本做的開發。
26 |
27 | ## 初始化 NanoPi 環境
28 | 我們需要在 NanoPi 搭建 Homebridge 和安裝 MIPlusServer。
29 |
30 | ### 安裝 Homebridge
31 |
32 | 參考這幾遍文章完成 Homebridge 的安裝。
33 | 1. http://wiki.friendlyarm.com/wiki/index.php/NanoPi_NEO/zh#.E5.87.86.E5.A4.87.E5.B7.A5.E4.BD.9C
34 | 2. http://blog.yongliang.info/2017/0101_play_with_nanopi/
35 | 3. http://djzhang.com/nozuonofun/realize-homekit-with-raspberry-pi-and-xiaomi-smart-devices/
36 |
37 | 建議安裝的 Homebridge 插件:
38 | 1. homebridge-yeelight 控制燈
39 | 2. homebridge-mi-aqara 改良版的 aqara 網關
40 | 3. **homebridge-switcheroo** MIPlusServer 基於這個插件和 Homebridge 通信的。
41 |
42 | **homebridge-miio** 這個插件聽說可以控制第一代 WIFI 插座,我沒裝,所以我不知道。
43 |
44 | 當你完成 Homebridge 的安裝后,我們就要開始進行 **MIPlusServer** 的安裝。
45 |
46 | ### 安裝 MIPlusServer
47 |
48 | #### 第一步,先 SSH Login 到你的 NanoPi,然後執行如下的命令
49 | ```shell
50 | git clone https://github.com/qoli/MiPlusServer.git
51 | cd ./MiPlusServer
52 | npm i
53 | chmod +x miServer.sh
54 | chmod +x run.sh
55 | ```
56 |
57 | #### 第二步,編寫你的 config.js 配置檔案
58 |
59 | ```shell
60 | touch config.js
61 | nano config.js
62 | ```
63 |
64 | ##### 配置檔
65 | ```javascript
66 | module.exports = {
67 | tgbot: false,
68 | token: "",
69 | adminChatID: ""
70 | }
71 | ```
72 |
73 | #### 第三步,啟動 Telegram BOT(可選步驟)
74 |
75 | 如果你有啟用 Telegram Bot 作為監視 MIPlusServer 的運行必要,可以參考
76 | https://neighborhood999.github.io/2016/07/19/Develop-telegram-bot/
77 | 這個教程,來獲取 Telegram BOT 的 token。
78 |
79 | ##### 暫時運行服務器
80 | ```javascript
81 | module.exports = {
82 | tgbot: false,
83 | token: "Your Token Here.",
84 | adminChatID: ""
85 | }
86 | ```
87 |
88 | 接著,先按照配置檔保存一下,使用 `./miServer.sh` 先讓服務器運行起來。
89 |
90 | ##### 獲取 adminChatID
91 | 按照下圖的辦法,加上你自己的機器人,就輸入 `id` 命令。機器人就會向你返回你的 Chat id 了。
92 |
93 | 
94 |
95 | ```javascript
96 | module.exports = {
97 | tgbot: false,
98 | token: "Your Token Here.",
99 | adminChatID: "Your chat id"
100 | }
101 | ```
102 |
103 | 保存配置檔案即可。
104 |
105 | #### 第四步,運行服務器
106 |
107 | ```shell
108 | screen -S miServer
109 | ./miServer.sh
110 | ```
111 |
112 | 
113 |
114 |
115 | 你看到這樣的信息就正確了。
116 |
117 | 隨後,你應該看到這個屏幕。那麼就可以按下「CTRL + A;CTRL + D」來退出 screen 屏幕。我教你記住的口訣,**控制你的 AD 鈣奶**。
118 |
119 | 現在,你的 NanoPi 服務器就初始化完成了。
120 |
121 | ## 安裝 MiHomePlus App 到 Android
122 |
123 | ### 安裝 App
124 | 打開 **https://github.com/qoli/MiHomePlus/releases** 下載 **MiHomePlus** 當前的發佈版本。
125 |
126 | 不過當前 App 我正在被標記為 0.1 版本,可能會有一些問題。也有可能在運行中就退出了。但是,我測試過,可以成功運行 1 天了。第二天我就沒遇到問題,只是我把他拿回來繼續開發了。
127 |
128 | ### 啟動 App
129 |
130 | 
131 |
132 | 在啟動 App 后,首先,我們進行設定。
133 |
134 | ### App 設定
135 |
136 | #### 第一步,米家 App 的調整
137 |
138 | 啟動米家的 App,把需要監視的設備都放在同一個房間中。Homebridge 插件能控制的就不要放進來了。
139 |
140 | ###### 注意
141 | 在當前版本下,在一個屏幕之外的設備無法監控。
142 |
143 | 按照圖片的步驟,把監控的設備都整理到 **AndroidAPI** 的房間中,當然,你可以叫其他的名字,例如「**MiPlusDevices**」。
144 | 然後,你的米家就應該像圖二一樣的狀態。
145 |
146 | 
147 |
148 | 
149 |
150 |
151 | #### 第二步,打開 Web 設定頁面
152 |
153 | 打來瀏覽器,我的 NanoPi 是 http://192.168.1.104:3002 。
154 | 所以,打開 **http://192.168.1.104:3002/setting**。
155 |
156 | 填寫房間名稱和設備列表,設備列表要使用「;」半角分號分開哦。在我這裡,主要就監視了這 4 個設備。
157 |
158 | ###### 注意
159 | 監控設備的名字必須和米家 App 顯示的名字一樣哦。
160 |
161 | 1. 空調伴侶 ﹣ 坑爹的空調伴侶不能被 Homebridge 控制!!!
162 | 2. 電腦燈﹣這個是使用了第一代的智能插座。
163 | 3. 落地燈﹣我也忘記這是什麼插座了,不連接網關的。
164 | 4. 空氣淨化器﹣還是第一代智能插座……
165 |
166 | OK,就這些了哦。
167 |
168 | 
169 | #### 第三部,設定 Homebridge 配置檔
170 |
171 | ###### 示例代碼:
172 | ```
173 | {
174 | "accessory": "Switcheroo",
175 | "type": "switch",
176 | "name": "空調伴侶",
177 | "host": "http://192.168.1.104:3002/device/%E7%A9%BA%E8%AA%BF%E4%BC%B4%E4%BE%B6",
178 | "on": "/ON",
179 | "off": "/OFF",
180 | "on_body": "ON",
181 | "off_body": "OFF"
182 | }
183 | ```
184 |
185 | 1. name﹣這個你真可以隨便叫,只會影響在 iOS 家庭 App 的顯示名字;
186 | 2. host﹣請務必輸入「http://192.168.1.104:3002/device/**設備名字**」設備名字一定要經過 URLEncode。
187 | 3. 有多少個設備就把上面的示例代碼添加多少次。
188 |
189 | 給大家一個 URLEncode 的網址:https://www.urlencoder.org
190 |
191 | 
192 |
193 |
194 | #### 第四步,設定 MiHomePlus!
195 |
196 | 1. 選擇「App 設定」
197 | 2. 設定伺服器地址。
198 |
199 | 我的 NanoPi 就是 192.168.1.104 嘛。所以輸入了 http://192.168.1.104:3002 。
200 | 保存后使用「讀取配置檔案」,就會看到按鈕下方的文字更新過來了。數據就會保存在 App 裡面了。
201 |
202 | 
203 |
204 | #### 第五步,關閉小米的神隱模式(小米的才需要)
205 |
206 | 這個神隱模式嘛,一開始我是不知道的。在開發過程中,每 5 分鐘就遇到 Socket.io 無故斷線,只有重啟才能恢復。後來轉用錘子開發,發現沒有遇到這個問題。我就上網 Google 了一下,才發現小米的神隱模式這個功能導致的。
207 |
208 | 
209 |
210 |
211 | #### 第六步,啟動 App
212 |
213 | 做了這麼多事情,終於開始了!
214 | 把你的 Android 拿去充電吧。反正我的紅米第一代,不充電可以運行一天。
215 |
216 | 
217 |
218 |
219 |
220 | ## 享受完整 HomeKit 帶來的快感吧。
221 |
222 | 
223 |
224 | 
225 |
226 | 
227 |
228 |
229 | ## Telegram BOT?
230 | 你啟動了這個選項的話。
231 | 就可以收到一些 MiHomePlus 的狀態。
232 | 
233 |
234 |
235 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Qoli Wong
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MiHomePlus
2 |
3 | MiHomePlus 是一個 Android App。
4 |
5 | 以透過 Android Accessibility 的特性,監視和控制「米家」App,並且和 MiPlusServer 通信。
6 |
7 | 從而達到代理米家 App 到 HomeKit 的解決方案。
8 |
9 | 
10 |
11 |
12 |
13 | ## APK 下載
14 |
15 | https://github.com/qoli/MiHomePlus/releases
16 |
17 |
18 |
19 | ## 幫助文檔
20 |
21 | https://github.com/qoli/MiHomePlus/blob/master/INTRODUCTION.md
22 |
23 |
24 |
25 | ## 演示視頻
26 |
27 | http://staticshare.5mlstudio.com/img-5168-mov(2017-06-20T08:13:45+08:00).mov
28 |
29 |
30 |
31 | ## 支持設備
32 |
33 | 大部分的「米家」設備。
34 |
35 | 包含空調伴侶、第一代智能開關等。
36 |
37 |
38 |
39 | ## 操作原理
40 |
41 | 調用「無障礙」特性,監視和控制米家 App,當「TYPE_WINDOW_CONTENT_CHANGED」觸發時候,與 MiPlusServer 同步設備狀態。
42 |
43 | 與 MiPlusServer 透過 Socket.io 觸發 MIHomePlus 進行狀態切換。
44 |
45 | 在 Homebridge 基於 Switcheroo 插件提供操作接口。
46 |
47 | 所以 MiPlusServer 本質是 Web 接口
48 |
49 |
50 |
51 | ## 關聯項目
52 |
53 | ##### MiPlusServer
54 |
55 | https://github.com/qoli/MiPlusServer
56 |
57 |
58 |
59 | ## 如何使用
60 |
61 | https://github.com/qoli/MiHomePlus/blob/master/INTRODUCTION.md
62 |
63 | 請查看「INTRODUCTION.md」文檔。
64 |
65 |
66 |
67 | ## 使用條件
68 |
69 | * 關閉手機的鎖屏程式,調整為開屏幕直接進入界面。
70 |
71 |
72 |
73 | ## 使用到的其他項目
74 |
75 | ##### homebridge-switcheroo
76 |
77 | https://github.com/chriszelazo/homebridge-switcheroo
78 |
79 |
80 |
81 | ## 示例 config.json
82 |
83 | host 必須經過 urlencode 才可正常工作。
84 |
85 | ```json
86 | {
87 | "accessory": "Switcheroo",
88 | "type": "switch",
89 | "name": "空調伴侶",
90 | "host": "http://192.168.1.104:3002/device/%E7%A9%BA%E8%AA%BF%E4%BC%B4%E4%BE%B6",
91 | "on": "/ON",
92 | "off": "/OFF",
93 | "on_body": "ON",
94 | "off_body": "OFF"
95 | }
96 | ```
97 |
98 |
99 |
100 | ## 已知 BUG
101 |
102 | ~~紅米 1 會因為 D/OpenGLRenderer: Flushing caches 退出。~~
103 |
104 | 重啟設備后解決了。
105 |
106 | 如果持續發生,請在進程管理器加鎖,以及關閉省電模式。
107 |
108 |
109 |
110 |
111 | ## 筆記
112 |
113 | terminal 指令
114 |
115 | ```shell
116 | pidcat com.example.qoli.myapplication -l I
117 | ```
118 |
119 |
--------------------------------------------------------------------------------
/README/Desktop.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/README/Desktop.png
--------------------------------------------------------------------------------
/README/HomeKit.sketch:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/README/HomeKit.sketch
--------------------------------------------------------------------------------
/README/IMG_5182.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/README/IMG_5182.JPG
--------------------------------------------------------------------------------
/README/IMG_5185.JPG:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/README/IMG_5185.JPG
--------------------------------------------------------------------------------
/README/MiHomePlus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/README/MiHomePlus.png
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/app-release.apk:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/app-release.apk
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 |
3 | android {
4 | compileSdkVersion 26
5 | buildToolsVersion "26.0.0"
6 | defaultConfig {
7 | applicationId "com.example.qoli.myapplication"
8 | minSdkVersion 19
9 | targetSdkVersion 26
10 | versionCode 1
11 | versionName "1.0"
12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
13 | }
14 | buildTypes {
15 | release {
16 | minifyEnabled false
17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
18 | }
19 | }
20 | }
21 |
22 | dependencies {
23 | compile fileTree(dir: 'libs', include: ['*.jar'])
24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
25 | exclude group: 'com.android.support', module: 'support-annotations'
26 | })
27 | compile 'com.android.support:appcompat-v7:26.+'
28 | compile 'com.android.support.constraint:constraint-layout:1.0.2'
29 | compile 'com.android.support:design:26.+'
30 | testCompile 'junit:junit:4.12'
31 |
32 | compile ('io.socket:socket.io-client:0.8.3') {
33 | exclude group: 'org.json', module: 'json'
34 | }
35 |
36 | compile ('io.socket:engine.io-client:0.8.3') {
37 | // excluding org.json which is provided by Android
38 | exclude group: 'org.json', module: 'json'
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/qoli/Library/Android/sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/example/qoli/myapplication/ExampleInstrumentedTest.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import android.content.Context;
4 | import android.support.test.InstrumentationRegistry;
5 | import android.support.test.runner.AndroidJUnit4;
6 |
7 | import org.junit.Test;
8 | import org.junit.runner.RunWith;
9 |
10 | import static org.junit.Assert.*;
11 |
12 | /**
13 | * Instrumentation test, which will execute on an Android device.
14 | *
15 | * @see Testing documentation
16 | */
17 | @RunWith(AndroidJUnit4.class)
18 | public class ExampleInstrumentedTest {
19 | @Test
20 | public void useAppContext() throws Exception {
21 | // Context of the app under test.
22 | Context appContext = InstrumentationRegistry.getTargetContext();
23 |
24 | assertEquals("com.example.qoli.myapplication", appContext.getPackageName());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
34 |
35 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/qoli/myapplication/AppSetting.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import android.content.Context;
4 | import android.content.SharedPreferences;
5 | import android.os.Bundle;
6 | import android.os.SystemClock;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.util.Log;
9 | import android.view.View;
10 | import android.widget.EditText;
11 | import android.widget.TextView;
12 | import android.widget.Toast;
13 |
14 | import org.json.JSONException;
15 | import org.json.JSONObject;
16 |
17 | import java.io.BufferedReader;
18 | import java.io.InputStreamReader;
19 | import java.net.HttpURLConnection;
20 | import java.net.URL;
21 | import java.util.regex.Matcher;
22 | import java.util.regex.Pattern;
23 |
24 | public class AppSetting extends AppCompatActivity {
25 | private static final String TAG = "AppSetting";
26 | private static String Hosts = "http://192.168.1.100:3002";
27 | private EditText address;
28 | private SharedPreferences settings;
29 | private static final String data = "DATA";
30 | private static final String addressField = "ADDRESS";
31 | private static final String settingRoomField = "ROOM";
32 | private static final String settingDevicesField = "DEVICES";
33 | private boolean isSettingReadly = false;
34 | private String getSettingJSON = "";
35 | private String settingRoom = "";
36 | private String settingDevices = "";
37 | private Toast toast = null;
38 |
39 | @Override
40 | protected void onCreate(Bundle savedInstanceState) {
41 | super.onCreate(savedInstanceState);
42 | setContentView(R.layout.activity_app_setting);
43 |
44 |
45 | settings = getSharedPreferences(data, 0);
46 | address = (EditText) findViewById(R.id.addressField);
47 | address.setText(settings.getString(addressField, ""));
48 | TextView room = (TextView) findViewById(R.id.roomField);
49 | room.setText(settings.getString(settingRoomField, "房間名稱"));
50 | TextView devices = (TextView) findViewById(R.id.devicesField);
51 | devices.setText(settings.getString(settingDevicesField, "設備列表"));
52 | }
53 |
54 | // 控件 View 的点击事件
55 | public void onClick(View v) {
56 | switch (v.getId()) {
57 | case R.id.SaveSetting:
58 | saveAction();
59 | break;
60 | case R.id.getSetting:
61 | getSetting();
62 | break;
63 | default:
64 | break;
65 | }
66 | }
67 |
68 | private boolean getSetting() {
69 |
70 | int readSettingTimes = 0;
71 |
72 | SharedPreferences settings = getSharedPreferences(data, 0);
73 | Hosts = settings.getString(addressField, "");
74 |
75 | if (Hosts.equals("")) {
76 | tellUser("伺服器不能為空");
77 | return false;
78 | }
79 |
80 | getSettingbyServer();
81 |
82 | TextView room = (TextView) findViewById(R.id.roomField);
83 | TextView devices = (TextView) findViewById(R.id.devicesField);
84 | room.setText("正在重新同步...");
85 | devices.setText("正在重新同步...");
86 |
87 | tellUser("正在讀取");
88 |
89 | do {
90 | readSettingTimes = readSettingTimes + 1;
91 | SystemClock.sleep(1500);
92 | if (readSettingTimes >= 3) {
93 | tellUser("讀取配置超時");
94 | return false;
95 | }
96 | } while (!isSettingReadly);
97 |
98 | Log.i(TAG, "getSettingJSON: " + getSettingJSON);
99 |
100 | try {
101 | JSONObject settingData = new JSONObject(getSettingJSON);
102 | settingDevices = settingData.getString("devices");
103 | settingRoom = settingData.getString("room");
104 |
105 | settings.edit()
106 | .putString(settingRoomField, settingRoom)
107 | .putString(settingDevicesField, settingDevices)
108 | .apply();
109 |
110 | Log.i(TAG, "settingDevices: " + settingDevices);
111 | Log.i(TAG, "settingRoom: " + settingRoom);
112 |
113 |
114 | settings = getSharedPreferences(data, 0);
115 |
116 | room.setText(settings.getString(settingRoomField, "房間名稱"));
117 | devices.setText(settings.getString(settingDevicesField, "設備列表"));
118 |
119 | tellUser("讀取成功");
120 |
121 | return true;
122 | } catch (JSONException e) {
123 | e.printStackTrace();
124 | }
125 |
126 | tellUser("讀取失敗");
127 | return false;
128 | }
129 |
130 | private void saveAction() {
131 | settings = getSharedPreferences(data, 0);
132 |
133 | String text = address.getText().toString();
134 |
135 | if (checkAddress(text)) {
136 |
137 | settings.edit()
138 | .putString(addressField, text)
139 | .apply();
140 |
141 | tellUser(getString(R.string.saveDone));
142 | } else {
143 | tellUser(getString(R.string.saveAddressErrorText));
144 | }
145 |
146 |
147 | }
148 |
149 | private void tellUser(String s) {
150 | if (toast != null) toast.cancel();
151 |
152 | Context context = getApplicationContext();
153 | CharSequence text = s;
154 | int duration = Toast.LENGTH_SHORT;
155 | toast = Toast.makeText(context, text, duration);
156 | toast.show();
157 | }
158 |
159 | private boolean checkAddress(String Address) {
160 | Pattern pattern = Pattern.compile("((http|ftp|https)://)(([a-zA-Z0-9\\._-]+\\.[a-zA-Z]{2,6})|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}))(:[0-9]{1,4})*(/[a-zA-Z0-9\\&%_\\./-~-]*)?");
161 | Matcher matcher = pattern.matcher(Address);
162 | return matcher.matches();
163 | }
164 |
165 | private String getSettingbyServer() {
166 |
167 | Thread readConfig = new Thread(new Runnable() {
168 | public void run() {
169 | System.out.println("Server Sync ... ");
170 | URL url;
171 | HttpURLConnection urlConnection = null;
172 | try {
173 | url = new URL(Hosts + "/getSetting");
174 |
175 | urlConnection = (HttpURLConnection) url.openConnection();
176 | urlConnection.setRequestMethod("GET");
177 | urlConnection.setRequestProperty("connection", "Keep-Alive");
178 | urlConnection.setRequestProperty("user-agent", "HomeKitProxy/1.0 (Android)");
179 |
180 | BufferedReader br = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
181 | StringBuilder sb = new StringBuilder();
182 | String line;
183 |
184 | while ((line = br.readLine()) != null) {
185 | sb.append(line + "\n");
186 | }
187 | br.close();
188 | getSettingJSON = sb.toString();
189 | isSettingReadly = true;
190 |
191 | Log.i(TAG, "run: " + getSettingJSON);
192 |
193 | } catch (Exception e) {
194 | e.printStackTrace();
195 | } finally {
196 | if (urlConnection != null) {
197 | urlConnection.disconnect();
198 | }
199 | }
200 |
201 | }
202 | });
203 | readConfig.start();
204 |
205 | return null;
206 |
207 | }
208 |
209 |
210 | }
211 |
212 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/qoli/myapplication/Help.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import android.os.Bundle;
4 | import android.support.v7.app.AppCompatActivity;
5 | import android.support.v7.widget.Toolbar;
6 | import android.webkit.WebView;
7 |
8 | public class Help extends AppCompatActivity {
9 |
10 | @Override
11 | protected void onCreate(Bundle savedInstanceState) {
12 | super.onCreate(savedInstanceState);
13 | setContentView(R.layout.activity_help);
14 | Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
15 | setSupportActionBar(toolbar);
16 |
17 |
18 | WebView webview = (WebView)findViewById(R.id.WebView);
19 | webview.getSettings().setJavaScriptEnabled(true);
20 | webview.loadUrl("https://github.com/qoli/MiHomePlus/blob/master/README.md");
21 |
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/example/qoli/myapplication/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import android.content.Context;
4 | import android.content.Intent;
5 | import android.os.Bundle;
6 | import android.provider.Settings;
7 | import android.support.v7.app.AppCompatActivity;
8 | import android.util.Log;
9 | import android.view.View;
10 | import android.widget.Toast;
11 |
12 |
13 | public class MainActivity extends AppCompatActivity {
14 | private static final String TAG = "MainActivity";
15 |
16 | @Override
17 | protected void onCreate(Bundle savedInstanceState) {
18 |
19 | Log.i(TAG, "onRun");
20 |
21 | super.onCreate(savedInstanceState);
22 | setContentView(R.layout.activity_main);
23 |
24 | startHomePlus();
25 | }
26 |
27 | private void startHomePlus() {
28 | // Code to start the Service
29 | startService(new Intent(getApplication(), MyAccessibility.class));
30 | }
31 |
32 |
33 | // 控件 View 的点击事件
34 | public void onClick(View v) {
35 | Intent intent = new Intent();
36 | switch (v.getId()) {
37 | case R.id.Accessibility:
38 | Context context = getApplicationContext();
39 | CharSequence text = "請激活 MiHomePlus 無障礙設定";
40 | int duration = Toast.LENGTH_SHORT;
41 | Toast.makeText(context, text, duration).show();
42 |
43 | //打开系统无障碍设置界面
44 | Intent accessibleIntent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
45 | startActivity(accessibleIntent);
46 | break;
47 | case R.id.AppSetting:
48 | intent.setClass(MainActivity.this, AppSetting.class);
49 | startActivity(intent);
50 | break;
51 | case R.id.Help:
52 | intent.setClass(MainActivity.this, Help.class);
53 | startActivity(intent);
54 | break;
55 | default:
56 | break;
57 | }
58 | }
59 |
60 |
61 | }
--------------------------------------------------------------------------------
/app/src/main/java/com/example/qoli/myapplication/MyAccessibility.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import android.accessibilityservice.AccessibilityService;
4 | import android.app.KeyguardManager;
5 | import android.content.Context;
6 | import android.content.Intent;
7 | import android.content.SharedPreferences;
8 | import android.os.Looper;
9 | import android.os.PowerManager;
10 | import android.util.Log;
11 | import android.view.accessibility.AccessibilityEvent;
12 | import android.view.accessibility.AccessibilityNodeInfo;
13 | import android.widget.Toast;
14 |
15 | import org.json.JSONException;
16 | import org.json.JSONObject;
17 |
18 | import java.io.BufferedReader;
19 | import java.io.InputStreamReader;
20 | import java.net.HttpURLConnection;
21 | import java.net.URISyntaxException;
22 | import java.net.URL;
23 | import java.net.URLEncoder;
24 | import java.util.List;
25 | import java.util.regex.Matcher;
26 | import java.util.regex.Pattern;
27 |
28 | import io.socket.client.IO;
29 | import io.socket.client.Socket;
30 | import io.socket.emitter.Emitter;
31 |
32 | public class MyAccessibility extends AccessibilityService {
33 | private static final String TAG = "MyAccessibility";
34 | private static final String data = "DATA";
35 | private static final String addressField = "ADDRESS";
36 | private static final String settingRoomField = "ROOM";
37 | private static final String settingDevicesField = "DEVICES";
38 |
39 | // 程序內變量
40 | private int Pong = 0;
41 | private Socket mSocket;
42 | private Toast toast = null;
43 |
44 | // 從配置讀取變量
45 | private static String Hosts = "http://192.168.1.100:3002";
46 | private String settingRoom = "";
47 | private String settingDevices = "";
48 |
49 | /**
50 | * pidcat com.example.qoli.myapplication -l I
51 | * terminal 指令
52 | */
53 |
54 | /**
55 | * Socket
56 | */
57 | private void initSocketHttp() {
58 |
59 | // 休眠后會斷線… 》小米手機的神隱模式「https://kknews.cc/tech/zpav83.html」
60 |
61 | // 從配置文件讀取伺服器地址
62 | SharedPreferences settings = getSharedPreferences(data, 0);
63 | Hosts = settings.getString(addressField, "");
64 |
65 | Log.i(TAG, "initSocketHttp: Hosts: " + Hosts);
66 |
67 | try {
68 | mSocket = IO.socket(Hosts);
69 | } catch (URISyntaxException e) {
70 | e.printStackTrace();
71 | }
72 |
73 | mSocket.on(Socket.EVENT_CONNECT, onConnect);
74 | mSocket.on(Socket.EVENT_DISCONNECT, onDisconnect);// 断开连接
75 | mSocket.on(Socket.EVENT_CONNECT_ERROR, onConnectError);// 连接异常
76 | mSocket.on(Socket.EVENT_CONNECT_TIMEOUT, onConnectTimeoutError);// 连接超时
77 |
78 | mSocket.on("update", onUpdate);
79 | mSocket.on("Ping", onPing);
80 |
81 | mSocket.connect();
82 | }
83 |
84 | /**
85 | * Socket 相關函數
86 | */
87 |
88 | private Emitter.Listener onConnect = new Emitter.Listener() {
89 | @Override
90 | public void call(Object... args) {
91 | mSocket.emit("android", "MiHomePlus ONLINE");
92 | }
93 | };
94 |
95 | private Emitter.Listener onUpdate = new Emitter.Listener() {
96 | @Override
97 | public void call(Object... args) {
98 |
99 | Log.i(TAG, "> Call: android action.");
100 | wakeAndUnlock(true);
101 |
102 | JSONObject obj = (JSONObject) args[0];
103 | try {
104 | Log.i(TAG, "updateDevice: " + obj.get("updateDevice"));
105 | Log.i(TAG, "status: " + obj.get("status"));
106 |
107 | boolean onView = gotoView(settingRoom);
108 |
109 | if (onView) {
110 | nodeAction(obj.get("updateDevice").toString(), obj.get("status").toString());
111 | } else {
112 | mSocket.emit("android", "Device: " + obj.get("updateDevice").toString() + " Action:" + obj.get("status").toString() + " > Failed");
113 | Log.i(TAG, "> Call: No");
114 | }
115 |
116 | } catch (JSONException e) {
117 | e.printStackTrace();
118 | }
119 |
120 | wakeAndUnlock(false);
121 | }
122 | };
123 |
124 | private Emitter.Listener onDisconnect = new Emitter.Listener() {
125 | @Override
126 | public void call(Object... args) {
127 | Log.i(TAG, "断开连接 " + args[0]);
128 | mSocket.emit("android", "MiHomePlus OFFLINE");
129 | onSocketFail();
130 | }
131 | };
132 |
133 | private Emitter.Listener onConnectError = new Emitter.Listener() {
134 | @Override
135 | public void call(final Object... args) {
136 | Log.i(TAG, "连接失败" + args[0]);
137 | networkTest();
138 | onSocketFail();
139 | }
140 | };
141 |
142 | private Emitter.Listener onConnectTimeoutError = new Emitter.Listener() {
143 | @Override
144 | public void call(final Object... args) {
145 | Log.i(TAG, "连接超时" + args[0]);
146 | networkTest();
147 | onSocketFail();
148 | }
149 | };
150 |
151 | private Emitter.Listener onPing = new Emitter.Listener() {
152 | @Override
153 | public void call(final Object... args) {
154 | Thread socketThread = new Thread(new Runnable() {
155 | public void run() {
156 | Pong++;
157 | System.out.println("Socket.io Reply Pong ... " + Pong);
158 | mSocket.emit("Pong", "Ping " + Pong);
159 | }
160 | });
161 | socketThread.start();
162 | }
163 | };
164 |
165 | private void onSocketFail() {
166 | wakeAndUnlock(true);
167 | mSocket.disconnect();
168 | initSocketHttp();
169 | wakeAndUnlock(false);
170 | }
171 |
172 |
173 | //锁屏、唤醒相关
174 | private PowerManager powerManager;
175 | private PowerManager.WakeLock wakeLock;
176 |
177 | // TODO 尋找良好的鎖屏代碼
178 | private void wakeAndUnlock(boolean b) {
179 | if (b) {
180 | //获取电源管理器对象
181 | powerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
182 | //获取PowerManager.WakeLock对象,后面的参数|表示同时传入两个值,最后的是调试用的Tag
183 | wakeLock = powerManager.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, TAG);
184 |
185 | // 屏幕解锁
186 | KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
187 | KeyguardManager.KeyguardLock keyguardLock = keyguardManager.newKeyguardLock("unLock");
188 |
189 | keyguardLock.reenableKeyguard();
190 | keyguardLock.disableKeyguard(); // 解锁
191 |
192 | //点亮屏幕
193 | wakeLock.acquire();
194 | } else {
195 | //释放wakeLock,关灯
196 | wakeLock.release();
197 | }
198 | }
199 |
200 |
201 | /**
202 | * 當服務成功激活
203 | */
204 |
205 | @Override
206 | protected void onServiceConnected() {
207 | Log.i(TAG, "> 無障礙設定已經激活!");
208 | tellUser("MiHomePlus 服務已經激活.");
209 | startApp("com.xiaomi.smarthome");
210 | initSocketHttp();
211 | networkTest();
212 | }
213 |
214 | /**
215 | * 網絡信息獲取
216 | */
217 |
218 | private boolean networkTest() {
219 | return false;
220 | }
221 |
222 |
223 | /*
224 | * 打開一個 App
225 | */
226 | public void startApp(String appPackageName) {
227 | try {
228 | Intent intent = this.getPackageManager().getLaunchIntentForPackage(appPackageName);
229 | startActivity(intent);
230 | } catch (Exception e) {
231 | Toast.makeText(this, "没有安装", Toast.LENGTH_LONG).show();
232 | }
233 | }
234 |
235 |
236 | @Override
237 | public void onAccessibilityEvent(AccessibilityEvent event) {
238 |
239 | SharedPreferences settings = getSharedPreferences(data, 0);
240 | settingRoom = settings.getString(settingRoomField, "");
241 | settingDevices = settings.getString(settingDevicesField, "");
242 |
243 | if (settingRoom.equals("") || settingDevices.equals("")) {
244 | tellUser("配置檔不完整");
245 | return;
246 | }
247 |
248 | int eventType = event.getEventType();
249 | if (eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
250 | preProcess(event);
251 | Log.i(TAG, "onAccessibilityEvent");
252 | }
253 |
254 | }
255 |
256 | /*
257 | * 處理……
258 | */
259 | private void preProcess(AccessibilityEvent event) {
260 |
261 | int nextNumber = 0;
262 |
263 | if (event.getSource() != null) {
264 | nextNumber = nextNumber + 1;
265 | }
266 |
267 | if (event.getPackageName().equals("com.xiaomi.smarthome")) {
268 | nextNumber = nextNumber + 1;
269 | }
270 |
271 | if (nextNumber == 2) {
272 |
273 | boolean onView = gotoView(settingRoom);
274 |
275 | if (onView) {
276 | tellUser("(≧▽≦)");
277 |
278 | String[] parts = settingDevices.split(";");
279 | for (String part : parts) {
280 | nodeAction(part, "read");
281 | }
282 |
283 | } else {
284 | tellUser("< Processing... >");
285 | }
286 |
287 | }
288 |
289 | }
290 |
291 | /*
292 | * 切換到正確的頁面
293 | */
294 | private boolean gotoView(String lookingTitle) {
295 | // TODO 如果 app 沒有在前台需要兩次發送才成功
296 |
297 | if (lookingTitle.equals("")) {
298 | tellUser("配置檔不完整");
299 | Log.i(TAG, "gotoView: Title 為空");
300 | return false;
301 | }
302 |
303 | AccessibilityNodeInfo source = getRootInActiveWindow();
304 |
305 | if (source == null) {
306 | Log.i(TAG, "gotoView: source == null");
307 | return false;
308 | }
309 |
310 | if (!source.getPackageName().equals("com.xiaomi.smarthome")) {
311 | startApp("com.xiaomi.smarthome");
312 | }
313 |
314 | List viewTitle = source.findAccessibilityNodeInfosByViewId("com.xiaomi.smarthome:id/module_a_2_more_title");
315 |
316 | if (!titleCheck(lookingTitle, viewTitle)) {
317 | List menuBtn = source.findAccessibilityNodeInfosByViewId("com.xiaomi.smarthome:id/drawer_btn");
318 | doClick(menuBtn);
319 | List backBtn = source.findAccessibilityNodeInfosByViewId("com.xiaomi.plugseat:id/title_bar_return");
320 | doClick(backBtn);
321 | List apiBtn = getRootInActiveWindow().findAccessibilityNodeInfosByText(lookingTitle);
322 | Log.i(TAG, "gotoView: " + apiBtn);
323 | if (apiBtn != null)
324 | for (AccessibilityNodeInfo n : apiBtn) {
325 | n.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
326 | }
327 | return false;
328 | } else {
329 | return true;
330 | }
331 |
332 |
333 | }
334 |
335 | /**
336 | * 標題檢查,配合
337 | */
338 | private boolean titleCheck(String title, List viewTitle) {
339 |
340 | if (viewTitle != null && !viewTitle.isEmpty()) {
341 | AccessibilityNodeInfo node;
342 | for (int i = 0; i < viewTitle.size(); i++) {
343 | node = viewTitle.get(i);
344 | Log.i(TAG, "> Title Check: " + title + " / " + node.getText() + ", index: " + i);
345 |
346 | Pattern pattern = Pattern.compile("^" + title + ".*");
347 | Matcher matcher = pattern.matcher(node.getText());
348 | if (matcher.matches()) {
349 | return true;
350 | }
351 | }
352 | }
353 | return false;
354 | }
355 |
356 | /**
357 | * 執行點擊
358 | *
359 | * @param infos
360 | */
361 | private void doClick(List infos) {
362 | if (infos != null)
363 | for (AccessibilityNodeInfo info : infos) {
364 | if (info.isEnabled() && info.isClickable()) {
365 | Log.i(TAG, "> doClick: " + info.getText());
366 | info.performAction(AccessibilityNodeInfo.ACTION_CLICK);
367 | }
368 |
369 | }
370 | }
371 |
372 | /**
373 | * 節點動作
374 | *
375 | * @param lookingName
376 | * @param action
377 | */
378 | private void nodeAction(String lookingName, String action) {
379 |
380 | // 查找基於關鍵字的設備
381 | List looking = getRootInActiveWindow().findAccessibilityNodeInfosByText(lookingName);
382 |
383 | if (looking != null && !looking.isEmpty()) {
384 |
385 | AccessibilityNodeInfo node;
386 | Log.i(TAG, "> " + lookingName + " now. Search Total: " + looking.size());
387 |
388 | for (int i = 0; i < looking.size(); i++) {
389 | node = looking.get(i);
390 |
391 | // 查找設備狀態
392 | List parent = node.getParent().findAccessibilityNodeInfosByViewId("com.xiaomi.smarthome:id/info_value");
393 |
394 | if (parent != null && !parent.isEmpty()) {
395 |
396 | AccessibilityNodeInfo nodeParent;
397 | for (int j = 0; j < parent.size(); j++) {
398 | nodeParent = parent.get(j);
399 | Log.i(TAG, "> " + node.getText() + " 狀態: " + nodeParent.getText() + " 操作: " + action);
400 |
401 | // 點擊或者讀取按鈕
402 | if (action.equals("read")) {
403 | sync(node.getText().toString(), nodeParent.getText().toString());
404 | } else {
405 | if (!nodeParent.getText().toString().equals(action)) {
406 | nodeParent.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK);
407 | sync(node.getText().toString(), nodeParent.getText().toString());
408 | }
409 | }
410 |
411 | }
412 | }
413 |
414 | break;
415 | }
416 | }
417 |
418 | }
419 |
420 | /**
421 | * 與伺服器同步函數
422 | *
423 | * @param name
424 | * @param status
425 | * @return
426 | */
427 | private boolean sync(final String name, final String status) {
428 |
429 | Thread t1 = new Thread(new Runnable() {
430 | public void run() {
431 | System.out.println("Server Sync ... ");
432 | URL url;
433 | HttpURLConnection urlConnection = null;
434 | try {
435 | url = new URL(Hosts + "/sync/" + URLEncoder.encode(name, "UTF-8") + "/" + URLEncoder.encode(status, "UTF-8"));
436 |
437 | urlConnection = (HttpURLConnection) url.openConnection();
438 |
439 | urlConnection.setRequestProperty("connection", "Keep-Alive");
440 | urlConnection.setRequestProperty("user-agent", "HomeKitProxy/1.0 (Android)");
441 |
442 | BufferedReader in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));
443 | String line;
444 | for (; (line = in.readLine()) != null; ) {
445 | System.out.println("> on Server: " + line);
446 | }
447 |
448 | System.out.println("> on Local: " + name + " => " + status);
449 | } catch (Exception e) {
450 | e.printStackTrace();
451 | } finally {
452 | if (urlConnection != null) {
453 | urlConnection.disconnect();
454 | }
455 | }
456 |
457 | }
458 | });
459 | t1.start();
460 |
461 | return false;
462 |
463 | }
464 |
465 | /**
466 | * toast 顯示函數
467 | */
468 | private void tellUser(final String s) {
469 | final CharSequence text = s;
470 | new Thread() {
471 | public void run() {
472 | Looper.prepare();
473 | if (toast != null) toast.cancel();
474 |
475 | Context context = getApplicationContext();
476 |
477 | int duration = Toast.LENGTH_SHORT;
478 | toast = Toast.makeText(context, text, duration);
479 | toast.show();
480 | Looper.loop();
481 | }
482 | }.start();
483 | }
484 |
485 | private void showToast() {
486 |
487 | }
488 |
489 | @Override
490 | public void onInterrupt() {
491 | }
492 |
493 |
494 | }
495 |
496 |
497 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_menu_camera.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_menu_gallery.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_menu_manage.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_menu_send.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_menu_share.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v21/ic_menu_slideshow.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/side_nav_bar.xml:
--------------------------------------------------------------------------------
1 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_app_setting.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
16 |
17 |
26 |
27 |
37 |
38 |
48 |
49 |
59 |
60 |
66 |
67 |
76 |
77 |
87 |
88 |
98 |
99 |
109 |
110 |
120 |
121 |
133 |
134 |
135 |
136 |
137 |
138 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_help.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
13 |
14 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
18 |
19 |
28 |
29 |
38 |
39 |
49 |
50 |
60 |
61 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_help.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
12 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/screen.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-mdpi/screen.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 | 16dp
3 |
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | MiHomePlus
3 | MiHomePlus 只會關心米家 App 的信息狀態。\n在激活 MiHomePlus「無障礙」選項后,將會啟動監視服務。
4 | Setting
5 | Settings
6 | About
7 | Help
8 | 地址格式一般以 http://192.168.1.100:3002 的格式出現,\n請使用基於 https://github.com/qoli/MiPlusServer 搭建的伺服器。
9 | http://ADDRESS/setting
10 | 數據格式錯誤,請使用 http://192.168.1.100:3002 的類似格式。
11 | 數據已經提交
12 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/accessibility.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/app/src/test/java/com/example/qoli/myapplication/ExampleUnitTest.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Example local unit test, which will execute on the development machine (host).
9 | *
10 | * @see Testing documentation
11 | */
12 | public class ExampleUnitTest {
13 | @Test
14 | public void addition_isCorrect() throws Exception {
15 | assertEquals(4, 2 + 2);
16 | }
17 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/qoli/myapplication/MainActivityTest.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import static org.junit.Assert.*;
4 |
5 | /**
6 | * Created by qoli on 2017/6/20.
7 | */
8 | public class MainActivityTest {
9 |
10 | }
--------------------------------------------------------------------------------
/app/src/test/java/com/example/qoli/myapplication/SettingTest.java:
--------------------------------------------------------------------------------
1 | package com.example.qoli.myapplication;
2 |
3 | import org.junit.Test;
4 |
5 | import static org.junit.Assert.*;
6 |
7 | /**
8 | * Created by qoli on 2017/6/20.
9 | */
10 | public class SettingTest {
11 | @Test
12 | public void onCreate() throws Exception {
13 |
14 | }
15 |
16 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | repositories {
5 | jcenter()
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:2.3.3'
9 | // NOTE: Do not place your application dependencies here; they belong
10 | // in the individual module build.gradle files
11 | }
12 | }
13 |
14 | allprojects {
15 | repositories {
16 | jcenter()
17 | }
18 | }
19 |
20 | task clean(type: Delete) {
21 | delete rootProject.buildDir
22 | }
23 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qoli/MiHomePlus/6d4c341874ef285864e69382c05abc2cad294a43/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Tue Jun 20 04:17:54 CST 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------