ADB 按鍵輸入

ADB shell command 裡有個 input,可以用來輸入文字或觸發鍵盤的事件。

$ input
usage: input [text|keyevent]
       input text <string>
       input keyevent <event_code>

首先來看 input keyevent <event_code>,例如:

$ input keyevent 3

Home 鍵的 key code 是 3,所以 input keyevent 3 可以模擬使用者按下 Home 鍵的效果。其他常用的 key code 有:

  • KEYCODE_HOME (3)
  • KEYCODE_BACK (4)
  • KEYCODE_SPACE (62)
  • KEYCODE_ENTER (66)
  • KEYCODE_MENU (82)

完整的清單可以參考這裡

接著來看 input text <string>,通常只要先讓文字輸入框取得焦點,然後把要輸入的文字接在 input text 後面即可:

$ input text hello
$
$ input text android&java 1
java: not found
[1]   Done                    input text android
$ input text 'android&java'
$
$ input text hello world 2
$ input text 'hello world'
$
$ input text hello
$ input keyevent 62
$ input text world
$
$ input text 你好 3
/mnt/.lfs: Function not implemented
/mnt/secure/asec: Permission denied
rename(/data/log/dumpstate_sys_error.txt.gz.tmp, /data/log/dumpstate_sys_error.txt.gz): Operation not permitted
Unable to chmod /data/log/dumpstate_sys_error.txt.gz: Operation not permitted
[1]   Killed                  input text 你好
1 & 在 shell 有特殊的意義,所以畫面上只出現 hello& 後面的 java 被視為另外一個指令;包在單引號裡就不會有問題了。
2 只會輸出 hello,空白字元後面的 " world" 全不見了,用單引號框起來或是把 software keyboard 整個收起來都沒用(跟輸入法無關?)。一個可行的解決方法,就是混用 input textinput keyevent,其中 62 是空白字元的 key code。
3 顯然 input text 目前還不支援非英數字元。
Tip 由於 input text 背後是送出 raw keyevent,所以想要透過它來輸入 unicode 是行不通的,不過倒可以試試透過 clipboard 來交換文字

撇開文字內容包含空白、特殊符號或非英數字的限制,就算是第一個 input text hello 這麼簡單的動作,都可能會失敗,原因就出在使用者開始要輸入文字之前,對螢幕點選的動作會叫輸入法的 software keyboard。input text 背後其實是送出一連串的 keyevent(也就是說,改用 input keyevent 也會有相同的問題),感覺 keyevent 會先被吃進 software keyboard 再轉換成對應的文字,因此最後的結果往往不如預期(字首變成大寫、只出現部份文字…)。

AndADBInput/ime_hello.png

Figure 1. 在 Samsung Galaxy S2 上採用 Chinese Keypad 時,input text hello 累積成一串無效的拼音

遇到這種情形,通常只要事先用 Back 鍵將輸入法收回去即可,keyevent 就不會先由輸入法過一手。不過某些 App 在按下 Back 鍵之後,輸入的焦點就會跟著移開,導致輸入的文字無法進到該文字輸入框,這時候只好透過 ime 切換輸入法來避開這個問題(每一種輸入法影響的程度不一)。下面示範在 Samsung Galaxy S2 上的做法:

$ ime list -a -s
com.swype.android.inputmethod/.SwypeInputMethod
com.samsung.inputmethod/.SamsungIME
com.sec.android.inputmethod.axt9/.AxT9IME
$
$ ime set com.samsung.inputmethod/.SamsungIME 1
Input method com.samsung.inputmethod/.SamsungIME selected
$ input text http://www.google.com.tw         2
$ input ABCabc123
$
$ ime set com.sec.android.inputmethod.axt9/.AxT9IME
Input method com.sec.android.inputmethod.axt9/.AxT9IME selected
$ input text http://www.google.com.tw         3
$ input text ABCabc123
1 別懷疑,com.sec.android.inputmethod.axt9/.AxT9IME 是 Samsung Keypad,com.samsung.inputmethod/.SamsungIME 才是會出狀況的 Chinese Keypad。
2 搭配 Chinese Keypad 時,輸入的網址會變成 http://www:google:com(句點全變成冒號了),下一行文字則變成 ABCAbc123(中間那個 a 被轉成大寫 A 了)。
3 切換回 Samsung Keypad 之後,兩串文字都能正常地輸入了。

一路測試下來,似乎沒有一個方法是萬無一失的。這裡總結一下不同的做法:

  • 要輸入 unicode,只能 透過 clipboard 來交換文字,而且會涉及 UI 的操作(長按後選 Paste,而且貼上的動作不一定有支援)。
  • 如果輸入法的 software keyboard 可以用 Back 鍵關閉,而且輸入的焦點不會跑掉,直接用 input event 輸入英數字即可。
  • 如果輸入的焦點會因為關閉輸入法的 software keyboard 跑掉,只好事先切換到不會出狀況的輸入法,不過每台裝置安裝的輸入法不同,是這個方法最大的缺點。

延伸閱讀


參考資料

其他文件

測試時如何避開 Android 的滑動解鎖(Keyguard)

測試過程中要避免螢幕自動上鎖?

有的手機會在 Settings > Applications > Developement 下提供 “Stay awake" 的選項,再不然也可以透過 Settings > Display > Screen timeout 設定永遠不關閉螢幕來達成類似的目的。但偏偏有些手機這兩個選項都沒有提供…

Tip

Keyguard 指的是 “slide to unlock",例如:

AndroidTestDisableKeyguard/emulator_keyguard.png

Figure 1. Emulator 的鍵盤鎖

AndroidTestDisableKeyguard/htc_keyguard.png

Figure 2. HTC Sense 的鍵盤鎖

它跟 Settings > Security > Set up screen lock 底下的 Screen Lock 不同,例如:

AndroidTestDisableKeyguard/pattern_lock.png

Figure 3. 用圖形來解鎖

Keyguard 功用就像早期手機要連續按斜對角的兩個按鈕來解鎖一樣,避免放在口袋裡就不小撥出電話了。從這個角度來看,"keyguard" 確實很直覺,但使用者似乎比較習慣用 “screen lock" 來稱呼 “keyguard",以 “keyguard" 為關鍵字反而找不太到防螢幕上鎖的程式,就是最好的證明。

官方文件提到,測試時可以在 Activity.onCreate() 加上下面的程式碼,自動解開鍵盤鎖;但正式發行時要記得把這些程式拿掉… XD!

mKeyGuardManager = (KeyguardManager) getSystemService(KEYGUARD_SERVICE);
mLock = mKeyGuardManager.newKeyguardLock("activity_classname");
mLock.disableKeyguard();

當然這得要求對應的權限才行:

<uses-permission android:name="android.permission.DISABLE_KEYGUARD"/>

如果是測試用途,可以考慮這下面這兩個 App,它們都只要求了 disable keyguard 的權限而已:

實驗發現,這在 HTC Desire S 跟 Samsung Galaxy S2 上都行得通。當螢幕全關時,下面幾種方式都可以喚醒手機:

  • adb shell input keyevent 82 – 按下 Menu 鈕。
  • MonkeyRunner.waitForConnection()MonkeyDevice.wake() – 透過 monkeyrunner API。由於接上手機 waitForConnection() 的動作就會喚醒手機,所以實務上 wake() 不太會用到。

參考資料

如何自動化測試 PDF 報表的內容

網站提供下載 PDF 報表的功能很常見。自動化測試 PDF 內容(文字、排版、圖像等)的方法,大概分為下列幾種:

  • 將 PDF 每一個頁面轉成圖像,然後做圖像對圖像的比對 – 一次比對到所有使用者會看到的東西,但如果報表內包含有會變動的內容(例如時間),事先要準備好驗證用的圖像就有困難。
  • 將整個 PDF 轉成另一種格式後(例如 HTML、XML 或單純的文字檔),然後再對轉檔後的結果做解析 – 這類的工具有很多選擇,有些還能個別取出圖像。挑戰來自於怎麼對文字檔做 parsing。
  • 透過 Object ID 直接取出要驗證的資料項目 – 這要事先跟程式開發人員溝通好,事先為要自動化驗證的對象埋下對應的 ID 才行。尤其適合用來取出經由 PDF Form Filler 填值的 PDF 文件?

由於測試框架採用 Python,所以對可能的方案有一些要求:

  • 免安裝 – 可以直接放進 VCS,簡化佈署到測試機器的工作,測試機只要更新自己的 local copy 即可。
  • Pure Python – 跨平台;跟上面一條 “免安裝" 的考量多少有點關係。
  • 支援多國語言 – 從 PDF 檔取出的文字要能夠統一轉成 Unicode。

python pdf (parser or extractor) 為關鍵字,經過一番衝浪之後,找到幾個可能的方案:

雖然說網路上也有一些例子用 pyPDF 來取出 PDF 的文字內容,但在 PDF parsing/extraction 這個領域而這,多數人在談論的還是 PDFMiner,畢竟 pyPDF 的專長是在對 PDF 做加工(例如分割、合併、加解密等),而 PDFMiner 才是專注在 extracting and analyzing text data

Note

可能的方案…


PDF Is Eval!

PDF 為什麼邪惡?

PDF is evil. Although it is called a PDF “document", it’s nothing like Word or HTML document. PDF is more like a graphic representation. PDF contents are just a bunch of instructions that tell how to place the stuff at each exact position on a display or paper. In most cases, it has no logical structure such as sentences or paragraphs and it cannot adapt itself when the paper size changes. PDFMiner attempts to reconstruct some of those structures by guessing from its positioning, but there’s nothing guaranteed to work. Ugly, I know. Again, PDF is evil.

Programming with PDFMiner
— Yusuke Shinyama

這也就是為什麼我們需要 PDFMiner 這類工具,幫我們把 “拼貼在一起的文字" 直的串接起來的原因。

PDF 不邪惡,因為它是為了 presentation 跟 printing 而生…

In a PDF, the text is not continous, but made from a lot of small groups of characters positioned absolutely in the page. The focus of PDF is to keep the layout intact. It’s not content oriented but presentation oriented.

Advanced PDF Parsing Using Python. What is the Best Library?
— Etienne


安裝 PDFMiner

最簡單的方式就是透過 EasyInstall 安裝 pdfminer 套件。

$ sudo easy_install pdfminer
install_dir /usr/local/lib/python2.6/dist-packages/
Searching for pdfminer
Reading http://pypi.python.org/simple/pdfminer/
Reading http://www.unixuser.org/~euske/python/pdfminer/index.html
Best match: pdfminer 20110515
Downloading http://pypi.python.org/packages/source/p/pdfminer/pdfminer-20110515.tar.gz#md5=f3905f801ed469900d9e5af959c7631a
Processing pdfminer-20110515.tar.gz
Running pdfminer-20110515/setup.py -q bdist_egg --dist-dir /tmp/easy_install-oY2vXu/pdfminer-20110515/egg-dist-tmp-fsF5jc
zip_safe flag not set; analyzing archive contents...
pdfminer.cmapdb: module references __file__
Adding pdfminer 20110515 to easy-install.pth file
Installing pdf2txt.py script to /usr/local/bin 1
Installing dumppdf.py script to /usr/local/bin
Installing latin2ascii.py script to /usr/local/bin

Installed /usr/local/lib/python2.6/dist-packages/pdfminer-20110515-py2.6.egg
Processing dependencies for pdfminer
Finished processing dependencies for pdfminer
1 過程中額外安裝了 pdf2txt.pydumppdf.py 以及 latin2ascii.py 三個 command-line tools。

在 Windows 下也是一樣:

C:\>easy_install pdfminer
Searching for pdfminer
Reading http://pypi.python.org/simple/pdfminer/
Reading http://www.unixuser.org/~euske/python/pdfminer/index.html
Best match: pdfminer 20110515
Downloading http://pypi.python.org/packages/source/p/pdfminer/pdfminer-20110515.tar.gz#md5=f3905f801ed469900d9e5af959c7631a
Processing pdfminer-20110515.tar.gz
Running pdfminer-20110515\setup.py -q bdist_egg --dist-dir c:\users\jeremy~1\appdata\local\temp\easy_install-dxz4iz\pdfminer-20110515\egg-dist-tmp-1ks9pp
zip_safe flag not set; analyzing archive contents...
pdfminer.cmapdb: module references __file__
Adding pdfminer 20110515 to easy-install.pth file
Installing dumppdf.py script to C:\Python27\Scripts 1
Installing latin2ascii.py script to C:\Python27\Scripts
Installing pdf2txt.py script to C:\Python27\Scripts

Installed c:\python27\lib\site-packages\pdfminer-20110515-py2.7.egg
Processing dependencies for pdfminer
Finished processing dependencies for pdfminer
1 同樣將三個 command-line tools 安裝到 C:\Python27\Scripts,將這個目錄加到 PATH 環境變數,就可以直接叫用這些 tools。

如果要支援 CJK languages,就必須從 source 安裝:

  1. PyPI 下載 pdfminer-<version>.tar.gz 後解壓縮。
  2. 切換到解壓縮縮的目錄,然後執行 make cmap。(不知道為什麼 Makefile 要寫成 PYTHON=python2,改成 python 即可)
    $ make cmap
    python tools/conv_cmap.py pdfminer/cmap Adobe-CNS1 cmaprsrc/cid2code_Adobe_CNS1.txt cp950 big5
    reading 'cmaprsrc/cid2code_Adobe_CNS1.txt'...
    writing 'CNS1-H.pickle.gz'...
    writing 'ETHK-B5-V.pickle.gz'...
    writing 'ETHK-B5-H.pickle.gz'...
    ...
    python tools/conv_cmap.py pdfminer/cmap Adobe-GB1 cmaprsrc/cid2code_Adobe_GB1.txt cp936 gb2312
    reading 'cmaprsrc/cid2code_Adobe_GB1.txt'...
    writing 'GBT-EUC-V.pickle.gz'...
    writing 'GB-EUC-H.pickle.gz'...
    writing 'UniGB-UTF32-H.pickle.gz'...
    ...
    python tools/conv_cmap.py pdfminer/cmap Adobe-Japan1 cmaprsrc/cid2code_Adobe_Japan1.txt cp932 euc-jp
    reading 'cmaprsrc/cid2code_Adobe_Japan1.txt'...
    writing 'Add-V.pickle.gz'...
    writing '78ms-RKSJ-H.pickle.gz'...
    writing 'Hankaku-V.pickle.gz'...
    ...
    python tools/conv_cmap.py pdfminer/cmap Adobe-Korea1 cmaprsrc/cid2code_Adobe_Korea1.txt cp949 euc-kr
    reading 'cmaprsrc/cid2code_Adobe_Korea1.txt'...
    writing 'KSCms-UHC-HW-V.pickle.gz'...
    writing 'UniKS-UTF32-V.pickle.gz'...
    writing 'KSC-V.pickle.gz'...

    在 Windows 下沒有 make,可以仿上面的輸出依序執行:

    python tools\conv_cmap.py pdfminer\cmap Adobe-CNS1 cmaprsrc\cid2code_Adobe_CNS1.txt cp950 big5
    python tools\conv_cmap.py pdfminer\cmap Adobe-GB1 cmaprsrc\cid2code_Adobe_GB1.txt cp936 gb2312
    python tools\conv_cmap.py pdfminer\cmap Adobe-Japan1 cmaprsrc\cid2code_Adobe_Japan1.txt cp932 euc-jp
    python tools\conv_cmap.py pdfminer\cmap Adobe-Korea1 cmaprsrc\cid2code_Adobe_Korea1.txt cp949 euc-kr
  3. 執行 python setup.py install 進行安裝。
    $ sudo python setup.py install
    ...
    running install_lib
    creating /usr/local/lib/python2.6/dist-packages/pdfminer
    copying build/lib.linux-x86_64-2.6/pdfminer/converter.py -> /usr/local/lib/python2.6/dist-packages/pdfminer
    copying build/lib.linux-x86_64-2.6/pdfminer/glyphlist.py -> /usr/local/lib/python2.6/dist-packages/pdfminer
    ...
    running install_scripts
    copying build/scripts-2.6/pdf2txt.py -> /usr/local/bin 1
    copying build/scripts-2.6/dumppdf.py -> /usr/local/bin
    copying build/scripts-2.6/latin2ascii.py -> /usr/local/bin
    changing mode of /usr/local/bin/pdf2txt.py to 755
    changing mode of /usr/local/bin/dumppdf.py to 755
    changing mode of /usr/local/bin/latin2ascii.py to 755
    running install_egg_info
    Writing /usr/local/lib/python2.6/dist-packages/pdfminer-20110515.egg-info
    1 同樣會安裝三個 command-line tools。

pdf2txt.py 測試安裝:

$ pdf2txt.py samples/simple1.pdf
Hello

World

Hello

World

H e l l o

W o r l d

H e l l o

W o r l d

非英文 PDF 也沒問題:

$ pdf2txt.py samples/jo.pdf
...
宇
宙
塵
を
た
べ
、
...
Tip 由於 PDFMiner 是純 Python 的實作,解壓縮後把 pdfminer 子目錄放到目前的工作目錄底下也可以運作。但 make cmap 產出的 *.pickle.gz 好像會跟平台相依?

從程式裡直接將 PDF 的文字取出來

既然 tools/pdf2txt.py 可以正常處理多國語言的 PDF 檔,就沒有什麼好擔心的了。剩下的只是如何把 pdf2txt.py 裡頭 “將 PDF 轉成文字檔" 的功能取出來…

from StringIO import StringIO
from pdfminer.pdfinterp import PDFResourceManager, process_pdf
from pdfminer.converter import TextConverter
from pdfminer.layout import LAParams

def pdf_to_text(pdf_file):
    rsrcmgr = PDFResourceManager()
    laparams = LAParams()

    try:
        infp = open(pdf_file, 'rb')
        outfp = StringIO()
        device = TextConverter(rsrcmgr, outfp, codec='utf-8', laparams=laparams) 1
        process_pdf(rsrcmgr, device, infp)

        return outfp.getvalue().decode('utf-8') 2
    finally:
        outfp.close()
        infp.close()
        device.close()
1 codec 一定要給,預設採用 UTF-8。
2 轉回 Unicode,方便後續的處理。

最後?

故事還沒結束…

取出 PDF 的文字內容之後,接下來就是要做對純文字做 parsing 的工作,這才是真正費時費工的部份…

在尋找解決方案的過程中,意外找到了一些有趣的東西,跟大家分享:


PDFMiner 的其他資源

其他文件