Accessibility Service

1. Create a new Service integration AccessibilityService

1.1,DingService.java

 public class DingService extends AccessibilityService {
     //Initialization work
    public void onServiceConnected() {

    } 
    //Get control node
    public void onAccessibilityEvent(AccessibilityEvent event) {
        //Get root node
        AccessibilityNodeInfo rowNode = getRootInActiveWindow();
        if (rowNode == null) {
            return;
        } else {
         //Here, the root node traversal and other operations are carried out to find the required controls and operate
         recycle(rowNode);
         }
    }
    @Override
    public void onInterrupt() {

    }
 }

1.2 control customization

  @SuppressLint("NewApi")
    public void recycle(AccessibilityNodeInfo info) {
        if (info.getChildCount() == 0) {
            click("register/Sign in");//Traverse to the control information named "register / login" and click it
              if (info.getClassName().equals("android.widget.EditText"))
              {
                setText(info, "Please input a password","123456");//Fill in 123456 data for EditText with "please enter password"
            }

    }
    else {//Continue traversing
            for (int i = 0; i < info.getChildCount(); i++) {
                if (info.getChild(i) != null) {
                    recycle(info.getChild(i));
                }
            }
        }

}

1.3. Click through text

    //Click through text
    @RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
    private boolean click(String viewText) {
        AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
        if (nodeInfo == null) {
            //Log.w(TAG, "click failed, rootWindow is empty");
            return false;
        }
        List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(viewText);
        if (list.isEmpty()) {
            //There is no control for this text
            //Log.w(TAG, "click failed," + viewText + "control list is empty");
            return false;
        } else {
            //There is the control.
            //Find clickable parent control
            AccessibilityNodeInfo view = list.get(0);
            //Log.i(TAG, "click" + viewText);
            Boolean fal = onclick(view);  //Traversal Click
            return fal;

        }
    }
    private boolean onclick(AccessibilityNodeInfo view) {

        if (view.isClickable()) {
            view.performAction(AccessibilityNodeInfo.ACTION_CLICK);
            //Log.i(TAG, "successful");
            return true;
        } else {

            AccessibilityNodeInfo parent = view.getParent();
            if (parent == null) {
                return false;
            }
            onclick(parent);
        }
        return false;
    }

1.4. Fill EditText with data through text

void setText(AccessibilityNodeInfo info, String title, String vaule) {
        if (info == null) {
            return;
        }
        String str = info.getText().toString();

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Log.i(TAG, "HintText" + info.getHintText() + "");
        }
        Log.i(TAG, "Text" + info.getText() + "");

        if (str.equals(title)) {
         putClipboard1(info, vaule);
        }
}

    //Automatically paste text for edittext
    public Boolean putClipboard1(AccessibilityNodeInfo edittext, String text) {
        if (edittext != null) {
            ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
            ClipData clip = ClipData.newPlainText("text", text);
            clipboard.setPrimaryClip(clip);
            //Focus (n is the AccessibilityNodeInfo object)
            edittext.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
            ////Paste entry
            return edittext.performAction(AccessibilityNodeInfo.ACTION_PASTE);
            //Send out
            //...
        }
        return false;
    }

1.5 print interface node

   private static int tabcount = -1;
    private static StringBuilder sb;

    //Print the interface status for analysis
    private static void analysisPacketInfo(AccessibilityNodeInfo info, int... ints) {
        if (info == null) {
            return;
        }
        if (tabcount > 0) {
            for (int i = 0; i < tabcount; i++) {
                sb.append("\t\t");
            }
        }
        if (ints != null && ints.length > 0) {
            StringBuilder s = new StringBuilder();
            for (int j = 0; j < ints.length; j++) {
                s.append(ints[j]).append(".");
            }
            sb.append(s).append(" ");
        }
        String name = info.getClassName().toString();
        String[] split = name.split("\\.");
        name = split[split.length - 1];
        if ("TextView".equals(name)) {
            CharSequence text = info.getText();
            sb.append("text:").append(text);
        } else if ("Button".equals(name)) {
            CharSequence text = info.getText();
            sb.append("Button:").append(text);
        } else {
            sb.append(name);
        }
        sb.append("\n");

        int count = info.getChildCount();
        if (count > 0) {
            tabcount++;
            int len = ints.length + 1;
            int[] newInts = Arrays.copyOf(ints, len);

            for (int i = 0; i < count; i++) {
                newInts[len - 1] = i;
                analysisPacketInfo(info.getChild(i), newInts);
            }
            tabcount--;
        }

    }
    public static void printPacketInfo(AccessibilityNodeInfo root) {
        sb = new StringBuilder();
        tabcount = 0;
        int[] is = {};
        analysisPacketInfo(root, is);
        Log.d("node", sb.toString());
    }
    //Search node
    public static AccessibilityNodeInfo findNodeByViewName(AccessibilityNodeInfo info, String viewName) {
        String name = info.getClassName().toString();
        String[] split = name.split("\\.");
        name = split[split.length - 1];
        if (name.equals(viewName)) {
            return info;
        } else {

            int count = info.getChildCount();
            if (count > 0) {
                for (int i = 0; i < count; i++) {
                    AccessibilityNodeInfo inf = findNodeByViewName(info.getChild(i), viewName);
                    if (inf != null) {
                        return inf;
                    }
                }
            } else {
                return null;
            }
        }
        return null;
    }
  • In this way, we can directly locate the View through the previous 0.0.0.1.1
       AccessibilityNodeInfo info = root;
        int[] path = {0, 0, 0, 1, 1};
        for (int i = 0; i < path.length; i++) {
            info = info.getChild(path[i]);
            if (info == null || info.getChildCount() <= 0) {
                return null;
            }
        }
        return info;
  • Of course, you may not know which view 0.0.0.1.1 corresponds to. You can
Rect rect = new Rect();
        info.getBoundsInScreen(rect);
        //Height of status bar
        int h = GUtil.getStatusBarHeight(context.getApplicationContext());
        rect.top -= h;
        rect.bottom -= h;rect´╝î

Print rect, or directly create window in global window, displaying rect as colored area

2, configuration

  • manifest
      <!-- AccessibilityService Service implementation permission -->
    <uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE"
        tools:ignore="ProtectedPermissions" />

<!-- Register to monitor mobile status Servicer -->
        <service
            android:name=".DingService"
            android:enabled="true"
            android:exported="true"
            android:label="@string/app_name"
            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>
  • accessibilityservice.xml
<?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="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_description"
    android:packageNames="com.hbc.hbc"//com.hbc.hbcIt's the name of the app package you want to monitor. If you don't write, you will monitor all packages
    android:notificationTimeout="200" />

3. Start in MainActivity

/ / calling startAccessibilityService in onCreate can turn on the service.

    /**
     * Go to the setting interface to start the service
     */
    private void startAccessibilityService() {
        new AlertDialog.Builder(this)
                .setTitle("Turn on auxiliary functions")
                .setIcon(R.mipmap.ic_launcher)
                .setMessage("You need to turn on auxiliary functions to use this function")
                .setPositiveButton("Open immediately", new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialog, int which) {
                        // Implicitly calling the system settings interface
                        Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS);
                        startActivity(intent);
                    }
                }).create().show();
    }
    /**
     * Determine whether the AccessibilityService of your application is running
     *
     * @return
     */
    private boolean serviceIsRunning() {
        ActivityManager am = (ActivityManager) getApplicationContext().getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningServiceInfo> services = am.getRunningServices(Short.MAX_VALUE);
        for (ActivityManager.RunningServiceInfo info : services) {
            if (info.service.getClassName().equals(getPackageName() + ".DingService")) {//DingService is your package name
                return true;
            }
        }
        return false;
    }

Operations supported by AccessibilityNodeInfo

AccessibilityService has its own method, analog return key, home key, etc

performGlobalAction(GLOBAL_ACTION_BACK)

AccessibilityNodeInfo can also directly simulate events such as click and long press.

info.performAction(AccessibilityNodeInfo.ACTION_CLICK);

However, sometimes performAction is useless!!!

Because now many applications are mixed applications. The content page may be written by Html5. It looks like a button. In fact, it's a normal View.. its click event is not generated by OnClick, but directly judged by TouchEvent. AccessibilityNodeInfo does not provide an api to send down, move or up events. I can't simulate all the operations through this series. Instead, I use the mobile phone after root to send the global click command to the system.

 /**Click on a view*/
     public static void perforGlobalClick(AccessibilityNodeInfo info) {
        Rect rect = new Rect();
        info.getBoundsInScreen(rect);
        perforGlobalClick(rect.centerX(), rect.centerY());
    }

//Click the designated point on the screen
    public static void perforGlobalClick(int x, int y) {
        execShellCmd("input tap " + x + " " + y);
    }
    /**
     * Execute shell command
     *
     * @param cmd
     */
    public static void execShellCmd(String cmd) {

        try {
            // It is very important to apply for root permission, otherwise it will not work
            Process process = Runtime.getRuntime().exec("su");
            // Get output stream
            OutputStream outputStream = process.getOutputStream();
            DataOutputStream dataOutputStream = new DataOutputStream(outputStream);
            dataOutputStream.writeBytes(cmd);
            dataOutputStream.flush();
            dataOutputStream.close();
            outputStream.close();
//            process.waitFor();
        } catch (Throwable t) {
            t.printStackTrace();
        }
    }

Reference materials

https://blog.csdn.net/u013147734/article/details/78490629
https://blog.csdn.net/c794904140/article/details/52153148
https://www.cnblogs.com/itchq/articles/5648657.html

Tags: Android xml Java Mobile

Posted on Mon, 06 Jan 2020 05:23:12 -0800 by smarthouseguy