0

permission prompt image

I am trying to use Accessibility to click "Allow in settings" using my application's Accessibility Service. I have looked at the AccessibilityNode, and I do not see anything that's interactable in the TextView. Here is the output from the node:

Event Type: TYPE_WINDOW_CONTENT_CHANGED com.google.android.permissioncontroller android.widget.FrameLayout
 Source: 
0 | class name: android.widget.FrameLayout text: null content description: null input type 0 actions: ACTION_SELECT, ACTION_CLEAR_SELECTION, ACTION_ACCESSIBILITY_FOCUS, ACTION_SHOW_ON_SCREEN  
1 | class name: android.widget.ScrollView text: null content description: null input type 0 actions: ACTION_FOCUS, ACTION_SELECT, ACTION_CLEAR_SELECTION, ACTION_ACCESSIBILITY_FOCUS, ACTION_SHOW_ON_SCREEN  
2 | class name: android.widget.TextView text: Change location access for AppName? content description: null input type 0 actions: ACTION_SELECT, ACTION_CLEAR_SELECTION, ACTION_ACCESSIBILITY_FOCUS, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, ACTION_SET_SELECTION, ACTION_SHOW_ON_SCREEN  
2 | class name: android.widget.TextView text: This app wants to access your location all the time, even when you’re not using the app. Allow in settings. content description: null input type 0 actions: ACTION_FOCUS, ACTION_SELECT, ACTION_CLEAR_SELECTION, ACTION_ACCESSIBILITY_FOCUS, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, ACTION_SET_SELECTION, ACTION_SHOW_ON_SCREEN  
2 | class name: android.widget.Button text: Keep “While the app is in use” content description: null input type 0 actions: ACTION_FOCUS, ACTION_SELECT, ACTION_CLEAR_SELECTION, ACTION_CLICK, ACTION_ACCESSIBILITY_FOCUS, ACTION_NEXT_AT_MOVEMENT_GRANULARITY, ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY, ACTION_SET_SELECTION, ACTION_SHOW_ON_SCREEN 

There are only a handful of available actions for the TextView. I have attempted the available actions to the best of my knowledge with no success.

I have also explored the idea of a direct Intent to the "Allow in Settings" section but at the moment there are none. Our app requires location to be on at all times.

Stephen
  • 29
  • 3

2 Answers2

1

So what I ended up doing was writing a static function for getting spans from a CharSequence

@NonNull
ClickableSpan[] getClickableSpans(CharSequence text) {
    try {
      if (text instanceof Spanned) {
        Spanned spanned = (Spanned) text;
        return spanned.getSpans(0, text.length(), ClickableSpan.class);
      }
    } catch (Exception e) {
    //log exception
    }
    return new ClickableSpan[0];
}

and to use it

public void onAccessibilityEvent(AccessibilityEvent) {
    AccessibilityNodeInfo nodeInfo = event.getSource();
    if (nodeInfo != null) {
        List<AccessibilityNodeInfo> nodeInfoList = nodeInfo.findAccessibilityNodeInfosByText(
          "This app wants to access your location all the time, even when you're not using the app. Allow in settings.");
        if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
            ClickableSpan[] spans = getClickableSpans(nodeInfoList.get(0).getText());
            if (spans.length > 0) {
                spans[0].onClick(null);
            }
        }
    }
}
Stephen
  • 29
  • 3
  • Good job figuring this out! You can find in the platform source code that a hidden class was added called `AccessibilityClickableSpan` that implements `ClickableSpan` and you can see that its implementation of `onClick` does indeed ignore the `View` parameter, as it would seemingly have to in this case. So while I still don't see any documentation of this, it seems like an implementation detail that can't reasonably change in the future. A `ClickableSpan` from an `AccessibilityNodeInfo` should continue to support passing null. Of course, a try/catch block never hurt anybody... – j__m Aug 20 '21 at 04:51
0

ClickableSpans are passed through to accessibility starting in Android O. You can find the ClickableSpans in the text and call onClick. The system will then make sure the original span's onClick method is called in the app process.

This is back-ported if you use AccessibilityNodeInfoCompat, but the app must also have called ViewCompat#enableAccessibleClickableSpanSupport(view).

Phil Weaver
  • 738
  • 3
  • 7
  • I looked into ClickableSpans a while ago while I was trying to get it to work. I'm just not really sure how they work. Can you give an example? I can't get the view object from another process so I'm not sure how I would call ViewCompat#enableAccessibleClickableSpanSupport(view). These are system dialogs that are shown when requesting location permission. @Phil Weaver – Stephen Jun 09 '20 at 15:29
  • The ViewCompat method needs to be called on the app side. As you say there's no way to get another process to run code to improve its accessibility reporting. – Phil Weaver Jun 09 '20 at 16:31
  • ViewCompat#enableAccessibleClickableSpanSupport(view) requires a view. What view do i pass to it? Or are you saying the system needs to have enabled clickable span support on that dialog for me to do anything related to this? @Phil Weaver – Stephen Jun 10 '20 at 13:48
  • Yes. Pre-O, whoever owns that view would need to have enabled the clickable span support. – Phil Weaver Jun 10 '20 at 16:53
  • Okay so how do I go about finding the ClickableSpan in the text? assuming Android 11 devs have called ViewCompat#enableAccessibleClickableSpanSupport(view) on this dialog. This dialog only exists in Android R(11). I only need to implement this for this case in my app. @Phil Weaver – Stephen Jun 10 '20 at 17:10
  • Since Android 11 is after O, the clickable spans should be there by default. You can find them with Spannable#getSpans. – Phil Weaver Jun 11 '20 at 18:35
  • Alright, after doing some digging I figured out how to get this to work. You were right about the ClickableSpan object but documentation on how to actually get them to work is sparse/non-existent for this instance. I'll post my solution in an answer. – Stephen Jun 11 '20 at 19:40