Virtual navigation based on Accessibility Service

Virtual navigation based on Accessibility Service

.

brief introduction

Accessibility Service (Accessibility Service) is designed to provide disabled users who cannot interact with the interface to assist and help them with interactive operations, such as clicking, returning, long pressing and obtaining screen information content.

But now accessibility service has basically deviated from its original design intention, at least in China. More and more apps use accessibility service to realize some other functions, even gray products.

Because the AccessibilityService service will run in the background and receive the system callback when AccessibilityEvents are triggered. Such events usually indicate some state transitions in the user interface, for example, the focus has changed, the button has been clicked, etc. Optionally, you can request to query the contents of the active window.

.

.

Introduction to common methods of Accessibility Service

1. disableSelf() method

Function: disable the current service, that is, close the current service

2. Dispatchgeture (gesture, callback, handler) method

Function: send gestures to the touch screen

3. findFocus(int focus) method

Purpose: find a view with the specified focus type

4. getRootInActiveWindow() method

Function: get the root node in the current active window

5. getServiceInfo() method

Function: get the configuration information of the current service

6. getSystemService(String name) method

Function: obtain system services

7. getWindows() method

Function: get the window on the screen

8. performGlobalAction(int action) method

Function: perform global operations, such as returning, returning to the home page, opening the latest, etc

9. Setserviceinfo (accessibilityserviceinfo) method

Function: set the configuration information of the current service

10. getAccessibilityButtonController() method

Function: return to the controller of auxiliary function buttons in the navigation area of the system

11. getFingerprintGestureController() method

Function: controller for obtaining fingerprint gesture

12. Getmagiconcontroller() method

Function: return to the amplifier controller, which can be used to query and modify the status of display magnification

13. getSoftKeyboardController() method

Function: return to the soft keyboard controller, which can be used to query and modify the soft keyboard display mode

.

.

Implementation of virtual navigation

1. Create AccessibilitySetting class and inherit AccessibilityService

Note: I don't do any logic code processing here. I just want to use the AccessibilityService service object to call its methods through this object. A notification will be enabled by default when the service is started. The purpose is to make AccessibilitySetting survive longer in the background.

public class AccessibilitySetting extends AccessibilityService {
    private static AccessibilityService service;

    public static AccessibilityService getService() {
        if (service == null) {
            return null;
        }
        return service;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        //Create foreground notification when creating service
        Notification notification = createForegroundNotification(getResources().getString(R.string.app_name),"App Running",R.mipmap.ic_launcher);
        //Start front desk service
        startForeground(1,notification);
    }

    //When the service is destroyed
    @Override
    public void onDestroy(){
        //Turn off the foreground service when the service is destroyed
        stopForeground(true);
        super.onDestroy();
    }

    public AccessibilitySetting() {
        service = this;
    }

    /**
     * It is called when the service is started
     */
    @Override
    protected void onServiceConnected() {
        super.onServiceConnected();
    }

    /**
     * Callback for monitoring window changes
     */
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
    }

    /**
     * Callback for service interruption
     */
    @Override
    public void onInterrupt() {

    }

    //Create a foreground notification, which can be written as a method body or a separate class
    public Notification createForegroundNotification(String title, String content, int smallIcon){
        //id name of foreground notification, any
        String channelId = "AccessibilityService";
        //The level of sending notification, which is high here, depends on the business situation
        int importance = NotificationManager.IMPORTANCE_HIGH;
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.O){
            NotificationChannel channel = new NotificationChannel("AccessibilityService","AccessibilityService",importance);
            channel.setLightColor(Color.BLUE);
            channel.setLockscreenVisibility(Notification.VISIBILITY_PRIVATE);
            NotificationManager notificationManager = (NotificationManager)getSystemService(Context.NOTIFICATION_SERVICE);
            notificationManager.createNotificationChannel(channel);
        }

        //The Activity that can be entered when you click notification
        Intent notificationIntent = new Intent(this, MainActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this,0,notificationIntent,0);

        return new NotificationCompat.Builder(this,channelId)
                .setContentTitle(title)
                .setContentText(content)
                .setSmallIcon(smallIcon)//Icon displayed for notification
                .setContentIntent(pendingIntent)//Click notification to enter Activity
                .build();
    }  
}

.

2. Create an xml directory under res directory and create accessibilityservice xml file

Function: configure accessibilityservice in two ways: one is to dynamically set in the code, and the other is to write a configuration file. Here, the second method is used to configure accessibilityservice.

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagRequestFilterKeyEvents"
    android:canRetrieveWindowContent="true"
    android:canRequestFilterKeyEvents="true"
    android:description="Function description prompt" />

Specific meaning of configuration content:

  • accessibilityEventTypes: indicates the event types (accessibilityevents) that this service wants to receive as specified

Optional parameters:

  • typeViewClicked: indicates to listen to click only events
  • typeViewSelected: indicates that only view is selected for listening
  • typeViewScrolled: indicates that only sliding events are monitored
  • typeWindowContentChanged: indicates that only the window content table is monitored
  • typeAllMask: indicates all events
  • accessibilityFeedbackType: indicates the feedback type provided by this service

Optional parameters:

  • feedbackSpoken: indicates voice feedback
  • feedbackHaptic: indicates tactile (vibration) feedback
  • feedbackAudible: indicates audio feedback
  • feedbackVisual: indicates video feedback
  • feedbackGeneric: indicates general feedback
  • feedbackAllMask: indicates that all of the above have
  • canRetrieveWindowContent: indicates whether the service can access the content in the active window. That is, if you want to get the form content in the service, you need to set its value to true
  • Description: description of the barrier free function.
  • notificationTimeout: the time interval for accepting events, which is usually set to 100
  • packageNames: indicates the event of which packet the service is used to listen for. Take the packet name of wechat as an example
  • canPerformGestures: indicates whether gesture distribution is allowed
  • canRequestFilterKeyEvents: the ancillary service wants to request filtering of key events.
  • Noninteractiveuittimeout: timeout setting. No interactive space is included. An appropriate value is returned

.

3. On Android manifest Registering services in XML

explain:

  • android:permission: bind needs to be specified_ ACCESSIBILITY_ Service permission, which is required by systems above 4.0
  • Intent filter: the name is fixed
<service
    android:name=".Services.AccessibilitySetting"
    android:enabled="true"
    android:exported="true"
    android:foregroundServiceType="location"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>

    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibilityservice" />
</service>

Supplement:
I set this service as the foreground service, that is, android:foregroundServiceType="location". You need to add the corresponding permission < uses permission Android: name = "Android. Permission. Foreground_service" / >.

.

4. Judge whether barrier free permission has been obtained. If not, jump to the authorization page

/**
 * Judge whether there is barrier free permission
 */
public boolean checkAccessPermission() {
    if (!isAccessibilityServiceRunning("AccessibilitySetting")) {
        DialogUtils.createDialog(mActivity,
                R.drawable.dialog_icon_warning,
                mActivity.getResources().getString(R.string.dialog_title),
                mActivity.getResources().getString(R.string.dialog_message),
                mActivity.getResources().getString(R.string.dialog_confirm_button),
                (dialog, which) -> jump2AccessPermissionSetting(mActivity, false),
                mActivity.getResources().getString(R.string.dialog_cancel_button), (dialog, which) -> Toast.makeText(mActivity, mActivity.getResources().getString(R.string.dialog_cancel_message), Toast.LENGTH_SHORT).show()
        ).show();
        return false;

    } else {
        return true;
    }
}

/**
 * Determine whether there is top accessible service
 *
 * @param name
 * @return
 */
private boolean isAccessibilityServiceRunning(String name) {
    AccessibilityManager am = (AccessibilityManager) mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE);
    List<AccessibilityServiceInfo> enableServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
    for (AccessibilityServiceInfo enableService : enableServices) {
        if (enableService.getId().endsWith(name)) {
            return true;
        }
    }
    return false;
}

/**
 * Jump to auxiliary function setting interface
 *
 * @param activity
 */
private static final String INTENT_ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
private static final int REQUEST_PERMISS_REQUEST_ACCESSABLE = 300;//Auxiliary function Key
private void jump2AccessPermissionSetting(Activity activity, boolean forResult) {
    if (forResult) {
        activity.startActivityForResult(new Intent(INTENT_ACTION_ACCESSIBILITY_SETTINGS), REQUEST_PERMISS_REQUEST_ACCESSABLE);
    } else {
        activity.startActivity(new Intent(INTENT_ACTION_ACCESSIBILITY_SETTINGS));
    }
}

.

5. Realize the return key, Home key, task key and other functions of virtual navigation

//Simulate global key
public void openAction(int action) {
    if (checkAccessPermission()) {
        if (Build.VERSION.SDK_INT < 16) {
            Toast.makeText(AccessibilitySetting.getService(), "Android 4.1 And above systems only support this function. Please upgrade and try again", Toast.LENGTH_SHORT).show();
        } else {
            if (AccessibilitySetting.getService() != null) {
                AccessibilitySetting.getService().performGlobalAction(action);
            }
        }
    }
}

.

6. invoke the method in Activity and pass in the corresponding parameters.

//Press the message notification center key
openAction(AccessibilityService.GLOBAL_ACTION_NOTIFICATIONS);

//Press the recent task key
openAction(AccessibilityService.GLOBAL_ACTION_RECENTS);

//Press the return key
openAction(AccessibilityService.GLOBAL_ACTION_BACK);

//Press the HOME key
openAction(AccessibilityService.GLOBAL_ACTION_HOME);

//Open quick settings
openAction(AccessibilityService.GLOBAL_ACTION_QUICK_SETTINGS);

//Long press the power button
openAction(AccessibilityService.GLOBAL_ACTION_POWER_DIALOG);

//Open split screen
openAction(AccessibilityService.GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN);

//Lock screen
utils.openAction(AccessibilityService.GLOBAL_ACTION_LOCK_SCREEN);

//Screenshot
openAction(AccessibilityService.GLOBAL_ACTION_TAKE_SCREENSHOT);

.

Here you can realize all the functions of the virtual navigation bar, and my introduction is over. You can streamline some functions according to your project needs. Now paste the complete code I encapsulated, which can be directly copied to your project for direct use.

public class AccessibilityServiceUtils {

    private Activity mActivity;

    public AccessibilityServiceUtils(Activity mActivity) {
        this.mActivity = mActivity;
    }

    /**
     * Judge whether there is barrier free permission
     */
    public boolean checkAccessPermission() {
        if (!isAccessibilityServiceRunning("AccessibilitySetting")) {
            DialogUtils.createDialog(mActivity,
                    R.drawable.dialog_icon_warning,
                    mActivity.getResources().getString(R.string.dialog_title),
                    mActivity.getResources().getString(R.string.dialog_message),
                    mActivity.getResources().getString(R.string.dialog_confirm_button),
                    (dialog, which) -> jump2AccessPermissionSetting(mActivity, false),
                    mActivity.getResources().getString(R.string.dialog_cancel_button), (dialog, which) -> Toast.makeText(mActivity, mActivity.getResources().getString(R.string.dialog_cancel_message), Toast.LENGTH_SHORT).show()
            ).show();
            return false;

        } else {
            return true;
        }
    }

    /**
     * Determine whether there is top accessible service
     *
     * @param name
     * @return
     */
    private boolean isAccessibilityServiceRunning(String name) {
        AccessibilityManager am = (AccessibilityManager) mActivity.getSystemService(Context.ACCESSIBILITY_SERVICE);
        List<AccessibilityServiceInfo> enableServices = am.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_GENERIC);
        for (AccessibilityServiceInfo enableService : enableServices) {
            if (enableService.getId().endsWith(name)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Jump to auxiliary function setting interface
     *
     * @param activity
     */
    private static final String INTENT_ACTION_ACCESSIBILITY_SETTINGS = "android.settings.ACCESSIBILITY_SETTINGS";
    private static final int REQUEST_PERMISS_REQUEST_ACCESSABLE = 300;//Auxiliary function Key
    private void jump2AccessPermissionSetting(Activity activity, boolean forResult) {
        if (forResult) {
            activity.startActivityForResult(new Intent(INTENT_ACTION_ACCESSIBILITY_SETTINGS), REQUEST_PERMISS_REQUEST_ACCESSABLE);
        } else {
            activity.startActivity(new Intent(INTENT_ACTION_ACCESSIBILITY_SETTINGS));
        }
    }

    //Simulate global key
    public void openAction(int action) {
        if (checkAccessPermission()) {
            if (Build.VERSION.SDK_INT < 16) {
                Toast.makeText(AccessibilitySetting.getService(), "Android 4.1 And above systems only support this function. Please upgrade and try again", Toast.LENGTH_SHORT).show();
            } else {
                if (AccessibilitySetting.getService() != null) {
                    AccessibilitySetting.getService().performGlobalAction(action);
                }
            }
        }
    }
}


class DialogUtils {

    public static AlertDialog createDialog(Context context, @DrawableRes int iconRes, String title, String message, String positiveTitle, DialogInterface.OnClickListener positiveClickListener, String negativeTitle, DialogInterface.OnClickListener negativeClickListener) {

        AlertDialog.Builder builder = new AlertDialog.Builder(context, R.style.AlertDialogCustom);
        builder.setIcon(iconRes);
        builder.setTitle(title);
        builder.setMessage(message);
        builder.setCancelable(true);
        builder.setPositiveButton(positiveTitle, positiveClickListener);
        builder.setNegativeButton(negativeTitle, negativeClickListener);
        AlertDialog alertDialog = builder.create();
        alertDialog.setOnShowListener(dialog -> {
            Button positiveButton = ((AlertDialog) dialog)
                    .getButton(AlertDialog.BUTTON_POSITIVE);
            //                positiveButton.setBackgroundColor(Color.BLUE);
            positiveButton.setTextColor(Color.RED);

            Button negativeButton = ((AlertDialog) dialog)
                    .getButton(AlertDialog.BUTTON_NEGATIVE);
            //                positiveButton.setBackgroundColor(Color.BLUE);
            negativeButton.setTextColor(Color.LTGRAY);

        });
        return alertDialog;
    }
}


private AccessibilityServiceUtils utils;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    utils = new AccessibilityServiceUtils(MainActivity.this);

	utils.checkAccessPermission();
}

.

.

Project address

Keywords: Java Android

Added by austenr on Thu, 23 Dec 2021 02:08:40 +0200