Android基于AccessibilityService制作的釘釘自動(dòng)簽到程序代碼
前兩天公司開(kāi)始宣布要使用阿里釘釘來(lái)簽到啦?。。~這就意味著,我必須老老實(shí)實(shí)每天按時(shí)簽到上班下班了,這真是一個(gè)悲傷的消息,可是?。。。∧敲礄C(jī)智(lan)的我,怎么可能就這么屈服?。?!阿里釘釘簽到,說(shuō)到底不就是手機(jī)軟件簽到嗎?我就是干移動(dòng)開(kāi)發(fā)的,做一個(gè)小應(yīng)用每天自動(dòng)簽到不就行了:)
說(shuō)干就干,首先分析一下,阿里釘釘?shù)暮灥搅鞒蹋?
打開(kāi)阿里釘釘->廣告頁(yè)停留2S左右->進(jìn)入主頁(yè)->點(diǎn)擊“工作”tab->點(diǎn)擊“簽到”模塊->進(jìn)入簽到頁(yè)面(可能會(huì)再次出現(xiàn)廣告和對(duì)話框)->點(diǎn)擊簽到
我們操作手機(jī)的過(guò)程就是這樣,要實(shí)現(xiàn)這些點(diǎn)擊,很自然想起了前段時(shí)間做的微信搶紅包小應(yīng)用,利用AccessibilityService服務(wù)幫助我們實(shí)現(xiàn)這些自動(dòng)化操作。
以上是分析過(guò)程,接下來(lái)是我對(duì)這個(gè)小功能實(shí)現(xiàn)的具體方案思路:
將測(cè)試手機(jī)放公司并且安裝這個(gè)應(yīng)用,通過(guò)我遠(yuǎn)程的電話撥打或者短信發(fā)送到測(cè)試手機(jī)(只要能產(chǎn)生廣播或者信息的就行),測(cè)試手機(jī)接受到廣播信息,喚醒釘釘,進(jìn)入釘釘頁(yè)面,AccessibilityService開(kāi)始工作,進(jìn)行一系列點(diǎn)擊簽到操作,結(jié)束操作后退出釘釘,簽到完成。
通過(guò)以上過(guò)程的分析我們大概要用到的知識(shí)有以下幾塊:
1. 喚醒非自己的其他第三方應(yīng)用
2. 廣播
3. AccessibilityService服務(wù)
以下是對(duì)這三部分代碼實(shí)現(xiàn):
喚醒第三方應(yīng)用
package net.fenzz.dingplug;
import java.util.List;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ResolveInfo;
public class Utils {
public static void openCLD(String packageName,Context context) {
PackageManager packageManager = context.getPackageManager();
PackageInfo pi = null;
try {
pi = packageManager.getPackageInfo("com.alibaba.android.rimet", 0);
} catch (NameNotFoundException e) {
}
Intent resolveIntent = new Intent(Intent.ACTION_MAIN, null);
resolveIntent.addCategory(Intent.CATEGORY_LAUNCHER);
resolveIntent.setPackage(pi.packageName);
List<ResolveInfo> apps = packageManager.queryIntentActivities(resolveIntent, 0);
ResolveInfo ri = apps.iterator().next();
if (ri != null ) {
String className = ri.activityInfo.name;
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.addCategory(Intent.CATEGORY_LAUNCHER);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
ComponentName cn = new ComponentName(packageName, className);
intent.setComponent(cn);
context.startActivity(intent);
}
}
}
接受電話廣播并且喚醒釘釘:
mainifest先注冊(cè)監(jiān)聽(tīng)器
<!-- 注冊(cè)監(jiān)聽(tīng)手機(jī)狀態(tài) -->
<receiver android:name=".PhoneReceiver">
<intent-filter android:priority="1000" >
<action android:name="android.intent.action.PHONE_STATE" />
</intent-filter>
</receiver>
相關(guān)權(quán)限
<!-- 讀取手機(jī)狀態(tài)的權(quán)限 --> <uses-permission android:name="android.permission.READ_PHONE_STATE" /> <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
代碼
package net.fenzz.dingplug;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.TelephonyManager;
import android.app.Service;
import android.util.Log;
public class PhoneReceiver extends BroadcastReceiver {
private static final String TAG = "message";
private static boolean mIncomingFlag = false;
private static String mIncomingNumber = null;
@Override
public void onReceive(Context context, Intent intent) {
// 如果是撥打電話
if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
mIncomingFlag = false;
String phoneNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
Log.i(TAG, "call OUT:" + phoneNumber);
} else {
// 如果是來(lái)電
TelephonyManager tManager = (TelephonyManager) context
.getSystemService(Service.TELEPHONY_SERVICE);
switch (tManager.getCallState()) {
case TelephonyManager.CALL_STATE_RINGING:
mIncomingNumber = intent.getStringExtra("incoming_number");
Log.i(TAG, "RINGING :" + mIncomingNumber);
if(mIncomingNumber!=null&&mIncomingNumber.equals(你的手機(jī)號(hào))){
Utils.openCLD("com.alibaba.android.rimet", context);
DingService.instance.setServiceEnable();
}
break;
case TelephonyManager.CALL_STATE_OFFHOOK:
if (mIncomingFlag) {
Log.i(TAG, "incoming ACCEPT :" + mIncomingNumber);
}
break;
case TelephonyManager.CALL_STATE_IDLE:
if (mIncomingFlag) {
Log.i(TAG, "incoming IDLE");
}
break;
}
}
}
}
AccessibilityService服務(wù)實(shí)現(xiàn):
相關(guān)權(quán)限及注冊(cè):
<uses-permission android:name="android.permission.BIND_ACCESSIBILITY_SERVICE" />
<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/red_service_config" />
</service>
需要在res文件夾下新建一個(gè)xml文件夾里面放入一個(gè)這樣的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:notificationTimeout="100" android:packageNames="com.alibaba.android.rimet" />
代碼:
package net.fenzz.dingplug;
import java.util.ArrayList;
import java.util.List;
import android.accessibilityservice.AccessibilityService;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
import android.widget.Toast;
public class DingService extends AccessibilityService {
private String TAG = getClass().getSimpleName();
private boolean isFinish = false;
public static DingService instance;
private int index = 1;
/**
* 獲取到短信通知
* 0.喚醒屏幕
* 1.打開(kāi)釘釘
* 2.確保當(dāng)前頁(yè)是主頁(yè)界面
* 3.找到“工作”tab并且點(diǎn)擊
* 4.確保到達(dá)簽到頁(yè)面
* 5.找到簽到按鈕,并且點(diǎn)擊
* 6.判斷簽到是否成功
* 1.成功,退出程序
* 2.失敗,返回到主頁(yè),重新從1開(kāi)始簽到
*/
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
// TODO Auto-generated method stub
// final int eventType = event.getEventType();
ArrayList<String> texts = new ArrayList<String>();
Log.i(TAG, "事件---->" + event.getEventType());
if(isFinish){
return;
}
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if(nodeInfo == null) {
Log.w(TAG, "rootWindow為空");
return ;
}
// nodeInfo.
// System.out.println("nodeInfo"+nodeInfo);
System.out.println("index:"+index);
switch (index) {
case 1: //進(jìn)入主頁(yè)
OpenHome(event.getEventType(),nodeInfo);
break;
case 2: //進(jìn)入簽到頁(yè)
OpenQianDao(event.getEventType(),nodeInfo);
break;
case 3:
doQianDao(event.getEventType(),nodeInfo);
break;
default:
break;
}
}
private ArrayList<String> getTextList(AccessibilityNodeInfo node,ArrayList<String> textList){
if(node == null) {
Log.w(TAG, "rootWindow為空");
return null;
}
if(textList==null){
textList = new ArrayList<String>();
}
String text = node.getText().toString();
if(text!=null&&text.equals("")){
textList.add(text);
}
// node.get
return null;
}
private void OpenHome(int type,AccessibilityNodeInfo nodeInfo) {
if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
//判斷當(dāng)前是否是釘釘主頁(yè)
List<AccessibilityNodeInfo> homeList = nodeInfo.findAccessibilityNodeInfosByText("工作");
if(!homeList.isEmpty()){
//點(diǎn)擊
boolean isHome = click( "工作");
System.out.println("---->"+isHome);
index = 2;
System.out.println("點(diǎn)擊進(jìn)入主頁(yè)簽到");
}
}
}
private void OpenQianDao(int type,AccessibilityNodeInfo nodeInfo) {
if(type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){
//判斷當(dāng)前是否是主頁(yè)的簽到頁(yè)
List<AccessibilityNodeInfo> qianList = nodeInfo.findAccessibilityNodeInfosByText("工作");
if(!qianList.isEmpty()){
boolean ret = click( "簽到");
index = 3;
System.out.println("點(diǎn)擊進(jìn)入簽到頁(yè)面詳情");
}
// index = ret?3:1;
}
}
private void doQianDao(int type,AccessibilityNodeInfo nodeInfo) {
if(type == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED){
//判斷當(dāng)前頁(yè)是否是簽到頁(yè)
List<AccessibilityNodeInfo> case1 = nodeInfo.findAccessibilityNodeInfosByText("開(kāi)啟我的簽到之旅");
if(!case1.isEmpty()){
click("開(kāi)啟我的簽到之旅");
System.out.println("點(diǎn)擊簽到之旅");
}
List<AccessibilityNodeInfo> case2 = nodeInfo.findAccessibilityNodeInfosByText("我知道了");
if(!case2.isEmpty()){
click("我知道了");
System.out.println("點(diǎn)擊我知道對(duì)話框");
}
List<AccessibilityNodeInfo> case3 = nodeInfo.findAccessibilityNodeInfosByText("簽到");
if(!case3.isEmpty()){
Toast.makeText(getApplicationContext(), "發(fā)現(xiàn)目標(biāo)啦?。~", 1).show();
System.out.println("發(fā)現(xiàn)目標(biāo)啦!");
click("簽到");
isFinish = true;
}
}
// if(type == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED){
// List<AccessibilityNodeInfo> case3 = nodeInfo.findAccessibilityNodeInfosByText("簽到");
// if(!case3.isEmpty()){
// Toast.makeText(getApplicationContext(), "發(fā)現(xiàn)目標(biāo)啦?。~", 1).show();
// }
// }
}
//通過(guò)文字點(diǎn)擊
private boolean click(String viewText){
AccessibilityNodeInfo nodeInfo = getRootInActiveWindow();
if(nodeInfo == null) {
Log.w(TAG, "點(diǎn)擊失敗,rootWindow為空");
return false;
}
List<AccessibilityNodeInfo> list = nodeInfo.findAccessibilityNodeInfosByText(viewText);
if(list.isEmpty()){
//沒(méi)有該文字的控件
Log.w(TAG, "點(diǎn)擊失敗,"+viewText+"控件列表為空");
return false;
}else{
//有該控件
//找到可點(diǎn)擊的父控件
AccessibilityNodeInfo view = list.get(0);
return onclick(view); //遍歷點(diǎn)擊
}
}
private boolean onclick(AccessibilityNodeInfo view){
if(view.isClickable()){
view.performAction(AccessibilityNodeInfo.ACTION_CLICK);
Log.w(TAG, "點(diǎn)擊成功");
return true;
}else{
AccessibilityNodeInfo parent = view.getParent();
if(parent==null){
return false;
}
onclick(parent);
}
return false;
}
//點(diǎn)擊返回按鈕事件
private void back(){
performGlobalAction(AccessibilityService.GLOBAL_ACTION_BACK);
}
@Override
public void onInterrupt() {
// TODO Auto-generated method stub
}
@Override
protected void onServiceConnected() {
// TODO Auto-generated method stub
super.onServiceConnected();
Log.i(TAG, "service connected!");
Toast.makeText(getApplicationContext(), "連接成功!", 1).show();
instance = this;
}
public void setServiceEnable(){
isFinish = false;
Toast.makeText(getApplicationContext(), "服務(wù)可用開(kāi)啟!", 1).show();
index = 1;
}
}
以上基本是所有代碼,這個(gè)小程序中可以不用Activity組件,也可以加一個(gè)小的Activity,用來(lái)作為系統(tǒng)的總開(kāi)關(guān),當(dāng)然也可以自動(dòng)檢測(cè)時(shí)間,來(lái)判斷是否開(kāi)啟服務(wù),這樣就不用Activity了,在這個(gè)小例子中,我使用了一個(gè)小activity,就放了一個(gè)button。
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
Android組件TabHost實(shí)現(xiàn)頁(yè)面中多個(gè)選項(xiàng)卡切換效果
這篇文章主要為大家詳細(xì)介紹了Android組件TabHost實(shí)現(xiàn)頁(yè)面中多個(gè)選項(xiàng)卡切換效果的相關(guān)資料,感興趣的小伙伴們可以參考一下2016-05-05
Android開(kāi)發(fā)手冊(cè)Chip監(jiān)聽(tīng)及ChipGroup監(jiān)聽(tīng)
這篇文章主要為大家介紹了Android開(kāi)發(fā)手冊(cè)Chip監(jiān)聽(tīng)及ChipGroup監(jiān)聽(tīng),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
Android開(kāi)發(fā)中的文件操作工具類FileUtil完整實(shí)例
這篇文章主要介紹了Android開(kāi)發(fā)中的文件操作工具類FileUtil,結(jié)合完整實(shí)例形式分析了Android文件操作的常用技巧,包括文件的獲取、遍歷、搜索、復(fù)制、刪除、判斷等功能,需要的朋友可以參考下2017-11-11
Android編程之簡(jiǎn)單啟動(dòng)畫面實(shí)現(xiàn)方法
這篇文章主要介紹了Android編程之簡(jiǎn)單啟動(dòng)畫面實(shí)現(xiàn)方法,結(jié)合實(shí)例形式較為詳細(xì)的分析了開(kāi)機(jī)啟動(dòng)畫面的制作步驟及布局、Activity跳轉(zhuǎn)、權(quán)限控制等的相關(guān)操作技巧,需要的朋友可以參考下2016-11-11
Android學(xué)習(xí)之Flux架構(gòu)入門
Flux是Facebook在14年提出的一種Web前端架構(gòu),主要用來(lái)處理復(fù)雜的UI邏輯的一致性問(wèn)題(當(dāng)時(shí)是為了解決Web頁(yè)面的消息通知問(wèn)題)。接下來(lái)從其特點(diǎn)和使用上來(lái)介紹Flux架構(gòu)。本文主要目的是讓你對(duì)Flux的一個(gè)架構(gòu)大體面貌有個(gè)了解。2016-08-08
Flutter持久化存儲(chǔ)之?dāng)?shù)據(jù)庫(kù)存儲(chǔ)(sqflite)詳解
這篇文章主要給大家介紹了關(guān)于Flutter持久化存儲(chǔ)之?dāng)?shù)據(jù)庫(kù)存儲(chǔ)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者使用Flutter具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
TextView實(shí)現(xiàn)跑馬燈效果 就這么簡(jiǎn)單!
TextView實(shí)現(xiàn)跑馬燈效果,就這么簡(jiǎn)單輕松實(shí)現(xiàn),這篇文章介紹了TextView制作跑馬燈效果的方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-08-08

