【Python】Java Access BridgeでJava自動化

# 目標

Java Access Bridgeを利用してJavaアプリを自動化する。

# 背景

ブラウザやWindowsアプリはたいていの場合、SeleniumやUIAutomationを利用すれば自動化できる。しかし、Javaアプリの場合、Java実行環境を介して実行しているためか、エレメント情報を取得することができず自動化することが難しい。
そこで調査したところ、JavaAccessBridgeというものを利用すればJavaアプリを自動化できるそう。
検証してみる。

UIAutomationではJavaのエレメント取得ができない理由については別の記事にまとめる。

# Java Access Bridgeとは何か

[公式](https://docs.oracle.com/javase/accessbridge/2.0.2/introduction.htm)の説明を読み解いていく。

> Microsoft Windows DLL で Java Accessibility API を公開するテクノロジで、Java Accessibility API を実装する Java アプリケーションおよびアプレットを Microsoft Windows システム上のユーザー補助テクノロジから可視にできます。

Javaアプリの情報を取得するためには、Javaに実装されている**Java Accessibility API**というものを利用する必要がある。Java Access Bridge（以下、JAB）はそのAPIをDLLとして公開することでWindowsからJavaアプリのエレメント情報にアクセスする手段を提供するものである。JABの実態は、`jdk/bin/windowsaccessbridge-64.dll`, `jdk/bin/javaaccessbridge.dll`からなる。前者はWindowsとのやり取りを行うもの、後者はJavaアプリとのやり取りを行うものである。
 
> Java Accessibility API は Java Accessibility ユーティリティー の一部で、Java Accessibility API を実装する GUI ツールキットへのアクセスをユーザー補助テクノロジが提供するのを支援するユーティリティークラスのセットです。

Java Accessibility APIはJavaアプリのGUI（ボタンやテキスト、フォーム等）のエレメント情報やイベント情報を取得するAPIということ。ユーザ補助テクノロジというのは**[Access Bridge Explorer（以下、ABE）](https://github.com/google/access-bridge-explorer)**のようにJavaアプリのエレメント情報をキャプチャできるツールのこと。ABEで取得したエレメント情報をもとにJavaアプリを操作することが可能となる。

> Microsoft Windows 上で実行されているユーザー補助テクノロジアプリケーション (画面リーダーなど) は Java Access Bridge DLL とやり取りし、Java Access Bridge DLL は Java Access Bridge Java ライブラリ経由で Java Virtual Machine とやり取りします。これらの Java ライブラリは Java Accessibility ユーティリティーとやり取りします。Java Accessibility ユーティリティーは Java アプリケーションで発生していることについての情報を収集し、それらは Java Access Bridge 経由で画面リーダーに転送されます。

どのようにJavaアプリから情報を取得しているかの説明。JAB DLLのはクラスライブラリであり、クラスを利用してJava Virtual Michine(Java Accessibility Utility)とやり取りしている。**[Java Accessibility Utility](https://docs.oracle.com/javase/jp/20/access/java-accessibility-utilities-overview.html#GUID-3F45CD9D-3BEB-406E-BC46-35E4829CE4D9)**こそが画面関連情報を取得している本体ということ。

JABは結果的にはJava Accessibility Utilityとやり取りして、Javaアプリの画面情報を取得している。

# 実際にPythonで書いてみる

ここでは**[pyjab](https://github.com/gaozhao1989/pyjab/tree/master)**というライブラリを利用して、Android Studioを操作してみる。使用感はSeleniumと似ていて、driverオブジェクトのメソッドを利用して操作する。


##  環境、必要なもの

- Windows 10
- Python 3.10
- pyjab 1.1.7
- OpenJDK 21
- Android Studio
- [Access Bridge Explorer（オプション）](https://github.com/google/access-bridge-explorer)

## 事前準備

- [JABを有効化しておくこと](https://docs.oracle.com/javase/jp/7/technotes/guides/access/enable_and_test.html)

## Android Studioを起動して色々試してみる

README.mdのサンプルプログラムをベースに書いていく。前提条件として、`jdk-21.0.1\bin\windowsaccessbridge-64.dll`をワーキングディレクトリに配置しておく必要がある。

```python
import pyjab.jabdriver
import pathlib

# Point 1. Create a JABDriver object.
jabdriver = pyjab.jabdriver.JABDriver(title='My Application', file_path=pathlib.Path(r'C:\Program Files\Android\Android Studio\bin\studio64.exe'))

# Point 2. Find a JABElement by element name
element = jabdriver.find_element_by_name("Resource Manager")

# Point 3. Click a JABElement
element.click()

# Point 4. Get property of JABElement
name = element.name
description = element.description
role = element.role
role_en_us = element.role_en_us
states = element.states
states_en_us = element.states_en_us
object_depth = element.object_depth
index_in_parent = element.index_in_parent
children_count = element.children_count
# Return {x, y, height, width}
bounds = element.bounds
accessible_component = element.accessible_component
accessible_action = element.accessible_action
accessible_selection = element.accessible_selection
accessible_text = element.accessible_text
text = element.text
table = element.table
```

Point 1ではJavaアプリ操作のためのJabdriverを作成している。Jabdriverの作成はJabを起動することと同じ意味。既に起動しているJavaアプリのjabdriverを作成する方法もあれば、Javaアプリを起動後jabdriverを作成する方法もある。ケースバイケースで使い分けるとよい。注意点は、titleに指定するウィンドウ名がGUIの表示されているものウィンドウ名と異なる場合がある。`no java window found by title 'My Application' in '30'seconds`のようなエラーが出る場合は、win32等で取得したウィンドウ名を確認してみるとよい。

Point 2では操作対象のエレメントを取得している。ここでは`name`属性で指定している。ほかにも

- description
- role
- states
- object depth
- children count
- index in parent
- xpath

の属性から検索することが可能。また、サンプルプログラムは最初にヒットしたエレメントを取得するメソッドを使用しているが、`find_elements_by_name`のようにヒットしたすべてのエレメントを取得するメソッドも存在する。個人的な感覚だが、`name`, `role`, `object depth`あたりが有用だと思う。`xpath`が取得できればよいが、その方法は調査中。

Point 3ではエレメントへのクリック命令の送信やエレメント情報を取得している。エレメントによっては送信できる命令や取得できる情報が異なるので注意。`AccessibleAction supported`が有効なエレメントはクリック操作が可能だったり、`AccessibleSelection supported`が有効なエレメントは選択操作が可能だったり、`AccessibleText supported`が有効なエレメントは表示テキストの取得が可能だったりする。

# JAB APIのドキュメントをみてみる

[Java Access Bridge APIのドキュメント](https://docs.oracle.com/javase/jp/7/technotes/guides/access/jab/api.html#jab-api-specification)を確認してみる。Java Access Bridgeを利用するフローは、

1. JABを起動(initializeAccessBridge関数)
2. JavaアプリのAccessibility API実装の有無を確認（IsJavaWindow関数）
3. JAB APIのベースとなるアプリ情報を取得（GetAccessibleContextFromHWND関数）
4. JAB APIでJavaアプリを操作（各種Accessible関数）
5. JABを終了（shutdownAccessBridge関数）

となる。1-3は上記プログラムのPoint 1でまとめて行っている。JABDriverのInit関数は`venv\Lib\site-packages\pyjab\jabdriver.py`に記載があるので確認してみるとよい。4はPoint 2-4で行っている。エレメントのデータ構造は[APIデータ構造](https://docs.oracle.com/javase/jp/7/technotes/guides/access/jab/api.html#jab-api-data-structures)に記載されているので、どのような情報を取得できるのか確認するとよい。個人的には、`AccessibleContextInfo`, `AccessibleTextInfo`のデータの使用頻度が高い。

# 参考

[ユーザー補助テクノロジ開発者向け Java Access Bridge プログラマーズガイド](https://docs.oracle.com/javase/jp/7/technotes/guides/access/jab/index.html)

[pyjab](https://github.com/gaozhao1989/pyjab/tree/master)