├── .gitignore ├── README.md ├── app-debug.apk ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── lanxingren │ │ └── testjumptowechat │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── lanxingren │ │ │ └── testjumptowechat │ │ │ ├── Constant.java │ │ │ ├── MainActivity.java │ │ │ └── WeChatService.java │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ └── activity_main.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ │ └── xml │ │ └── config_accessibility.xml │ └── test │ └── java │ └── com │ └── lanxingren │ └── testjumptowechat │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | .idea 4 | /local.properties 5 | /.idea/caches/build_file_checksums.ser 6 | /.idea/libraries 7 | /.idea/modules.xml 8 | /.idea/workspace.xml 9 | .DS_Store 10 | /build 11 | /captures 12 | .externalNativeBuild 13 | /app/build -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # JumpToWeChat 2 | 通过AccessibilityService服务直接从app内部跳转到指定好友聊天界面。 3 | -------------------------------------------------------------------------------- /app-debug.apk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lxr17/JumpToWeChat/b223f525587c743a7fbdfb4f73866082c6862cec/app-debug.apk -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 27 5 | defaultConfig { 6 | applicationId "com.lanxingren.testjumptowechat" 7 | minSdkVersion 21 8 | targetSdkVersion 27 9 | versionCode 1 10 | versionName "1.0" 11 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 12 | } 13 | buildTypes { 14 | release { 15 | minifyEnabled false 16 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 17 | } 18 | } 19 | } 20 | 21 | dependencies { 22 | implementation fileTree(dir: 'libs', include: ['*.jar']) 23 | implementation 'com.android.support:appcompat-v7:27.1.1' 24 | implementation 'com.android.support.constraint:constraint-layout:1.1.3' 25 | testImplementation 'junit:junit:4.12' 26 | androidTestImplementation 'com.android.support.test:runner:1.0.2' 27 | androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2' 28 | } 29 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/lanxingren/testjumptowechat/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.lanxingren.testjumptowechat; 2 | 3 | import android.content.Context; 4 | import android.support.test.InstrumentationRegistry; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | 10 | import static org.junit.Assert.*; 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see Testing documentation 16 | */ 17 | @RunWith(AndroidJUnit4.class) 18 | public class ExampleInstrumentedTest { 19 | @Test 20 | public void useAppContext() { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getTargetContext(); 23 | 24 | assertEquals("com.xiaositv.testjumptowechat", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 28 | 29 | 30 | 31 | 32 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/lanxingren/testjumptowechat/Constant.java: -------------------------------------------------------------------------------- 1 | package com.lanxingren.testjumptowechat; 2 | 3 | /** 4 | * Created by he_jhua on 2019/1/17. 5 | */ 6 | public class Constant { 7 | 8 | /** 9 | * 判断是否需要监听 10 | */ 11 | public static int flag = 0; 12 | 13 | /** 14 | * 微信号 15 | */ 16 | public static String wechatId; 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/lanxingren/testjumptowechat/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.lanxingren.testjumptowechat; 2 | 3 | import android.content.ComponentName; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.AppCompatActivity; 7 | import android.view.View; 8 | import android.widget.Button; 9 | import android.widget.EditText; 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | @Override 14 | protected void onCreate(Bundle savedInstanceState) { 15 | super.onCreate(savedInstanceState); 16 | setContentView(R.layout.activity_main); 17 | 18 | final EditText editText = findViewById(R.id.et_wechat_id); 19 | Button clearButton = findViewById(R.id.btn_clear); 20 | Button jumpButton = findViewById(R.id.btn_jump); 21 | 22 | clearButton.setOnClickListener(new View.OnClickListener() { 23 | @Override 24 | public void onClick(View v) { 25 | editText.setText(""); 26 | } 27 | }); 28 | 29 | jumpButton.setOnClickListener(new View.OnClickListener() { 30 | @Override 31 | public void onClick(View view) { 32 | Intent intent = new Intent(Intent.ACTION_MAIN); 33 | ComponentName cmp = new ComponentName("com.tencent.mm", "com.tencent.mm.ui.LauncherUI"); 34 | intent.addCategory(Intent.CATEGORY_LAUNCHER); 35 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 36 | intent.setComponent(cmp); 37 | startActivity(intent); 38 | 39 | Constant.flag = 1; 40 | Constant.wechatId = editText.getText().toString(); 41 | } 42 | }); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/java/com/lanxingren/testjumptowechat/WeChatService.java: -------------------------------------------------------------------------------- 1 | package com.lanxingren.testjumptowechat; 2 | 3 | import android.accessibilityservice.AccessibilityService; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.util.Log; 7 | import android.view.accessibility.AccessibilityEvent; 8 | import android.view.accessibility.AccessibilityNodeInfo; 9 | 10 | import java.util.List; 11 | 12 | /** 13 | * Created by he_jhua on 2019/1/17. 14 | */ 15 | public class WeChatService extends AccessibilityService { 16 | private final String TAG = "WeChatService_TAG"; 17 | 18 | /** 19 | * 微信主页面的“搜索”按钮id 20 | */ 21 | private final String SEARCH_ID = "com.tencent.mm:id/ij"; 22 | 23 | /** 24 | * 微信主页面bottom的“微信”按钮id 25 | */ 26 | private final String WECHAT_ID = "com.tencent.mm:id/d3t"; 27 | 28 | /** 29 | * 微信搜索页面的输入框id 30 | */ 31 | private final String EDIT_TEXT_ID = "com.tencent.mm:id/ka"; 32 | 33 | /** 34 | * 微信主页面ViePage的id 35 | */ 36 | private final String VIEW_PAGE_ID = "com.tencent.mm:id/bko"; 37 | 38 | /** 39 | * 微信主页面活动id 40 | */ 41 | private String LAUNCHER_ACTIVITY_NAME = "com.tencent.mm.ui.LauncherUI"; 42 | 43 | /** 44 | * 微信搜索页面活动id 45 | */ 46 | private String SEARCH_ACTIVITY_NAME = "com.tencent.mm.plugin.fts.ui.FTSMainUI"; 47 | 48 | /** 49 | * 微信备注组件id 50 | */ 51 | private String USERNAME_ID = "com.tencent.mm:id/jw"; 52 | 53 | private String LIST_VIEW_NAME = "android.widget.ListView"; 54 | private String WECHAT_TEXT_ID = "com.tencent.mm:id/km"; 55 | 56 | Handler handler = new Handler(); 57 | Runnable runnable = new Runnable() { 58 | @Override 59 | public void run() { 60 | Constant.flag = 0; 61 | Constant.wechatId = null; 62 | } 63 | }; 64 | 65 | @Override 66 | public void onAccessibilityEvent(AccessibilityEvent event) { 67 | 68 | // 两秒后如果还没有任何的事件,则停止监听 69 | handler.removeCallbacks(runnable); 70 | handler.postDelayed(runnable, 2000); 71 | 72 | Log.e(TAG, event.getEventType() + ""); 73 | Log.e(TAG, event.getClassName() + ""); 74 | 75 | // 只有从app进入微信才进行监听 76 | if (Constant.flag == 0) { 77 | return; 78 | } 79 | 80 | // 页面改变时需要延迟一段时间进行布局加载 81 | if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { 82 | try { 83 | Thread.sleep(300); 84 | } catch (InterruptedException e) { 85 | e.printStackTrace(); 86 | } 87 | } 88 | 89 | if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && !LAUNCHER_ACTIVITY_NAME.equals(event.getClassName().toString()) && !SEARCH_ACTIVITY_NAME.equals(event.getClassName().toString())) { 90 | // 如果当前页面不是微信主页面也不是微信搜索页面,就模拟点击返回键 91 | performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); 92 | return; 93 | } else if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED && LAUNCHER_ACTIVITY_NAME.equals(event.getClassName().toString())) { 94 | List list = event.getSource().findAccessibilityNodeInfosByViewId(USERNAME_ID); 95 | if (list.size() > 0) { 96 | // 如果是微信主页面,但是是微信聊天页面,则模拟点击返回键 97 | performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK); 98 | return; 99 | } 100 | } 101 | 102 | // 用getRootInActiveWindow是为了防止找不到搜索按钮的问题 103 | List searchNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(SEARCH_ID); 104 | List wechatNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(WECHAT_ID); 105 | List viewPageNode = getRootInActiveWindow().findAccessibilityNodeInfosByViewId(VIEW_PAGE_ID); 106 | 107 | Log.e(TAG, "searchNode:" + searchNode.size()); 108 | Log.e(TAG, "viewPageNode:" + viewPageNode.size()); 109 | 110 | // 由于搜索控件在多个页面都有,所以还得判断是否在主页面 111 | if (searchNode.size() > 1 && viewPageNode.size() > 0) { 112 | // 点击“搜索”按钮 113 | if (searchNode.get(0).getParent().isClickable()) { 114 | searchNode.get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); 115 | return; 116 | } 117 | } else if (searchNode.size() == 1) { 118 | // 如果在“我”页面,则进入“微信”页面 119 | for (AccessibilityNodeInfo info : wechatNode) { 120 | if (info.getText().toString().equals("微信") && !info.isChecked()) { 121 | 122 | if (info.getParent().isClickable()) { 123 | info.getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); 124 | return; 125 | } 126 | break; 127 | } 128 | } 129 | } 130 | 131 | // 当前页面是搜索页面 132 | if (SEARCH_ACTIVITY_NAME.equals(event.getClassName().toString())) { 133 | 134 | List editTextNode = event.getSource().findAccessibilityNodeInfosByViewId(EDIT_TEXT_ID); 135 | 136 | if (editTextNode.size() > 0) { 137 | 138 | /*ClipboardManager clipboard = (ClipboardManager) this.getSystemService(Context.CLIPBOARD_SERVICE); 139 | ClipData clip = ClipData.newPlainText("text", "tianheng48"); 140 | clipboard.setPrimaryClip(clip); 141 | //焦点(n是AccessibilityNodeInfo对象) 142 | editTextNode.get(0).performAction(AccessibilityNodeInfo.ACTION_FOCUS); 143 | ////粘贴进入内容 144 | editTextNode.get(0).performAction(AccessibilityNodeInfo.ACTION_PASTE);*/ 145 | 146 | try { 147 | Thread.sleep(300); 148 | } catch (InterruptedException e) { 149 | e.printStackTrace(); 150 | } 151 | 152 | // 输入框内清空 153 | Bundle clear = new Bundle(); 154 | clear.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, ""); 155 | editTextNode.get(0).performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, clear); 156 | 157 | // 输入框内输入查询的微信号 158 | Bundle arguments = new Bundle(); 159 | arguments.putCharSequence(AccessibilityNodeInfo.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, Constant.wechatId); 160 | editTextNode.get(0).performAction(AccessibilityNodeInfo.ACTION_SET_TEXT, arguments); 161 | } 162 | 163 | } else if (LIST_VIEW_NAME.equals(event.getClassName().toString())) { 164 | // 如果监听到了ListView的内容改变,则找到查询到的人,并点击进入 165 | List textNodeList = event.getSource().findAccessibilityNodeInfosByText("微信号: " + Constant.wechatId); 166 | if (textNodeList.size() > 0) { 167 | textNodeList.get(0).getParent().performAction(AccessibilityNodeInfo.ACTION_CLICK); 168 | 169 | // 模拟点击之后将暂存值置空,类似于取消监听 170 | Constant.flag = 0; 171 | Constant.wechatId = null; 172 | } 173 | } 174 | 175 | } 176 | 177 | @Override 178 | public void onInterrupt() { 179 | Log.e(TAG, "onInterrupt"); 180 | } 181 | 182 | @Override 183 | protected void onServiceConnected() { 184 | super.onServiceConnected(); 185 | Log.e(TAG, "connected"); 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 11 | 12 | 18 | 19 |