# スクリーンリーダー対応

## 概要

KARTEのアプリ内メッセージが表示されている場合のOSのスクリーンリーダー機能である**VoiceOver / TalkBack**の挙動について説明します。

## 要約

* 配信されるアプリ内メッセージは必ず`message_close` イベントを発火させる必要があり、場合によってはアクションのカスタマイズが必要です
* HTML,CSSが登録されず、JavaScriptのみ記載されているアクションについては、スクリーンリーダー利用環境では非推奨です
* `message_close`イベントを発火できない接客を配信する場合は、アプリ側にスクリーンリーダー機能のフォーカスが当たりません。
  * 適切な体験を提供できない場合には、スクリーンリーダーに対応できないアクションはスクリーンリーダー利用環境下に配信しない、もしくはスクリーンリーダー利用環境下でも利用可能なアクションを用意するといった対応が必要です
  * `message_close`できない接客の具体例としては、画面に常駐する接客(例: chat機能、最小化時にユーザーの操作で閉じることができない接客)などが挙げられます

## KARTEのアプリ内メッセージの挙動と、スクリーンリーダーのフォーカス

iOSの**VoiceOver**/ Androidの**TalkBack機能**はジェスチャに基づくスクリーンリーダー機能です。

KARTEのアプリ内メッセージはアプリの前面のWebViewを展開し、その上に表示しています。この前面のWebview上に表示されているアプリ内メッセージがスクリーンリーダーの読み上げ/操作の対象になります。アプリ内メッセージを閉じた場合には前面のWebviewが消え、Webviewからアプリにフォーカスが戻りユーザーは引き続きアプリの操作を続けることができます。

![](/files/ROCH7fv0PHSti2ZWlZJP)

## アプリ内メッセージのスクリーンリーダー対応

### 配信されるアクションの推奨要件

**配信する接客は、必ずユーザーの操作でアクションを閉じれるようにする必要があります**。\
通常はアクションを閉じた場合、アプリ側にスクリーンリーダーのフォーカスが戻るため、ユーザーは通常通りアプリの利用を続けることができます。閉じれない状態になっているアクションが配信された場合は、アプリ側にフォーカスを戻すことができなくなります。

以下がアクションを閉じる方法です。

* タップ時に閉じる要素にはkarte-closeのクラスを付与して、タップ時に確実に閉じるようにする
  * 注意点として、閉じるためのUI(バツボタン等)がスクリーンリーダーで操作できない状態になっていると、閉じることはできないため、アクション自体は[スクリーンリーダーに対応した記述](https://support.karte.io/post/4j9g0Ml00NeOdpr0NecQKI)である必要があります
* アクションのスクリプト上で`tracker.track(”message_close”)`を実行し、`message_close`イベントを発火させる

`message_close`できないアクションはアプリ側にスクリーンリーダーのフォーカスを戻せないため、そのような状態のアクションはスクリーンリーダー環境下には配信しないことが適切です。\
以下ではスクリーンリーダー環境下での配信制御、もしくは配信されてしまったアクションを非表示化するためのワークアラウンドを紹介します。

## ターゲティングのためにスクリーンリーダーの状態をイベントで送信する

### iOSでの実装方法

#### VoiceOver有効化状態のイベント送信

Swift APIの`UIAccessibility.isVoiceOverRunning` の値をイベントのフィールドとして送信してください。以下が実装例です。

{% code overflow="wrap" %}

```swift
let is_screen_reader = UIAccessibility.isVoiceOverRunning;
if is_screen_reader {
    Tracker.track("screen_reader", values: [
        "screen_reader_enabled": true
    ])
}
```

{% endcode %}

送信されたイベントに基づき、スクリーンリーダーの有効化状態を判定するセグメントが作成可能です。

`UIAccessibility.isVoiceOverRunning` に関してはこちらの参照をお願いします

<https://developer.apple.com/documentation/uikit/uiaccessibility/1615187-isvoiceoverrunning>

#### スクリーンリーダー有効化状態の切り替え検知

アプリ上でスクリーンリーダーの有効化状態の切り替えを検知してイベントを送信する方法もあります。以下は`UIAccessibility.voiceOverStatusDidChangeNotification` の値の変更を検知してイベント送信する例です。

{% code overflow="wrap" %}

```swift
 NotificationCenter.default.addObserver(self,
                                        selector: #selector(voiceOverStatusChanged),
                                        name: UIAccessibility.voiceOverStatusDidChangeNotification,
                                        object: nil)
```

{% endcode %}

{% code overflow="wrap" %}

```swift
@objc func voiceOverStatusChanged() {
    if UIAccessibility.isVoiceOverRunning {
		    Tracker.track("screen_reader", values: [
		        "screen_reader_enabled": true
		    ])
    }
}
```

{% endcode %}

### Androidでの実装方法

#### TalkBack有効化状態の送信

`accessibilityManager` の値でスクリーンリーダーの作動状態を取得します。以下が取得とイベント送信の実装例です。

{% code overflow="wrap" %}

```java
AccessibilityManager accessibilityManager = (AccessibilityManager) this.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled = accessibilityManager.isEnabled();
boolean isExploreByTouchEnabled = accessibilityManager.isTouchExplorationEnabled();
boolean isScreenReader = isAccessibilityEnabled && isExploreByTouchEnabled;

if (isScreenReader) {
    Tracker.track("screen_reader", new HashMap<String, Object>() {{
        put("screen_reader_enabled", true);
    }});
}
```

{% endcode %}

送信されたイベントに基づき、スクリーンリーダーの有効化状態を判定するセグメントが作成可能です。

#### スクリーンリーダー有効化状態の切り替え検知

アプリ上でスクリーンリーダーの有効化状態の切り替えを検知してイベント送信する場合は、`accessibilityManager.addTouchExplorationStateChangeListener` に変更時に行いたい関数を登録することで実現可能です。以下がその実装例です。

{% code overflow="wrap" %}

```java
accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);

listener = new TouchExplorationStateChangeListener() {
    @Override
    public void onTouchExplorationStateChanged(boolean enabled) {
        if (enabled) {
				    Tracker.track("screen_reader", new HashMap<String, Object>() {{
				        put("screen_reader_enabled", true);
				    }});
        }
    }
};

// リスナーを登録
accessibilityManager.addTouchExplorationStateChangeListener(listener);
```

{% endcode %}

## アプリ内メッセージを抑制/非表示化させるワークアラウンド

スクリーンリーダーに対応していないアクションを配信し、アプリ内メッセージがスクリーンリーダーを阻害してしまうような場合に、強制的にアプリ内メッセージの表示を抑制する方法を以下で説明します。

### iOSでの抑制/非表示化の実装方法

アプリ上でスクリーンリーダーが利用されている場合に`UIAccessibility.isVoiceOverRunning`の値が`true`になります。この値を利用してKARTEによるアプリ内メッセージの表示を抑制します。

抑制したい場合には、下記のコードのような形式で表示抑制モードにすることができます。

{% code overflow="wrap" %}

```swift
if UIAccessibility.isVoiceOverRunning {
    InAppMessaging.shared.suppress()
}
```

{% endcode %}

`InAppMessaging.shared.suppress()` に関してはサポートサイトのこちらの参照をお願いします。

[../ios-sdk-appendix/appendix-iam-control-ios-sdk#アプリ内メッセージの表示を抑制する](https://app.developers.karte.io/app-faq/pages/HovR7VPq6Fhx8QHaj3YM#アプリ内メッセージの表示を抑制する)

画面上でスクリーンリーダーのON/OFFを切り替えを検知する場合は`UIAccessibility.voiceOverStatusDidChangeNotification` の値の変更を検知して、接客の表示抑制モードの切り替えを行います。表示抑制モードを解除した場合、アクセシビリティの処理に関係のないところで表示抑制モードへの切り替えを行っている場合でも接客の表示が再開されるためアプリに合わせて実装してください。

{% code overflow="wrap" %}

```swift
 NotificationCenter.default.addObserver(self,
                                        selector: #selector(voiceOverStatusChanged),
                                        name: UIAccessibility.voiceOverStatusDidChangeNotification,
                                        object: nil)
```

{% endcode %}

{% code overflow="wrap" %}

```swift
@objc func voiceOverStatusChanged() {
  if UIAccessibility.isVoiceOverRunning {
    InAppMessaging.shared.suppress();
  } else {
    InAppMessaging.shared.unsuppress();
  }
}
```

{% endcode %}

### Androidでの抑制/非表示化の実装方法

スクリーンリーダーが利用されている場合に`accessibilityManager.isTouchExplorationEnabled()` の値がtrueになります。この値がtrueの時は表示抑制モードにして接客が表示されないようにできます。

{% code overflow="wrap" %}

```swift
AccessibilityManager accessibilityManager = (AccessibilityManager) this.getApplicationContext().getSystemService(Context.ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled = accessibilityManager.isEnabled();
boolean isExploreByTouchEnabled = accessibilityManager.isTouchExplorationEnabled();
if (isAccessibilityEnabled && isExploreByTouchEnabled) {
    InAppMessaging.suppress();
}
```

{% endcode %}

`InAppMessaging.shared.suppress()` に関してはサポートサイトのこちらの参照をお願いします。

[../android-sdk-appendix/appendix-iam-control-android-sdk#アプリ内メッセージの表示を抑制する](https://app.developers.karte.io/app-faq/pages/KRt8gy8ifbj5tx2sqMdX#アプリ内メッセージの表示を抑制する)

画面上でスクリーンリーダーのON/OFFを切り替えを検知する場合は`accessibilityManager.addTouchExplorationStateChangeListener` に変更時に行いたい関数を登録することで検知時に関数を実行することができます。現在のスクリーンリーダーの有効化状態に対応して接客の表示抑制モードを切り替えることができます。

{% code overflow="wrap" %}

```kotlin
accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);

listener = new TouchExplorationStateChangeListener() {
    @Override
    public void onTouchExplorationStateChanged(boolean enabled) {
        if (enabled) {
		        InAppMessaging.suppress();
        } else {
		        InAppMessaging.unsuppress();
        }
    }
};

// リスナーを登録
accessibilityManager.addTouchExplorationStateChangeListener(listener);
```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://app.developers.karte.io/app-faq/supporting-screen-readers.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
