Skip to content
This repository was archived by the owner on Nov 16, 2023. It is now read-only.

Commit 43359f9

Browse files
authored
feat(focus-mvp-android-device): wire up components for focus visualization (#86)
* feat(focus-mvp-android-device): wire up components for focus visualization * wire up such that everything is functional without tests * updated with tests * style fixes * removed unnecessary redraw event type
1 parent e3ffff2 commit 43359f9

File tree

9 files changed

+182
-81
lines changed

9 files changed

+182
-81
lines changed

AccessibilityInsightsForAndroidService/app/src/main/AndroidManifest.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
package="com.microsoft.accessibilityinsightsforandroidservice">
2121

2222
<uses-permission android:name="android.permission.INTERNET" />
23+
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
2324
<application
2425
android:allowBackup="false"
2526
android:icon="@mipmap/blue_launcher"

AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AccessibilityEventDispatcher.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,16 @@ public AccessibilityEventDispatcher() {
2626

2727
public static List<Integer> redrawEventTypes =
2828
Arrays.asList(
29-
AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
3029
AccessibilityEvent.TYPE_VIEW_SCROLLED,
3130
AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
3231
AccessibilityEvent.TYPE_WINDOWS_CHANGED);
3332

3433
public void onAccessibilityEvent(AccessibilityEvent event, AccessibilityNodeInfo rootNode) {
3534
int eventType = event.getEventType();
3635

37-
if (previousPackageName == null || !previousPackageName.equals(rootNode.getPackageName())) {
36+
if (rootNode != null
37+
&& (previousPackageName == null
38+
|| !previousPackageName.equals(rootNode.getPackageName()))) {
3839
previousPackageName = rootNode.getPackageName();
3940
this.callListeners(onAppChangedListeners, rootNode);
4041
}

AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AccessibilityInsightsForAndroidService.java

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import android.os.Handler;
2525
import android.os.HandlerThread;
2626
import android.util.DisplayMetrics;
27+
import android.view.WindowManager;
2728
import android.view.accessibility.AccessibilityEvent;
2829

2930
public class AccessibilityInsightsForAndroidService extends AccessibilityService {
@@ -39,15 +40,17 @@ public class AccessibilityInsightsForAndroidService extends AccessibilityService
3940
private ScreenshotController screenshotController = null;
4041
private int activeWindowId = -1; // Set initial state to an invalid ID
4142
private FocusVisualizationStateManager focusVisualizationStateManager;
43+
private FocusVisualizer focusVisualizer;
44+
private FocusVisualizerController focusVisualizerController;
45+
private FocusVisualizationCanvas focusVisualizationCanvas;
46+
private AccessibilityEventDispatcher accessibilityEventDispatcher;
4247
private DeviceOrientationHandler deviceOrientationHandler;
4348

4449
public AccessibilityInsightsForAndroidService() {
4550
deviceConfigFactory = new DeviceConfigFactory();
4651
axeScanner =
4752
AxeScannerFactory.createAxeScanner(deviceConfigFactory, this::getRealDisplayMetrics);
4853
eventHelper = new EventHelper(new ThreadSafeSwapper<>());
49-
focusVisualizationStateManager = new FocusVisualizationStateManager();
50-
deviceOrientationHandler = new DeviceOrientationHandler(getResources().getConfiguration().orientation);
5154
}
5255

5356
private DisplayMetrics getRealDisplayMetrics() {
@@ -108,13 +111,31 @@ protected void onServiceConnected() {
108111

109112
StopServerThread();
110113

114+
WindowManager windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
115+
focusVisualizationStateManager = new FocusVisualizationStateManager();
116+
LayoutParamGenerator layoutParamGenerator = new LayoutParamGenerator(this::getRealDisplayMetrics);
117+
focusVisualizationCanvas = new FocusVisualizationCanvas(this);
118+
focusVisualizer = new FocusVisualizer(new FocusVisualizerStyles(), focusVisualizationCanvas);
119+
focusVisualizerController = new FocusVisualizerController(focusVisualizer, focusVisualizationStateManager, new UIThreadRunner(), windowManager, layoutParamGenerator, focusVisualizationCanvas);
120+
accessibilityEventDispatcher = new AccessibilityEventDispatcher();
121+
deviceOrientationHandler = new DeviceOrientationHandler(getResources().getConfiguration().orientation);
122+
123+
setupFocusVisualizationListeners();
124+
111125
ResponseThreadFactory responseThreadFactory =
112126
new ResponseThreadFactory(
113127
screenshotController, eventHelper, axeScanner, deviceConfigFactory, focusVisualizationStateManager);
114128
ServerThread = new ServerThread(new ServerSocketFactory(), responseThreadFactory);
115129
ServerThread.start();
116130
}
117131

132+
private void setupFocusVisualizationListeners() {
133+
accessibilityEventDispatcher.addOnRedrawEventListener(focusVisualizerController::onRedrawEvent);
134+
accessibilityEventDispatcher.addOnFocusEventListener(focusVisualizerController::onFocusEvent);
135+
accessibilityEventDispatcher.addOnAppChangedListener(focusVisualizerController::onAppChanged);
136+
deviceOrientationHandler.subscribe(focusVisualizerController::onOrientationChanged);
137+
}
138+
118139
@Override
119140
public boolean onUnbind(Intent intent) {
120141
Logger.logVerbose(TAG, "*** onUnbind");
@@ -126,6 +147,8 @@ public boolean onUnbind(Intent intent) {
126147

127148
@Override
128149
public void onAccessibilityEvent(AccessibilityEvent event) {
150+
accessibilityEventDispatcher.onAccessibilityEvent(event, getRootInActiveWindow());
151+
129152
// This logic ensures that we only track events from the active window, as
130153
// described under "Retrieving window content" of the Android service docs at
131154
// https://www.android-doc.com/reference/android/accessibilityservice/AccessibilityService.html

AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizer.java

Lines changed: 7 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -24,27 +24,11 @@ public FocusVisualizer(
2424
this.focusVisualizationCanvas = focusVisualizationCanvas;
2525
}
2626

27-
public void HandleAccessibilityRedrawEvent(AccessibilityEvent event) {
28-
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
29-
return;
30-
}
31-
32-
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
33-
this.resetVisualizations();
34-
return;
35-
}
36-
37-
if (event.getEventType() == AccessibilityEvent.TYPE_VIEW_SCROLLED) {
38-
this.updateDrawingsWithNewCoordinates();
39-
return;
40-
}
41-
if (event.getEventType() == AccessibilityEvent.TYPE_WINDOWS_CHANGED) {
42-
this.resetVisualizations();
43-
return;
44-
}
27+
public void refreshHighlights() {
28+
this.updateDrawingsWithNewCoordinates();
4529
}
4630

47-
public void HandleAccessibilityFocusEvent(AccessibilityEvent event) {
31+
public void addNewFocusedElement(AccessibilityEvent event) {
4832
tabStopCount++;
4933

5034
AccessibilityNodeInfo eventSource = event.getSource();
@@ -64,8 +48,10 @@ public void HandleAccessibilityFocusEvent(AccessibilityEvent event) {
6448
this.setDrawItemsAndRedraw();
6549
}
6650

67-
public void setFocusVisualizationCanvas(FocusVisualizationCanvas view) {
68-
this.focusVisualizationCanvas = view;
51+
public void resetVisualizations() {
52+
this.tabStopCount = 0;
53+
this.focusElementHighlights.clear();
54+
this.focusElementLines.clear();
6955
this.setDrawItemsAndRedraw();
7056
}
7157

@@ -106,13 +92,6 @@ private AccessibilityNodeInfo getPreviousEventSource() {
10692
return this.focusElementHighlights.get(this.focusElementHighlights.size() - 1).getEventSource();
10793
}
10894

109-
public void resetVisualizations() {
110-
this.tabStopCount = 0;
111-
this.focusElementHighlights.clear();
112-
this.focusElementLines.clear();
113-
this.setDrawItemsAndRedraw();
114-
}
115-
11695
private void setDrawItemsAndRedraw() {
11796
this.focusVisualizationCanvas.setDrawItems(this.focusElementHighlights, this.focusElementLines);
11897
this.focusVisualizationCanvas.redraw();

AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizerController.java

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,20 +3,31 @@
33

44
package com.microsoft.accessibilityinsightsforandroidservice;
55

6+
import android.view.WindowManager;
67
import android.view.accessibility.AccessibilityEvent;
8+
import android.view.accessibility.AccessibilityNodeInfo;
79

810
public class FocusVisualizerController {
911
private FocusVisualizer focusVisualizer;
1012
private FocusVisualizationStateManager focusVisualizationStateManager;
1113
private UIThreadRunner uiThreadRunner;
14+
private WindowManager windowManager;
15+
private LayoutParamGenerator layoutParamGenerator;
16+
private FocusVisualizationCanvas focusVisualizationCanvas;
1217

1318
public FocusVisualizerController(
1419
FocusVisualizer focusVisualizer,
1520
FocusVisualizationStateManager focusVisualizationStateManager,
16-
UIThreadRunner uiThreadRunner) {
21+
UIThreadRunner uiThreadRunner,
22+
WindowManager windowManager,
23+
LayoutParamGenerator layoutParamGenerator,
24+
FocusVisualizationCanvas focusVisualizationCanvas) {
1725
this.focusVisualizer = focusVisualizer;
1826
this.focusVisualizationStateManager = focusVisualizationStateManager;
1927
this.uiThreadRunner = uiThreadRunner;
28+
this.windowManager = windowManager;
29+
this.layoutParamGenerator = layoutParamGenerator;
30+
this.focusVisualizationCanvas = focusVisualizationCanvas;
2031
this.focusVisualizationStateManager.subscribe(this::onFocusVisualizationStateChange);
2132
}
2233

@@ -25,22 +36,48 @@ public void onFocusEvent(AccessibilityEvent event) {
2536
return;
2637
}
2738

28-
focusVisualizer.HandleAccessibilityFocusEvent(event);
39+
focusVisualizer.addNewFocusedElement(event);
2940
}
3041

3142
public void onRedrawEvent(AccessibilityEvent event) {
3243
if (focusVisualizationStateManager.getState() == false) {
3344
return;
3445
}
3546

36-
focusVisualizer.HandleAccessibilityRedrawEvent(event);
47+
focusVisualizer.refreshHighlights();
3748
}
3849

39-
private void onFocusVisualizationStateChange(boolean newState) {
40-
if (newState) {
50+
public void onAppChanged(AccessibilityNodeInfo nodeInfo) {
51+
if (focusVisualizationStateManager.getState() == false) {
52+
return;
53+
}
54+
55+
focusVisualizer.resetVisualizations();
56+
}
57+
58+
public void onOrientationChanged(Integer orientation) {
59+
if (focusVisualizationStateManager.getState() == false) {
4160
return;
4261
}
4362

44-
uiThreadRunner.run(focusVisualizer::resetVisualizations);
63+
windowManager.updateViewLayout(focusVisualizationCanvas, layoutParamGenerator.get());
64+
focusVisualizer.resetVisualizations();
65+
}
66+
67+
private void onFocusVisualizationStateChange(boolean enabled) {
68+
if (enabled) {
69+
uiThreadRunner.run(this::addFocusVisualizationToScreen);
70+
} else {
71+
uiThreadRunner.run(this::removeFocusVisualizationToScreen);
72+
}
73+
}
74+
75+
private void addFocusVisualizationToScreen() {
76+
windowManager.addView(focusVisualizationCanvas, layoutParamGenerator.get());
77+
}
78+
79+
private void removeFocusVisualizationToScreen() {
80+
focusVisualizer.resetVisualizations();
81+
windowManager.removeView(focusVisualizationCanvas);
4582
}
4683
}

AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AccessibilityEventDispatcherTest.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,17 @@ public void onAppChangedFiresWithoutPreviousPackageName() {
5353
verify(onAppChangedListenerMock, times(1)).accept(rootNodeMock);
5454
}
5555

56+
@Test
57+
public void onAppChangedDoesNotFireWhenRootNodeIsNull() {
58+
int trivialEventType = -1;
59+
when(eventMock.getEventType()).thenReturn(trivialEventType);
60+
61+
testSubject.addOnAppChangedListener(onAppChangedListenerMock);
62+
testSubject.onAccessibilityEvent(eventMock, null);
63+
64+
verify(onAppChangedListenerMock, times(0)).accept(rootNodeMock);
65+
}
66+
5667
@Test
5768
public void onAppChangedFiresWhenPackageNameChanged() {
5869
int trivialEventType = -1;
@@ -103,7 +114,7 @@ public void onRedrawEventListenerFiresOnRedrawEvents() {
103114
reset(eventMock);
104115
});
105116

106-
verify(onRedrawEventListenerMock, times(4)).accept(eventMock);
117+
verify(onRedrawEventListenerMock, times(3)).accept(eventMock);
107118
}
108119

109120
@Test

0 commit comments

Comments
 (0)