The Principle of Automatic Return and Red Packet Grabbing by Wechat (2): Automatic Return

Keywords: Android Mobile REST

Before reading this article, you need to know about Accessibility Service. You can read my last article first.
The Principle of Automatic Return and Red Packet Grabbing in Wechat (1): Introduction and Configuration of Accessibility Service
Friends already know can read this article directly.

After completing the configuration of Accessibility Service, it seems that there is no way to start. Don't worry. Print some log s first. Put the following method in onAccessibilityEvent():

    private void printEventLog(AccessibilityEvent event) {
        Log.i(TAG, "-------------------------------------------------------------");
        int eventType = event.getEventType(); //Event type
        Log.i(TAG, "PackageName:" + event.getPackageName() + ""); // Package name of response event
        Log.i(TAG, "Source Class:" + event.getClassName() + ""); // Class name of event source
        Log.i(TAG, "Description:" + event.getContentDescription()+ ""); // Event source description
        Log.i(TAG, "Event Type(int):" + eventType + "");

        switch (eventType) {
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// Notification Bar Events
                Log.i(TAG, "event type:TYPE_NOTIFICATION_STATE_CHANGED");
                break;
            case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED://Form status change
                Log.i(TAG, "event type:TYPE_WINDOW_STATE_CHANGED");
                break;
            case AccessibilityEvent.TYPE_VIEW_ACCESSIBILITY_FOCUSED://View Gets Focus
                Log.i(TAG, "event type:TYPE_VIEW_ACCESSIBILITY_FOCUSED");
                break;
            case AccessibilityEvent.TYPE_GESTURE_DETECTION_START:
                Log.i(TAG, "event type:TYPE_VIEW_ACCESSIBILITY_FOCUSED");
                break;
            case AccessibilityEvent.TYPE_GESTURE_DETECTION_END:
                Log.i(TAG, "event type:TYPE_GESTURE_DETECTION_END");
                break;
            case AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED:
                Log.i(TAG, "event type:TYPE_WINDOW_CONTENT_CHANGED");
                break;
            case AccessibilityEvent.TYPE_VIEW_CLICKED:
                Log.i(TAG, "event type:TYPE_VIEW_CLICKED");
                break;
            case AccessibilityEvent.TYPE_VIEW_TEXT_CHANGED:
                Log.i(TAG, "event type:TYPE_VIEW_TEXT_CHANGED");
                break;
            case AccessibilityEvent.TYPE_VIEW_SCROLLED:
                Log.i(TAG, "event type:TYPE_VIEW_SCROLLED");
                break;
            case AccessibilityEvent.TYPE_VIEW_TEXT_SELECTION_CHANGED:
                Log.i(TAG, "event type:TYPE_VIEW_TEXT_SELECTION_CHANGED");
                break;
            default:
                Log.i(TAG, "no listen event");
        }

        for (CharSequence txt : event.getText()) {
            Log.i(TAG, "text:" + txt);
        }

        Log.i(TAG, "-------------------------------------------------------------");
    }

Send a tweet message to the mobile phone with the service installed and check the printed log:

Non-lock screen (in the background):
-------------------------------------------------------------
packageName:com.tencent.mm
source:null
source class:android.app.Notification
event type(int):64
event type:TYPE_NOTIFICATION_STATE_CHANGED
 text: [Contacts]: Oh
-------------------------------------------------------------
Non lock screen(Front desk main interface):
-------------------------------------------------------------
packageName:com.tencent.mm
source:android.view.accessibility.AccessibilityNodeInfo@8009b539; boundsInParent: Rect(0, 0 - 38, 38); boundsInScreen: Rect(103, 1181 - 141, 1219); packageName: com.tencent.mm; className: android.widget.TextView; text: 1; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: false; longClickable: false; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null]
source class:android.widget.TextView
event type(int):2048
event type:TYPE_WINDOW_CONTENT_CHANGED
-------------------------------------------------------------
-------------------------------------------------------------
packageName:com.tencent.mm
source:null
source class:android.app.Notification
event type(int):64
event type:TYPE_NOTIFICATION_STATE_CHANGED
text:[Contacts]: Ha-ha
-------------------------------------------------------------
-------------------------------------------------------------
packageName:com.tencent.mm
source:android.view.accessibility.AccessibilityNodeInfo@80043582; boundsInParent: Rect(0, 0 - 38, 38); boundsInScreen: Rect(96, 153 - 134, 191); packageName: com.tencent.mm; className: android.widget.TextView; text: 1; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: false; longClickable: false; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null]
source class:android.widget.TextView
event type(int):2048
event type:TYPE_WINDOW_CONTENT_CHANGED
-------------------------------------------------------------
Non lock screen(Open the speaker's interface at the front desk)(noNotification)
-------------------------------------------------------------
packageName:com.tencent.mm
source:android.view.accessibility.AccessibilityNodeInfo@8009cbbf; boundsInParent: Rect(0, 0 - 720, 1038); boundsInScreen: Rect(0, 146 - 720, 1184); packageName: com.tencent.mm; className: android.widget.ListView; text: null; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: true; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: true; actions: [AccessibilityAction: ACTION_FOCUS - null, AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_SCROLL_BACKWARD - null]
source class:android.widget.ListView
event type(int):2048
event type:TYPE_WINDOW_CONTENT_CHANGED
-------------------------------------------------------------
-------------------------------------------------------------
packageName:com.tencent.mm
source:android.view.accessibility.AccessibilityNodeInfo@801086ca; boundsInParent: Rect(0, 0 - 415, 80); boundsInScreen: Rect(201, 146 - 616, 150); packageName: com.tencent.mm; className: android.widget.TextView; text: I'm typing the code and I'll reply later.~; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null]
source class:android.widget.TextView
event type(int):2048
event type:TYPE_WINDOW_CONTENT_CHANGED
-------------------------------------------------------------
-------------------------------------------------------------
packageName:com.tencent.mm
source:android.view.accessibility.AccessibilityNodeInfo@8009cbbf; boundsInParent: Rect(0, 0 - 720, 1038); boundsInScreen: Rect(0, 146 - 720, 1184); packageName: com.tencent.mm; className: android.widget.ListView; text: null; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: true; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: true; actions: [AccessibilityAction: ACTION_FOCUS - null, AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_SCROLL_BACKWARD - null]
source class:android.widget.ListView
event type(int):4096
event type:TYPE_VIEW_SCROLLED
-------------------------------------------------------------
Non lock screen(Open a non-conversational interface at the front desk):
-------------------------------------------------------------
packageName:com.tencent.mm
source:android.view.accessibility.AccessibilityNodeInfo@800ce011; boundsInParent: Rect(0, 0 - 95, 80); boundsInScreen: Rect(104, 851 - 199, 931); packageName: com.tencent.mm; className: android.widget.TextView; text: [Roll one's eyes]; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: true; longClickable: true; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_LONG_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null]
source class:android.widget.TextView
event type(int):2048
event type:TYPE_WINDOW_CONTENT_CHANGED
-------------------------------------------------------------
-------------------------------------------------------------
packageName:com.tencent.mm
source:null
source class:android.app.Notification
event type(int):64
event type:TYPE_NOTIFICATION_STATE_CHANGED
text:[Contacts]: Ha-ha
-------------------------------------------------------------
-------------------------------------------------------------
packageName:com.tencent.mm
source:android.view.accessibility.AccessibilityNodeInfo@8012f5f0; boundsInParent: Rect(0, 0 - 371, 60); boundsInScreen: Rect(174, 591 - 545, 651); packageName: com.tencent.mm; className: android.widget.TextView; text: "?A kind of????" A message was withdrawn.; error: null; maxTextLength: -1; contentDescription: null; viewIdResName: null; checkable: false; checked: false; focusable: false; focused: false; selected: false; clickable: true; longClickable: false; enabled: true; password: false; scrollable: false; actions: [AccessibilityAction: ACTION_SELECT - null, AccessibilityAction: ACTION_CLEAR_SELECTION - null, AccessibilityAction: ACTION_CLICK - null, AccessibilityAction: ACTION_ACCESSIBILITY_FOCUS - null, AccessibilityAction: ACTION_NEXT_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY - null, AccessibilityAction: ACTION_SET_SELECTION - null]
source class:android.widget.TextView
event type(int):2048
event type:TYPE_WINDOW_CONTENT_CHANGED
-------------------------------------------------------------

It should be known from the printed log s and our usual use of Wechat that, in addition to opening the conversation interface, there will be Notification, which can be used as a starting point, then our work will be simple. The steps are as follows:

  1. Listening for TYPE_NOTIFICATION_STATE_CHANGED events
  2. Open the conversation interface according to Notification
  3. Search Input Box Control
  4. Enter reply text in the input box
  5. Click the Send button
  6. Return to the Wechat main interface

The idea is clear, the difficulty is how to find the corresponding control. Rest assured, Android also provides us with a class to help us—— AccessibilityNodeInfo  It contains information about some controls, which can be used to find the corresponding controls and make corresponding operations. Common methods:

- CharSequence getClassName () // Get the control class name, such as the button will return android.widget.Button
- CharSequence getText () // Getting the text of the control, such as the send button of Wechat, returns "Send"
- String getViewIdResourceName () // Getting control's id

Code comments are very detailed, so they are not explained one by one. There is a sticker behind the source code.

/**
 * Automatic response service
 */
public class AutoReplyService extends AccessibilityService{

    private static final String TAG = AutoReplyService.class.getSimpleName();

    private Handler handler = new Handler();
    private boolean hasNotify = false;

    /**
     * Methods that must be rewritten to respond to events.
     */
    @Override
    public void onAccessibilityEvent(final AccessibilityEvent event) { 
        int eventType = event.getEventType(); // Event type
        switch (eventType) {
            case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED: // Notification Bar Events
                Log.i(TAG, "TYPE_NOTIFICATION_STATE_CHANGED");
                if(PhoneController.isLockScreen(this)) { // Lock screen
                    PhoneController.wakeAndUnlockScreen(this);   // Wake up and light up the screen
                }
                openAppByNotification(event);
                hasNotify = true;
                break;

            default:
                Log.i(TAG, "DEFAULT");
                if (hasNotify) { // If notified
                    try {
                        Thread.sleep(1000); // Stop for 1 second, or fill InputBar will be executed without entering the chat interface in the main interface of Wechat.
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    if (fillInputBar("I'm typing the code and I'll reply later.~")) { // Find the input box, EditText
                        findAndPerformAction(UI.BUTTON, "Send out"); // Click to send
                        handler.postDelayed(new Runnable() { // Return to the main interface, where execution is delayed for better interaction
                            @Override
                            public void run() {
                                performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);   // Return
                            }
                        }, 1500);

                    }
                    hasNotify = false;
                }
                break;
        }
    }

    @Override
    public void onInterrupt() {
    }

    /**
     * Find UI controls and click
     * @param widget Control full name, such as android.widget.Button, android.widget.TextView
     * @param text Control text
     */
    private void findAndPerformAction(String widget, String text) {
        // Gets the root node of the current active form
        if (getRootInActiveWindow() == null) {
            return;
        }

        // Find the current node through text
        List<AccessibilityNodeInfo> nodes = getRootInActiveWindow().findAccessibilityNodeInfosByText(text);
        if(nodes != null) {
            for (AccessibilityNodeInfo node : nodes) {
                if (node.getClassName().equals(widget) && node.isEnabled()) {
                    node.performAction(AccessibilityNodeInfo.ACTION_CLICK); // Execution Click
                    break;
                }
            }
        }
    }

    /**
     * Open WeChat
     * @param event Event
     */
    private void openAppByNotification(AccessibilityEvent event) {
        if (event.getParcelableData() != null  && event.getParcelableData() instanceof Notification) {
            Notification notification = (Notification) event.getParcelableData();
            try {
                PendingIntent pendingIntent = notification.contentIntent;
                pendingIntent.send();
            } catch (PendingIntent.CanceledException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Fill in the input box
     */
    private boolean fillInputBar(String reply) {
        AccessibilityNodeInfo rootNode = getRootInActiveWindow();
        if (rootNode != null) {
            return findInputBar(rootNode, reply);
        }
        return false;
    }

    /**
     * Find the EditText control
     * @param rootNode Root node
     * @param reply Reply content
     * @return Find true, otherwise return false
     */
    private boolean findInputBar(AccessibilityNodeInfo rootNode, String reply) {
        int count = rootNode.getChildCount();
        for (int i = 0; i < count; i++) {
            AccessibilityNodeInfo node = rootNode.getChild(i);

            if (UI.EDITTEXT.equals(node.getClassName())) {   // Find the input box and enter text
                setText(node, reply);
                return true;
            }

            if (findInputBar(node, reply)) {    // recursive lookup
                return true;
            }
        }
        return false;
    }

    /**
     * Set text
     */
    private void setText(AccessibilityNodeInfo node, String reply) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            Bundle args = new Bundle();
            args.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE,
                    reply);
            node.performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, args);
        } else {
            ClipData data = ClipData.newPlainText("reply", reply);
            ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            clipboardManager.setPrimaryClip(data);
            node.performAction(AccessibilityNodeInfo.ACTION_FOCUS); // Focus of acquisition
            node.performAction(AccessibilityNodeInfo.ACTION_PASTE); // Execution paste
        }
    }
}

Here I have not dealt with the situation of opening the conversation interface, I think you have opened the conversation interface, it proves that you want to chat with him, there is no need for automatic reply. Of course, if you want it everywhere (without your perfunctory friends like you), just add a little simple logic to realize the function of automatic reply to Wechat here, how is it simple? Interested friends can continue to read my next article:
Principle of Automatic Return and Red Packet Grabbing by Wechat (3): Red Packet Grabbing by Wechat

Source download


Original address: http://www.jianshu.com/p/5b4a4a5aca7e

Posted by jd6th on Wed, 10 Apr 2019 00:24:31 -0700