Android微信自動回復功能

AntCTVG 8年前發布 | 58K 次閱讀 Android 微信 Android開發 移動開發

寫在前面

最近接到老大的一個需求,要求在手機端攔截微信的通知(Notification),從而獲得聯系人和內容。之后將聯系人和內容發送到我們的硬件產品上,展示出來之后,再將我們想回復內容傳給微信,并且發送給相應聯系人。

老大還提示我需要用AccessibilityService去實現它,當然在此之前我并不知道AccessibilityService是什么鬼,不過沒關系, just do IT

AccessibilityService

AccessibilityService官方文檔(需KX上網)

上面這個鏈接是AccessibilityService的官方文檔,可以KX上網點進去了解下,我再給大家總結一下:

AccessibilityService是Android系統框架提供給安裝在設備上應用的一個可選的導航反饋特性。AccessibilityService 可以替代應用與用戶交流反饋,比如將文本轉化為語音提示,或是用戶的手指懸停在屏幕上一個較重要的區域時的觸摸反饋等。

如果感覺上面的描述比較抽象,沒關系,也許你見過下面這張圖:

輔助功能中的服務

打開你手機的設置--輔助功能中,有很多APP提供的服務,他們都是基于AccessibilityService編寫的,AccessibilityService可以偵聽你的點擊,長按,手勢,通知欄的變化等。并且你可以通過很多種方式找到窗體中的EditText,Button等組件,去填充他們,去點擊他們來幫你實現自動化的功能。

像360助手的自動安裝功能,它就是偵聽著系統安裝的APP,然后找到“安裝”按鈕,實現了自動點擊。微信自動搶紅包功能,實現方式都是如此。

配置AccessibilityService

首先我們在res文件夾下創建xml文件夾,然后創建一個名為auto_reply_service_config的文件,一會我們會在清單文件中引用它。

AccessibilityService配置文件

代碼:

<accessibility-service               xmlns:android="http://schemas.android.com/apk/res/android"     android:accessibilityEventTypes="typeNotificationStateChanged|typeWindowStateChanged"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="true"
    android:description="@string/accessibility_description"
    android:notificationTimeout="100"
    android:packageNames="com.tencent.mm" />

這個文件表示我們對AccessibilityService服務未來偵聽的行為做了一些配置,比如 typeNotificationStateChangedtypeWindowStateChanged 表示我們需要偵聽通知欄的狀態變化和窗體狀態改變。
android:packageNames="com.tencent.mm" 這是微信的包名,表示我們只關心微信這一個應用。

代碼不打算帶著大家一行一行看了,如果有不明白的,去看看文檔,或者下面回復我,我給大家解答~

創建AccessibilityService

下面貼出AccessibilityService類的全部代碼,注釋還算詳盡,如有疑問,下方回復。

package com.ileja.autoreply;

import android.accessibilityservice.AccessibilityService; import android.annotation.SuppressLint; import android.app.ActivityManager; import android.app.KeyguardManager; import android.app.Notification; import android.app.PendingIntent; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.PowerManager; import android.text.TextUtils; import android.view.KeyEvent; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityNodeInfo;

import java.io.IOException; import java.util.List;

public class AutoReplyService extends AccessibilityService { private final static String MM_PNAME = "com.tencent.mm"; boolean hasAction = false; boolean locked = false; boolean background = false; private String name; private String scontent; AccessibilityNodeInfo itemNodeinfo; private KeyguardManager.KeyguardLock kl; private Handler handler = new Handler();

/**
 * 必須重寫的方法,響應各種事件。
 * @param event
 */
@Override
public void onAccessibilityEvent(final AccessibilityEvent event) {
    int eventType = event.getEventType();
    android.util.Log.d("maptrix", "get event = " + eventType);
    switch (eventType) {
        case AccessibilityEvent.TYPE_NOTIFICATION_STATE_CHANGED:// 通知欄事件
            android.util.Log.d("maptrix", "get notification event");
            List<CharSequence> texts = event.getText();
            if (!texts.isEmpty()) {
                for (CharSequence text : texts) {
                    String content = text.toString();
                    if (!TextUtils.isEmpty(content)) {
                        if (isScreenLocked()) {
                            locked = true;
                            wakeAndUnlock();
                            android.util.Log.d("maptrix", "the screen is locked");
                            if (isAppForeground(MM_PNAME)) {
                                background = false;
                                android.util.Log.d("maptrix", "is mm in foreground");
                                sendNotifacationReply(event);
                                handler.postDelayed(new Runnable() {
                                    @Override
                                    public void run() {
                                        sendNotifacationReply(event);
                                        if (fill()) {
                                            send();
                                        }
                                    }
                                }, 1000);
                            } else {
                                background = true;
                                android.util.Log.d("maptrix", "is mm in background");
                                sendNotifacationReply(event);
                            }
                        } else {
                            locked = false;
                            android.util.Log.d("maptrix", "the screen is unlocked");
                            // 監聽到微信紅包的notification,打開通知
                            if (isAppForeground(MM_PNAME)) {
                                background = false;
                                android.util.Log.d("maptrix", "is mm in foreground");
                                sendNotifacationReply(event);
                                handler.postDelayed(new Runnable() {
                                    @Override
                                    public void run() {
                                        if (fill()) {
                                            send();
                                        }
                                    }
                                }, 1000);
                            } else {
                                background = true;
                                android.util.Log.d("maptrix", "is mm in background");
                                sendNotifacationReply(event);
                            }
                        }
                    }
                }
            }
            break;
        case AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED:
            android.util.Log.d("maptrix", "get type window down event");
            if (!hasAction) break;
            itemNodeinfo = null;
            String className = event.getClassName().toString();
            if (className.equals("com.tencent.mm.ui.LauncherUI")) {
                if (fill()) {
                    send();
                }else {
                    if(itemNodeinfo != null){
                        itemNodeinfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
                        handler.postDelayed(new Runnable() {
                            @Override
                            public void run() {
                                if (fill()) {
                                    send();
                                }
                                back2Home();
                                release();
                                hasAction = false;
                            }
                        }, 1000);
                        break;
                    }
                }
            }

            //bring2Front();
            back2Home();
            release();
            hasAction = false;
            break;
    }
}

/**
 * 尋找窗體中的“發送”按鈕,并且點擊。
 */
@SuppressLint("NewApi")
private void send() {
    AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
    if (nodeInfo != null) {
        List<AccessibilityNodeInfo> list = nodeInfo
                .findAccessibilityNodeInfosByText("發送");
        if (list != null && list.size() > 0) {
            for (AccessibilityNodeInfo n : list) {
                if(n.getClassName().equals("android.widget.Button") && n.isEnabled())
                {    
                    n.performAction(AccessibilityNodeInfo.ACTION_CLICK);}
                }

        } else {
            List<AccessibilityNodeInfo> liste = nodeInfo
                    .findAccessibilityNodeInfosByText("Send");
            if (liste != null && liste.size() > 0) {
                for (AccessibilityNodeInfo n : liste) {
                    if(n.getClassName().equals("android.widget.Button") && n.isEnabled())
                    {    
                         n.performAction(AccessibilityNodeInfo.ACTION_CLICK);}
                    }
                }
            }
        }
        pressBackButton();
    }

}

/**
 * 模擬back按鍵
 */
private void pressBackButton(){
    Runtime runtime = Runtime.getRuntime();
    try {
        runtime.exec("input keyevent " + KeyEvent.KEYCODE_BACK);
    } catch (IOException e) {
        e.printStackTrace();
    }
}

/**
 *
 * @param event
 */
private void sendNotifacationReply(AccessibilityEvent event) {
    hasAction = true;
    if (event.getParcelableData() != null
            && event.getParcelableData() instanceof Notification) {
        Notification notification = (Notification) event
                .getParcelableData();
        String content = notification.tickerText.toString();
        String[] cc = content.split(":");
        name = cc[0].trim();
        scontent = cc[1].trim();

        android.util.Log.i("maptrix", "sender name =" + name);
        android.util.Log.i("maptrix", "sender content =" + scontent);


        PendingIntent pendingIntent = notification.contentIntent;
        try {
            pendingIntent.send();
        } catch (PendingIntent.CanceledException e) {
            e.printStackTrace();
        }
    }
}

@SuppressLint("NewApi")
private boolean fill() {
    AccessibilityNodeInfo rootNode = getRootInActiveWindow();
    if (rootNode != null) {
        return findEditText(rootNode, "正在忙,稍后回復你");
    }
    return false;
}


private boolean findEditText(AccessibilityNodeInfo rootNode, String content) {
    int count = rootNode.getChildCount();

    android.util.Log.d("maptrix", "root class=" + rootNode.getClassName() + ","+ rootNode.getText()+","+count);
    for (int i = 0; i < count; i++) {
        AccessibilityNodeInfo nodeInfo = rootNode.getChild(i);
        if (nodeInfo == null) {
            android.util.Log.d("maptrix", "nodeinfo = null");
            continue;
        }

        android.util.Log.d("maptrix", "class=" + nodeInfo.getClassName());
        android.util.Log.e("maptrix", "ds=" + nodeInfo.getContentDescription());
        if(nodeInfo.getContentDescription() != null){
            int nindex = nodeInfo.getContentDescription().toString().indexOf(name);
            int cindex = nodeInfo.getContentDescription().toString().indexOf(scontent);
            android.util.Log.e("maptrix", "nindex=" + nindex + " cindex=" +cindex);
            if(nindex != -1){
                itemNodeinfo = nodeInfo;
                android.util.Log.i("maptrix", "find node info");
            }
        }
        if ("android.widget.EditText".equals(nodeInfo.getClassName())) {
            android.util.Log.i("maptrix", "==================");
            Bundle arguments = new Bundle();
            arguments.putInt(AccessibilityNodeInfo.ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT,
                    AccessibilityNodeInfo.MOVEMENT_GRANULARITY_WORD);
            arguments.putBoolean(AccessibilityNodeInfo.ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN,
                    true);
            nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY,
                    arguments);
            nodeInfo.performAction(AccessibilityNodeInfo.ACTION_FOCUS);
            ClipData clip = ClipData.newPlainText("label", content);
            ClipboardManager clipboardManager = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
            clipboardManager.setPrimaryClip(clip);
            nodeInfo.performAction(AccessibilityNodeInfo.ACTION_PASTE);
            return true;
        }

        if (findEditText(nodeInfo, content)) {
            return true;
        }
    }

    return false;
}

@Override
public void onInterrupt() {

}

/**
 * 判斷指定的應用是否在前臺運行
 *
 * @param packageName
 * @return
 */
private boolean isAppForeground(String packageName) {
    ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    ComponentName cn = am.getRunningTasks(1).get(0).topActivity;
    String currentPackageName = cn.getPackageName();
    if (!TextUtils.isEmpty(currentPackageName) && currentPackageName.equals(packageName)) {
        return true;
    }

    return false;
}


/**
 * 將當前應用運行到前臺
 */
private void bring2Front() {
    ActivityManager activtyManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
    List<ActivityManager.RunningTaskInfo> runningTaskInfos = activtyManager.getRunningTasks(3);
    for (ActivityManager.RunningTaskInfo runningTaskInfo : runningTaskInfos) {
        if (this.getPackageName().equals(runningTaskInfo.topActivity.getPackageName())) {
            activtyManager.moveTaskToFront(runningTaskInfo.id, ActivityManager.MOVE_TASK_WITH_HOME);
            return;
        }
    }
}

/**
 * 回到系統桌面
 */
private void back2Home() {
    Intent home = new Intent(Intent.ACTION_MAIN);

    home.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    home.addCategory(Intent.CATEGORY_HOME);

    startActivity(home);
}


/**
 * 系統是否在鎖屏狀態
 *
 * @return
 */
private boolean isScreenLocked() {
    KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    return keyguardManager.inKeyguardRestrictedInputMode();
}

private void wakeAndUnlock() {
    //獲取電源管理器對象
    PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);

    //獲取PowerManager.WakeLock對象,后面的參數|表示同時傳入兩個值,最后的是調試用的Tag
    PowerManager.WakeLock wl = pm.newWakeLock(PowerManager.ACQUIRE_CAUSES_WAKEUP | PowerManager.SCREEN_BRIGHT_WAKE_LOCK, "bright");

    //點亮屏幕
    wl.acquire(1000);

    //得到鍵盤鎖管理器對象
    KeyguardManager km = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
    kl = km.newKeyguardLock("unLock");

    //解鎖
    kl.disableKeyguard();

}

private void release() {

    if (locked && kl != null) {
        android.util.Log.d("maptrix", "release the lock");
        //得到鍵盤鎖管理器對象
        kl.reenableKeyguard();
        locked = false;
    }
}

}</code></pre>

接著配置清單文件,權限和service的配置比較重要。

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="

<uses-permission android:name="android.permission.DISABLE_KEYGUARD" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<uses-permission android:name="android.permission.GET_TASKS" />
<uses-permission android:name="android.permission.REORDER_TASKS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />

<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>

    <service
        android:name=".AutoReplyService"
        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/auto_reply_service_config"/>
    </service>
</application>

</manifest></code></pre>

為了使用某些必要的API,最低API level應該是18

運行程序,打開服務,看看效果如何把~

打開輔助服務

接著用其他手機試著發送給我幾條微信

自動回復微信

可以看到,自動回復功能就實現了。

寫在后面

代碼沒有給大家詳細講解,不過看注釋應該可以看懂個大概。當微信程序切換到后臺,或者鎖屏(無鎖屏密碼)時,只要有通知出現,都可以實現自動回復。

關于AccessibilityService可以監控的行為非常多,所以我覺得可以實現各種各樣炫酷的功能,不過我并不建議你打開某些流氓軟件的AccessibilityService服務,因為很有可能造成一些安全問題,所以,自己動手寫就安全多了嘛。


閱讀原文

 本文由用戶 AntCTVG 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!