web-dev-qa-db-ja.com

「ユーザーのために行動を起こす」ためのアクセシビリティサービスの使い方は?

バックグラウンド

戻る 数年前 、TeamViewerを使用すると、ユーザーがデバイスを通常操作せずにデバイスを制御できるようにする方法を尋ねました。これは、メーカーがこのアプリに対して特別に許可している特別な「バックドア」であり、他のアプリのルート権限を使用した場合にのみ可能であると言われました。

"飛行機モードのショートカット" のようなアプリを見ると、画面への自動ナビゲーションと切り替えにより、飛行機モードを切り替えることができます。スイッチ、それは私にこの状況が変わったことに気づきました。

問題

ドキュメント で言われています:

Android 4.0(APIレベル14)以降、ユーザー補助サービスは、入力フォーカスの変更やユーザーインターフェイス要素の選択(アクティブ化)など、ユーザーに代わって機能します。InAndroid 4.1(APIレベル16)アクションの範囲が拡張され、リストのスクロールやテキストフィールドの操作が含まれるようになりました。ユーザー補助サービスでは、ホーム画面への移動、[戻る]ボタンの押下、通知のオープンなどのグローバルアクションを実行することもできます。画面と最近のアプリケーションのリストAndroid 4.1には、新しいタイプのフォーカスであるAccessibilty Focusも含まれています。これにより、すべての表示要素をユーザー補助サービスで選択できるようになります。

これらの新機能により、ユーザー補助サービスの開発者はジェスチャーナビゲーションなどの代替ナビゲーションモードを作成し、障害を持つユーザーがAndroidデバイスの制御を改善できるようになります。

しかし、それを使用する方法についてのこれ以上の情報はありません。私が見つけたサンプルだけが一番下にありますが、それらは非常に古く、apiDemosバンドルの一部です。

質問

クエリ、フォーカス、クリック、テキストの入力、およびその他のUI関連の操作を実行できるサービスを作成するにはどうすればよいですか?

12

AccessibilityServicehttps://developer.Android.com/training/accessibility/service.html )を実装することで、その機能にアクセスできます。

ユーザーが最後に操作した要素を検査またはアクションを実行するか、現在アクティブなアプリケーション全体を検査できます。

onAccessibilityEvent(AccessibilityEvent event)を実装してユーザーイベントをインターセプトします。ここでは、event.getSource()を使用して仮想ビュー(元のビューを表す)を取得し、getClassName()またはgetText()などを使用して検査できます。ドキュメントにあります。

getRootInActiveWindow()を呼び出してアプリケーション全体を検査し、getRootInActiveWindow().getChild(index)を使用して仮想ビューのツリー全体を反復処理します。

getRootInActiveWindow()event.getSource()はどちらもAccessibilityNodeInfoを返します。この上で、 performAction(action) を呼び出して、クリックテキストの設定など。

例:Playストア

Playストアアプリを開いたら、「Facebook」アプリを検索して、Playストアでそのページを開きます。

    @Override
    public void onAccessibilityEvent(final AccessibilityEvent event) {

        AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow();
        //Inspect app elements if ready
        if (rootInActiveWindow != null) {
            //Search bar is covered with textview which need to be clicked
            List<AccessibilityNodeInfo> searchBarIdle = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.Android.vending:id/search_box_idle_text");
            if (searchBarIdle.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBarIdle.get(0);
                searchBar.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
            //Check is search bar is visible
            List<AccessibilityNodeInfo> searchBars = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.Android.vending:id/search_box_text_input");
            if (searchBars.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBars.get(0);
                //Check is searchbar have the required text, if not set the text
                if (searchBar.getText() == null || !searchBar.getText().toString().equalsIgnoreCase("facebook")) {
                    Bundle args = new Bundle();
                    args.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "facebook");
                    searchBar.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
                } else {
                    //There is no way to press Enter to perform search, so find corresponding suggestion and click
                    List<AccessibilityNodeInfo> searchSuggestions = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.Android.vending:id/suggest_text");
                    for (AccessibilityNodeInfo suggestion : searchSuggestions) {
                        if(suggestion.getText().toString().equals("Facebook")) {
                            //We found textview, but its not clickable, so we should perform the click on the parent
                            AccessibilityNodeInfo clickableParent = suggestion.getParent();
                            clickableParent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        }
                    }
                }
            }


        }
   }

編集:以下の完全なコード:

MyAccessibilityService

public class MyAccessibilityService extends AccessibilityService {

    @Override
    public void onCreate() {
        super.onCreate();
        Log.d("MyAccessibilityService", "onCreate");
    }

    @Override
    public void onAccessibilityEvent(final AccessibilityEvent event) {
        Log.d("MyAccessibilityService", "onAccessibilityEvent");
        AccessibilityNodeInfo rootInActiveWindow = getRootInActiveWindow();
        //Inspect app elements if ready
        if (rootInActiveWindow != null) {
            //Search bar is covered with textview which need to be clicked
            List<AccessibilityNodeInfo> searchBarIdle = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.Android.vending:id/search_box_idle_text");
            if (searchBarIdle.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBarIdle.get(0);
                searchBar.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            }
            //Check is search bar is visible
            List<AccessibilityNodeInfo> searchBars = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.Android.vending:id/search_box_text_input");
            if (searchBars.size() > 0) {
                AccessibilityNodeInfo searchBar = searchBars.get(0);
                //Check is searchbar have the required text, if not set the text
                if (searchBar.getText() == null || !searchBar.getText().toString().equalsIgnoreCase("facebook")) {
                    Bundle args = new Bundle();
                    args.putString(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, "facebook");
                    searchBar.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
                } else {
                    //There is no way to press Enter to perform search, so find corresponding suggestion and click
                    List<AccessibilityNodeInfo> searchSuggestions = rootInActiveWindow.findAccessibilityNodeInfosByViewId("com.Android.vending:id/suggest_text");
                    for (AccessibilityNodeInfo suggestion : searchSuggestions) {
                        if (suggestion.getText().toString().equals("Facebook")) {
                            //We found textview, but its not clickable, so we should perform the click on the parent
                            AccessibilityNodeInfo clickableParent = suggestion.getParent();
                            clickableParent.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        }
                    }
                }
            }
        }
    }

    @Override
    public void onInterrupt() {
    }
}

AndroidManifest.xml

<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android"
          package="com.example.findfacebookapp">

    <application
        Android:allowBackup="true"
        Android:icon="@mipmap/ic_launcher"
        Android:label="@string/app_name"
        Android:supportsRtl="true"
        Android:theme="@style/AppTheme">
        <activity Android:name=".MainActivity">
            <intent-filter>
                <action Android:name="Android.intent.action.MAIN"/>

                <category Android:name="Android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
        <service
            Android:name=".MyAccessibilityService"
            Android:label="@string/accessibility_service_label"
            Android:permission="Android.permission.BIND_ACCESSIBILITY_SERVICE">
            <intent-filter>
                <action Android:name="Android.accessibilityservice.AccessibilityService"/>
            </intent-filter>

            <meta-data
                Android:name="Android.accessibilityservice"
                Android:resource="@xml/accessibility_service_config"/>
        </service>
    </application>

</manifest>

res/xml/accessibility_service_config.xml

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service
    xmlns:Android="http://schemas.Android.com/apk/res/Android"
    Android:accessibilityEventTypes="typeAllMask"
    Android:accessibilityFeedbackType="feedbackAllMask"
    Android:accessibilityFlags="flagDefault"
    Android:canRequestEnhancedWebAccessibility="true"
    Android:canRetrieveWindowContent="true"
    Android:description="@string/app_name"
    Android:notificationTimeout="100"/>

MainActivity

public class MainActivity extends AppCompatActivity {

    public void onEnableAccClick(View view) {
        startActivityForResult(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS), 1);
    }

}
13
Nikola Minoski