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(); }
.
.