1. 소개
접근성 서비스는 Android 기기에 설치된 애플리케이션을 대신하여 사용자에게 대체 탐색 피드백을 제공하도록 설계된 Android 프레임워크의 기능입니다. 접근성 서비스는 애플리케이션을 대신하여 사용자와 통신할 수 있습니다. 예를 들어 사용자가 화면의 중요한 영역에 마우스를 가져가면 텍스트를 음성으로 변환하거나 햅틱 반응을 보일 수 있습니다. 이 Codelab에서는 매우 간단한 접근성 서비스를 만드는 방법을 보여줍니다.
접근성 서비스란 무엇인가요?
접근성 서비스는 장애가 있는 사용자가 Android 기기와 앱을 사용하도록 지원합니다. 이는 사용자가 화면의 정보를 처리하고 기기와 의미 있게 상호작용할 수 있도록 지원하는 장기 실행 권한 서비스입니다.
일반적인 접근성 서비스의 예
- 스위치 제어: 거동이 불편한 Android 사용자가 하나 이상의 스위치를 사용하여 기기와 상호작용할 수 있도록 허용합니다.
- 음성 액세스 (베타): 거동이 불편한 Android 사용자가 음성 명령으로 기기를 제어할 수 있도록 지원합니다.
- TalkBack: 시각 장애가 있거나 시각 장애가 있는 사용자가 일반적으로 사용하는 스크린 리더입니다.
접근성 서비스 빌드
Google은 Android 사용자를 위해 스위치 제어, 음성 액세스, TalkBack과 같은 서비스를 제공하지만 이러한 서비스는 장애가 있는 모든 사용자에게 서비스를 제공할 수 없습니다. 많은 장애가 있는 사용자에게 고유한 니즈가 있으므로 접근성 서비스를 만들기 위한 Android의 API가 공개되어 있으며 개발자는 자유롭게 접근성 서비스를 만들고 Play 스토어를 통해 배포할 수 있습니다.
빌드할 항목
이 Codelab에서는 Accessibility API를 사용하여 몇 가지 유용한 작업을 하는 간단한 서비스를 개발합니다. 기본적인 Android 앱을 작성할 수 있다면 유사한 서비스를 개발할 수 있습니다.
접근성 API는 강력합니다. 빌드할 서비스의 코드는 4개의 파일에만 포함되어 있으며 약 200줄의 코드를 사용합니다.
최종 사용자
다음 특성을 가진 가상의 사용자를 위한 서비스를 빌드합니다.
- 사용자가 기기의 측면 버튼에 접근하기 어렵습니다.
- 사용자가 스크롤하거나 스와이프하는 데 어려움을 겪습니다.
서비스 세부정보
서비스가 화면의 전역 작업 모음을 오버레이합니다. 사용자는 이 바의 버튼을 터치하여 다음 작업을 수행할 수 있습니다.
- 휴대전화 측면에 있는 실제 전원 버튼에 닿지 않고 기기의 전원을 끕니다.
- 휴대전화 측면에 있는 볼륨 버튼을 터치하지 않고 볼륨을 조절합니다.
- 실제로 스크롤하지 않고 스크롤 작업을 실행합니다.
- 스와이프 동작을 사용하지 않고 스와이프를 실행합니다.
필요한 항목
이 Codelab에서는 다음을 사용한다고 가정합니다.
- Android 스튜디오를 실행하는 컴퓨터
- 간단한 셸 명령어를 실행하는 터미널입니다.
- 개발에 사용할 컴퓨터에 연결된 Android 7.0 (Nougat)을 실행하는 기기
지금 시작해 보세요.
2. 설정
터미널을 사용하여 작업할 디렉터리를 만듭니다. 이 디렉터리로 변경합니다.
코드 다운로드
이 Codelab의 코드가 포함된 저장소를 클론할 수 있습니다.
git clone https://github.com/android/codelab-android-accessibility.git
저장소에는 여러 Android 스튜디오 프로젝트가 포함되어 있습니다. Android 스튜디오를 사용하여 GlobalActionBarService를 엽니다.
스튜디오 아이콘을 클릭하여 Android 스튜디오를 실행합니다.
Import project (Eclipse ADT, Gradle, etc.) 옵션을 선택합니다.
소스를 클론한 위치로 이동하여 GlobalActionBarService를 선택합니다.
그런 다음 터미널을 사용하여 루트 디렉터리로 변경합니다.
3. 시작 코드 이해
연 프로젝트를 살펴봅니다.
접근성 서비스의 기본 뼈대가 이미 만들어져 있습니다. 이 Codelab에서 작성할 코드는 모두 다음 4개 파일로 제한됩니다.
- app/src/main/AndroidManifest.xml
- app/src/main/res/layout/action_bar.xml
- app/src/main/res/xml/global_action_bar_service.xml
- app/src/main/java/com/example/android/globalactionbarservice/GlobalActionBarService.java
다음은 각 파일의 콘텐츠를 둘러봅니다.
AndroidManifest.xml
접근성 서비스에 관한 정보는 다음과 같이 매니페스트에 선언됩니다.
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.android.globalactionbarservice">
<application>
<service
android:name=".GlobalActionBarService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/global_action_bar_service" />
</service>
</application>
</manifest>
다음 세 가지 필수 항목은 AndroidManifest.xml에 선언되어 있습니다.
- 접근성 서비스에 바인딩할 수 있는 권한:
<service
...
android:permission = "android.permission.BIND_ACCESSIBILITY_SERVICE">
...
</service>
- AccessibilityService 인텐트:
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
- 생성 중인 서비스의 메타데이터가 포함된 파일의 위치입니다.
<meta-data
...
android:resource="@xml/global_action_bar_service" />
</service>
global_action_bar_service.xml
이 파일에는 서비스의 메타데이터가 포함됩니다.
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canPerformGestures="true"
android:canRetrieveWindowContent="true" />
<accessibility-service> 요소를 사용하여 다음 메타데이터가 정의되었습니다.
- 이 서비스의 의견 유형입니다. 이 Codelab에서는 적절한 기본값인 feedbackGeneric을 사용합니다.
- 서비스의 접근성 플래그 (이 Codelab에서는 기본 플래그 사용)
- 서비스에 필요한 기능은 다음과 같습니다.
- 스와이프를 실행하려면 android:canPerformGestures를 true로 설정합니다.
- 창 콘텐츠를 가져오기 위해 android:canRetrieveWindowContent를 true로 설정합니다.
GlobalActionBarService.java
접근성 서비스의 코드는 대부분 GlobalActionBarService.java에 있습니다. 처음에는 파일에 접근성 서비스를 위한 최소한의 코드가 포함되어 있습니다.
- AccessibilityService를 확장하는 클래스입니다.
- 몇 가지 필수 재정의된 메서드 (이 Codelab에서는 비어 있음)
public class GlobalActionBarService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}
@Override
public void onInterrupt() {
}
}
Codelab을 진행하는 동안 이 파일에 코드를 추가합니다.
action_bar.xml
이 서비스는 버튼 4개가 있는 UI를 노출하고 action_bar.xml 레이아웃 파일에는 버튼을 표시하기 위한 마크업이 포함되어 있습니다.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
</LinearLayout>
현재 이 파일에는 빈 LinearLayout이 포함되어 있습니다. Codelab을 진행하는 동안 버튼에 관한 마크업을 추가합니다.
애플리케이션 시작
기기가 컴퓨터에 연결되어 있는지 확인합니다. 화면 상단의 메뉴 바에서 녹색의 재생 아이콘 을 누릅니다. 그러면 작업 중인 앱이 실행됩니다.
설정으로 이동합니다. 접근성. 전역 작업 모음 서비스가 기기에 설치되어 있습니다.
Global Action Bar Service를 클릭하고 사용 설정합니다. 다음 권한 대화상자가 표시됩니다.
접근성 서비스가 사용자 작업을 관찰하고 창 콘텐츠를 가져오며 사용자를 대신하여 동작을 실행할 권한을 요청합니다. 서드 파티 접근성 서비스를 사용할 때는 출처를 정말로 신뢰하는지 확인하세요.
아직 기능을 추가하지 않았으므로 서비스를 실행하는 것은 별다른 작업이 아닙니다. 시작해 봅시다.
4. 버튼 만들기
res/layout에서 action_bar.xml을 엽니다. 현재 비어 있는 LinearLayout 내에 마크업을 추가합니다.
<LinearLayout ...>
<Button
android:id="@+id/power"
android:text="@string/power"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/volume_up"
android:text="@string/volume"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/scroll"
android:text="@string/scroll"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:id="@+id/swipe"
android:text="@string/swipe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
이렇게 하면 사용자가 기기에서 작업을 트리거하기 위해 누르는 버튼이 생성됩니다.
GlobalActionBarService.java를 열고 작업 모음의 레이아웃을 저장할 변수를 추가합니다.
public class GlobalActionBarService extends AccessibilityService {
FrameLayout mLayout;
...
}
이제 onServiceStarted() 메서드를 추가합니다.
public class GlobalActionBarService extends AccessibilityService {
FrameLayout mLayout;
@Override
protected void onServiceConnected() {
// Create an overlay and display the action bar
WindowManager wm = (WindowManager) getSystemService(WINDOW_SERVICE);
mLayout = new FrameLayout(this);
WindowManager.LayoutParams lp = new WindowManager.LayoutParams();
lp.type = WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
lp.format = PixelFormat.TRANSLUCENT;
lp.flags |= WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
lp.width = WindowManager.LayoutParams.WRAP_CONTENT;
lp.height = WindowManager.LayoutParams.WRAP_CONTENT;
lp.gravity = Gravity.TOP;
LayoutInflater inflater = LayoutInflater.from(this);
inflater.inflate(R.layout.action_bar, mLayout);
wm.addView(mLayout, lp);
}
}
코드는 레이아웃을 확장하고 화면 상단에 작업 모음을 추가합니다.
onServiceConnected() 메서드는 서비스가 연결되면 실행됩니다. 현재 접근성 서비스는 작동하는 데 필요한 모든 권한을 가지고 있습니다. 여기서 사용할 주요 권한은 WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY 권한입니다. 이 권한을 사용하면 복잡한 권한 흐름을 거치지 않고 기존 콘텐츠 위에 직접 그릴 수 있습니다.
접근성 서비스 수명 주기
접근성 서비스의 수명 주기는 시스템에서 단독으로 관리하며 설정된 서비스 수명 주기를 따릅니다.
- 접근성 서비스는 사용자가 기기 설정에서 명시적으로 서비스를 사용 설정하면 시작됩니다.
- 시스템이 서비스에 바인딩된 후에는 onServiceConnected()를 호출합니다. 이 메서드는 결합 후 설정을 수행하려는 서비스에서 재정의할 수 있습니다.
- 접근성 서비스는 사용자가 기기 설정에서 사용 중지하거나 disableSelf()를 호출하면 중지됩니다.
서비스 실행
Android 스튜디오를 사용하여 서비스를 시작하려면 먼저 Run 설정이 올바르게 구성되어 있는지 확인해야 합니다.
실행 구성을 수정합니다 (상단 메뉴의 '실행'을 사용하고 '구성 수정'으로 이동). 그런 다음 드롭다운을 사용하여 '기본 활동'에서 실행 옵션을 변경합니다. 'Nothing'으로 변경합니다.
이제 Android 스튜디오를 사용하여 서비스를 시작할 수 있습니다.
화면 상단의 메뉴 바에서 녹색의 재생 아이콘 을 누릅니다. 그런 다음 설정 > 접근성을 클릭하고 전역 작업 모음 서비스를 사용 설정합니다.
화면에 표시된 콘텐츠 위에 서비스 UI를 구성하는 버튼 네 개가 오버레이되어 있어야 합니다.
이제 사용자가 버튼을 터치하여 유용한 작업을 실행할 수 있도록 4개의 버튼에 기능을 추가합니다.
5. 전원 버튼 구성
configurePowerButton() 메서드를 configurePowerButton()에 추가합니다.
private void configurePowerButton() {
Button powerButton = (Button) mLayout.findViewById(R.id.power);
powerButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
performGlobalAction(GLOBAL_ACTION_POWER_DIALOG);
}
});
}
전원 버튼 메뉴에 액세스하기 위해 configurePowerButton()은 AccessibilityService에서 제공하는 performGlobalAction() 메서드를 사용합니다. 방금 추가한 코드는 간단합니다. 버튼을 클릭하면 onClickListener()가 트리거됩니다. 그러면 performGlobalAction(GLOBAL_ACTION_POWER_DIALOG)을 호출하고 전원 대화상자를 사용자에게 표시합니다.
전체 작업은 보기와 연결되지 않습니다. 뒤로 버튼, 홈 버튼, 최근 버튼을 누르는 동작도 전역 작업의 다른 예입니다.
이제 configurePowerButton()을 onServiceConnected() 메서드 끝에 추가합니다.
@Override
protected void onServiceConnected() {
...
configurePowerButton();
}
화면 상단의 메뉴 바에서 녹색의 재생 아이콘 을 누릅니다. 그런 다음 설정 > 접근성을 클릭하고 전역 작업 모음 서비스를 시작합니다.
전원 버튼을 눌러 전원 대화상자를 표시합니다.
6. 볼륨 버튼 구성
configureVolumeButton() 메서드를 configureVolumeButton()에 추가합니다.
private void configureVolumeButton() {
Button volumeUpButton = (Button) mLayout.findViewById(R.id.volume_up);
volumeUpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AudioManager audioManager = (AudioManager) getSystemService(AUDIO_SERVICE);
audioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC,
AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI);
}
});
}
configureVolumeButton() 메서드는 사용자가 볼륨 버튼을 누를 때 트리거되는 onClickListener()를 추가합니다. 이 리스너 내에서 configureVolumeButton()은 AudioManager를 사용하여 스트림 볼륨을 조정합니다.
누구나 볼륨을 제어할 수 있습니다. 접근성 서비스가 아니어도 볼륨을 제어할 수 있습니다.
이제 configureVolumeButton()을 onServiceConnected() 메서드의 끝에 추가합니다.
@Override
protected void onServiceConnected() {
...
configureVolumeButton();
}
화면 상단의 메뉴 바에서 녹색의 재생 아이콘 을 누릅니다. 그런 다음 설정 > 접근성을 확인하고 전역 작업 모음 서비스를 시작합니다.
볼륨 버튼을 눌러 볼륨을 조절합니다.
기기 측면에 있는 볼륨 컨트롤에 연결할 수 없는 가상의 사용자는 이제 전역 작업 모음 서비스를 사용하여 볼륨을 변경 (증)할 수 있습니다.
7. 스크롤 버튼 구성
이 섹션에서는 두 가지 메서드를 코딩합니다. 첫 번째 메서드는 스크롤 가능한 노드를 찾고 두 번째 메서드는 사용자 대신 스크롤 작업을 실행합니다.
findScrollableNode에 findScrollableNode 메서드를 추가합니다.
private AccessibilityNodeInfo findScrollableNode(AccessibilityNodeInfo root) {
Deque<AccessibilityNodeInfo> deque = new ArrayDeque<>();
deque.add(root);
while (!deque.isEmpty()) {
AccessibilityNodeInfo node = deque.removeFirst();
if (node.getActionList().contains(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD)) {
return node;
}
for (int i = 0; i < node.getChildCount(); i++) {
deque.addLast(node.getChild(i));
}
}
return null;
}
접근성 서비스는 화면의 실제 뷰에 액세스할 수 없습니다. 대신 화면에 있는 내용이 AccessibilityNodeInfo 개체로 구성된 트리 형태로 반영된 것을 확인합니다. 이러한 객체에는 자신이 나타내는 뷰에 대한 정보 (뷰의 위치, 뷰와 연결된 텍스트, 접근성을 위해 추가된 메타데이터, 뷰에서 지원하는 작업 등)가 포함됩니다. findScrollableNode() 메서드는 루트 노드에서 시작하여 이 트리의 폭 첫 순회를 수행합니다. 스크롤 가능한 노드 (즉, AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD 작업)
를 지원하는 노드)를 찾으면 이를 반환하고 그렇지 않으면 null을 반환합니다.
이제 configureScrollButton() 메서드를 configureScrollButton()에 추가합니다.
private void configureScrollButton() {
Button scrollButton = (Button) mLayout.findViewById(R.id.scroll);
scrollButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
AccessibilityNodeInfo scrollable = findScrollableNode(getRootInActiveWindow());
if (scrollable != null) {
scrollable.performAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId());
}
}
});
}
이 메서드는 스크롤 버튼을 클릭하면 실행되는 onClickListener()를 만듭니다. 스크롤 가능한 노드를 찾으려고 시도하고 성공하면 스크롤 작업을 실행합니다.
이제 configureScrollButton()을 configureScrollButton()에 추가합니다.
@Override
protected void onServiceConnected() {
...
configureScrollButton();
}
화면 상단의 메뉴 바에서 녹색의 재생 아이콘 을 누릅니다. 그런 다음 설정 > 접근성을 확인하고 전역 작업 모음 서비스를 시작합니다.
뒤로 버튼을 눌러 설정 > 접근성. 접근성 설정 활동의 항목은 스크롤할 수 있으며 스크롤 버튼을 터치하면 스크롤 작업이 실행됩니다. 스크롤 작업을 쉽게 수행할 수 없는 가상의 사용자는 이제 스크롤 버튼을 사용하여 항목 목록을 스크롤할 수 있습니다.
8. 스와이프 버튼 구성
configureSwipeButton() 메서드를 configureSwipeButton()에 추가합니다.
private void configureSwipeButton() {
Button swipeButton = (Button) mLayout.findViewById(R.id.swipe);
swipeButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Path swipePath = new Path();
swipePath.moveTo(1000, 1000);
swipePath.lineTo(100, 1000);
GestureDescription.Builder gestureBuilder = new GestureDescription.Builder();
gestureBuilder.addStroke(new GestureDescription.StrokeDescription(swipePath, 0, 500));
dispatchGesture(gestureBuilder.build(), null, null);
}
});
}
configureSwipeButton() 메서드는 N에 추가된 새 API를 사용합니다. 이 API는 사용자 대신 동작을 실행합니다. 이 코드는 GestureDescription 객체를 사용하여 실행할 동작의 경로를 지정한 후 (이 Codelab에서는 하드코딩된 값을 사용함) AccessibilityService dispatchGesture() 메서드를 사용하여 사용자 대신 스와이프 동작을 전달합니다.
이제 configureSwipeButton()을 configureSwipeButton()에 추가합니다.
@Override
protected void onServiceConnected() {
...
configureSwipeButton();
}
화면 상단의 메뉴 바에서 녹색의 재생 아이콘 을 누릅니다. 그런 다음 설정 > 접근성을 확인하고 전역 작업 모음 서비스를 시작합니다.
스와이프 기능을 테스트하는 가장 쉬운 방법은 휴대전화에 설치된 지도 애플리케이션을 여는 것입니다. 지도가 로드되면 스와이프 버튼을 터치하면 화면이 오른쪽으로 스와이프됩니다.
9. 요약
축하합니다. 간단하고 기능적인 접근성 서비스를 빌드했습니다.
이 서비스는 다양한 방법으로 확장할 수 있습니다. 예를 들면 다음과 같습니다.
- 작업 표시줄을 이동 가능하게 만듭니다 (지금은 화면 상단에 위치함).
- 사용자가 볼륨을 높이거나 낮출 수 있도록 허용합니다.
- 사용자가 왼쪽과 오른쪽 모두로 스와이프할 수 있도록 허용합니다.
- 작업 모음이 응답할 수 있는 추가 동작 지원을 추가합니다.
이 Codelab에서는 접근성 API에서 제공하는 기능의 일부만 다룹니다. 이 API는 다음 항목도 다룹니다 (일부 목록).
- 여러 창 지원
- AccessibilityEvent를 지원합니다. UI가 변경되면 AccessibilityEvent 객체를 사용하여 접근성 서비스에 이러한 변경사항에 관한 알림을 받습니다. 그러면 서비스가 UI 변경사항에 적절하게 응답할 수 있습니다.
- 확대를 제어할 수 있습니다.
이 Codelab을 통해 접근성 서비스 작성을 시작할 수 있습니다. 해결하려는 특정 접근성 문제가 있는 사용자를 알고 있다면 이제 그 사용자를 지원하는 서비스를 빌드할 수 있습니다.