4

This question asked how to know if Android Talkback is active; that worked until Jelly Bean. Starting from Android 4.1, that steps no longer work, because the mentioned cursor is empty.

Having this said, I want to ask is if there is a way to do the same checking in Jelly Bean.

EDIT I tried to search for TalkBack code and I found it here. For checking if TalkBack is active, I am using the following code:

Intent screenReaderIntent = new Intent("android.accessibilityservice.AccessibilityService");
screenReaderIntent.addCategory("android.accessibilityservice.category.FEEDBACK_SPOKEN");
List<ResolveInfo> screenReaders = getPackageManager().queryIntentServices(screenReaderIntent, 0);
Cursor cursor = null;
ContentResolver cr = getContentResolver();
for (ResolveInfo screenReader : screenReaders) {
    cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName
            + ".providers.StatusProvider"), null, null, null, null);
    //here, cursor is not null, but calling cursor.moveToFirst() returns false, which means the cursor is empty
}

Having this said, if the cursor is empty, how do we know if TalkBack is running?

EDIT 2 Following @JoxTraex suggestions, I am now sending a broadcast to query whether or not TalkBack is enabled:

Intent i = new Intent();
i.setAction("com.google.android.marvin.talkback.ACTION_QUERY_TALKBACK_ENABLED_COMMAND");
sendBroadcast(i);

Now how should I receive the response?

I tried adding the following to manifest, but my receiver does not receive any response:

<receiver android:name="my.package.MyBroadcastReceiver"
android:permission="com.google.android.marvin.talkback.PERMISSION_SEND_INTENT_BROADCAST_COMMANDS_TO_TALKBACK">
<intent-filter>
    <action android:name="com.google.android.marvin.talkback.ACTION_QUERY_TALKBACK_ENABLED_COMMAND" />
</intent-filter>

Jonik
  • 80,077
  • 70
  • 264
  • 372
pandre
  • 6,685
  • 7
  • 42
  • 50
  • My suggestion would be to navigate the AOSP source code and see what APIs are used for TalkBack. If the source code for TalkBack is available. – JoxTraex Aug 06 '12 at 15:55
  • @JoxTraex thanks for the reply. I edited the post with new information, do you have any suggestion? – pandre Aug 06 '12 at 17:20

4 Answers4

21

This can be achieved much easier by using AccessibilityManager.

AccessibilityManager am = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
boolean isAccessibilityEnabled = am.isEnabled();
boolean isExploreByTouchEnabled = am.isTouchExplorationEnabled();
caseyburkhardt
  • 1,145
  • 7
  • 9
1

You can check the enabled spoken accessibility servers

fun Context.isScreenReaderEnabled(): Boolean {
    val accessibilityManager = getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager

    if (!accessibilityManager.isEnabled)
        return false

    val serviceInfoList = accessibilityManager.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN)

    if (serviceInfoList.isNullOrEmpty())
        return false

    return true
}
Diego Malone
  • 1,044
  • 9
  • 25
0

While looking at TalkBackService.java, I found these code segments. These segments should provide some insight on how to query the status.

Code

/**
     * {@link Intent} broadcast action for querying the state of TalkBack. </p>
     * Note: Sending intent broadcast commands to TalkBack must be performed
     * through {@link Context#sendBroadcast(Intent, String)}
     */
    @Deprecated
    // TODO(caseyburkhardt): Remove when we decide to no longer support intent broadcasts for
    // querying the current state of TalkBack.
    public static final String ACTION_QUERY_TALKBACK_ENABLED_COMMAND = "com.google.android.marvin.talkback.ACTION_QUERY_TALKBACK_ENABLED_COMMAND";

    /**
     * Result that TalkBack is enabled.
     *
     * @see #ACTION_QUERY_TALKBACK_ENABLED_COMMAND
     */
    public static final int RESULT_TALKBACK_ENABLED = 0x00000001;

    /**
     * Result that TalkBack is disabled.
     *
     * @see #ACTION_QUERY_TALKBACK_ENABLED_COMMAND
     */
    public static final int RESULT_TALKBACK_DISABLED = 0x00000002;

    /**
     * Permission to send {@link Intent} broadcast commands to TalkBack.
     */
    public static final String PERMISSION_SEND_INTENT_BROADCAST_COMMANDS_TO_TALKBACK = "com.google.android.marvin.talkback.PERMISSION_SEND_INTENT_BROADCAST_COMMANDS_TO_TALKBACK";

    /**
     * Tag for logging.
     */
    private static final String LOG_TAG = "TalkBackService";

public static final String ACTION_QUERY_TALKBACK_ENABLED_COMMAND = "com.google.android.marvin.talkback.ACTION_QUERY_TALKBACK_ENABLED_COMMAND";
..

            } else if (ACTION_QUERY_TALKBACK_ENABLED_COMMAND.equals(intentAction)) {
            // TODO(caseyburkhardt): Remove this block when we decide to no
            // longer support
            // intent broadcasts for determining the state of TalkBack in
            // favor of the content
            // provider method.
            if (sInfrastructureInitialized) {
                setResultCode(RESULT_TALKBACK_ENABLED);
            } else {
                setResultCode(RESULT_TALKBACK_DISABLED);
            }
        }
            ...
    }

Explanation

You must send an Intent broadcast to the TalkBackService using the action of:

public static final String ACTION_QUERY_TALKBACK_ENABLED_COMMAND = "com.google.android.marvin.talkback.ACTION_QUERY_TALKBACK_ENABLED_COMMAND";

Then examine the contents of the Extras and process it accordingly.

ALSO insure that you have the right permission:

public static final String PERMISSION_SEND_INTENT_BROADCAST_COMMANDS_TO_TALKBACK = "com.google.android.marvin.talkback.PERMISSION_SEND_INTENT_BROADCAST_COMMANDS_TO_TALKBACK";
JoxTraex
  • 13,423
  • 6
  • 32
  • 45
  • So I guess I should use `Intent i = new Intent(); i.setAction(ACTION_QUERY_TALKBACK_ENABLED_COMMAND); sendBroadcast(i);` to send the broadcast. But how to receive the answer? Should I declare a receiver in app manifest and it will receive the answer? – pandre Aug 06 '12 at 17:54
  • Hi again @JoxTraex! I am trying to figure out how to receive the response from the broadcast but so far I was unsuccessful (I didn't receive any answer in my `BroadcastReceiver`). Can you explain with more detail how to receive the response from talkback? – pandre Aug 07 '12 at 09:31
  • upvote for the suggestions, but I posted the solution I found – pandre Aug 07 '12 at 17:49
  • Hey look... There I am in a TODO! :) If you've just stumbled on this page, please avoid this approach, as there are no guarantees this behavior is supported by a screen reader. Instead, use `AccessibilityManager` to query the screen reader state, if necessary. – caseyburkhardt Nov 05 '15 at 04:34
0

I'm not sure this is the best way of achieving what is proposed, but I managed to make this work by using the following code:

Intent screenReaderIntent = new Intent("android.accessibilityservice.AccessibilityService");
screenReaderIntent.addCategory("android.accessibilityservice.category.FEEDBACK_SPOKEN");
List<ResolveInfo> screenReaders = getPackageManager().queryIntentServices(screenReaderIntent, 0);
Cursor cursor = null;
int status = 0;
ContentResolver cr = getContentResolver();

List<String> runningServices = new ArrayList<String>();
ActivityManager manager = (ActivityManager) getSystemService(ACTIVITY_SERVICE);
for (RunningServiceInfo service : manager.getRunningServices(Integer.MAX_VALUE)) {
    runningServices.add(service.service.getPackageName());
}

for (ResolveInfo screenReader : screenReaders) {
    cursor = cr.query(Uri.parse("content://" + screenReader.serviceInfo.packageName
            + ".providers.StatusProvider"), null, null, null, null);

    if (cursor != null && cursor.moveToFirst()) { //this part works for Android <4.1
        status = cursor.getInt(0);
        cursor.close();
        if (status == 1) {
            //screen reader active!
        } else {
            //screen reader inactive
        }
    } else {  //this part works for Android 4.1+
        if (runningServices.contains(screenReader.serviceInfo.packageName)) {
            //screen reader active!
        } else {
            //screen reader inactive
        }
    }
}

Probably this is not the best way but it is the only one I can think of that works in Jelly Bean and in previous Android versions

pandre
  • 6,685
  • 7
  • 42
  • 50