flutter_accessibility_service 1.2.0

SDKflutter
Platformandroid

Flutter plugin for interacting with Accessibility Service in Android.

flutter_accessibility_service

a plugin for interacting with Accessibility Service in Android.

Accessibility services are intended to assist users with disabilities in using Android devices and apps, or I can say to get android os events like keyboard key press events or notification received events etc.

for more info check Accessibility Service

Installation and usage

Add package to your pubspec:

dependencies:
  flutter_accessibility_service: any # or the latest version on Pub

Inside AndroidManifest add this to bind your accessibility service with your application

    .
    .
    <service android:name="slayer.accessibility.service.flutter_accessibility_service.AccessibilityListener"
                android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE" android:exported="false">
      <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
      </intent-filter>
      <meta-data android:name="android.accessibilityservice" android:resource="@xml/accessibilityservice" />
    </service>
    .
    .
</application>

Create Accesiblity config file named accessibilityservice.xml inside res/xml and add the following code inside it:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeWindowsChanged|typeWindowStateChanged|typeWindowContentChanged"
    android:accessibilityFeedbackType="feedbackVisual"
    android:notificationTimeout="300"
    android:accessibilityFlags="flagDefault|flagIncludeNotImportantViews|flagRequestTouchExplorationMode|flagRequestEnhancedWebAccessibility|flagReportViewIds|flagRetrieveInteractiveWindows"
    android:canRetrieveWindowContent="true"
    android:canPerformGestures="true"
>
</accessibility-service>

USAGE

 /// check if accessibility permission is enebaled
 final bool status = await FlutterAccessibilityService.isAccessibilityPermissionEnabled();

 /// request accessibility permission
 /// it will open the accessibility settings page and return `true` once the permission granted.
 final bool status = await FlutterAccessibilityService.requestAccessibilityPermission();

 /// stream the incoming Accessibility events
  FlutterAccessibilityService.accessStream.listen((event) {
    log("Current Event: $event");

  /*
  Current Event: AccessibilityEvent: (
     Action Type: 0
     Event Time: 2022-04-11 14:19:56.556834
     Package Name: com.facebook.katana
     Event Type: EventType.typeWindowContentChanged
     Captured Text: events you may like
     content Change Types: ContentChangeTypes.contentChangeTypeSubtree
     Movement Granularity: 0
     Is Active: true
     is focused: true
     in Pip: false
     window Type: WindowType.typeApplication
     Screen bounds: left: 0 - right: 720 - top: 0 - bottom: 1544 - width: 720 - height: 1544
)
  */

  });

The AccessibilityEvent provides:

  /// the performed action that triggered this event
  int? actionType;

  /// the time in which this event was sent.
  DateTime? eventTime;

  /// the package name of the source
  String? packageName;

  /// the event type.
  EventType? eventType;

  /// Gets the text of this node.
  String? capturedText;

  /// the bit mask of change types signaled by a `TYPE_WINDOW_CONTENT_CHANGED` event or `TYPE_WINDOW_STATE_CHANGED`. A single event may represent multiple change types
  ContentChangeTypes? contentChangeTypes;

  /// the movement granularity that was traversed
  int? movementGranularity;

  /// the type of the window
  WindowType? windowType;

  /// check if this window is active. An active window is the one the user is currently touching or the window has input focus and the user is not touching any window.
  bool? isActive;

  /// check if this window has input focus.
  bool? isFocused;

  /// Check if the window is in picture-in-picture mode.
  bool? isPip;

  /// Gets the node bounds in screen coordinates.
  ScreenBounds? screenBounds;

  /// Get the node childrens and sub childrens text
  List<String>? nodesText;

AUTOMATION & ACTIONS

Perform actions with Accessibility Service

  /// perform a click action
 final hasBeenClicked = await FlutterAccessibilityService.performAction(
              event.nodeId!,
              NodeAction.actionClick,
            );
          }
Enum ValueDescriptionArguments/Example
actionAccessibilityFocusAction that gives accessibility focus to the node.
actionClearAccessibilityFocusAction that clears accessibility focus of the node.
actionClearFocusAction that clears input focus of the node.
actionClearSelectionAction that deselects the node.
actionClickAction that clicks on the node.
actionCollapseAction to collapse an expandable node.
actionCopyAction to copy the current selection to the clipboard.
actionCutAction to cut the current selection and place it to the clipboard.
actionDismissAction to dismiss a dismissable node.
actionExpandAction to expand an expandable node.
actionFocusAction that gives input focus to the node.
actionLongClickAction that long clicks on the node.
actionNextAtMovementGranularityAction that requests to go to the next entity in this node's text at a given movement granularity. Pass an argument when you perform an action.boolean
actionNextHtmlElementAction to move to the next HTML element of a given type. Pass an argument when you perform an action.NodeAction.actionNextHtmlElement with argument "BUTTON"
actionPasteAction to paste the current clipboard content.
actionPreviousAtMovementGranularityAction that requests to go to the previous entity in this node's text at a given movement granularity. Pass an argument when you perform an action.NodeAction.actionPreviousAtMovementGranularity with argument false
actionPreviousHtmlElementAction to move to the previous HTML element of a given type. Pass an argument when you perform an action.NodeAction.actionPreviousHtmlElement with argument "BUTTON"
actionScrollBackwardAction to scroll the node content backward.
actionScrollForwardAction to scroll the node content forward.
actionSelectAction that selects the node.
actionSetSelectionAction to set the selection. Performing this action with no arguments clears the selection. Pass an argument when you perform an action.NodeAction.actionSetSelection with argument {"start": 1, "end": 2}
actionSetTextAction that sets the text of the node. Performing the action without argument, using null or empty CharSequence will clear the text. This action will also put the cursor at the end of text. Pass an argument when you perform an action.NodeAction.actionSetText with argument "Flutter"
focusAccessibilityThe accessibility focus.
focusInputThe input focus.
movementGranularityCharacterMovement granularity bit for traversing the text of a node by character.
movementGranularityLineMovement granularity bit for traversing the text of a node by line.
movementGranularityPageMovement granularity bit for traversing the text of a node by page.
movementGranularityParagraphMovement granularity bit for traversing the text of a node by paragraph.
movementGranularityWordMovement granularity bit for traversing the text of a node by word.
unknownUnknown action.

For more details about the action check here

Accessibility Overlay

This will help to cover the window with an overlay by an accessibility service

Inside main.dart creates an entry point for your Accessibility Overlay widget;

@pragma("vm:entry-point")
void accessibilityOverlay() {
  runApp(const MaterialApp(
    debugShowCheckedModeBanner: false,
    home: Material(child: Text("My Accessibility Overlay"))
  ));
}

Usage

/// Show overlay
 await FlutterAccessibilityService.showOverlayWindow();

/// hide overlay
 await FlutterAccessibilityService.hideOverlayWindow();

Perform Global Actions

Such an action can be performed at any moment regardless of the current application or user location in that application For example going back, going home, opening recents, etc.

  await FlutterAccessibilityService.performGlobalAction(
    GlobalAction.globalActionTakeScreenshot,
  );

Returns a list of system actions available in the system right now.

  final list = await FlutterAccessibilityService.getSystemActions();
  print(list); // [GlobalAction.globalActionAccessibilityAllApps,GlobalAction.globalActionTakeScreenshot .....]

Dispatch Gestures

Programmatically inject touch gestures (tap, swipe, double-tap, etc.) on the screen through the accessibility service.

Requires Android 7.0 (API 24) or higher. Returns true when the gesture completes, false if it was cancelled or the service is not running.

A gesture is made up of one or more GestureStrokes. Each stroke has:

ParameterTypeDescription
pathList<GesturePoint>Ordered screen coordinates (pixels) for the stroke
startTimeint (ms)Delay after gesture start before this stroke fires
durationint (ms)How long the stroke lasts

Tap

await FlutterAccessibilityService.dispatchGesture(
  const GestureDescription(
    strokes: [
      GestureStroke(
        path: [GesturePoint(500, 1000)],
        startTime: 0,
        duration: 100,
      ),
    ],
  ),
);

Swipe up

await FlutterAccessibilityService.dispatchGesture(
  const GestureDescription(
    strokes: [
      GestureStroke(
        path: [
          GesturePoint(500, 1500), // start
          GesturePoint(500, 300),  // end
        ],
        startTime: 0,
        duration: 400,
      ),
    ],
  ),
);

Double-tap (two strokes at the same point, 150 ms apart)

await FlutterAccessibilityService.dispatchGesture(
  const GestureDescription(
    strokes: [
      GestureStroke(
        path: [GesturePoint(500, 1000)],
        startTime: 0,
        duration: 100,
      ),
      GestureStroke(
        path: [GesturePoint(500, 1000)],
        startTime: 150,
        duration: 100,
      ),
    ],
  ),
);

Pinch-to-zoom (two simultaneous strokes moving in opposite directions)

await FlutterAccessibilityService.dispatchGesture(
  const GestureDescription(
    strokes: [
      // Finger 1: moves outward from centre-left
      GestureStroke(
        path: [GesturePoint(400, 1000), GesturePoint(100, 1000)],
        startTime: 0,
        duration: 300,
      ),
      // Finger 2: moves outward from centre-right
      GestureStroke(
        path: [GesturePoint(600, 1000), GesturePoint(900, 1000)],
        startTime: 0,
        duration: 300,
      ),
    ],
  ),
);