demo示例說明
Manifest聲明
AccessibilityService的XML配置文件
創建繼承自AccessibilityService的服務類
MainActivity檢測服務是否開啟
UiAutomatorViewer
參考
簡介
由於許多Android用戶由於某些原因(視力,身體,年齡)要求他們以不同的方式與手機設備交互。
安卓提供了輔助功能特性和服務來幫助這些用戶更容易的操作他們的設備,包括文字轉語音(原生不支持中文,國內ROM可能會有,我的測試OPPO自帶中文喲!),觸覺反饋,手勢操作,軌跡球和手柄操作。開發者可以利用這些服務使得程序更好用,也可以創建自己的輔助服務。
隨着Android版本的不斷升級,Android Accessibility功能也越來越強大,Android 4.0版本以前,系統輔助服務功能比較單一,僅僅能過單向獲取窗口元素信息,比如獲取輸入框用戶輸入內容。到Android 4.1版本以后(現在主流廠商的版本都在KITKAT4.4),系統輔助服務增加了與窗口元素的雙向交互,此時可以通過輔助功能服務操作窗口元素,比如點擊按鈕等。
由於系統輔助服務能夠實時獲取您當前操作應用的窗口元素信息,這有可能給你帶來隱私信息的泄露風險,比如獲取非密碼輸入框的輸入內容等。同時通過輔助功能也可以模擬用戶自動化點擊應用內元素,也會帶來一定的安全風險。
已經有其他同學使用AccessibilityService實現了搶紅包~以及自動安裝等,大家可以自行百度。
demo示例說明
接下來我們將通過AccessibilityService實現點擊元素之后語言反饋所點擊的內容。
測試輔助抓包應用為【微信】
下載地址
鏈接:http://pan.baidu.com/s/1pJVyo0z 密碼:00i6
Manifest聲明
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.leestar.example.accessibilityservice"
android:versionCode="1"
android:versionName="1.0" >
<uses-sdk
android:minSdkVersion="19"
android:targetSdkVersion="21" />
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<activity
android:name=".MainActivity"
android:label="@string/app_name" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".AccessibilityServiceExample"
android:label="@string/accessibility_service_label"
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/accessibility_service_config" />
</service>
</application>
</manifest>
AccessibilityService的XML配置文件
保存路徑為<project_dir>/res/xml/accessibility_service_config.xml
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_service_description"
android:packageNames="com.tencent.mm"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFlags="flagDefault"
android:accessibilityFeedbackType="feedbackSpoken"
android:notificationTimeout="100"
android:canRetrieveWindowContent="true"
android:settingsActivity="com.example.android.accessibility.ServiceSettingsActivity"
/>
其中
android:packageNames為需要捕獲的包名
android:accessibilityEventTypes為需要回調的事件
android:notificationTimeout為事件回調的延遲事件
其他具體含義請查詢官方文檔~
創建繼承自AccessibilityService的服務類
具體見代碼以及注釋,里面也實現了語言反饋功能。
package com.leestar.example.accessibilityservice;
import android.accessibilityservice.AccessibilityService;
import android.annotation.TargetApi;
import android.content.Intent;
import android.os.Build;
import android.speech.tts.TextToSpeech;
import android.util.Log;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityNodeInfo;
public class AccessibilityServiceExample extends AccessibilityService {
private AccessibilityNodeInfo rootNodeInfo;
private TextToSpeech tts = null;
private boolean ttsIsInit = false;
/**
* 可選。系統會在成功連接上你的服務的時候調用這個方法,在這個方法里你可以做一下初始化工作,例如設備的聲音震動管理,
* 也可以調用setServiceInfo()進行配置工作。
*/
@Override
protected void onServiceConnected() {
// TODO Auto-generated method stub
super.onServiceConnected();
tts = new TextToSpeech(getApplicationContext(), new android.speech.tts.TextToSpeech.OnInitListener() {
@Override
public void onInit(int status) {
if (status == TextToSpeech.SUCCESS) {
ttsIsInit = true;
}
}
});
}
/**
* 可選。在系統將要關閉這個AccessibilityService會被調用。在這個方法中進行一些釋放資源的工作。
*/
@Override
public boolean onUnbind(Intent intent) {
// TODO Auto-generated method stub
return super.onUnbind(intent);
}
/**
* 根據XML的android:accessibilityEventTypes來進行事件回調,所有的業務邏輯都要在回調中完成
*/
@TargetApi(Build.VERSION_CODES.KITKAT)
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
rootNodeInfo = event.getSource();
if (rootNodeInfo == null)
return;
switch (event.getEventType()) {
case AccessibilityEvent.TYPE_VIEW_CLICKED:
// 語音反饋
if (ttsIsInit) {
// event.getContentDescription根據觸發事件節點來自動查找子樹的ContentDescription
//所以你會發現UIAutomatorViewer上的相關節點信息為空,但是event里賦值了。
// event.getText同上
if (event.getContentDescription() != null) {
tts.speak(event.getContentDescription().toString(), TextToSpeech.QUEUE_ADD, null);
} else if (event.getText() != null) {
tts.speak(event.getText().toString(), TextToSpeech.QUEUE_ADD, null);
}
}
// 獲取精確節點信息,可以通過UIAutomatorViewer檢索,實現相關模擬操作
//AccessibilityNodeInfo提供了2種自動查找字節點的函數
//rootNodeInfo.findAccessibilityNodeInfosByText
//rootNodeInfo.findAccessibilityNodeInfosByViewId(viewId)
if (rootNodeInfo.getChild(0) != null
&& rootNodeInfo.getChild(0).getClassName().equals("android.widget.TextView")
&& rootNodeInfo.getChild(0).getText() != null
&& rootNodeInfo.getChild(0).getText().toString().equals("我")) {
// 如果發現按的是【我】,模擬全局返回按鈕,即退出微信
performGlobalAction(GLOBAL_ACTION_BACK);
// 模擬點擊元素
// if (rootNodeInfo.isClickable()) {
// rootNodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
// }
}
break;
default:
break;
}
Log.i("leestar", AccessibilityEvent.eventTypeToString(event.getEventType()));
Log.i("leestar", rootNodeInfo.getClassName().toString());
}
/**
* 這個在系統想要中斷AccessibilityService返給的響應時會調用。在整個生命周期里會被調用多次。
*/
@Override
public void onInterrupt() {
Log.i("leestar", "onInterrupt");
}
}
MainActivity檢測服務是否開啟
package com.leestar.example.accessibilityservice;
import java.util.List;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.provider.Settings;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.widget.Button;
public class MainActivity extends Activity {
private Button button_switch;
@Override
protected void onCreate(Bundle savedInstanceState) {
// TODO Auto-generated method stub
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
button_switch = (Button) findViewById(R.id.button_switch);
}
@Override
protected void onResume() {
super.onResume();
updateServiceStatus();
}
@Override
protected void onDestroy() {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
super.onDestroy();
}
public void onButtonClick(View view) {
startActivity(new Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS));
}
private void updateServiceStatus() {
boolean ServiceEnabled = false;
// 循環遍歷所有服務,查看是否開啟
AccessibilityManager accessibilityManager = (AccessibilityManager) getSystemService(Context.ACCESSIBILITY_SERVICE);
List<AccessibilityServiceInfo> accessibilityServices = accessibilityManager
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
for (AccessibilityServiceInfo info : accessibilityServices) {
if (info.getId().equals(getPackageName() + "/.AccessibilityServiceExample")) {
ServiceEnabled = true;
break;
}
}
if (ServiceEnabled) {
button_switch.setText("關閉測試服務");
// Prevent screen from dimming
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
} else {
button_switch.setText("開啟測試服務");
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
}
}
UiAutomatorViewer
在需要精確元素節點操作檢索的時候,就需要用到UiAutomatorViewer工具,一般情況下,Android SDK下會自帶這個工具
找到uiautomatorviewer.bat即可,打開之后,點擊看起來是這個樣子的
我想你應該知道怎么做了,樹形結構UI,AccessibilityNodeInfo通過getChild、getParent盡情的識別吧~!
參考
http://www.android-doc.com/guide/topics/ui/accessibility/services.html