소스 각도 에서 Accessibility Service 를 보다
22232 단어 accessibilityservice소스 코드android
Accessibility Service 의 디자인 은 신체 적 결함 이 있 는 단체 가 안 드 로 이 드 애플 리 케 이 션 을 사용 하 는 것 을 보조 하기 위 한 것 으로 안 드 로 이 드 의 컨트롤 트 리 View,ViewGroup,ViewRootImpl 체 계 를 관통 시 켰 다.system 의 힘 을 빌리다server 프로 세 스 의 중계,Accessibility 이 벤트 를 등록 할 수 있 는 클 라 이언 트 는 systemserver 가 제공 하 는 Accessibility 서 비 스 는 다른 응용 보 기 를 감청 하고 조작 하 는 기능 을 실현 합 니 다.이 기능 은 매우 강력 해서 사용자 의 행동 을 모 의하여 다른 앱 을 조작 할 수 있 으 며 자동화 테스트,위 챗 보너스 쟁탈,자동 답장 등 기능 의 실현 에 자주 사용 된다.
이 글 을 쓰 는 취 지 는 두 가지 가 있다.
4.567917.이전에 안 드 로 이 드 View 컨트롤 트 리 의 그리 기,사건 배포 의 소스 코드 분석 을 완 성 했 고 지식 비축 이 충분 합 니 다4.567917.최근 에 자동화 분야 의 프로젝트 를 접 했 고 무장 애 서 비 스 를 이용 하여 이 루어 진 자동 위 챗 보너스 쟁탈 기능 원리 에 대해 매우 궁금 하 다.
전체 도
아 날로 그 그래프
Accessibility InteractionClient:본질 적 으로 binder 서비스 로 Node 정 보 를 얻 는 데 사 용 됩 니 다Accessibility Manager Service:system 에서 실행server 의 실명 binder 서 비 스 는 전체적인 관리 류 입 니 다
인 스 턴 스 코드
public class AutoDismissService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event == null) {
return;
}
// android crash dialog
dismissAppErrorDialogIfExists(event);
}
private void dismissAppErrorDialogIfExists(AccessibilityEvent event) {
// WINDOW
if ((event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
&& event.getPackageName().equals("android")) {
// "OK" Node
AccessibilityNodeInfo nodeInfo = findViewByText("OK", true);
if (nodeInfo != null) {
//
performViewClick(nodeInfo);
}
}
public AccessibilityNodeInfo findViewByText(String text, boolean clickable) {
//
AccessibilityNodeInfo accessibilityNodeInfo = getRootInActiveWindow();
if (accessibilityNodeInfo == null) {
return null;
}
//
List<AccessibilityNodeInfo> nodeInfoList = accessibilityNodeInfo.findAccessibilityNodeInfosByText(text);
if (nodeInfoList != null && !nodeInfoList.isEmpty()) {
for (AccessibilityNodeInfo nodeInfo : nodeInfoList) {
if (nodeInfo != null && (nodeInfo.isClickable() == clickable)) {
return nodeInfo;
}
}
}
return null;
}
public void performViewClick(AccessibilityNodeInfo nodeInfo) {
if (nodeInfo == null) {
return;
}
// ,
while (nodeInfo != null) {
if (nodeInfo.isClickable()) {
nodeInfo.performAction(AccessibilityNodeInfo.ACTION_CLICK);
break;
}
nodeInfo = nodeInfo.getParent();
}
}
}
이상 은 Accessibility 기능 을 실현 하 는 전형 적 인 JAVA 코드 로 주로 세 가지 기능 과 관련된다.소스 코드 분석
일반적인 AccessibilityEvent 이벤트 종류
번호
종류 이름
촉발 시기
1
TYPE_VIEW_CLICKED
클릭 가능 한 구성 요소 가 클릭 됨
2
TYPE_VIEW_LONG_CLICKED
클릭 가능 한 구성 요소 길 게 누 르 기
3
TYPE_VIEW_SELECTED
구성 요소 가 선택 됨
4
TYPE_VIEW_FOCUSED
구성 요소 가 초점 을 가 져 왔 습 니 다.
5
TYPE_VIEW_TEXT_CHANGED
구성 요소 의 텍스트 변화
6
TYPE_VIEW_SCROLLED
구성 요소 가 미끄러지다
7
TYPE_WINDOW_STATE_CHANGED
dialog 등 이 열 립 니 다.
8
TYPE_NOTIFICATION_STATE_CHANGED
알림 팝 업
9
TYPE_WINDOW_CONTENT_CHANGED
구성 요소 트 리 에 변화 가 생 겼 습 니 다.
onAccessibilityEvent 트리거 프로 세 스
여기 서 TextView.setText 트리거 이벤트 변화 프로 세 스 를 예 로 들 어 분석 합 니 다.
TextView.setText
응용 구성 요소 상태 변화
frameworks/base/core/java/android/widget/TextView.java
private void setText(CharSequence text, BufferType type,
boolean notifyBefore, int oldlen) {
...
notifyViewAccessibilityStateChangedIfNeeded(AccessibilityEvent.CONTENT_CHANGE_TYPE_TEXT);
...
}
public void notifyViewAccessibilityStateChangedIfNeeded(int changeType) {
if (!AccessibilityManager.getInstance(mContext).isEnabled() || mAttachInfo == null) {
return;
}
if (mSendViewStateChangedAccessibilityEvent == null) {
// Runnable,
mSendViewStateChangedAccessibilityEvent =
new SendViewStateChangedAccessibilityEvent();
}
mSendViewStateChangedAccessibilityEvent.runOrPost(changeType);
}
private class SendViewStateChangedAccessibilityEvent implements Runnable {
...
@Override
public void run() {
mPosted = false;
mPostedWithDelay = false;
mLastEventTimeMillis = SystemClock.uptimeMillis();
if (AccessibilityManager.getInstance(mContext).isEnabled()) {
final AccessibilityEvent event = AccessibilityEvent.obtain();
event.setEventType(AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED);
event.setContentChangeTypes(mChangeTypes);
// TYPE_WINDOW_CONTENT_CHANGED
sendAccessibilityEventUnchecked(event);
}
mChangeTypes = 0;
}
...
}
public void sendAccessibilityEventUnchecked(AccessibilityEvent event) {
if (mAccessibilityDelegate != null) {
mAccessibilityDelegate.sendAccessibilityEventUnchecked(this, event);
} else {
sendAccessibilityEventUncheckedInternal(event);
}
}
public void sendAccessibilityEventUnchecked(View host, AccessibilityEvent event) {
host.sendAccessibilityEventUncheckedInternal(event);
}
public void sendAccessibilityEventUncheckedInternal(AccessibilityEvent event) {
if (!isShown()) {
return;
}
...
// TextView View , , , ViewRootImpl
ViewParent parent = getParent();
if (parent != null) {
getParent().requestSendAccessibilityEvent(this, event);
}
}
ViewRootImpl.requestSendAccessibilityEventViewRootImpl 이 사건 을 system 에 보 냅 니 다.server
frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public boolean requestSendAccessibilityEvent(View child, AccessibilityEvent event) {
...
// AccessibilityManager
mAccessibilityManager.sendAccessibilityEvent(event);
return true;
}
frameworks/base/core/java/android/view/accessibility/AccessibilityManager.java
public void sendAccessibilityEvent(AccessibilityEvent event) {
final IAccessibilityManager service;
final int userId;
synchronized (mLock) {
// system_server Accessibility
service = getServiceLocked();
...
}
try {
...
long identityToken = Binder.clearCallingIdentity();
// binder call ,
doRecycle = service.sendAccessibilityEvent(event, userId);
Binder.restoreCallingIdentity(identityToken);
...
} catch (RemoteException re) {
Log.e(LOG_TAG, "Error during sending " + event + " ", re);
} finally {
...
}
}
AccessibilityManagerService.sendAccessibilityEventsystem_server 는 이 벤트 를 각 감청 구성 요소 가 변 하 는 Service 에 배포 합 니 다.
frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
// binder call ,
@Override
public boolean sendAccessibilityEvent(AccessibilityEvent event, int userId) {
synchronized (mLock) {
...
if (mSecurityPolicy.canDispatchAccessibilityEventLocked(event)) {
...
notifyAccessibilityServicesDelayedLocked(event, false);
notifyAccessibilityServicesDelayedLocked(event, true);
}
...
}
return (OWN_PROCESS_ID != Binder.getCallingPid());
}
private void notifyAccessibilityServicesDelayedLocked(AccessibilityEvent event,
boolean isDefault) {
try {
UserState state = getCurrentUserStateLocked();
for (int i = 0, count = state.mBoundServices.size(); i < count; i++) {
Service service = state.mBoundServices.get(i);
if (service.mIsDefault == isDefault) {
if (canDispatchEventToServiceLocked(service, event)) {
// ,
service.notifyAccessibilityEvent(event);
}
}
}
} catch (IndexOutOfBoundsException oobe) {
...
}
}
class Service extends IAccessibilityServiceConnection.Stub
implements ServiceConnection, DeathRecipient {
public void notifyAccessibilityEvent(AccessibilityEvent event) {
synchronized (mLock) {
...
if ((mNotificationTimeout > 0)
&& (eventType != AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED)) {
...
// ,
message = mEventDispatchHandler.obtainMessage(eventType);
} else {
message = mEventDispatchHandler.obtainMessage(eventType, newEvent);
}
mEventDispatchHandler.sendMessageDelayed(message, mNotificationTimeout);
}
}
}
public Handler mEventDispatchHandler = new Handler(mMainHandler.getLooper()) {
@Override
public void handleMessage(Message message) {
final int eventType = message.what;
AccessibilityEvent event = (AccessibilityEvent) message.obj;
notifyAccessibilityEventInternal(eventType, event);
}
};
private void notifyAccessibilityEventInternal(int eventType, AccessibilityEvent event) {
IAccessibilityServiceClient listener;
...
// mServiceInterface bind AccessibilityService, onServiceConnected , binder proxy , system_server
listener = mServiceInterface;
...
try {
listener.onAccessibilityEvent(event);
if (DEBUG) {
Slog.i(LOG_TAG, "Event " + event + " sent to " + listener);
}
} catch (RemoteException re) {
Slog.e(LOG_TAG, "Error during sending " + event + " to " + listener, re);
} finally {
event.recycle();
}
}
AccessibilityService.onAccessibilityEventAPP 는 구성 요소 가 변 하 는 이 벤트 를 수신 하고 해당 하 는 처 리 를 선택 할 수 있 습 니 다.
frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java
// , ,
public abstract void onAccessibilityEvent(AccessibilityEvent event);
// service system_server , IAccessibilityServiceClientWrapper proxy
@Override
public final IBinder onBind(Intent intent) {
return new IAccessibilityServiceClientWrapper(this, getMainLooper(), new Callbacks() {
...
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
AccessibilityService.this.onAccessibilityEvent(event);
}
...
}
}
// binder , handler
public void onAccessibilityEvent(AccessibilityEvent event) {
Message message = mCaller.obtainMessageO(DO_ON_ACCESSIBILITY_EVENT, event);
mCaller.sendMessage(message);
}
@Override
public void executeMessage(Message message) {
switch (message.what) {
case DO_ON_ACCESSIBILITY_EVENT: {
AccessibilityEvent event = (AccessibilityEvent) message.obj;
if (event != null) {
AccessibilityInteractionClient.getInstance().onAccessibilityEvent(event);
//
mCallback.onAccessibilityEvent(event);
...
}
} return;
}
}
getRootInActiveWindow 부모 노드 가 져 오기 프로 세 스find Accessibility NodeInfosByText 를 호출 하기 전에 getRootInActiveWindow 방법 으로 부모 노드 를 가 져 와 야 부모 Accessibility NodeInfo 를 호출 하 는 방법 으로 하위 노드 정 보 를 조회 할 수 있 습 니 다.
AccessibilityService.getRootInActiveWindow
frameworks/base/core/java/android/accessibilityservice/AccessibilityService.java
public AccessibilityNodeInfo getRootInActiveWindow() {
// , Client
return AccessibilityInteractionClient.getInstance().getRootInActiveWindow(mConnectionId);
}
frameworks/base/core/java/android/view/accessibility/AccessibilityInteractionClient.java
public AccessibilityNodeInfo getRootInActiveWindow(int connectionId) {
return findAccessibilityNodeInfoByAccessibilityId(connectionId,
AccessibilityNodeInfo.ACTIVE_WINDOW_ID, AccessibilityNodeInfo.ROOT_NODE_ID,
false, AccessibilityNodeInfo.FLAG_PREFETCH_DESCENDANTS);
}
public AccessibilityNodeInfo findAccessibilityNodeInfoByAccessibilityId(int connectionId,
int accessibilityWindowId, long accessibilityNodeId, boolean bypassCache,
int prefetchFlags) {
...
// binder call system_server, APP , AccessibilityInteractionClient binder , this system_server , binder proxy,
final boolean success = connection.findAccessibilityNodeInfoByAccessibilityId(
accessibilityWindowId, accessibilityNodeId, interactionId, this,
prefetchFlags, Thread.currentThread().getId());
Binder.restoreCallingIdentity(identityToken);
// If the scale is zero the call has failed.
if (success) {
// ,
List<AccessibilityNodeInfo> infos = getFindAccessibilityNodeInfosResultAndClear(
interactionId);
finalizeAndCacheAccessibilityNodeInfos(infos, connectionId);
if (infos != null && !infos.isEmpty()) {
return infos.get(0);
}
}
...
}
Service.findAccessibilityNodeInfoByAccessibilityId여기 서 비 스 는 안 드 로 이 드 의 4 대 구성 요소 인 Service 가 아 닙 니 다.Accessiblit Manager Service Internal 이 라 고 부 르 는 것 이 더 적합 합 니 다.
frameworks/base/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@Override
public boolean findAccessibilityNodeInfoByAccessibilityId(
int accessibilityWindowId, long accessibilityNodeId, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
long interrogatingTid) throws RemoteException {
...
// APP
IAccessibilityInteractionConnection connection = null;
...
resolvedWindowId = resolveAccessibilityWindowIdLocked(accessibilityWindowId);
...
if (!permissionGranted) {
return false;
} else {
connection = getConnectionLocked(resolvedWindowId);
if (connection == null) {
return false;
}
}
...
// callback proxy , system_server , APP APP
connection.findAccessibilityNodeInfoByAccessibilityId(accessibilityNodeId,
partialInteractiveRegion, interactionId, callback, mFetchFlags | flags,
interrogatingPid, interrogatingTid, spec);
...
}
AccessibilityInteractionConnection.findAccessibilityNodeInfoByAccessibilityId여기 서 APP 단 으로 호출 되 었 습 니 다.사실은 onAccessibility Event 호출 절차 와 마찬가지 로 APP->SYSTEM->APP 호출 순서 입 니 다.
frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void findAccessibilityNodeInfoByAccessibilityId(long accessibilityNodeId,
Region interactiveRegion, int interactionId,
IAccessibilityInteractionConnectionCallback callback, int flags,
int interrogatingPid, long interrogatingTid, MagnificationSpec spec) {
ViewRootImpl viewRootImpl = mViewRootImpl.get();
if (viewRootImpl != null && viewRootImpl.mView != null) {
//
viewRootImpl.getAccessibilityInteractionController()
.findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityNodeId,
interactiveRegion, interactionId, callback, flags, interrogatingPid,
interrogatingTid, spec);
} else {
...
}
}
frameworks/base/core/java/android/view/AccessibilityInteractionController.java
private void findAccessibilityNodeInfoByAccessibilityIdUiThread(Message message) {
...
//
List<AccessibilityNodeInfo> infos = mTempAccessibilityNodeInfoList;
infos.clear();
try {
if (mViewRootImpl.mView == null || mViewRootImpl.mAttachInfo == null) {
return;
}
mViewRootImpl.mAttachInfo.mAccessibilityFetchFlags = flags;
View root = null;
if (accessibilityViewId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
root = mViewRootImpl.mView;
} else {
root = findViewByAccessibilityId(accessibilityViewId);
}
...
} finally {
try {
...
adjustIsVisibleToUserIfNeeded(infos, interactiveRegion);
// callback binder proxy , binder
callback.setFindAccessibilityNodeInfosResult(infos, interactionId);
infos.clear();
} catch (RemoteException re) {
/* ignore - the other side will time out */
}
...
}
}
AccessibilityInteractionClient.setFindAccessibilityNodeInfosResultframeworks/base/core/java/android/view/accessibility/AccessibilityInteractionClient.java
public void setFindAccessibilityNodeInfosResult(List<AccessibilityNodeInfo> infos,
int interactionId) {
synchronized (mInstanceLock) {
if (interactionId > mInteractionId) {
if (infos != null) {
...
//
if (!isIpcCall) {
mFindAccessibilityNodeInfosResult = new ArrayList<>(infos);
} else {
mFindAccessibilityNodeInfosResult = infos;
}
} else {
mFindAccessibilityNodeInfosResult = Collections.emptyList();
}
mInteractionId = interactionId;
}
// , ,
mInstanceLock.notifyAll();
}
}
findAccessibility NodeInfosByText 와 permAction 은 목표 노드 를 조작 합 니 다.AccessibilityNodeInfo.findAccessibilityNodeInfosByText
부모 노드 정 보 를 찾 으 면 부모 노드 를 통 해 해당 하 는 하위 노드 정 보 를 얻 을 수 있 습 니 다.
frameworks/base/core/java/android/view/accessibility/AccessibilityNodeInfo.java
public List<AccessibilityNodeInfo> findAccessibilityNodeInfosByText(String text) {
...
// , AccessibilityInteractionClient
AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance();
return client.findAccessibilityNodeInfosByText(mConnectionId, mWindowId, mSourceNodeId,
text);
}
```
다음 코드 프로 세 스 는 getRootInActiveWindow 와 대체적으로 일치 하 므 로 상세 하 게 분석 하지 않 습 니 다.#### AccessibilityNodeInfo.performAction
대응 하 는 하위 노드 를 가 져 온 후 permAction 을 통 해 해당 하 는 동작 을 수행 할 수 있 습 니 다.예 를 들 어 자주 사용 하 는 클릭 과 같 습 니 다.
최종 적 으로 Accessibility InteractionController 로 호출 되 었 습 니 다.Accessibility Provier 를 얻 으 면 permaction 의 최종 작업 을 수행 할 수 있 습 니 다.
frameworks/base/core/java/android/view/AccessibilityInteractionController.java
```java
private void performAccessibilityActionUiThread(Message message) {
View target = null;
if (accessibilityViewId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
target = findViewByAccessibilityId(accessibilityViewId);
} else {
target = mViewRootImpl.mView;
}
if (target != null && isShown(target)) {
AccessibilityNodeProvider provider = target.getAccessibilityNodeProvider();
if (provider != null) {
if (virtualDescendantId != AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
// performAction
succeeded = provider.performAction(virtualDescendantId, action,
arguments);
} else {
succeeded = provider.performAction(AccessibilityNodeProvider.HOST_VIEW_ID,
action, arguments);
}
} else if (virtualDescendantId == AccessibilityNodeInfo.UNDEFINED_ITEM_ID) {
succeeded = target.performAccessibilityAction(action, arguments);
}
}
}
frameworks/base/core/java/android/view/View.java
public boolean performAccessibilityActionInternal(int action, Bundle arguments) {
...
switch (action) {
case AccessibilityNodeInfo.ACTION_CLICK: {
if (isClickable()) {
// View.performClick
performClick();
return true;
}
} break;
...
}
분석 을 통 해 알 수 있 듯 이 Accessibility 서비스 프레임 워 크 는 hook 이 Android View 구성 요소 트 리 에서 의 실현 과 유사 하 다.이것 은 독립 된 메커니즘 이 아니 라'기생'이 View 의 디 스 플레이,이벤트 배포 절차 에 있다.총결산
기능 은 ViewRootImpl,ViewGroup,View 보기 등급 관리 에 의존 하 는 기본 구 조 를 실현 합 니 다.보기 가 변 할 때 이 벤트 를 보 내 고 보기 작업 요청 을 받 았 을 때 도 응답 할 수 있 습 니 다.
system_server 는 이 기능 을 실현 하 는 과정 에서 중개인 역할 을 하고 있다.감 청 된 앱 의 보기 가 바 뀌 면 앱 이 먼저 system 로 이 벤트 를 보 냅 니 다.server,그 다음 에 감청 자 앱 으로 넘 어 갑 니 다.감청 자 앱 이 보기 동작 을 수행 하려 고 할 때 도 먼저 systemserver 에서 해당 하 는 클 라 이언 트 binder proxy 를 찾 은 다음 해당 인 터 페 이 스 를 감청 된 앱 으로 호출 합 니 다.해당 작업 을 완료 한 후 이미 가 져 온 감청 앱 binder proxy 핸들 을 통 해 해당 감청 클 라 이언 트 에 직접 binder call 합 니 다.
무장 애 권한 은 매우 중요 하 므 로 남용 해 서 는 안 된다 는 것 을 명심 해 야 한다.앱 자체 도 충분 한 안전 의식 을 가지 고 악성 앱 이 이 서 비 스 를 통 해 사용자 의 프라이버시 정 보 를 얻 는 것 을 방지 해 야 한다.
자,이상 이 이 글 의 전체 내용 입 니 다.본 논문 의 내용 이 여러분 의 학습 이나 업무 에 어느 정도 참고 학습 가치 가 있 기 를 바 랍 니 다.궁금 한 점 이 있 으 시 면 댓 글 을 남 겨 주 셔 서 저희 에 대한 지지 에 감 사 드 립 니 다.
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
iPhone에서 WEB 페이지의 소스 코드를보고 싶을 때~통근중의 전철내에서~ 이번에 닛케이 평균 주가를 스크래핑하여 CSV에 기록하자. 빨리 사이트의 소스 코드를 보자. 뭐야? iPhone으로는 볼 수 없습니까? 곤란했다… 통근 시간 1시간도 있는데… 이제 진짜 무리…...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.