Double Tap to Wake (Nexus 6P)

前言

把服役 3 年的蝴蝶機汰換成了 Google 的 Nexus 6P,依照阿宅工程師的一定要先 Root 在加裝 Xposed framework 的阿!一開始一切都很美好,直到我發現當把手機放在桌上想看時間,卻需要按下 Power 鍵來打開螢幕,而不是時下最流行的雙擊螢幕,心中的不滿不斷地增加…

準備工作

在 XDA 這個開發者的大本營上找到了如何透過命令的方式來開起雙擊螢幕的功能,自己也寫了一個簡單的 APP 來達成將下面的指令自動寫入系統。

1
echo 1 > /sys/devices/soc.0/f9924000.i2c/i2c-2/2-0070/input/input0/wake_gesture

那個 But

如同在其他文章都會遇到的情況,就是那個 But;APP 再重開機之後無法自動開啟這個功能,即使加入了 BOOT_COMPLETED 的 Receiver 也一樣,本來想著是可能是剛開機系統還沒有完全 Ready,需要多寫入幾次,但發現這樣是不行的…

測試方法:
安裝 APP 開啟雙擊功能,重開機後,不要按 Power 鍵或是掃描指紋,就這樣放著等幾分鐘,
你會發現雙擊功能還是沒辦法用,必須解鎖後重新執行 APP 才可以。

原因

經過調查,Double to Wake 的 Sensor 是屬於 Non-wake-up sensors,根據 Google Android 說明 指出:

Non-wake-up sensors are sensors that do not prevent the SoC from going 
into suspend mode and do not wake the SoC up to report data. 
In particular, the drivers are not allowed to hold wake-locks.

簡單來說,當 SoC 處在 Suspend mode 之時,SoC 不會處理對應的事件(Event)。重開機之後的 Nexus 6P,預設的電源狀態是 Suspend mode,對應的 Sensor 也不會啟動,導致重開機後 APP 執行前述命令後,該設定仍然無法寫入到 Sensor,自然的 Double Tap to Wake 的功能就自動失效了。

解決方案

  1. 重新編譯 Kernel,重新定義 Sensor type
  2. Boot completed 後,喚醒 APP 後讓 SoC 先進入 “On mode” 再執行前述命令

最後採用了方案 2:簡單直接,同樣的這也有兩種方案可以選擇

  1. APP 使用 WakeUp lock 來喚醒 SoC
  2. APP 模擬 Power Button 動作

當然方案 2,還是我的首選 :p

結論

廢話不多說 APP 與 Source Code 在這裡

參考文件

台灣公車 Open Data

前言

最近有機會研究了一下台灣公車的 Open Data,發現各個縣市政府都是各自為政,相關的文件也是有不同程度的缺失,需要自行各個突破,幫自己做個記錄。

公車必備資訊

不管是哪個城市,公車都需要以下的資訊。

路線


  1. 路線名稱
  2. 路線 ID
  3. 路線起點
  4. 路線終點

站牌


  1. 站牌名稱
  2. 站牌 ID
  3. 站牌位置(經緯度)
  4. 站牌屬於去或回程
  5. 站牌在路線中的順序

預計到達時間


  1. 路線 ID
  2. 站牌 ID
  3. 站牌預計到達時間

台北市

資料來源:

資料格式:

  • JSON
  • XML

使用心得:

主要的問題有 2 個:

  1. Open Data 的來源不一致是個問題,路線與站牌是一個,到站時間是另一個。
  2. 預計到達時間 API,不知為何針對單條路線進行查詢的時候,台北市政府的網站常常都會回應 Cache Miss,取而代之的就只能下載全部的路線的到站時間再做後置處理。

新北市

資料來源:

資料格式:

  • JSON
  • XML
  • CSV

使用心得:

主要的問題有 3 個:

  1. 路線的資料中沒有起點與終點站 Orz,雖然站牌的資料中是有路線的起點與終點,但是…是錯的!最後只能到新北市政府的公車動態網站抓資料了。
  2. 預計到達時間的單位是分鐘,台北市的單位是秒。
  3. 某些路線的預計到達時間的站牌 ID 竟然會與站牌 API 抓到的不一致,舉例來說站牌 API 回傳的站牌 ID 是 “801”,預計到達時間的站牌 ID 回傳的是 “0801” 我也真是無語了。

高雄市

資料來源:

資料格式:

  • XML
  • JSON (隱藏版)

使用心得:

主要的問題有 3 個:

  1. 預計到達時間是使用真實到站時間,比如說現在時間是 15:00,143 路線的車會再 3 分鐘後到達站牌 A,API 回傳的到站時間會是 15:03
  2. 站牌屬於去或回程的代碼相較於台北與新北市的有所不同 (0: 去, 1: 回),高雄市用得是另一組代碼 (1: 去, 2: 回)
  3. Open Data 上有直接標明有 Json 格式的 API 只有一部分,但我又很不想爬 XML,經過了一番努力找到了公車的 JSON 版的資料,不過網站上都沒有標明這種做法,而且 JSON 格式也不是完全的正確,需要再後置處理。Json 版與 XML 版的 URL 就是差了幾個字 (xml => json),舉例取得公車站點資料的 URL 如下:

[XML]

http://ibus.tbkc.gov.tw/xmlbus/StaticData/GetStop.xml?routeIds=111

[JSON]

http://ibus.tbkc.gov.tw/xmlbus/StaticData/GetStop.json?routeIds=111

總結

好像跟前言差不多,大概就是各個政府的 Open Data 說明文件都不是很齊全,都得開發人員自己想辦法去對照,還有格式也都不太統一,希望政府未來可以統籌規劃吧。

Shadowsocks for Java

Preface

自從來了大陸之後,VPN 就是日常必需品,經過了多次的血淚測試,證實 PPTP=容易被檔 IP,L2TP=速度太慢,目光轉向了新寵 Shadowsocks(以下簡稱 SS),用起來也都很愉快方便,瀏覽器跟手機都有對應的 Client 端可以使用,但不知為何在我的手機上效率很容易下滑,於是就想 trace 手機版 soure code (shadowsocks-android)來找原因,但發現他需要採用 NDK 的方式來編譯,這跟我想的有些出入,我以為 Java 就可以搞定了,於是就促使了下面研究。

What’s Shadowsocks?

Shadowsocks 是個輕量化的自定義的 VPN 協定,封包格式是採用 Socks5 的 Header + 加密的 payload,其他細節可以參考網路上的其他說明。

Java shadowsocks client

trace shadowsocks server 程式後,覺得寫一個純 Java 的 Client 應該不難,就是搞懂 Socks5 的 Protocol + 加密資料後送出,然後等待回應,解密。於是乎,問題回到了為何 shadowsocks-android 要用 NDK 來編譯,可能是原作者不想重造輪子,所以直接拿現成的 C 實作來兜,我自己有個猜測(後述),但令我好奇的是也沒有人寫出完整的 Java Client,唯一的找到實作也被原作者捨棄了!?

想當然爾的,自己動手豐衣足食,正當覺得世界如此美好之時,Bug 隨之而來OOXX…只有第一次的 Request 會取得正確的 Response,之後的 Response 解密後都會是亂碼。在研究了七七四十九天後得到了以下的推論:

Java 加密跟 Shadowsocks Server 不相容(我使用的是 AES-256-CFB 加密)

Java 資料的加解密是有以下步驟,init、update、doFinal;而 SS 是把資料視為 Stream,除非 socket 中斷,不然就是一直處在 update 的步驟 (SS 用的是 OpenSSL),讓我們來看看 Java 的 Code

updateCipherCore.java
1
2
3
4
5
6
7
8
9
10
11
12
int update(byte[] input, int inputOffset, int inputLen, byte[] output,
int outputOffset) throws ShortBufferException
{

// figure out how much can be sent to crypto function
int len = buffered + inputLen - minBytes;
if (padding != null && decrypting) {
// do not include the padding bytes when decrypting
len -= blockSize;
}
// do not count the trailing bytes which do not make up a unit
len = (len > 0 ? (len - (len%unitBytes)) : 0);
....
}

這樣乍看之下也沒有太大的問題,But,人生就是有這個 But,AES 是 Block cipher,換言之資料的加解密是以 Block (128 bit) 為單位進行,在 Java 的實作中,所以如果資料長度不到 128 bit,呼叫 update,Java 會把長度不夠的資料存在 buffer,不吐資料出來,舉個例子:

SS                 Java Client         Browser         
 |(1)-------------> |                     |
 |                  |-----+               |
 |                  |(2)  |               |
 |                  |<----+               |
 |                  |                     |(3)
  1. SS 送出 5 bytes 給 Java Client
  2. Java Client 進行解密,但因為長度不夠把資料暫存了起來
  3. Browser 傻傻的在等待

兇手在這裡,unitBytes 的 AES 常數定義

len = (len > 0 ? (len - (len%unitBytes)) : 0);

所以就是這樣囉,江湖一點訣,說破不值錢。

最後我用了 Bouncy Castle 來繞過原生的 Java 限制。 可以在 Github 上找到我的 shadowsocks-java

Why NDK?

差點忘了,為何 Android 版的需要用 NDK,撇去 SS client 的部分,我猜是因為沒有 Java 版的 tun2socks…XD

以上,打包收工。