Android Performance

Android Service: Building Your Own Notification Center (1) - Introduction to ...

Word count: 1.3kReading time: 7 min
2014/03/17
loading

1. Introduction to Accessibility Service

Accessibility services are a feature of the Android framework designed to provide alternative navigation feedback to users on behalf of applications installed on Android devices. An accessibility service can communicate information about the application to the user, such as text-to-speech, or haptic feedback when the user’s finger hovers over an important area of the screen.

This section covers how to create an accessibility service, how to handle information received from applications, and how to provide feedback to the user.

2. Creating Your Own Accessibility Service

2.1 Inheriting from AccessibilityService

An accessibility service can be bundled with a standard application or created as a standalone Android project. In either case, the steps to create such a service are the same. In your project, create a class that extends AccessibilityService.

1
2
3
4
5
6
7
8
9
10
11
12
import android.accessibilityservice.AccessibilityService;
import android.view.accessibility.AccessibilityEvent;

public class MyAccessibilityService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
}

@Override
public void onInterrupt() {
}
}

2.2 Declaring the Service in Manifest

Like any other service, you must declare it in the manifest file. Remember to specify that it handles the android.accessibilityservice.AccessibilityService intent, so that the service is called when an application triggers an AccessibilityEvent.

1
2
3
4
5
6
7
8
9
10
<application>
...
<service android:name=".MyAccessibilityService">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
. . .
</service>
...
</application>

2.3 Configuring the Service

If you are creating a new project for this service and do not intend to have an application component, you can remove the Activity class (usually MainActivity.java) from your source files and the corresponding <activity> element from your manifest file.

Configuring Your Accessibility Service

You must provide configuration parameters for your accessibility service to tell the system how and when you want it to run. Which event types do you want to respond to? Is the service active for all applications or only specific package names? What kind of feedback does it use?

You have two ways to set these variables. The backward-compatible method is to set them in code using setServiceInfo(android.accessibilityservice.AccessibilityServiceInfo). To do this, override the onServiceConnected() method and configure your service there.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import android.accessibilityservice.AccessibilityServiceInfo;
import android.view.accessibility.AccessibilityEvent;

@Override
protected void onServiceConnected() {
AccessibilityServiceInfo info = new AccessibilityServiceInfo();
info.eventTypes = AccessibilityEvent.TYPE_VIEW_CLICKED |
AccessibilityEvent.TYPE_VIEW_FOCUSED;
info.packageNames = new String[]
{"com.example.android.myFirstApp", "com.example.android.mySecondApp"};
info.feedbackType = AccessibilityServiceInfo.FEEDBACK_SPOKEN;
info.flags = AccessibilityServiceInfo.DEFAULT;
info.notificationTimeout = 100;
this.setServiceInfo(info);
}

Since Android 4.0, there is another way: verify the service using an XML file. If you define your service via XML, certain configurable options like canRetrieveWindowContent become available. The same configuration as above, defined in XML, would look like this:

1
2
3
4
5
6
7
8
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeViewClicked|typeViewFocused"
android:packageNames="com.example.android.myFirstApp, com.example.android.mySecondApp"
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:settingsActivity="com.example.android.apis.accessibility.TestBackActivity"
android:canRetrieveWindowContent="true"
/>

If using the XML path, you must specify it in your manifest file by adding a <meta-data> tag to your service declaration pointing to the XML resource file. Assuming you stored your XML file at res/xml/serviceconfig.xml, the new tag would look like this:

1
2
3
4
5
6
7
<service android:name=".MyAccessibilityService">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data android:name="android.accessibilityservice"
android:resource="@xml/serviceconfig" />
</service>

2.4 Responding to AccessibilityEvents

Now that your service is set up to run and listen for events, write some code so it knows what to do when an AccessibilityEvent actually arrives!

Start by overriding the onAccessibilityEvent(AccessibilityEvent) method. use getEventType() to determine the event type, and getContentDescription() to extract any label text associated with the event.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import android.view.accessibility.AccessibilityNodeInfo;

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
final int eventType = event.getEventType();
String eventText = null;
switch(eventType) {
case AccessibilityEvent.TYPE_VIEW_CLICKED:
eventText = "Clicked: ";
break;
case AccessibilityEvent.TYPE_VIEW_FOCUSED:
eventText = "Focused: ";
break;
default:
eventText = "Unknown Event: ";
break;
}

eventText = eventText + event.getContentDescription();

// Do something nifty with this text, like speak the composed string
// back to the user.
// speakToUser(eventText); // Assuming speakToUser is defined elsewhere
// ...
}

Querying the View Hierarchy for More Context

This step is optional but very useful. A feature introduced in Android 4.0 (API level 14) allows an AccessibilityService to query the view hierarchy to collect information about the UI component that generated the event, as well as its parent and children. To do this, make sure you have set the following in your XML configuration:

1
android:canRetrieveWindowContent="true"

If set, you can obtain an AccessibilityNodeInfo object via getSource(). If the window where the event originated is still the active window, this call returns an object; otherwise, it returns null. The following code demonstrates how to receive an event and perform the following steps:

  1. Immediately capture the parent of the view that triggered the event.
  2. In that view, look for a child view that is a label and a checkbox.
  3. If found, create a string to report to the user indicating whether the item is checked.
  4. If traversal of the view hierarchy returns null at any point, exit the method.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
// Alternative onAccessibilityEvent, that uses AccessibilityNodeInfo

@Override
public void onAccessibilityEvent(AccessibilityEvent event) {

AccessibilityNodeInfo source = event.getSource();
if (source == null) {
return;
}

// Grab the parent of the view that fired the event.
// Assuming getListItemNodeInfo is a helper method to find the list item parent
AccessibilityNodeInfo rowNode = getListItemNodeInfo(source);
if (rowNode == null) {
return;
}

// Using this parent, get references to both child nodes, the label and the checkbox.
AccessibilityNodeInfo labelNode = rowNode.getChild(0);
if (labelNode == null) {
rowNode.recycle();
return;
}

AccessibilityNodeInfo completeNode = rowNode.getChild(1);
if (completeNode == null) {
rowNode.recycle();
return;
}

// Determine what the task is and whether or not it's complete, based on
// the text inside the label, and the state of the check-box.
if (rowNode.getChildCount() < 2 || !rowNode.getChild(1).isCheckable()) {
rowNode.recycle();
return;
}

CharSequence taskLabel = labelNode.getText();
final boolean isComplete = completeNode.isChecked();
String completeStr = null;

// Assuming getString(R.string.checked) and getString(R.string.not_checked) are defined
if (isComplete) {
// completeStr = getString(R.string.checked);
completeStr = " (checked)";
} else {
// completeStr = getString(R.string.not_checked);
completeStr = " (not checked)";
}
String reportStr = taskLabel + completeStr;
// speakToUser(reportStr); // Assuming speakToUser is defined elsewhere
rowNode.recycle(); // Recycle the node info to avoid memory leaks
}

Now you have a complete, working accessibility service. You can also try configuring how it interacts with the user using Android’s text-to-speech engine or using the Vibrator for haptic feedback.

Finally, to use the configured service, you must go to “Settings -> Accessibility” and enable the corresponding service for it to respond to events.

About Me && Blog

Below is my personal intro and related links. I look forward to exchanging ideas with fellow professionals. “When three walk together, one can always be my teacher!”

  1. Blogger Intro
  2. Blog Content Navigation: A guide for my blog content.
  3. Curated Excellent Blog Articles - Android Performance Optimization Must-Knows
  4. Android Performance Optimization Knowledge Planet

One walks faster alone, but a group walks further together.

Scan WeChat QR Code

CATALOG
  1. 1. 1. Introduction to Accessibility Service
  2. 2. 2. Creating Your Own Accessibility Service
    1. 2.1. 2.1 Inheriting from AccessibilityService
    2. 2.2. 2.2 Declaring the Service in Manifest
    3. 2.3. 2.3 Configuring the Service
    4. 2.4. 2.4 Responding to AccessibilityEvents
  3. 3. About Me && Blog