├── AccessibilityInsightsForAndroidService
├── app
│ ├── .gitignore
│ ├── src
│ │ ├── main
│ │ │ ├── res
│ │ │ │ ├── values
│ │ │ │ │ ├── blue_launcher_background.xml
│ │ │ │ │ └── strings.xml
│ │ │ │ ├── mipmap-hdpi
│ │ │ │ │ ├── blue_launcher.png
│ │ │ │ │ └── blue_launcher_foreground.png
│ │ │ │ ├── mipmap-mdpi
│ │ │ │ │ ├── blue_launcher.png
│ │ │ │ │ └── blue_launcher_foreground.png
│ │ │ │ ├── mipmap-xhdpi
│ │ │ │ │ ├── blue_launcher.png
│ │ │ │ │ └── blue_launcher_foreground.png
│ │ │ │ ├── mipmap-xxhdpi
│ │ │ │ │ ├── blue_launcher.png
│ │ │ │ │ └── blue_launcher_foreground.png
│ │ │ │ ├── mipmap-xxxhdpi
│ │ │ │ │ ├── blue_launcher.png
│ │ │ │ │ └── blue_launcher_foreground.png
│ │ │ │ ├── mipmap-anydpi-v26
│ │ │ │ │ └── blue_launcher.xml
│ │ │ │ └── xml
│ │ │ │ │ └── accessibility_insights_service.xml
│ │ │ ├── java
│ │ │ │ └── com
│ │ │ │ │ └── microsoft
│ │ │ │ │ └── accessibilityinsightsforandroidservice
│ │ │ │ │ ├── RunnableFunction.java
│ │ │ │ │ ├── DateProvider.java
│ │ │ │ │ ├── ScanException.java
│ │ │ │ │ ├── ImageFormatException.java
│ │ │ │ │ ├── RequestFulfiller.java
│ │ │ │ │ ├── ATFAScannerFactory.java
│ │ │ │ │ ├── ByteArrayOutputStreamProvider.java
│ │ │ │ │ ├── UIThreadRunner.java
│ │ │ │ │ ├── BitmapProvider.java
│ │ │ │ │ ├── AxeRectProvider.java
│ │ │ │ │ ├── ViewChangedException.java
│ │ │ │ │ ├── ResultsV2Container.java
│ │ │ │ │ ├── RequestReaderFactory.java
│ │ │ │ │ ├── StackTrace.java
│ │ │ │ │ ├── OnScreenshotAvailableProvider.java
│ │ │ │ │ ├── OffsetHelper.java
│ │ │ │ │ ├── DeviceConfig.java
│ │ │ │ │ ├── UnrecognizedRequestFulfiller.java
│ │ │ │ │ ├── MediaProjectionHolder.java
│ │ │ │ │ ├── AxeImageFactory.java
│ │ │ │ │ ├── ThreadSafeSwapper.java
│ │ │ │ │ ├── NodeViewBuilderFactory.java
│ │ │ │ │ ├── TabStopsRequestFulfiller.java
│ │ │ │ │ ├── DisplayMetricsHelper.java
│ │ │ │ │ ├── RootNodeFinder.java
│ │ │ │ │ ├── AxeRunnerFactory.java
│ │ │ │ │ ├── OrderedValue.java
│ │ │ │ │ ├── RequestReader.java
│ │ │ │ │ ├── EventHelper.java
│ │ │ │ │ ├── WorkManagerHolder.java
│ │ │ │ │ ├── AxeScanner.java
│ │ │ │ │ ├── Logger.java
│ │ │ │ │ ├── FocusVisualizationStateManager.java
│ │ │ │ │ ├── AxeScannerFactory.java
│ │ │ │ │ ├── DeviceOrientationHandler.java
│ │ │ │ │ ├── DeviceConfigFactory.java
│ │ │ │ │ ├── LayoutParamGenerator.java
│ │ │ │ │ ├── AxeDeviceFactory.java
│ │ │ │ │ ├── ScreenshotActivity.java
│ │ │ │ │ ├── ConfigRequestFulfiller.java
│ │ │ │ │ ├── AxeContextFactory.java
│ │ │ │ │ ├── ScreenshotAxeImage.java
│ │ │ │ │ ├── FocusVisualizationCanvas.java
│ │ │ │ │ ├── AccessibilityNodeInfoQueueBuilder.java
│ │ │ │ │ ├── SynchronizedRequestDispatcher.java
│ │ │ │ │ ├── ResultsV2ContainerSerializer.java
│ │ │ │ │ ├── ATFAResultsSerializer.java
│ │ │ │ │ ├── FocusElementLine.java
│ │ │ │ │ ├── ATFAScanner.java
│ │ │ │ │ ├── RequestDispatcher.java
│ │ │ │ │ ├── AccessibilityEventDispatcher.java
│ │ │ │ │ ├── NodeViewBuilder.java
│ │ │ │ │ ├── FocusElementHighlight.java
│ │ │ │ │ ├── ScreenshotController.java
│ │ │ │ │ ├── FocusVisualizer.java
│ │ │ │ │ ├── AccessibilityInsightsContentProvider.java
│ │ │ │ │ ├── TempFileProvider.java
│ │ │ │ │ ├── AxeViewsFactory.java
│ │ │ │ │ ├── ResultV2RequestFulfiller.java
│ │ │ │ │ └── FocusVisualizerController.java
│ │ │ └── AndroidManifest.xml
│ │ └── test
│ │ │ └── java
│ │ │ └── com
│ │ │ └── microsoft
│ │ │ └── accessibilityinsightsforandroidservice
│ │ │ ├── DisplayMetricsStub.java
│ │ │ ├── AxeRunnerFactoryTest.java
│ │ │ ├── BoundingRectProviderTest.java
│ │ │ ├── ATFAScannerFactoryTest.java
│ │ │ ├── ByteArrayOutputStreamProviderTest.java
│ │ │ ├── AxeScannerFactoryTest.java
│ │ │ ├── AxeImageFactoryTest.java
│ │ │ ├── NodeViewBuilderFactoryTest.java
│ │ │ ├── DeviceConfigTest.java
│ │ │ ├── TabStopsRequestFulfillerTest.java
│ │ │ ├── OffsetHelperTest.java
│ │ │ ├── UnrecognizedRequestFulfillerTest.java
│ │ │ ├── BitmapProviderTest.java
│ │ │ ├── DeviceOrientationHandlerTest.java
│ │ │ ├── AxeDeviceFactoryTest.java
│ │ │ ├── AxeScannerTest.java
│ │ │ ├── FocusVisualizationStateManagerTest.java
│ │ │ ├── UIThreadRunnerTest.java
│ │ │ ├── LayoutParamGeneratorTest.java
│ │ │ ├── AxeContextFactoryTest.java
│ │ │ ├── RequestReaderTest.java
│ │ │ ├── DisplayMetricsHelperTest.java
│ │ │ ├── ThreadSafeSwapperTest.java
│ │ │ ├── DeviceConfigFactoryTest.java
│ │ │ ├── RootNodeFinderTest.java
│ │ │ ├── FocusVisualizerStylesTest.java
│ │ │ ├── EventHelperTest.java
│ │ │ ├── FocusVisualizationCanvasTest.java
│ │ │ ├── ScreenshotAxeImageTest.java
│ │ │ ├── LoggerTest.java
│ │ │ ├── ConfigRequestFulfillerTest.java
│ │ │ └── AccessibilityNodeInfoQueueBuilderTest.java
│ └── proguard-rules.pro
├── .idea
│ ├── .name
│ ├── codeStyles
│ │ ├── codeStyleConfig.xml
│ │ └── Project.xml
│ ├── compiler.xml
│ ├── vcs.xml
│ ├── google-java-format.xml
│ ├── misc.xml
│ └── gradle.xml
├── gradle
│ └── wrapper
│ │ ├── gradle-wrapper.jar
│ │ └── gradle-wrapper.properties
├── settings.gradle
├── .gitignore
├── cgmanifest.json
├── gradle.properties
├── gradlew.bat
└── build.gradle
├── npm-wrapper
├── .gitignore
├── index.d.ts
├── index.js
├── package.json
└── README.md
├── .github
├── CODEOWNERS
├── dependabot.yml
├── ISSUE_TEMPLATE
│ ├── general_question.md
│ ├── feature_request.md
│ └── bug_report.md
├── pull_request_template.md
└── workflows
│ ├── combine-dependabot-prs.yml
│ └── codeql-analysis.yml
├── .gitattributes
├── CODE_OF_CONDUCT.md
├── guardian
└── SDL
│ └── .gdnbaselines
├── CONTRIBUTING.md
├── pipeline
├── e2e-emulator-template.yaml
├── infer-version-code-from-version-name.js
├── verify-clean-lockfile.js
├── scripts
│ └── setup-emulator.sh
└── validation-build.yaml
├── LICENSE
└── SECURITY.md
/AccessibilityInsightsForAndroidService/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/.name:
--------------------------------------------------------------------------------
1 | Accessibility Insights For Android Service
--------------------------------------------------------------------------------
/npm-wrapper/.gitignore:
--------------------------------------------------------------------------------
1 | # These files are lifted from ../ during release builds
2 | NOTICE.*
3 | LICENSE
4 | *.apk
5 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # default owners for everything in the repository.
2 | * @Microsoft/accessibility-insights-code-owners
3 |
--------------------------------------------------------------------------------
/npm-wrapper/index.d.ts:
--------------------------------------------------------------------------------
1 | export const apkPath: string;
2 | export const apkVersionName: string;
3 | export const noticePath: string;
4 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/values/blue_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #FFFFFF
4 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/npm-wrapper/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | apkPath: `${__dirname}/AccessibilityInsightsforAndroidService.apk`,
3 | apkVersionName: require('./package.json').version,
4 | noticePath: `${__dirname}/NOTICE.html`,
5 | };
6 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-hdpi/blue_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-hdpi/blue_launcher.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-mdpi/blue_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-mdpi/blue_launcher.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/google-java-format.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xhdpi/blue_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xhdpi/blue_launcher.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxhdpi/blue_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxhdpi/blue_launcher.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxxhdpi/blue_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxxhdpi/blue_launcher.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-hdpi/blue_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-hdpi/blue_launcher_foreground.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-mdpi/blue_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-mdpi/blue_launcher_foreground.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xhdpi/blue_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xhdpi/blue_launcher_foreground.png
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text eol=lf
2 |
3 | # Gradle always writes its lockfile with OS-default line endings
4 | # and can't be configured to use LF-only
5 | gradle.lockfile text -eol
6 |
7 | # Denote all files that are truly binary and should not be modified.
8 | *.png binary
9 | *.jar binary
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxhdpi/blue_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxhdpi/blue_launcher_foreground.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxxhdpi/blue_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/microsoft/accessibility-insights-for-android-service/main/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-xxxhdpi/blue_launcher_foreground.png
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Mon Aug 24 10:57:42 EDT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-all.zip
7 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/settings.gradle:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | include ':app'
5 | rootProject.name='Accessibility Insights For Android Service'
6 |
7 | // This is easier to manage and will become the default in Gradle 7
8 | enableFeaturePreview('ONE_LOCKFILE_PER_PROJECT')
9 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | updates:
3 | - package-ecosystem: "gradle"
4 | directory: "/AccessibilityInsightsForAndroidService/app/"
5 | labels:
6 | - "category: engineering"
7 | - "dependencies"
8 | commit-message:
9 | prefix: "chore"
10 | include: "scope"
11 | schedule:
12 | interval: "daily"
13 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/mipmap-anydpi-v26/blue_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/RunnableFunction.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | @FunctionalInterface
7 | public interface RunnableFunction {
8 | void run();
9 | }
10 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/DateProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.util.Date;
7 |
8 | public class DateProvider {
9 | public Date get() {
10 | return new Date();
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ScanException.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | public class ScanException extends Exception {
7 |
8 | public ScanException(String message) {
9 | super(message);
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.gitignore:
--------------------------------------------------------------------------------
1 | .cxx
2 | .DS_Store
3 | .externalNativeBuild
4 | .gradle
5 | *.iml
6 | /.idea/assetWizardSettings.xml
7 | /.idea/caches
8 | /.idea/jarRepositories.xml
9 | /.idea/libraries
10 | /.idea/modules.xml
11 | /.idea/navEditor.xml
12 | /.idea/workspace.xml
13 | /.idea/deploymentTargetDropDown.xml
14 | /.idea/runConfigurations.xml
15 | /build
16 | /captures
17 | /local.properties
18 | libs
19 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ImageFormatException.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | class ImageFormatException extends Exception {
7 | public ImageFormatException(String message) {
8 | super(message);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/RequestFulfiller.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.CancellationSignal;
7 |
8 | public interface RequestFulfiller {
9 | String fulfillRequest(CancellationSignal cancellationSignal) throws Exception;
10 | }
11 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/cgmanifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://json.schemastore.org/component-detection-manifest.json",
3 | "Registrations": [
4 | {
5 | "component": {
6 | "type": "git",
7 | "git": {
8 | "repositoryUrl": "https://github.com/googlecodelabs/android-accessibility",
9 | "commitHash": "d78994bf77b2438f4bc31e557d4c12e35a0219fb"
10 | }
11 | }
12 | }
13 | ],
14 | "Version": 1
15 | }
16 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ATFAScannerFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.Context;
7 |
8 | public class ATFAScannerFactory {
9 | public static ATFAScanner createATFAScanner(Context context) {
10 | return new ATFAScanner(context);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ByteArrayOutputStreamProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.io.ByteArrayOutputStream;
7 |
8 | public class ByteArrayOutputStreamProvider {
9 |
10 | public ByteArrayOutputStream get() {
11 | return new ByteArrayOutputStream();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/UIThreadRunner.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.Handler;
7 | import android.os.Looper;
8 |
9 | public class UIThreadRunner {
10 | public void run(Runnable runnable) {
11 | new Handler(Looper.getMainLooper()).post(runnable);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/DisplayMetricsStub.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.util.DisplayMetrics;
7 |
8 | public class DisplayMetricsStub extends DisplayMetrics {
9 | public int densityDpi = 111;
10 | public int heightPixels = 222;
11 | public int widthPixels = 333;
12 | }
13 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/BitmapProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 |
8 | public class BitmapProvider {
9 |
10 | public Bitmap createBitmap(int width, int height, Bitmap.Config config) {
11 | return Bitmap.createBitmap(width, height, config);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeRectProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import com.deque.axe.android.wrappers.AxeRect;
7 |
8 | public class AxeRectProvider {
9 |
10 | public AxeRect createAxeRect(int left, int right, int top, int bottom) {
11 | return new AxeRect(left, right, top, bottom);
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/general_question.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: General Question
3 | about: This is a template for people to ask questions that don't fit into any other issue categories
4 | title: ""
5 | labels: question
6 | assignees: ""
7 | ---
8 |
9 | **We encourage tool related questions to be asked on stackoverflow.com and tagged with `accessibility-insights`.**
10 |
11 | ## Your question here
12 |
13 |
17 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ViewChangedException.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | class ViewChangedException extends Exception {
7 | public ViewChangedException() {
8 | this("");
9 | }
10 |
11 | public ViewChangedException(String additionalMessage) {
12 | super("The view hierarchy changed while building the AxeView tree. " + additionalMessage);
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | # Microsoft Open Source Code of Conduct
7 |
8 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/).
9 |
10 | Resources:
11 |
12 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/)
13 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/)
14 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns
15 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ResultsV2Container.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import com.deque.axe.android.AxeResult;
7 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
8 | import java.util.List;
9 |
10 | public class ResultsV2Container {
11 | public List ATFAResults;
12 | public AxeResult AxeResult;
13 | }
14 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/RequestReaderFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.InputStream;
8 | import java.io.InputStreamReader;
9 |
10 | public class RequestReaderFactory {
11 |
12 | public RequestReader createRequestReader(InputStream inputStream) {
13 | return new RequestReader(new BufferedReader(new InputStreamReader(inputStream)));
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/StackTrace.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.io.PrintWriter;
7 | import java.io.StringWriter;
8 |
9 | public class StackTrace {
10 |
11 | public static String getStackTrace(Exception e) {
12 | StringWriter stringWriter = new StringWriter();
13 | PrintWriter printWriter = new PrintWriter(stringWriter);
14 | e.printStackTrace(printWriter);
15 | return stringWriter.toString();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/OnScreenshotAvailableProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import android.util.DisplayMetrics;
8 | import java.util.function.Consumer;
9 |
10 | public class OnScreenshotAvailableProvider {
11 | public OnScreenshotAvailable getOnScreenshotAvailable(
12 | DisplayMetrics metrics, BitmapProvider bitmapProvider, Consumer bitmapConsumer) {
13 | return new OnScreenshotAvailable(metrics, bitmapProvider, bitmapConsumer);
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/OffsetHelper.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.View;
7 |
8 | public class OffsetHelper {
9 | public static int getYOffset(View view) {
10 | int offset = 0;
11 | int resourceId = view.getResources().getIdentifier("status_bar_height", "dimen", "android");
12 | if (resourceId > 0) {
13 | offset = view.getResources().getDimensionPixelSize(resourceId);
14 | }
15 | // divide by 2 to center
16 | offset = offset / 2;
17 | return offset;
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: feature request
6 | assignees: ''
7 | ---
8 |
9 | ## Is your feature request related to a problem? Please describe.
10 |
11 |
12 | ## Describe the desired outcome
13 |
14 |
15 | ## Describe alternatives you've considered
16 |
17 |
18 | ## Additional context
19 |
20 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/DeviceConfig.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import com.deque.axe.android.utils.JsonSerializable;
7 |
8 | public class DeviceConfig implements JsonSerializable {
9 | public final String deviceName;
10 | public final String packageName;
11 | public final String serviceVersion;
12 |
13 | public DeviceConfig(String deviceName, String packageName, String serviceVersion) {
14 | this.deviceName = deviceName;
15 | this.packageName = packageName;
16 | this.serviceVersion = serviceVersion;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/UnrecognizedRequestFulfiller.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.CancellationSignal;
7 |
8 | public class UnrecognizedRequestFulfiller implements RequestFulfiller {
9 | private final String requestMethod;
10 |
11 | public UnrecognizedRequestFulfiller(String requestMethod) {
12 | this.requestMethod = requestMethod;
13 | }
14 |
15 | public String fulfillRequest(CancellationSignal cancellationSignal) {
16 | throw new RuntimeException("Unrecognized request: " + requestMethod);
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/guardian/SDL/.gdnbaselines:
--------------------------------------------------------------------------------
1 | {
2 | "version": "latest",
3 | "baselines": {
4 | "default": {
5 | "name": "default",
6 | "createdDate": "2023-03-30 17:45:22Z",
7 | "lastUpdatedDate": "2023-03-30 17:47:53Z"
8 | }
9 | },
10 | "results": {
11 | "b3736a918ff8d688843a2c76bfce1661c484e260144140c9df23f1147da102c0": {
12 | "signature": "b3736a918ff8d688843a2c76bfce1661c484e260144140c9df23f1147da102c0",
13 | "alternativeSignatures": [],
14 | "target": "spotbugs.xml",
15 | "memberOf": [
16 | "default"
17 | ],
18 | "tool": "spotbugs",
19 | "ruleId": "gdn.unknownFormatResult",
20 | "justification": null,
21 | "createdDate": "2023-03-30 17:45:22Z",
22 | "expirationDate": null,
23 | "type": null
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AxeRunnerFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.junit.MockitoJUnitRunner;
11 |
12 | @RunWith(MockitoJUnitRunner.class)
13 | public class AxeRunnerFactoryTest {
14 |
15 | AxeRunnerFactory testSubject;
16 |
17 | @Before
18 | public void prepare() {
19 | testSubject = new AxeRunnerFactory();
20 | }
21 |
22 | @Test
23 | public void axeRunnerIsNotNull() {
24 | Assert.assertNotNull(testSubject.createAxeRunner());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/BoundingRectProviderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.junit.MockitoJUnitRunner;
11 |
12 | @RunWith(MockitoJUnitRunner.class)
13 | public class BoundingRectProviderTest {
14 |
15 | AxeRectProvider testSubject;
16 |
17 | @Before
18 | public void prepare() {
19 | testSubject = new AxeRectProvider();
20 | }
21 |
22 | @Test
23 | public void boundingRectExists() {
24 | Assert.assertNotNull(testSubject.createAxeRect(0, 1, 2, 3));
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
19 |
20 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/ATFAScannerFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.Context;
7 | import org.junit.Assert;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.junit.MockitoJUnitRunner;
13 |
14 | @RunWith(MockitoJUnitRunner.class)
15 | public class ATFAScannerFactoryTest {
16 | @Mock Context contextMock;
17 |
18 | @Before
19 | public void prepare() {}
20 |
21 | @Test
22 | public void atfaScannerExists() {
23 | Assert.assertNotNull(ATFAScannerFactory.createATFAScanner(contextMock));
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/MediaProjectionHolder.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.media.projection.MediaProjection;
7 |
8 | public class MediaProjectionHolder {
9 | private static MediaProjection sharedMediaProjection = null;
10 |
11 | public static void cleanUp() {
12 | if (sharedMediaProjection != null) {
13 | sharedMediaProjection.stop();
14 | sharedMediaProjection = null;
15 | }
16 | }
17 |
18 | public static MediaProjection get() {
19 | return sharedMediaProjection;
20 | }
21 |
22 | public static void set(MediaProjection projection) {
23 | sharedMediaProjection = projection;
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
5 |
6 | ## Contributing
7 |
8 | This project welcomes contributions and suggestions. Most contributions require you to agree to a
9 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us
10 | the rights to use your contribution. For details, visit https://cla.microsoft.com.
11 |
12 | When you submit a pull request, a CLA-bot will automatically determine whether you need to provide
13 | a CLA and decorate the PR appropriately (e.g., label, comment). Simply follow the instructions
14 | provided by the bot. You will only need to do this once across all repos using our CLA.
15 |
16 | ## Code of Conduct
17 |
18 | Please read through our [Code of Conduct](./CODE_OF_CONDUCT.md) to this project.
19 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/ByteArrayOutputStreamProviderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.junit.MockitoJUnitRunner;
11 |
12 | @RunWith(MockitoJUnitRunner.class)
13 | public class ByteArrayOutputStreamProviderTest {
14 |
15 | ByteArrayOutputStreamProvider testSubject;
16 |
17 | @Before
18 | public void prepare() {
19 | testSubject = new ByteArrayOutputStreamProvider();
20 | }
21 |
22 | @Test
23 | public void byteArrayOutputStreamExists() {
24 | Assert.assertNotNull(testSubject.get());
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | #### Details
2 |
3 |
4 |
5 | ##### Motivation
6 |
7 |
8 |
9 | ##### Context
10 |
11 |
12 |
13 |
14 |
15 | #### Pull request checklist
16 |
17 |
18 | - [ ] Addresses an existing issue: #0000
19 | - [ ] Added/updated relevant unit test(s)
20 | - [ ] Ran `./gradlew fastpass` from `AccessibilityInsightsForAndroidService`
21 | - [ ] PR title _AND_ final merge commit title both start with a semantic tag (`fix:`, `chore:`, `feat(feature-name):`, `refactor:`).
22 |
--------------------------------------------------------------------------------
/.github/workflows/combine-dependabot-prs.yml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation. All rights reserved.
2 | # Licensed under the MIT License.
3 |
4 | name: "Combine Dependabot PRs"
5 | on:
6 | schedule:
7 | - cron: '0 13 * * 1'
8 | workflow_dispatch:
9 |
10 | permissions:
11 | contents: write
12 | pull-requests: write
13 |
14 | jobs:
15 | combine-prs:
16 | if: github.event_name != 'schedule' || github.repository_owner == 'microsoft'
17 | runs-on: ubuntu-latest
18 | steps:
19 | - uses: actions/checkout@v2.3.3
20 | - uses: maadhattah/combine-dependabot-prs@e4dc7e045b018ee1e963a1a67bccbbf8ff3b176f
21 | with:
22 | branchPrefix: "dependabot"
23 | mustBeGreen: true
24 | combineBranchName: combined-prs-${{ github.run_id }}
25 | ignoreLabel: "pr: do not combine"
26 | baseBranch: "main"
27 | openPR: true
28 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeImageFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import com.deque.axe.android.colorcontrast.AxeImage;
8 |
9 | public class AxeImageFactory {
10 | private ByteArrayOutputStreamProvider byteArrayOutputStreamProvider;
11 |
12 | public AxeImageFactory(ByteArrayOutputStreamProvider byteArrayOutputStreamProvider) {
13 | this.byteArrayOutputStreamProvider = byteArrayOutputStreamProvider;
14 | }
15 |
16 | public AxeImage createAxeImage(Bitmap screenshot) {
17 | if (screenshot == null) {
18 | return null;
19 | }
20 |
21 | return new ScreenshotAxeImage(screenshot, byteArrayOutputStreamProvider);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ThreadSafeSwapper.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | public class ThreadSafeSwapper {
7 | private final Object lock_object = new Object();
8 | private T currentObject;
9 |
10 | public T swap(T newObject) {
11 | synchronized (lock_object) {
12 | T oldObject = currentObject;
13 | currentObject = newObject;
14 | return oldObject;
15 | }
16 | }
17 |
18 | public boolean setIfCurrentlyNull(T newObject) {
19 | synchronized (lock_object) {
20 | T oldObject = currentObject;
21 | if (oldObject != null) {
22 | return false;
23 | }
24 |
25 | currentObject = newObject;
26 | return true;
27 | }
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/NodeViewBuilderFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 | import com.deque.axe.android.AxeView;
8 | import java.util.List;
9 |
10 | public class NodeViewBuilderFactory {
11 | // The reason we need something called BuilderFactory is because axe-android requires us to
12 | // implement the AxeView.builder interface, which we do in NodeViewBuilder. This is a factory
13 | // for that class.
14 |
15 | public NodeViewBuilder createNodeViewBuilder(
16 | AccessibilityNodeInfo node, List children, AxeView labeledBy) {
17 | return new NodeViewBuilder(node, children, labeledBy, new AxeRectProvider());
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/npm-wrapper/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "accessibility-insights-for-android-service-bin",
3 | "version": "0.0.0-set-by-release-build",
4 | "description": "Wrapper package containing a pre-built APK version of Accessibility Insights for Android Service",
5 | "main": "index.js",
6 | "types": "index.d.ts",
7 | "files": [
8 | "AccessibilityInsightsforAndroidService.apk",
9 | "index.d.ts",
10 | "index.js",
11 | "LICENSE",
12 | "NOTICE.html",
13 | "package.json",
14 | "README.md"
15 | ],
16 | "repository": {
17 | "type": "git",
18 | "url": "git+https://github.com/microsoft/accessibility-insights-for-android-service.git"
19 | },
20 | "author": "Microsoft",
21 | "license": "MIT",
22 | "bugs": {
23 | "url": "https://github.com/microsoft/accessibility-insights-for-android-service/issues"
24 | },
25 | "homepage": "https://github.com/microsoft/accessibility-insights-for-android-service#readme"
26 | }
27 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation.
2 | # Licensed under the MIT License.
3 |
4 | # Add project specific ProGuard rules here.
5 | # You can control the set of applied configuration files using the
6 | # proguardFiles setting in build.gradle.
7 | #
8 | # For more details, see
9 | # http://developer.android.com/guide/developing/tools/proguard.html
10 |
11 | # If your project uses WebView with JS, uncomment the following
12 | # and specify the fully qualified class name to the JavaScript interface
13 | # class:
14 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
15 | # public *;
16 | #}
17 |
18 | # Uncomment this to preserve the line number information for
19 | # debugging stack traces.
20 | #-keepattributes SourceFile,LineNumberTable
21 |
22 | # If you keep the line number information, uncomment this to
23 | # hide the original source file name.
24 | #-renamesourcefileattribute SourceFile
25 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AxeScannerFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.util.DisplayMetrics;
7 | import org.junit.Assert;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.junit.MockitoJUnitRunner;
13 |
14 | @RunWith(MockitoJUnitRunner.class)
15 | public class AxeScannerFactoryTest {
16 |
17 | @Mock DeviceConfigFactory deviceConfigFactoryMock;
18 | @Mock DisplayMetrics displayMetricsMock;
19 |
20 | @Before
21 | public void prepare() {}
22 |
23 | @Test
24 | public void axeScannerExists() {
25 | Assert.assertNotNull(
26 | AxeScannerFactory.createAxeScanner(deviceConfigFactoryMock, () -> displayMetricsMock));
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/TabStopsRequestFulfiller.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.CancellationSignal;
7 |
8 | public class TabStopsRequestFulfiller implements RequestFulfiller {
9 | private final FocusVisualizationStateManager focusVisualizationStateManager;
10 | private final boolean requestValue;
11 |
12 | public TabStopsRequestFulfiller(
13 | FocusVisualizationStateManager focusVisualizationStateManager, boolean requestValue) {
14 | this.focusVisualizationStateManager = focusVisualizationStateManager;
15 | this.requestValue = requestValue;
16 | }
17 |
18 | @Override
19 | public String fulfillRequest(CancellationSignal cancellationSignal) {
20 | focusVisualizationStateManager.setState(requestValue);
21 | return "";
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/pipeline/e2e-emulator-template.yaml:
--------------------------------------------------------------------------------
1 | parameters:
2 | apiLevel: null
3 |
4 | steps:
5 | - task: DownloadPipelineArtifact@2
6 | inputs:
7 | artifact: debugApk
8 | path: $(system.defaultWorkingDirectory)
9 |
10 | - task: Bash@3
11 | displayName: Setup Android emulator
12 | inputs:
13 | filePath: 'pipeline/scripts/setup-emulator.sh'
14 | env:
15 | ANDROID_API_LEVEL: ${{ parameters.apiLevel }}
16 |
17 | - task: Bash@3
18 | displayName: Test Config Command
19 | inputs:
20 | targetType: 'inline'
21 | script: |
22 | $ANDROID_HOME/platform-tools/adb shell content read --uri content://com.microsoft.accessibilityinsightsforandroidservice/config
23 | failOnStderr: true
24 |
25 | - task: Bash@3
26 | displayName: Test Result Command
27 | inputs:
28 | targetType: 'inline'
29 | script: |
30 | $ANDROID_HOME/platform-tools/adb shell content read --uri content://com.microsoft.accessibilityinsightsforandroidservice/result
31 | failOnStderr: true
32 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/DisplayMetricsHelper.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.Context;
7 | import android.content.res.Resources;
8 | import android.util.DisplayMetrics;
9 | import android.view.Display;
10 | import android.view.WindowManager;
11 |
12 | public class DisplayMetricsHelper {
13 |
14 | public static DisplayMetrics getRealDisplayMetrics(Context context) {
15 |
16 | DisplayMetrics displayMetrics = Resources.getSystem().getDisplayMetrics(); // Default values
17 |
18 | Display display = getDefaultDisplay(context);
19 |
20 | display.getRealMetrics(displayMetrics);
21 | return displayMetrics;
22 | }
23 |
24 | private static Display getDefaultDisplay(Context context) {
25 | return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/RootNodeFinder.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 |
8 | public class RootNodeFinder {
9 | public AccessibilityNodeInfo getRootNodeFromSource(AccessibilityNodeInfo source) {
10 | AccessibilityNodeInfo rootNode = null;
11 |
12 | if (source != null) {
13 | AccessibilityNodeInfo currentNode = source;
14 |
15 | while (true) {
16 | AccessibilityNodeInfo parent = currentNode.getParent();
17 |
18 | if (parent == null) {
19 | rootNode = currentNode;
20 | break;
21 | }
22 |
23 | if (source != currentNode) { // Don't recycle the source!
24 | currentNode.recycle();
25 | }
26 | currentNode = parent;
27 | }
28 | }
29 |
30 | return rootNode;
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AxeImageFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import org.junit.Assert;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.Mock;
12 | import org.mockito.junit.MockitoJUnitRunner;
13 |
14 | @RunWith(MockitoJUnitRunner.class)
15 | public class AxeImageFactoryTest {
16 |
17 | @Mock ByteArrayOutputStreamProvider byteArrayOutputStreamProviderMock;
18 | @Mock Bitmap screenshotMock;
19 |
20 | AxeImageFactory testSubject;
21 |
22 | @Before
23 | public void prepare() {
24 | testSubject = new AxeImageFactory(byteArrayOutputStreamProviderMock);
25 | }
26 |
27 | @Test
28 | public void axeImageIsNotNull() {
29 | Assert.assertNotNull(testSubject.createAxeImage(screenshotMock));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeRunnerFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import com.deque.axe.android.Axe;
7 | import com.deque.axe.android.AxeConf;
8 | import com.deque.axe.android.constants.AxeStandard;
9 |
10 | public class AxeRunnerFactory {
11 | // AxeConf.removeStandard is marked deprecated, but is still suggested in the axe-android docs as
12 | // the recommended way to constrain which standards we scan against.
13 | //
14 | // https://github.com/dequelabs/axe-android/issues/145 tracks the request for a non-deprecated
15 | // replacement option.
16 | @SuppressWarnings("deprecation")
17 | public Axe createAxeRunner() {
18 | AxeConf axeConf = new AxeConf();
19 |
20 | axeConf.removeStandard(AxeStandard.BEST_PRACTICE);
21 | axeConf.removeStandard(AxeStandard.PLATFORM);
22 | return new Axe(axeConf);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/OrderedValue.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.util.Objects;
7 |
8 | public class OrderedValue implements Comparable> {
9 | public final long order;
10 | public final T value;
11 |
12 | public OrderedValue(T value, long order) {
13 | this.value = value;
14 | this.order = order;
15 | }
16 |
17 | @Override
18 | public int compareTo(OrderedValue other) {
19 | return Long.compare(this.order, other.order);
20 | }
21 |
22 | @Override
23 | public boolean equals(Object o) {
24 | if (this == o) return true;
25 | if (o == null || getClass() != o.getClass()) return false;
26 | OrderedValue> that = (OrderedValue>) o;
27 | return order == that.order;
28 | }
29 |
30 | @Override
31 | public int hashCode() {
32 | return Objects.hash(order);
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/xml/accessibility_insights_service.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
24 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/NodeViewBuilderFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 | import com.deque.axe.android.AxeView;
8 | import org.junit.Assert;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.MockitoJUnitRunner;
14 |
15 | @RunWith(MockitoJUnitRunner.class)
16 | public class NodeViewBuilderFactoryTest {
17 |
18 | @Mock AccessibilityNodeInfo node;
19 | @Mock AxeView view;
20 |
21 | NodeViewBuilderFactory testSubject;
22 |
23 | @Before
24 | public void prepare() {
25 | testSubject = new NodeViewBuilderFactory();
26 | }
27 |
28 | @Test
29 | public void nodeViewIsNotNull() {
30 | Assert.assertNotNull(testSubject.createNodeViewBuilder(node, null, null));
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/RequestReader.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.io.BufferedReader;
7 | import java.io.IOException;
8 |
9 | public class RequestReader {
10 |
11 | private BufferedReader reader;
12 | private static final int maxLineLength = 256;
13 |
14 | public RequestReader(BufferedReader reader) {
15 | this.reader = reader;
16 | }
17 |
18 | public String readRequest() throws IOException {
19 |
20 | StringBuffer buffer = new StringBuffer();
21 | int intC = reader.read();
22 | while (intC != -1) {
23 | char c = (char) intC;
24 | if (c == '\n') {
25 | break;
26 | }
27 | if (buffer.length() >= maxLineLength) {
28 | throw new IOException("input too long");
29 | }
30 | buffer.append(c);
31 | intC = reader.read();
32 | }
33 |
34 | return buffer.toString();
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/EventHelper.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 |
8 | public class EventHelper {
9 | private final ThreadSafeSwapper swapper;
10 |
11 | public EventHelper(ThreadSafeSwapper swapper) {
12 | this.swapper = swapper;
13 | }
14 |
15 | public void recordEvent(AccessibilityNodeInfo source) {
16 | if (source != null) {
17 | AccessibilityNodeInfo lastSource = swapper.swap(source);
18 | if (lastSource != null) {
19 | lastSource.recycle();
20 | }
21 | }
22 | }
23 |
24 | public AccessibilityNodeInfo claimLastSource() {
25 | return swapper.swap(null);
26 | }
27 |
28 | public boolean restoreLastSource(AccessibilityNodeInfo previousSource) {
29 | return swapper.setIfCurrentlyNull(previousSource);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/DeviceConfigTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 |
10 | public class DeviceConfigTest {
11 | String deviceName = "test-device-name";
12 | String packageName = "test-package-name";
13 | String serviceVersion = "test-service-version";
14 |
15 | DeviceConfig testSubject;
16 |
17 | @Before
18 | public void prepare() {
19 | testSubject = new DeviceConfig(deviceName, packageName, serviceVersion);
20 | }
21 |
22 | @Test
23 | public void deviceConfigExists() {
24 | Assert.assertNotNull(testSubject);
25 | }
26 |
27 | @Test
28 | public void deviceConfigHasExpectedProperties() {
29 | Assert.assertEquals(deviceName, testSubject.deviceName);
30 | Assert.assertEquals(packageName, testSubject.packageName);
31 | Assert.assertEquals(serviceVersion, testSubject.serviceVersion);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/TabStopsRequestFulfillerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.junit.Assert.assertEquals;
7 | import static org.mockito.Mockito.verify;
8 |
9 | import android.os.CancellationSignal;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.Mock;
13 | import org.mockito.junit.MockitoJUnitRunner;
14 |
15 | @RunWith(MockitoJUnitRunner.class)
16 | public class TabStopsRequestFulfillerTest {
17 | @Mock FocusVisualizationStateManager focusVisualizationStateManager;
18 | @Mock CancellationSignal cancellationSignal;
19 |
20 | TabStopsRequestFulfiller testSubject;
21 |
22 | @Test
23 | public void fulfillRequestSetsTabStopState() {
24 | testSubject = new TabStopsRequestFulfiller(focusVisualizationStateManager, true);
25 | assertEquals("", testSubject.fulfillRequest(cancellationSignal));
26 |
27 | verify(focusVisualizationStateManager).setState(true);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/WorkManagerHolder.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.Context;
7 | import androidx.work.Configuration;
8 | import androidx.work.WorkManager;
9 | import java.util.WeakHashMap;
10 |
11 | public class WorkManagerHolder {
12 | private static final Object LockObject = new Object();
13 | private static WeakHashMap ContextToManagerMap =
14 | new WeakHashMap();
15 |
16 | public static WorkManager getWorkManager(Context context) {
17 | synchronized (LockObject) {
18 | WorkManager managerForContext = ContextToManagerMap.get(context);
19 |
20 | if (managerForContext == null) {
21 | WorkManager.initialize(context, new Configuration.Builder().build());
22 | managerForContext = WorkManager.getInstance(context);
23 | ContextToManagerMap.put(context, managerForContext);
24 | }
25 | return managerForContext;
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/OffsetHelperTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.ArgumentMatchers.anyInt;
7 | import static org.mockito.Mockito.when;
8 |
9 | import android.content.res.Resources;
10 | import android.view.View;
11 | import org.junit.Assert;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class OffsetHelperTest {
19 |
20 | @Mock View viewMock;
21 | @Mock Resources resourcesMock;
22 |
23 | @Test
24 | public void getYOffsetReturnsCenterOfElement() {
25 | when(viewMock.getResources()).thenReturn(resourcesMock);
26 | when(resourcesMock.getIdentifier("status_bar_height", "dimen", "android")).thenReturn(1);
27 | when(resourcesMock.getDimensionPixelSize(anyInt())).thenReturn(10);
28 |
29 | Assert.assertEquals(OffsetHelper.getYOffset(viewMock), 5);
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeScanner.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import android.view.accessibility.AccessibilityNodeInfo;
8 | import com.deque.axe.android.Axe;
9 | import com.deque.axe.android.AxeContext;
10 | import com.deque.axe.android.AxeResult;
11 |
12 | public class AxeScanner {
13 | private final AxeRunnerFactory axeRunnerFactory;
14 | private final AxeContextFactory axeContextFactory;
15 |
16 | public AxeScanner(AxeRunnerFactory axeRunnerFactory, AxeContextFactory axeContextFactory) {
17 | this.axeRunnerFactory = axeRunnerFactory;
18 | this.axeContextFactory = axeContextFactory;
19 | }
20 |
21 | public AxeResult scanWithAxe(AccessibilityNodeInfo rootNode, Bitmap screenshot)
22 | throws ViewChangedException {
23 | final Axe axe = axeRunnerFactory.createAxeRunner();
24 | final AxeContext axeContext = axeContextFactory.createAxeContext(rootNode, screenshot);
25 | return axe.run(axeContext);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) Microsoft Corporation.
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE
22 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/Logger.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.util.Log;
7 | import androidx.annotation.VisibleForTesting;
8 |
9 | public class Logger {
10 | @VisibleForTesting public static boolean ENABLE_LOGGING = BuildConfig.DEBUG_MODE;
11 |
12 | public static void logVerbose(String tag, String message) {
13 | if (ENABLE_LOGGING) {
14 | Log.v(tag, message);
15 | }
16 | }
17 |
18 | public static void logDebug(String tag, String message) {
19 | if (ENABLE_LOGGING) {
20 | Log.d(tag, message);
21 | }
22 | }
23 |
24 | public static void logError(String tag, String message) {
25 | if (ENABLE_LOGGING) {
26 | Log.e(tag, message);
27 | }
28 | }
29 |
30 | public static void logInfo(String tag, String message) {
31 | if (ENABLE_LOGGING) {
32 | Log.i(tag, message);
33 | }
34 | }
35 |
36 | public static void logWarning(String tag, String message) {
37 | if (ENABLE_LOGGING) {
38 | Log.w(tag, message);
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizationStateManager.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.util.ArrayList;
7 | import java.util.function.Consumer;
8 |
9 | public class FocusVisualizationStateManager {
10 | private boolean enabled = false;
11 | private ArrayList> onChangedListeners;
12 |
13 | public FocusVisualizationStateManager() {
14 | onChangedListeners = new ArrayList>();
15 | }
16 |
17 | public void subscribe(Consumer listener) {
18 | onChangedListeners.add(listener);
19 | }
20 |
21 | public void setState(boolean enabled) {
22 | if (this.enabled == enabled) {
23 | return;
24 | }
25 |
26 | this.enabled = enabled;
27 | this.emitChanged(enabled);
28 | }
29 |
30 | public boolean getState() {
31 | return this.enabled;
32 | }
33 |
34 | private void emitChanged(boolean enabled) {
35 | onChangedListeners.forEach(
36 | listener -> {
37 | listener.accept(enabled);
38 | });
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeScannerFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.util.DisplayMetrics;
7 | import java.util.function.Supplier;
8 |
9 | public class AxeScannerFactory {
10 | public static AxeScanner createAxeScanner(
11 | DeviceConfigFactory deviceConfigFactory, Supplier displayMetricsSupplier) {
12 | final AxeViewsFactory axeViewsFactory =
13 | new AxeViewsFactory(new NodeViewBuilderFactory(), new AccessibilityNodeInfoQueueBuilder());
14 | final AxeImageFactory axeImageFactory =
15 | new AxeImageFactory(new ByteArrayOutputStreamProvider());
16 | final AxeDeviceFactory axeDeviceFactory =
17 | new AxeDeviceFactory(deviceConfigFactory, displayMetricsSupplier);
18 | final AxeContextFactory axeContextFactory =
19 | new AxeContextFactory(axeImageFactory, axeViewsFactory, axeDeviceFactory);
20 | final AxeRunnerFactory axeRunnerFactory = new AxeRunnerFactory();
21 |
22 | return new AxeScanner(axeRunnerFactory, axeContextFactory);
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/DeviceOrientationHandler.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import java.util.ArrayList;
7 | import java.util.function.Consumer;
8 |
9 | public class DeviceOrientationHandler {
10 | private int orientation;
11 | private ArrayList> onOrientationChangedListeners;
12 |
13 | public DeviceOrientationHandler(int orientation) {
14 | this.orientation = orientation;
15 | this.onOrientationChangedListeners = new ArrayList<>();
16 | }
17 |
18 | public void subscribe(Consumer listener) {
19 | this.onOrientationChangedListeners.add(listener);
20 | }
21 |
22 | public void setOrientation(int orientation) {
23 | if (this.orientation == orientation) {
24 | return;
25 | }
26 |
27 | this.orientation = orientation;
28 | this.emitChanged(orientation);
29 | }
30 |
31 | private void emitChanged(int orientation) {
32 | this.onOrientationChangedListeners.forEach(
33 | listener -> {
34 | listener.accept(orientation);
35 | });
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/gradle.properties:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation.
2 | # Licensed under the MIT License.
3 |
4 | # Project-wide Gradle settings.
5 | # IDE (e.g. Android Studio) users:
6 | # Gradle settings configured through the IDE *will override*
7 | # any settings specified in this file.
8 | # For more details on how to configure your build environment visit
9 | # http://www.gradle.org/docs/current/userguide/build_environment.html
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx4608m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 | # AndroidX package structure to make it clearer which packages are bundled with the
19 | # Android operating system, and which are packaged with your app's APK
20 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
21 | android.useAndroidX=true
22 | # Automatically convert third-party libraries to use AndroidX
23 | android.enableJetifier=true
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
20 |
21 |
22 | Accessibility Insights for Android Service
23 | This service allows Accessibility Insights for Android to scan your device for accessibility issues.
24 | Note: permission to capture the screen is required for ColorContrast rules.
25 |
26 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/DeviceConfigFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.Build;
7 | import android.view.accessibility.AccessibilityNodeInfo;
8 |
9 | public class DeviceConfigFactory {
10 | public DeviceConfigFactory(String buildModel) {
11 | this.buildModel = buildModel;
12 | }
13 |
14 | public DeviceConfigFactory() {
15 | this(Build.MODEL);
16 | }
17 |
18 | private String serviceVersion = "0.1.0";
19 | private String buildModel;
20 |
21 | public DeviceConfig getDeviceConfig(AccessibilityNodeInfo rootNode) {
22 | String packageName = getPackageNameFromAccessibilityNode(rootNode);
23 |
24 | return new DeviceConfig(buildModel, packageName, serviceVersion);
25 | }
26 |
27 | private String getPackageNameFromAccessibilityNode(AccessibilityNodeInfo rootNode) {
28 | String packageName = "No application detected";
29 | if (rootNode != null) {
30 | CharSequence sequence = rootNode.getPackageName();
31 | if (sequence != null) {
32 | packageName = sequence.toString();
33 | }
34 | }
35 | return packageName;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ""
5 | labels: bug, needs triage
6 | assignees: ""
7 | ---
8 |
9 | ## Describe the bug
10 |
11 |
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 |
16 | 1. Go to '...'
17 | 2. Click on '...'
18 | 3. Scroll down to '...'
19 | 4. See error
20 |
21 | ## Expected behavior
22 |
23 |
24 |
25 | ## Screenshots
26 |
27 |
28 |
29 | ## Context (please complete the following information)
30 |
31 | - Android Version:
32 | - Service Version & Environment:
33 | - Target Application:
34 |
35 | ## Are you willing to submit a PR?
36 |
37 |
38 |
39 | ## Did you search for similar existing issues?
40 |
41 |
42 |
43 | ## Additional context
44 |
45 |
46 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/UnrecognizedRequestFulfillerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.junit.Assert.assertThrows;
7 |
8 | import android.os.CancellationSignal;
9 | import org.junit.Assert;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mock;
14 | import org.mockito.junit.MockitoJUnitRunner;
15 |
16 | @RunWith(MockitoJUnitRunner.class)
17 | public class UnrecognizedRequestFulfillerTest {
18 | final String requestMethod = "Test request method";
19 |
20 | @Mock CancellationSignal cancellationSignal;
21 |
22 | UnrecognizedRequestFulfiller testSubject;
23 |
24 | @Before
25 | public void prepare() {
26 | testSubject = new UnrecognizedRequestFulfiller(requestMethod);
27 | }
28 |
29 | @Test
30 | public void unrecognizedResponseFulfillerExists() {
31 | Assert.assertNotNull(testSubject);
32 | }
33 |
34 | @Test
35 | public void fulfillsRequestByThrowingPinnedException() {
36 | assertThrows(
37 | "Unrecognized request: Test request method",
38 | RuntimeException.class,
39 | () -> testSubject.fulfillRequest(cancellationSignal));
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/LayoutParamGenerator.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.PixelFormat;
7 | import android.util.DisplayMetrics;
8 | import android.view.WindowManager;
9 | import java.util.function.Supplier;
10 |
11 | public class LayoutParamGenerator {
12 | private Supplier displayMetricsSupplier;
13 |
14 | public LayoutParamGenerator(Supplier displayMetricsSupplier) {
15 | this.displayMetricsSupplier = displayMetricsSupplier;
16 | }
17 |
18 | public WindowManager.LayoutParams get() {
19 | DisplayMetrics displayMetrics = displayMetricsSupplier.get();
20 | WindowManager.LayoutParams params =
21 | new WindowManager.LayoutParams(
22 | displayMetrics.widthPixels,
23 | displayMetrics.heightPixels,
24 | WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY,
25 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
26 | | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
27 | | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
28 | | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
29 | PixelFormat.TRANSLUCENT);
30 |
31 | return params;
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeDeviceFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.Build;
7 | import android.util.DisplayMetrics;
8 | import android.view.accessibility.AccessibilityNodeInfo;
9 | import com.deque.axe.android.AxeDevice;
10 | import java.util.function.Supplier;
11 |
12 | public class AxeDeviceFactory {
13 | private final DeviceConfigFactory deviceConfigFactory;
14 | private final Supplier displayMetricsSupplier;
15 |
16 | public AxeDeviceFactory(
17 | DeviceConfigFactory deviceConfigFactory, Supplier displayMetricsSupplier) {
18 | this.deviceConfigFactory = deviceConfigFactory;
19 | this.displayMetricsSupplier = displayMetricsSupplier;
20 | }
21 |
22 | public AxeDevice createAxeDevice(AccessibilityNodeInfo rootNode) {
23 | DisplayMetrics displayMetrics = displayMetricsSupplier.get();
24 | String compoundVersion = Build.VERSION.RELEASE + " API Level " + Build.VERSION.SDK_INT;
25 | DeviceConfig deviceConfig = deviceConfigFactory.getDeviceConfig(rootNode);
26 | return new AxeDevice(
27 | displayMetrics.density,
28 | deviceConfig.deviceName,
29 | compoundVersion,
30 | displayMetrics.heightPixels,
31 | displayMetrics.widthPixels);
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ScreenshotActivity.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.app.Activity;
7 | import android.content.Intent;
8 | import android.media.projection.MediaProjectionManager;
9 | import android.os.Bundle;
10 | import android.widget.Toast;
11 | import androidx.annotation.Nullable;
12 |
13 | public class ScreenshotActivity extends Activity {
14 | private MediaProjectionManager mediaManager;
15 | private static final int SCREENSHOT = 99999;
16 |
17 | @Override
18 | protected void onCreate(@Nullable Bundle savedInstanceState) {
19 | super.onCreate(savedInstanceState);
20 |
21 | mediaManager = (MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
22 | startActivityForResult(mediaManager.createScreenCaptureIntent(), SCREENSHOT);
23 | }
24 |
25 | @Override
26 | protected void onActivityResult(int requestCode, int resultCode, Intent data) {
27 | if (requestCode == SCREENSHOT) {
28 | if (resultCode == RESULT_OK) {
29 | MediaProjectionHolder.set(mediaManager.getMediaProjection(resultCode, data));
30 | }
31 | }
32 |
33 | if (MediaProjectionHolder.get() == null) {
34 | Toast.makeText(this, R.string.screenshot_permission_not_granted, Toast.LENGTH_LONG).show();
35 | }
36 |
37 | finish();
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/pipeline/infer-version-code-from-version-name.js:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation. All rights reserved.
2 | // Licensed under the MIT License.
3 | const process = require('process');
4 | const versionName = process.env['APK_VERSION_NAME'];
5 |
6 | if (versionName == null) {
7 | console.error('APK_VERSION_NAME env var not set!');
8 | process.exit(1);
9 | }
10 |
11 | const versionNameRegex = /^(\d+)\.(\d+)\.(\d+)$/;
12 | const versionNameMatches = versionNameRegex.exec(versionName);
13 | if (versionNameMatches == null) {
14 | console.error(`APK_VERSION_NAME ${versionName} not in expected format (1.2.3)`);
15 | process.exit(1);
16 | }
17 |
18 | const [major, minor, patch] = versionNameMatches.slice(1).map(component => parseInt(component));
19 | if (`${major}.${minor}.${patch}` !== versionName) {
20 | console.error(`APK_VERSION_NAME ${versionName} has extra leading zeros (should be ${major}.${minor}.${patch})`)
21 | process.exit(1);
22 | }
23 |
24 | if (major > 99 || minor > 99 || patch > 999) {
25 | console.error(`APK_VERSION_NAME ${versionName} has an oversized component; it must fit in format xx.yy.zzz`);
26 | process.exit(1);
27 | }
28 |
29 | // for version x.y.z, versionCode is xxyyzzz
30 | const versionCode = (major * 100000) + (minor * 1000) + patch;
31 | console.log(`Success! Inferred version code: ${versionCode}`);
32 |
33 | // This is the magic syntax to set an output variable in Azure Pipelines
34 | console.log(`##vso[task.setvariable variable=APK_VERSION_CODE]${versionCode}`);
35 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ConfigRequestFulfiller.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.CancellationSignal;
7 | import android.view.accessibility.AccessibilityNodeInfo;
8 |
9 | public class ConfigRequestFulfiller implements RequestFulfiller {
10 | private final RootNodeFinder rootNodeFinder;
11 | private final EventHelper eventHelper;
12 | private final DeviceConfigFactory deviceConfigFactory;
13 |
14 | public ConfigRequestFulfiller(
15 | RootNodeFinder rootNodeFinder,
16 | EventHelper eventHelper,
17 | DeviceConfigFactory deviceConfigFactory) {
18 | this.rootNodeFinder = rootNodeFinder;
19 | this.deviceConfigFactory = deviceConfigFactory;
20 | this.eventHelper = eventHelper;
21 | }
22 |
23 | public String fulfillRequest(CancellationSignal cancellationSignal) {
24 | AccessibilityNodeInfo source = eventHelper.claimLastSource();
25 | AccessibilityNodeInfo rootNode = rootNodeFinder.getRootNodeFromSource(source);
26 |
27 | try {
28 | return deviceConfigFactory.getDeviceConfig(rootNode).toJson();
29 | } finally {
30 | if (rootNode != null && rootNode != source) {
31 | rootNode.recycle();
32 | }
33 | if (source != null && !eventHelper.restoreLastSource(source)) {
34 | source.recycle();
35 | }
36 | }
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/BitmapProviderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import org.junit.After;
8 | import org.junit.Assert;
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.Mock;
13 | import org.mockito.MockedStatic;
14 | import org.mockito.Mockito;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class BitmapProviderTest {
19 |
20 | @Mock Bitmap.Config config;
21 | @Mock Bitmap bitmapMock;
22 | MockedStatic bitmapStaticMock;
23 |
24 | private final int width = 1;
25 | private final int height = 2;
26 |
27 | BitmapProvider testSubject;
28 |
29 | @Before
30 | public void prepare() {
31 | bitmapStaticMock = Mockito.mockStatic(Bitmap.class);
32 | bitmapStaticMock.when(() -> Bitmap.createBitmap(width, height, config)).thenReturn(bitmapMock);
33 | testSubject = new BitmapProvider();
34 | }
35 |
36 | @After
37 | public void cleanUp() {
38 | bitmapStaticMock.close();
39 | }
40 |
41 | @Test
42 | public void bitmapIsNotNull() {
43 | Bitmap createdBitmap = testSubject.createBitmap(width, height, config);
44 | Assert.assertNotNull(createdBitmap);
45 | Assert.assertEquals(createdBitmap, bitmapMock);
46 |
47 | bitmapStaticMock.verify(() -> Bitmap.createBitmap(width, height, config));
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeContextFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import android.view.accessibility.AccessibilityNodeInfo;
8 | import com.deque.axe.android.AxeContext;
9 | import com.deque.axe.android.AxeDevice;
10 | import com.deque.axe.android.AxeView;
11 | import com.deque.axe.android.colorcontrast.AxeImage;
12 | import com.deque.axe.android.wrappers.AxeEventStream;
13 |
14 | public class AxeContextFactory {
15 | private final AxeImageFactory axeImageFactory;
16 | private final AxeViewsFactory axeViewsFactory;
17 | private final AxeDeviceFactory axeDeviceFactory;
18 |
19 | public AxeContextFactory(
20 | AxeImageFactory axeImageFactory,
21 | AxeViewsFactory axeViewsFactory,
22 | AxeDeviceFactory axeDeviceFactory) {
23 | this.axeImageFactory = axeImageFactory;
24 | this.axeViewsFactory = axeViewsFactory;
25 | this.axeDeviceFactory = axeDeviceFactory;
26 | }
27 |
28 | public AxeContext createAxeContext(AccessibilityNodeInfo rootNode, Bitmap screenshot)
29 | throws ViewChangedException {
30 | AxeView axeView = axeViewsFactory.createAxeViews(rootNode);
31 | AxeDevice axeDevice = axeDeviceFactory.createAxeDevice(rootNode);
32 | AxeImage axeImage = axeImageFactory.createAxeImage(screenshot);
33 | AxeEventStream axeEventStream = new AxeEventStream();
34 | return new AxeContext(axeView, axeDevice, axeImage, axeEventStream);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/pipeline/verify-clean-lockfile.js:
--------------------------------------------------------------------------------
1 | // USAGE:
2 | //
3 | // node verify-clean-lockfile.js
4 | //
5 | // Verifies that "git status" does not report any pending changed to gradle.lockfile
6 | //
7 | // This is a workaround for Dependabot not understanding natively how to update Gradle
8 | // lockfiles; it serves as a reminder for humans looking at Dependabot PRs to update
9 | // it manually.
10 |
11 | const path = require('path');
12 | const process = require('process');
13 | const child_process = require('child_process');
14 |
15 | const lockfilePath = path.join(__dirname, '..', 'AccessibilityInsightsForAndroidService', 'app', 'gradle.lockfile');
16 |
17 | const gitStatusResult = child_process.execFileSync('git', ['status', '--porcelain=1', '--', lockfilePath]);
18 | const isLockfileChanged = gitStatusResult.toString() !== '';
19 |
20 | if (isLockfileChanged) {
21 | const gitDiffResult = child_process.execFileSync('git', ['diff', '--', lockfilePath]);
22 | console.error(`
23 | Error: git status reports that there is an unexpected lockfile change
24 | in ${lockfilePath}.
25 |
26 | This probably means that you (or Dependabot) has updated a dependency
27 | in a build.gradle file without updating the lockfile. To update the
28 | lockfile:
29 |
30 | 1) Pull this branch
31 | 2) Run "./gradlew build --no-daemon" from /AccessibilityInsightsForAndroidService
32 | 3) Commit and push the resulting change to gradle.lockfile
33 |
34 | Diff of the unexpected change:
35 |
36 | ${gitDiffResult.toString()}
37 | `);
38 | process.exit(1);
39 | } else {
40 | console.log('Success! git status reports no unexpected lockfile change.');
41 | process.exit(0);
42 | }
43 |
--------------------------------------------------------------------------------
/pipeline/scripts/setup-emulator.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 | emulator_config="system-images;android-$ANDROID_API_LEVEL;google_apis;x86_64"
3 | emulator_name="emulator_api_$ANDROID_API_LEVEL"
4 |
5 | # Install AVD files
6 | echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install "$emulator_config"
7 | # Create emulator
8 | echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd -n "$emulator_name" -k "$emulator_config" --force
9 |
10 | $ANDROID_HOME/emulator/emulator -list-avds
11 |
12 | echo "Starting emulator"
13 | # Start emulator in background
14 | nohup $ANDROID_HOME/emulator/emulator -avd "$emulator_name" -no-snapshot -no-boot-anim > /dev/null 2>&1 &
15 |
16 | echo "Waiting for boot_completed to have value"
17 | $ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do echo -n "."; sleep 1; done; input keyevent 82; echo "."'
18 |
19 | echo "Pausing to allow emulator to finish booting"
20 | sleep 20 # pause to wait for the emulator to finish booting
21 |
22 | $ANDROID_HOME/platform-tools/adb devices
23 | echo "Emulator started"
24 |
25 | echo "Installing and enabling Accessibility Insights for Android Service"
26 | $ANDROID_HOME/platform-tools/adb install app-debug.apk
27 | $ANDROID_HOME/platform-tools/adb shell appops set com.microsoft.accessibilityinsightsforandroidservice PROJECT_MEDIA allow
28 | $ANDROID_HOME/platform-tools/adb shell settings put secure enabled_accessibility_services com.microsoft.accessibilityinsightsforandroidservice/com.microsoft.accessibilityinsightsforandroidservice.AccessibilityInsightsForAndroidService
29 |
30 | echo "Pausing to allow service to start"
31 | sleep 5 # pause to let the accessibility service start
32 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/DeviceOrientationHandlerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.times;
7 | import static org.mockito.Mockito.verify;
8 |
9 | import java.util.function.Consumer;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mock;
14 | import org.mockito.junit.MockitoJUnitRunner;
15 |
16 | @RunWith(MockitoJUnitRunner.class)
17 | public class DeviceOrientationHandlerTest {
18 |
19 | @Mock Consumer onChangeMock;
20 | int initialValue = 1;
21 |
22 | DeviceOrientationHandler testSubject;
23 |
24 | @Before
25 | public void prepare() {
26 | testSubject = new DeviceOrientationHandler(initialValue);
27 | }
28 |
29 | @Test
30 | public void setOrientationDoesNotCallOnChangeListenersIfOrientationDoesNotChange() {
31 | testSubject.subscribe(onChangeMock);
32 | testSubject.setOrientation(initialValue);
33 | verify(onChangeMock, times(0)).accept(2);
34 | }
35 |
36 | @Test
37 | public void setOrientationCallsOnChangeListenersOnOrientationchange() {
38 | testSubject.subscribe(onChangeMock);
39 | testSubject.setOrientation(2);
40 | verify(onChangeMock, times(1)).accept(2);
41 | }
42 |
43 | @Test
44 | public void setOrientationSupportsMultipleListeners() {
45 | testSubject.subscribe(onChangeMock);
46 | testSubject.subscribe(onChangeMock);
47 |
48 | testSubject.setOrientation(2);
49 |
50 | verify(onChangeMock, times(2)).accept(2);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AxeDeviceFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.when;
7 |
8 | import android.util.DisplayMetrics;
9 | import android.view.accessibility.AccessibilityNodeInfo;
10 | import org.junit.Assert;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class AxeDeviceFactoryTest {
19 |
20 | @Mock DeviceConfigFactory deviceConfigFactoryMock;
21 | @Mock AccessibilityNodeInfo rootNodeMock;
22 | String deviceName = "test-device-name";
23 | String packageName = "test-package-name";
24 | String serviceVersion = "test-service-version";
25 | DeviceConfig deviceConfig;
26 | DisplayMetrics displayMetrics;
27 |
28 | AxeDeviceFactory testSubject;
29 |
30 | @Before
31 | public void prepare() {
32 | deviceConfig = new DeviceConfig(deviceName, packageName, serviceVersion);
33 | when(deviceConfigFactoryMock.getDeviceConfig(rootNodeMock)).thenReturn(deviceConfig);
34 |
35 | displayMetrics = new DisplayMetrics();
36 | displayMetrics.density = 1;
37 | displayMetrics.heightPixels = 2;
38 | displayMetrics.widthPixels = 3;
39 |
40 | testSubject = new AxeDeviceFactory(deviceConfigFactoryMock, () -> displayMetrics);
41 | }
42 |
43 | @Test
44 | public void axeDeviceIsNotNull() {
45 | Assert.assertNotNull(testSubject.createAxeDevice(rootNodeMock));
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ScreenshotAxeImage.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import android.util.Base64;
8 | import com.deque.axe.android.colorcontrast.AxeColor;
9 | import com.deque.axe.android.colorcontrast.AxeImage;
10 | import com.deque.axe.android.wrappers.AxeRect;
11 | import java.io.ByteArrayOutputStream;
12 |
13 | public class ScreenshotAxeImage extends AxeImage {
14 | private final AxeRect frameRect;
15 | private Bitmap screenshot;
16 | private ByteArrayOutputStreamProvider byteArrayOutputStreamProvider;
17 |
18 | public ScreenshotAxeImage(
19 | Bitmap screenshot, ByteArrayOutputStreamProvider byteArrayOutputStreamProvider) {
20 | this.screenshot = screenshot;
21 | this.byteArrayOutputStreamProvider = byteArrayOutputStreamProvider;
22 | frameRect = new AxeRect(0, screenshot.getWidth() - 1, 0, screenshot.getHeight() - 1);
23 | }
24 |
25 | @Override
26 | public AxeRect frame() {
27 | return frameRect;
28 | }
29 |
30 | @Override
31 | public AxeColor pixel(int x, int y) {
32 | AxeColor color = new AxeColor(this.screenshot.getPixel(x, y));
33 | return color;
34 | }
35 |
36 | @Override
37 | public String toBase64Png() {
38 | ByteArrayOutputStream byteArrayOutputStream = byteArrayOutputStreamProvider.get();
39 | screenshot.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream);
40 | byte[] byteArray = byteArrayOutputStream.toByteArray();
41 | return Base64.encodeToString(byteArray, Base64.NO_WRAP);
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AxeScannerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.when;
7 |
8 | import android.graphics.Bitmap;
9 | import android.view.accessibility.AccessibilityNodeInfo;
10 | import com.deque.axe.android.Axe;
11 | import com.deque.axe.android.AxeContext;
12 | import com.deque.axe.android.AxeResult;
13 | import org.junit.Assert;
14 | import org.junit.Before;
15 | import org.junit.Test;
16 | import org.junit.runner.RunWith;
17 | import org.mockito.Mock;
18 | import org.mockito.junit.MockitoJUnitRunner;
19 |
20 | @RunWith(MockitoJUnitRunner.class)
21 | public class AxeScannerTest {
22 |
23 | @Mock Bitmap screenshotMock;
24 | @Mock AccessibilityNodeInfo accessibilityNodeInfoMock;
25 | @Mock AxeRunnerFactory axeRunnerFactoryMock;
26 | @Mock AxeContextFactory axeContextFactoryMock;
27 | @Mock AxeResult axeResultMock;
28 | @Mock Axe axeMock;
29 | @Mock AxeContext axeContextMock;
30 |
31 | AxeScanner testSubject;
32 |
33 | @Before
34 | public void prepare() {
35 | testSubject = new AxeScanner(axeRunnerFactoryMock, axeContextFactoryMock);
36 | }
37 |
38 | @Test
39 | public void scanWithAxeReturnsCorrectResult() throws ViewChangedException {
40 | when(axeRunnerFactoryMock.createAxeRunner()).thenReturn(axeMock);
41 | when(axeContextFactoryMock.createAxeContext(accessibilityNodeInfoMock, screenshotMock))
42 | .thenReturn(axeContextMock);
43 | when(axeMock.run(axeContextMock)).thenReturn(axeResultMock);
44 |
45 | Assert.assertEquals(
46 | testSubject.scanWithAxe(accessibilityNodeInfoMock, screenshotMock), axeResultMock);
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizationStateManagerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.times;
7 | import static org.mockito.Mockito.verify;
8 |
9 | import java.util.function.Consumer;
10 | import org.junit.Assert;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class FocusVisualizationStateManagerTest {
19 |
20 | @Mock Consumer onChangeMock;
21 |
22 | FocusVisualizationStateManager testSubject;
23 |
24 | @Before
25 | public void prepare() {
26 | testSubject = new FocusVisualizationStateManager();
27 | }
28 |
29 | @Test
30 | public void exists() {
31 | Assert.assertNotNull(testSubject);
32 | }
33 |
34 | @Test
35 | public void getStateReturnsFalseByDefault() {
36 | Assert.assertFalse(testSubject.getState());
37 | }
38 |
39 | @Test
40 | public void getStateReturnsUpdatedState() {
41 | testSubject.setState(true);
42 | Assert.assertTrue(testSubject.getState());
43 | }
44 |
45 | @Test
46 | public void setStateDoesNotCallOnChangeListenersIfStateDoesNotChange() {
47 | testSubject.subscribe(onChangeMock);
48 | testSubject.setState(false);
49 | verify(onChangeMock, times(0)).accept(false);
50 | }
51 |
52 | @Test
53 | public void setStateCallsOnChangeListenersOnStateChange() {
54 | testSubject.subscribe(onChangeMock);
55 | testSubject.setState(true);
56 | Assert.assertTrue(testSubject.getState());
57 | verify(onChangeMock, times(1)).accept(true);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/UIThreadRunnerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.ArgumentMatchers.any;
7 | import static org.mockito.Mockito.doAnswer;
8 | import static org.mockito.Mockito.verify;
9 |
10 | import android.os.Handler;
11 | import android.os.Looper;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mock;
15 | import org.mockito.MockedConstruction;
16 | import org.mockito.MockedStatic;
17 | import org.mockito.Mockito;
18 | import org.mockito.junit.MockitoJUnitRunner;
19 |
20 | @RunWith(MockitoJUnitRunner.class)
21 | public class UIThreadRunnerTest {
22 |
23 | @Mock Looper looperMock;
24 | @Mock Runnable runnableMock;
25 |
26 | UIThreadRunner testSubject;
27 |
28 | @Test
29 | public void createsNewHandlerUsingMainLooper() throws Exception {
30 | try (MockedStatic looperStaticMock = Mockito.mockStatic(Looper.class)) {
31 | looperStaticMock.when(Looper::getMainLooper).thenReturn(looperMock);
32 | try (MockedConstruction handlerConstructionMock =
33 | Mockito.mockConstruction(
34 | Handler.class,
35 | (handlerMock, context) -> {
36 | doAnswer(
37 | invocation -> {
38 | Runnable runnable = invocation.getArgument(0);
39 | runnable.run();
40 | return null;
41 | })
42 | .when(handlerMock)
43 | .post(any());
44 | })) {
45 | testSubject = new UIThreadRunner();
46 | testSubject.run(runnableMock);
47 |
48 | verify(runnableMock).run();
49 | }
50 | }
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizationCanvas.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.Context;
7 | import android.graphics.Canvas;
8 | import android.view.View;
9 | import androidx.annotation.VisibleForTesting;
10 | import java.util.ArrayList;
11 |
12 | public class FocusVisualizationCanvas extends View {
13 | private ArrayList focusElementHighlights;
14 | private ArrayList focusElementLines;
15 |
16 | public FocusVisualizationCanvas(Context context) {
17 | super(context);
18 | }
19 |
20 | @Override
21 | protected void onDraw(Canvas canvas) {
22 | super.onDraw(canvas);
23 | this.drawHighlightsAndLines(canvas);
24 | }
25 |
26 | @VisibleForTesting
27 | public void drawHighlightsAndLines(Canvas canvas) {
28 | if (this.focusElementHighlights == null) {
29 | return;
30 | }
31 |
32 | for (int elementIndex = 0; elementIndex < this.focusElementHighlights.size(); elementIndex++) {
33 | if (elementIndex != 0) {
34 | this.drawTrailingHighlights(elementIndex, canvas);
35 | }
36 |
37 | this.focusElementHighlights.get(elementIndex).drawElementHighlight(canvas);
38 | }
39 | }
40 |
41 | private void drawTrailingHighlights(int elementIndex, Canvas canvas) {
42 | this.focusElementLines.get(elementIndex).drawLine(canvas);
43 | this.focusElementHighlights.get(elementIndex - 1).drawElementHighlight(canvas);
44 | }
45 |
46 | public void setDrawItems(
47 | ArrayList highlights, ArrayList lines) {
48 | this.focusElementHighlights = highlights;
49 | this.focusElementLines = lines;
50 | }
51 |
52 | public void redraw() {
53 | this.invalidate();
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | name: "CodeQL"
2 |
3 | on:
4 | push:
5 | branches: [main]
6 | pull_request:
7 | # The branches below must be a subset of the branches above
8 | branches: [main]
9 | schedule:
10 | - cron: '0 5 * * 5'
11 |
12 | jobs:
13 | analyze:
14 | name: Analyze
15 | runs-on: ubuntu-latest
16 |
17 | strategy:
18 | fail-fast: false
19 | matrix:
20 | # Override automatic language detection by changing the below list
21 | # Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
22 | language: ['java', 'javascript']
23 | # Learn more...
24 | # https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
25 |
26 | steps:
27 | - name: Checkout repository
28 | uses: actions/checkout@v2
29 | with:
30 | # We must fetch at least the immediate parents so that if this is
31 | # a pull request then we can checkout the head.
32 | fetch-depth: 2
33 |
34 | # If this run was triggered by a pull request event, then checkout
35 | # the head of the pull request instead of the merge commit.
36 | - run: git checkout HEAD^2
37 | if: ${{ github.event_name == 'pull_request' }}
38 |
39 | # Initializes the CodeQL tools for scanning.
40 | - name: Initialize CodeQL
41 | uses: github/codeql-action/init@v1
42 | with:
43 | languages: ${{ matrix.language }}
44 |
45 | # ℹ️ Command-line programs to run using the OS shell.
46 | # 📚 https://git.io/JvXDl
47 |
48 | - name: make gradlew executable
49 | run: chmod +x ./AccessibilityInsightsForAndroidService/gradlew
50 |
51 | - name: build
52 | run: ./gradlew build
53 | working-directory: ./AccessibilityInsightsForAndroidService
54 |
55 | - name: Perform CodeQL Analysis
56 | uses: github/codeql-action/analyze@v1
57 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AccessibilityNodeInfoQueueBuilder.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 | import java.util.PriorityQueue;
8 | import java.util.Queue;
9 |
10 | public class AccessibilityNodeInfoQueueBuilder {
11 | public Queue> buildPriorityQueue(
12 | AccessibilityNodeInfo rootNode) {
13 | PriorityQueue> queue = new PriorityQueue<>();
14 | recursivelyEnqueueNodes(queue, rootNode, Long.MAX_VALUE);
15 | return queue;
16 | }
17 |
18 | private void recursivelyEnqueueNodes(
19 | PriorityQueue> queue,
20 | AccessibilityNodeInfo node,
21 | Long order) {
22 | // The AxeView object requires that we create the AxeView
23 | // objects for both child nodes and for any labeledBy nodes. Child nodes use
24 | // easily predictable rules, but labeledBy nodes are less structured. We use
25 | // a PriorityQueue where children are given a slightly higher priority than
26 | // the parent, but nodes that are used as labels are given a significantly
27 | // higher priority. This will work for most "typical" cases, but not the case
28 | // where a node's labeledBy value points to a direct ancestor. That scenario
29 | // could require changes to the AxeView class, which is immutable after construction.
30 |
31 | if (node == null) {
32 | return;
33 | }
34 |
35 | if (node.getLabelFor() != null) {
36 | order /= 2;
37 | }
38 |
39 | queue.add(new OrderedValue<>(node, order));
40 |
41 | int childCount = node.getChildCount();
42 |
43 | for (int loop = 0; loop < childCount; loop++) {
44 | recursivelyEnqueueNodes(queue, node.getChild(loop), order - 1);
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/SynchronizedRequestDispatcher.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.CancellationSignal;
7 | import androidx.annotation.AnyThread;
8 | import androidx.annotation.NonNull;
9 |
10 | public class SynchronizedRequestDispatcher {
11 | public static final SynchronizedRequestDispatcher SharedInstance =
12 | new SynchronizedRequestDispatcher();
13 |
14 | private RequestDispatcher underlyingDispatcher = null;
15 | private Object lock = new Object();
16 | private CancellationSignal teardownSignal = new CancellationSignal();
17 |
18 | @AnyThread
19 | public void setup(@NonNull RequestDispatcher instance) {
20 | synchronized (lock) {
21 | if (this.underlyingDispatcher != null) {
22 | throw new RuntimeException("Attempt to double-initialize instance");
23 | }
24 | this.teardownSignal = new CancellationSignal();
25 | this.underlyingDispatcher = instance;
26 | }
27 | }
28 |
29 | @AnyThread
30 | public void teardown() {
31 | CancellationSignal teardownSignal = this.teardownSignal;
32 | if (teardownSignal != null) {
33 | teardownSignal.cancel();
34 | }
35 |
36 | synchronized (lock) {
37 | this.underlyingDispatcher = null;
38 | this.teardownSignal = null;
39 | }
40 | }
41 |
42 | @AnyThread
43 | public String request(@NonNull String method, @NonNull CancellationSignal cancellationSignal)
44 | throws Exception {
45 | CancellationSignal combinedCancellationSignal = new CancellationSignal();
46 |
47 | synchronized (lock) {
48 | if (underlyingDispatcher == null) {
49 | throw new Exception("Service is not running");
50 | }
51 |
52 | teardownSignal.setOnCancelListener(combinedCancellationSignal::cancel);
53 | cancellationSignal.setOnCancelListener(combinedCancellationSignal::cancel);
54 | combinedCancellationSignal.throwIfCanceled();
55 |
56 | return underlyingDispatcher.request(method, combinedCancellationSignal);
57 | }
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/LayoutParamGeneratorTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.junit.Assert.assertEquals;
7 | import static org.mockito.Mockito.when;
8 |
9 | import android.graphics.PixelFormat;
10 | import android.util.DisplayMetrics;
11 | import android.view.WindowManager;
12 | import java.util.List;
13 | import java.util.function.Supplier;
14 | import org.junit.Before;
15 | import org.junit.Test;
16 | import org.junit.runner.RunWith;
17 | import org.mockito.Mock;
18 | import org.mockito.MockedConstruction;
19 | import org.mockito.Mockito;
20 | import org.mockito.junit.MockitoJUnitRunner;
21 |
22 | @RunWith(MockitoJUnitRunner.class)
23 | public class LayoutParamGeneratorTest {
24 |
25 | @Mock Supplier displayMetricsSupplier;
26 |
27 | @Mock DisplayMetrics displayMetrics;
28 |
29 | LayoutParamGenerator testSubject;
30 |
31 | @Before
32 | public void prepare() {
33 | testSubject = new LayoutParamGenerator(displayMetricsSupplier);
34 | }
35 |
36 | @Test
37 | public void generatesLayoutParams() throws Exception {
38 | when(displayMetricsSupplier.get()).thenReturn(displayMetrics);
39 | try (MockedConstruction layoutParamsConstructionMock =
40 | Mockito.mockConstruction(
41 | WindowManager.LayoutParams.class,
42 | (mockLayoutParams, context) -> {
43 | List> args = context.arguments();
44 | assertEquals(displayMetrics.widthPixels, args.get(0));
45 | assertEquals(displayMetrics.heightPixels, args.get(1));
46 | assertEquals(WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY, args.get(2));
47 | assertEquals(
48 | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
49 | | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
50 | | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
51 | | WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
52 | args.get(3));
53 | assertEquals(PixelFormat.TRANSLUCENT, args.get(4));
54 | })) {
55 | testSubject.get();
56 | }
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AxeContextFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.when;
7 |
8 | import android.graphics.Bitmap;
9 | import android.view.accessibility.AccessibilityNodeInfo;
10 | import com.deque.axe.android.AxeContext;
11 | import com.deque.axe.android.AxeDevice;
12 | import com.deque.axe.android.AxeView;
13 | import com.deque.axe.android.colorcontrast.AxeImage;
14 | import org.junit.Assert;
15 | import org.junit.Before;
16 | import org.junit.Test;
17 | import org.junit.runner.RunWith;
18 | import org.mockito.Mock;
19 | import org.mockito.junit.MockitoJUnitRunner;
20 |
21 | @RunWith(MockitoJUnitRunner.class)
22 | public class AxeContextFactoryTest {
23 |
24 | @Mock AxeImageFactory axeImageFactoryMock;
25 | @Mock AxeImage axeImageMock;
26 | @Mock AxeViewsFactory axeViewsFactoryMock;
27 | @Mock AxeView axeViewMock;
28 | @Mock AxeDeviceFactory axeDeviceFactoryMock;
29 | @Mock AxeDevice axeDeviceMock;
30 | @Mock AccessibilityNodeInfo rootNodeMock;
31 | @Mock Bitmap screenshotMock;
32 |
33 | AxeContextFactory testSubject;
34 |
35 | @Before
36 | public void prepare() throws ViewChangedException {
37 | when(axeImageFactoryMock.createAxeImage(screenshotMock)).thenReturn(axeImageMock);
38 | when(axeViewsFactoryMock.createAxeViews(rootNodeMock)).thenReturn(axeViewMock);
39 | when(axeDeviceFactoryMock.createAxeDevice(rootNodeMock)).thenReturn(axeDeviceMock);
40 |
41 | testSubject =
42 | new AxeContextFactory(axeImageFactoryMock, axeViewsFactoryMock, axeDeviceFactoryMock);
43 | }
44 |
45 | @Test
46 | public void axeContentIsNotNull() throws ViewChangedException {
47 | Assert.assertNotNull(testSubject.createAxeContext(rootNodeMock, screenshotMock));
48 | }
49 |
50 | @Test
51 | public void axeContentHasCorrectProperties() throws ViewChangedException {
52 | AxeContext axeContext = testSubject.createAxeContext(rootNodeMock, screenshotMock);
53 | Assert.assertEquals(axeContext.screenshot, axeImageMock);
54 | Assert.assertEquals(axeContext.axeDevice, axeDeviceMock);
55 | Assert.assertEquals(axeContext.axeView, axeViewMock);
56 | Assert.assertNotNull(axeContext.axeEventStream);
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/RequestReaderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.when;
7 |
8 | import java.io.BufferedReader;
9 | import java.io.IOException;
10 | import org.junit.Assert;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 | import org.mockito.stubbing.OngoingStubbing;
17 |
18 | @RunWith(MockitoJUnitRunner.class)
19 | public class RequestReaderTest {
20 |
21 | @Mock BufferedReader bufferedReaderMock;
22 |
23 | RequestReader testSubject;
24 |
25 | @Before
26 | public void prepare() {
27 | testSubject = new RequestReader(bufferedReaderMock);
28 | }
29 |
30 | @Test
31 | public void requestReaderExists() {
32 | Assert.assertNotNull(testSubject);
33 | }
34 |
35 | @Test
36 | public void readsFromBufferedReader() throws IOException {
37 | String requestString = "test request string";
38 | setupReadLine(requestString);
39 |
40 | String actualRequestString = testSubject.readRequest();
41 |
42 | Assert.assertEquals(actualRequestString, requestString);
43 | }
44 |
45 | @Test
46 | public void limitsInputLength() throws IOException {
47 | OngoingStubbing bufferedReaderStubbing = when(bufferedReaderMock.read());
48 | for (int i = 0; i < 300; i++) {
49 | bufferedReaderStubbing = bufferedReaderStubbing.thenReturn(42);
50 | }
51 |
52 | try {
53 | testSubject.readRequest();
54 | Assert.fail("Should have thrown exception");
55 | } catch (IOException e) {
56 | Assert.assertEquals(e.getMessage(), "input too long");
57 | }
58 | }
59 |
60 | private void setupReadLine(String str) {
61 | OngoingStubbing bufferedReaderStubbing;
62 | try {
63 | bufferedReaderStubbing = when(bufferedReaderMock.read());
64 | } catch (IOException e) {
65 | Assert.fail(e.getMessage());
66 | return;
67 | }
68 |
69 | for (int i = 0; i < str.length(); i++) {
70 | bufferedReaderStubbing = bufferedReaderStubbing.thenReturn((int) str.charAt(i));
71 | }
72 |
73 | bufferedReaderStubbing.thenReturn((int) '\n');
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/DisplayMetricsHelperTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.verify;
7 | import static org.mockito.Mockito.when;
8 | import static org.mockito.internal.verification.VerificationModeFactory.times;
9 |
10 | import android.content.Context;
11 | import android.content.res.Resources;
12 | import android.util.DisplayMetrics;
13 | import android.view.Display;
14 | import android.view.WindowManager;
15 | import org.junit.After;
16 | import org.junit.Assert;
17 | import org.junit.Before;
18 | import org.junit.Test;
19 | import org.junit.runner.RunWith;
20 | import org.mockito.Mock;
21 | import org.mockito.MockedStatic;
22 | import org.mockito.Mockito;
23 | import org.mockito.junit.MockitoJUnitRunner;
24 |
25 | @RunWith(MockitoJUnitRunner.class)
26 | public class DisplayMetricsHelperTest {
27 |
28 | @Mock Resources resourcesMock;
29 | @Mock DisplayMetrics displayMetricsMock;
30 | @Mock Context contextMock;
31 | @Mock WindowManager windowManagerMock;
32 | @Mock Display displayMock;
33 |
34 | MockedStatic resourcesStaticMock;
35 |
36 | @Before
37 | public void prepare() {
38 | setupDisplayMocks();
39 | }
40 |
41 | @After
42 | public void cleanUp() {
43 | resourcesStaticMock.close();
44 | }
45 |
46 | @Test
47 | public void returnsNotNull() {
48 | Assert.assertNotNull(DisplayMetricsHelper.getRealDisplayMetrics(contextMock));
49 | }
50 |
51 | @Test
52 | public void returnsExpectedDisplayMetrics() {
53 | DisplayMetrics actualDisplayMetrics = DisplayMetricsHelper.getRealDisplayMetrics(contextMock);
54 |
55 | verify(displayMock, times(1)).getRealMetrics(displayMetricsMock);
56 | Assert.assertEquals(actualDisplayMetrics, displayMetricsMock);
57 | }
58 |
59 | private void setupDisplayMocks() {
60 | resourcesStaticMock = Mockito.mockStatic(Resources.class);
61 | resourcesStaticMock.when(Resources::getSystem).thenReturn(resourcesMock);
62 | when(resourcesMock.getDisplayMetrics()).thenReturn(displayMetricsMock);
63 | when(contextMock.getSystemService(Context.WINDOW_SERVICE)).thenReturn(windowManagerMock);
64 | when(windowManagerMock.getDefaultDisplay()).thenReturn(displayMock);
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/ThreadSafeSwapperTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import org.junit.Assert;
7 | import org.junit.Before;
8 | import org.junit.Test;
9 | import org.junit.runner.RunWith;
10 | import org.mockito.Mock;
11 | import org.mockito.junit.MockitoJUnitRunner;
12 |
13 | @RunWith(MockitoJUnitRunner.class)
14 | public class ThreadSafeSwapperTest {
15 | @Mock GenericTestObject mockOldObject;
16 |
17 | @Mock GenericTestObject mockIntermediateObject;
18 |
19 | @Mock GenericTestObject mockNewObject;
20 |
21 | ThreadSafeSwapper testSubject;
22 |
23 | @Before
24 | public void prepare() {
25 | testSubject = new ThreadSafeSwapper<>();
26 | }
27 |
28 | @Test
29 | public void threadSafeSwapperExists() {
30 | Assert.assertNotNull(testSubject);
31 | }
32 |
33 | @Test
34 | public void swapReturnsSwappedOutObject() {
35 | testSubject.swap(mockOldObject);
36 |
37 | GenericTestObject actualReturnedObject = testSubject.swap(mockNewObject);
38 |
39 | Assert.assertEquals(mockOldObject, actualReturnedObject);
40 | }
41 |
42 | @Test
43 | public void swapReplacesCurrentObjectWithMethodParameter() {
44 | testSubject.swap(mockOldObject);
45 |
46 | GenericTestObject actualOldObject = testSubject.swap(mockIntermediateObject);
47 | GenericTestObject actualIntermediateObject = testSubject.swap(mockNewObject);
48 |
49 | Assert.assertEquals(mockOldObject, actualOldObject);
50 | Assert.assertEquals(mockIntermediateObject, actualIntermediateObject);
51 | }
52 |
53 | @Test
54 | public void setIfCurrentlyNullDoesNotSetCurrentObjectIfNotNull() {
55 | boolean expectedReturnValue = false;
56 | testSubject.swap(mockOldObject);
57 |
58 | boolean actualReturnValue = testSubject.setIfCurrentlyNull(mockNewObject);
59 |
60 | Assert.assertEquals(expectedReturnValue, actualReturnValue);
61 | }
62 |
63 | @Test
64 | public void setIfCurrentlyNullSetsCurrentObjectIfNull() {
65 | boolean expectedReturnValue = true;
66 | testSubject.swap(null);
67 |
68 | boolean actualReturnValue = testSubject.setIfCurrentlyNull(mockNewObject);
69 |
70 | Assert.assertEquals(expectedReturnValue, actualReturnValue);
71 | }
72 |
73 | private class GenericTestObject {}
74 | }
75 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ResultsV2ContainerSerializer.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import com.deque.axe.android.AxeResult;
7 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
8 | import com.google.gson.Gson;
9 | import com.google.gson.GsonBuilder;
10 | import com.google.gson.TypeAdapter;
11 | import com.google.gson.stream.JsonReader;
12 | import com.google.gson.stream.JsonWriter;
13 | import java.io.IOException;
14 | import java.util.List;
15 |
16 | public class ResultsV2ContainerSerializer {
17 | private final ATFARulesSerializer atfaRulesSerializer;
18 | private final ATFAResultsSerializer atfaResultsSerializer;
19 | private final Gson gson;
20 | private final TypeAdapter resultsContainerTypeAdapter =
21 | new TypeAdapter() {
22 | @Override
23 | public void write(JsonWriter out, ResultsV2Container value) throws IOException {
24 | out.beginObject();
25 | out.name("AxeResults").jsonValue(value.AxeResult.toJson());
26 | out.name("ATFARules").jsonValue(atfaRulesSerializer.serializeATFARules());
27 | out.name("ATFAResults")
28 | .jsonValue(atfaResultsSerializer.serializeATFAResults(value.ATFAResults));
29 | out.endObject();
30 | }
31 |
32 | @Override
33 | public ResultsV2Container read(JsonReader in) {
34 | return null;
35 | }
36 | };
37 |
38 | public ResultsV2ContainerSerializer(
39 | ATFARulesSerializer atfaRulesSerializer,
40 | ATFAResultsSerializer atfaResultsSerializer,
41 | GsonBuilder gsonBuilder) {
42 | this.atfaRulesSerializer = atfaRulesSerializer;
43 | this.atfaResultsSerializer = atfaResultsSerializer;
44 | this.gson =
45 | gsonBuilder
46 | .registerTypeAdapter(ResultsV2Container.class, this.resultsContainerTypeAdapter)
47 | .create();
48 | }
49 |
50 | public String createResultsJson(
51 | AxeResult axeResult, List atfaResults) {
52 | ResultsV2Container container = new ResultsV2Container();
53 | container.ATFAResults = atfaResults;
54 | container.AxeResult = axeResult;
55 | return gson.toJson(container);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ATFAResultsSerializer.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheck;
7 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
8 | import com.google.android.apps.common.testing.accessibility.framework.uielement.WindowHierarchyElement;
9 | import com.google.android.apps.common.testing.accessibility.framework.uielement.WindowHierarchyElementAndroid;
10 | import com.google.gson.ExclusionStrategy;
11 | import com.google.gson.FieldAttributes;
12 | import com.google.gson.FieldNamingStrategy;
13 | import com.google.gson.Gson;
14 | import com.google.gson.GsonBuilder;
15 | import com.google.gson.JsonPrimitive;
16 | import com.google.gson.JsonSerializer;
17 | import java.util.Arrays;
18 | import java.util.List;
19 |
20 | public class ATFAResultsSerializer {
21 | private static final List ClassesToSkip =
22 | Arrays.asList(WindowHierarchyElement.class, WindowHierarchyElementAndroid.class);
23 |
24 | private static final FieldNamingStrategy ATFAFieldNamingStrategy =
25 | f -> f.getDeclaringClass().getSimpleName() + "." + f.getName();
26 |
27 | private static final ExclusionStrategy ATFAExclusionStrategy =
28 | new ExclusionStrategy() {
29 | @Override
30 | public boolean shouldSkipField(FieldAttributes f) {
31 | return false;
32 | }
33 |
34 | @Override
35 | public boolean shouldSkipClass(Class> clazz) {
36 | return ClassesToSkip.contains(clazz);
37 | }
38 | };
39 |
40 | private static final JsonSerializer> ClassSerializer =
41 | (src, typeOfSrc, context) -> new JsonPrimitive(src.getSimpleName());
42 |
43 | private final Gson gsonSerializer;
44 |
45 | public ATFAResultsSerializer(GsonBuilder gsonBuilder) {
46 | gsonSerializer =
47 | gsonBuilder
48 | .setFieldNamingStrategy(ATFAFieldNamingStrategy)
49 | .setExclusionStrategies(ATFAExclusionStrategy)
50 | .registerTypeAdapter(Class.class, ClassSerializer)
51 | .create();
52 | }
53 |
54 | public String serializeATFAResults(List atfaResults) {
55 | return gsonSerializer.toJson(atfaResults);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/DeviceConfigFactoryTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.when;
7 |
8 | import android.view.accessibility.AccessibilityNodeInfo;
9 | import org.junit.Assert;
10 | import org.junit.Before;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mock;
14 | import org.mockito.junit.MockitoJUnitRunner;
15 |
16 | @RunWith(MockitoJUnitRunner.class)
17 | public class DeviceConfigFactoryTest {
18 | final CharSequence samplePackageName = "test-package-name";
19 | final String packageNameUnavailable = "No application detected";
20 | final String sampleBuildModel = "test-build-model";
21 |
22 | @Mock AccessibilityNodeInfo mockRootNode;
23 |
24 | DeviceConfigFactory testSubject;
25 |
26 | @Before
27 | public void prepare() {
28 | testSubject = new DeviceConfigFactory(sampleBuildModel);
29 | }
30 |
31 | @Test
32 | public void deviceConfigFactoryExists() {
33 | Assert.assertNotNull(testSubject);
34 | }
35 |
36 | @Test
37 | public void getDeviceConfigReturnsNonNullDeviceConfig() {
38 | Assert.assertNotNull(testSubject.getDeviceConfig(mockRootNode));
39 | }
40 |
41 | @Test
42 | public void deviceConfigFactoryPropertiesExist() {
43 | DeviceConfig deviceConfig = testSubject.getDeviceConfig(mockRootNode);
44 |
45 | Assert.assertNotNull(deviceConfig.deviceName);
46 | Assert.assertEquals(sampleBuildModel, deviceConfig.deviceName);
47 | Assert.assertNotNull(deviceConfig.packageName);
48 | Assert.assertNotNull(deviceConfig.serviceVersion);
49 | }
50 |
51 | @Test
52 | public void deviceConfigFactoryGetsProperPackageName() {
53 | when(mockRootNode.getPackageName()).thenReturn(samplePackageName);
54 |
55 | Assert.assertEquals(samplePackageName, getActualPackageName(mockRootNode));
56 | }
57 |
58 | @Test
59 | public void deviceConfigFactoryGetsNoPackageNameWhenPackageNameIsNull() {
60 | when(mockRootNode.getPackageName()).thenReturn(null);
61 |
62 | Assert.assertEquals(packageNameUnavailable, getActualPackageName(mockRootNode));
63 | }
64 |
65 | @Test
66 | public void deviceConfigFactoryGetsNoPackageNameWhenRootNodeIsNull() {
67 | Assert.assertEquals(packageNameUnavailable, getActualPackageName(null));
68 | }
69 |
70 | private String getActualPackageName(AccessibilityNodeInfo rootNode) {
71 | return testSubject.getDeviceConfig(rootNode).packageName;
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/RootNodeFinderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.never;
7 | import static org.mockito.Mockito.times;
8 | import static org.mockito.Mockito.verify;
9 | import static org.mockito.Mockito.when;
10 |
11 | import android.view.accessibility.AccessibilityNodeInfo;
12 | import org.junit.Assert;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 | import org.junit.runner.RunWith;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.MockitoJUnitRunner;
18 |
19 | @RunWith(MockitoJUnitRunner.class)
20 | public class RootNodeFinderTest {
21 |
22 | @Mock AccessibilityNodeInfo sourceMock;
23 | @Mock AccessibilityNodeInfo parentMock;
24 | @Mock AccessibilityNodeInfo grandparentMock;
25 |
26 | RootNodeFinder testSubject;
27 |
28 | @Before
29 | public void prepare() {
30 | testSubject = new RootNodeFinder();
31 | }
32 |
33 | @Test
34 | public void returnNullIfSourceIsNull() {
35 | Assert.assertNull(testSubject.getRootNodeFromSource(null));
36 | }
37 |
38 | @Test
39 | public void rootNodeExistsIfSourceExists() {
40 | Assert.assertNotNull(testSubject.getRootNodeFromSource(sourceMock));
41 | }
42 |
43 | @Test
44 | public void rootNodeIsSource() {
45 | AccessibilityNodeInfo rootNode = testSubject.getRootNodeFromSource(sourceMock);
46 | Assert.assertEquals(rootNode, sourceMock);
47 | }
48 |
49 | @Test
50 | public void rootNodeIsSourceParent() {
51 | when(sourceMock.getParent()).thenReturn(parentMock);
52 |
53 | AccessibilityNodeInfo rootNode = testSubject.getRootNodeFromSource(sourceMock);
54 | Assert.assertEquals(rootNode, parentMock);
55 | }
56 |
57 | @Test
58 | public void rootNodeIsSourceAncestor() {
59 | when(sourceMock.getParent()).thenReturn(parentMock);
60 | when(parentMock.getParent()).thenReturn(grandparentMock);
61 |
62 | AccessibilityNodeInfo rootNode = testSubject.getRootNodeFromSource(sourceMock);
63 | Assert.assertEquals(rootNode, grandparentMock);
64 | }
65 |
66 | @Test
67 | public void uneededNodesGetRecycled() {
68 | when(sourceMock.getParent()).thenReturn(parentMock);
69 | when(parentMock.getParent()).thenReturn(grandparentMock);
70 |
71 | AccessibilityNodeInfo rootNode = testSubject.getRootNodeFromSource(sourceMock);
72 | verify(sourceMock, never()).recycle();
73 | verify(rootNode, never()).recycle();
74 | verify(parentMock, times(1)).recycle();
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizerStylesTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Color;
7 | import android.graphics.Paint;
8 | import java.util.HashMap;
9 | import org.junit.After;
10 | import org.junit.Assert;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.MockedConstruction;
15 | import org.mockito.MockedStatic;
16 | import org.mockito.Mockito;
17 | import org.mockito.junit.MockitoJUnitRunner;
18 |
19 | @RunWith(MockitoJUnitRunner.class)
20 | public class FocusVisualizerStylesTest {
21 |
22 | FocusVisualizerStyles testSubject;
23 |
24 | MockedConstruction paintConstructionMock;
25 | MockedStatic colorStaticMock;
26 |
27 | @Before
28 | public void prepare() throws Exception {
29 | paintConstructionMock = Mockito.mockConstruction(Paint.class);
30 | colorStaticMock = Mockito.mockStatic(Color.class);
31 | testSubject = new FocusVisualizerStyles();
32 | }
33 |
34 | @After
35 | public void cleanUp() {
36 | colorStaticMock.close();
37 | paintConstructionMock.close();
38 | }
39 |
40 | @Test
41 | public void getCurrentElementPaintsReturnsAllRelevantPaints() {
42 | HashMap paints = testSubject.getCurrentElementPaints();
43 | Assert.assertNotNull(paints.get("outerCircle"));
44 | Assert.assertNotNull(paints.get("innerCircle"));
45 | Assert.assertNotNull(paints.get("number"));
46 | Assert.assertNotNull(paints.get("transparentInnerCircle"));
47 | }
48 |
49 | @Test
50 | public void getNonCurrentElementPaintsReturnsAllRelevantPaints() {
51 | HashMap paints = testSubject.getNonCurrentElementPaints();
52 | Assert.assertNotNull(paints.get("outerCircle"));
53 | Assert.assertNotNull(paints.get("innerCircle"));
54 | Assert.assertNotNull(paints.get("number"));
55 | }
56 |
57 | @Test
58 | public void getNonCurrentLinePaintsReturnsAllRelevantPaints() {
59 | HashMap paints = testSubject.getNonCurrentLinePaints();
60 | Assert.assertNotNull(paints.get("foregroundLine"));
61 | Assert.assertNotNull(paints.get("backgroundLine"));
62 | }
63 |
64 | @Test
65 | public void getCurrentLinePaintsReturnsAllRelevantPaints() {
66 | HashMap paints = testSubject.getCurrentLinePaints();
67 | Assert.assertNotNull(paints.get("foregroundLine"));
68 | Assert.assertNotNull(paints.get("backgroundLine"));
69 | }
70 | }
71 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/EventHelperTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.times;
7 | import static org.mockito.Mockito.verify;
8 | import static org.mockito.Mockito.when;
9 |
10 | import android.view.accessibility.AccessibilityNodeInfo;
11 | import org.junit.Assert;
12 | import org.junit.Before;
13 | import org.junit.Test;
14 | import org.junit.runner.RunWith;
15 | import org.mockito.Mock;
16 | import org.mockito.junit.MockitoJUnitRunner;
17 |
18 | @RunWith(MockitoJUnitRunner.class)
19 | public class EventHelperTest {
20 | @Mock ThreadSafeSwapper mockSwapper;
21 |
22 | @Mock AccessibilityNodeInfo mockNodeInfo;
23 |
24 | @Mock AccessibilityNodeInfo mockLastNodeInfo;
25 |
26 | EventHelper testSubject;
27 |
28 | @Before
29 | public void prepare() {
30 | testSubject = new EventHelper(mockSwapper);
31 | }
32 |
33 | @Test
34 | public void eventHelperExists() {
35 | Assert.assertNotNull(testSubject);
36 | }
37 |
38 | @Test
39 | public void claimLastSourceReturnsExpectedNodeInfo() {
40 | when(mockSwapper.swap(null)).thenReturn(mockNodeInfo);
41 |
42 | AccessibilityNodeInfo actualResponse = testSubject.claimLastSource();
43 |
44 | Assert.assertEquals(mockNodeInfo, actualResponse);
45 | }
46 |
47 | @Test
48 | public void claimLastSourceCallsSwapWithNullObjectOnlyOnce() {
49 | testSubject.claimLastSource();
50 |
51 | verify(mockSwapper, times(1)).swap(null);
52 | }
53 |
54 | @Test
55 | public void restoreLastSourceCallsSetIfCurrentlyNullOnlyOnce() {
56 | testSubject.restoreLastSource(mockNodeInfo);
57 |
58 | verify(mockSwapper, times(1)).setIfCurrentlyNull(mockNodeInfo);
59 | }
60 |
61 | @Test
62 | public void recordEventProperlyHandlesNonNullEventSource() {
63 | when(mockSwapper.swap(mockNodeInfo)).thenReturn(mockLastNodeInfo);
64 |
65 | testSubject.recordEvent(mockNodeInfo);
66 |
67 | verify(mockLastNodeInfo, times(1)).recycle();
68 | }
69 |
70 | @Test
71 | public void recordEventProperlyHandlesNullEventSource() {
72 | testSubject.recordEvent(null);
73 |
74 | verify(mockSwapper, times(0)).swap(mockNodeInfo);
75 | verify(mockLastNodeInfo, times(0)).recycle();
76 | }
77 |
78 | @Test
79 | public void recordEventProperlyHandlesNullLastSource() {
80 | when(mockSwapper.swap(mockNodeInfo)).thenReturn(null);
81 |
82 | testSubject.recordEvent(mockNodeInfo);
83 |
84 | verify(mockLastNodeInfo, times(0)).recycle();
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizationCanvasTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.ArgumentMatchers.any;
7 | import static org.mockito.Mockito.times;
8 | import static org.mockito.Mockito.verify;
9 |
10 | import android.content.Context;
11 | import android.graphics.Canvas;
12 | import java.util.ArrayList;
13 | import org.junit.Before;
14 | import org.junit.Test;
15 | import org.junit.runner.RunWith;
16 | import org.mockito.Mock;
17 | import org.mockito.junit.MockitoJUnitRunner;
18 |
19 | @RunWith(MockitoJUnitRunner.class)
20 | public class FocusVisualizationCanvasTest {
21 | FocusVisualizationCanvas testSubject;
22 |
23 | @Mock Context contextMock;
24 | @Mock FocusElementHighlight focusElementHighlightMock;
25 | @Mock FocusElementLine focusElementLineMock;
26 | @Mock Canvas canvasMock;
27 |
28 | @Before
29 | public void prepare() {
30 | testSubject = new FocusVisualizationCanvas(contextMock);
31 | }
32 |
33 | @Test
34 | public void drawHighlightsAndLinesOnlyDrawsHighlightOnFirstPass() throws Exception {
35 | ArrayList lineStub = new ArrayList<>();
36 | lineStub.add(focusElementLineMock);
37 |
38 | ArrayList highlightStub = new ArrayList<>();
39 | highlightStub.add(focusElementHighlightMock);
40 |
41 | testSubject.setDrawItems(highlightStub, lineStub);
42 | testSubject.drawHighlightsAndLines(canvasMock);
43 |
44 | verify(focusElementHighlightMock, times(1)).drawElementHighlight(any(Canvas.class));
45 | verify(focusElementLineMock, times(0)).drawLine(any(Canvas.class));
46 | }
47 |
48 | @Test
49 | public void drawHighlightsAndLinesDrawsAllRelevantObjectsOnSubsequentPasses() throws Exception {
50 | ArrayList lineStub = new ArrayList<>();
51 | lineStub.add(focusElementLineMock);
52 | lineStub.add(focusElementLineMock);
53 |
54 | ArrayList highlightStub = new ArrayList<>();
55 | highlightStub.add(focusElementHighlightMock);
56 | highlightStub.add(focusElementHighlightMock);
57 |
58 | testSubject.setDrawItems(highlightStub, lineStub);
59 | testSubject.drawHighlightsAndLines(canvasMock);
60 |
61 | // Note: drawElementHighlight will call twice for each subsequent onDraw event. This is to
62 | // ensure that the line is drawn underneath the highlight, as the canvas drawings draw on
63 | // top of any previous drawings by default.
64 | verify(focusElementHighlightMock, times(3)).drawElementHighlight(any(Canvas.class));
65 | verify(focusElementLineMock, times(1)).drawLine(any(Canvas.class));
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusElementLine.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import android.graphics.Rect;
9 | import android.view.View;
10 | import android.view.accessibility.AccessibilityNodeInfo;
11 | import java.util.HashMap;
12 |
13 | public class FocusElementLine {
14 | private AccessibilityNodeInfo eventSource;
15 | private AccessibilityNodeInfo previousEventSource;
16 | private int yOffset;
17 | private int xStart;
18 | private int yStart;
19 | private int xEnd;
20 | private int yEnd;
21 | private HashMap paints;
22 | private Rect currentRect;
23 | private Rect prevRect;
24 | private View view;
25 |
26 | public FocusElementLine(
27 | AccessibilityNodeInfo eventSource,
28 | AccessibilityNodeInfo previousEventSource,
29 | HashMap Paints,
30 | View view) {
31 | this.view = view;
32 | this.eventSource = eventSource;
33 | this.previousEventSource = previousEventSource;
34 | this.paints = Paints;
35 | this.currentRect = new Rect();
36 | this.prevRect = new Rect();
37 | }
38 |
39 | public void drawLine(Canvas canvas) {
40 | if (this.eventSource == null || this.previousEventSource == null) {
41 | return;
42 | }
43 |
44 | if (!this.eventSource.refresh() || !this.previousEventSource.refresh()) {
45 | return;
46 | }
47 |
48 | this.updateWithNewCoordinates();
49 | this.drawConnectingLine(
50 | this.xStart, this.yStart, this.xEnd, this.yEnd, this.paints.get("backgroundLine"), canvas);
51 | this.drawConnectingLine(
52 | this.xStart, this.yStart, this.xEnd, this.yEnd, this.paints.get("foregroundLine"), canvas);
53 | }
54 |
55 | private void setCoordinates() {
56 | this.eventSource.getBoundsInScreen(this.currentRect);
57 | this.currentRect.offset(0, this.yOffset);
58 |
59 | this.previousEventSource.getBoundsInScreen(this.prevRect);
60 | this.prevRect.offset(0, this.yOffset);
61 |
62 | this.xStart = currentRect.centerX();
63 | this.yStart = currentRect.centerY();
64 | this.xEnd = prevRect.centerX();
65 | this.yEnd = prevRect.centerY();
66 | }
67 |
68 | private void drawConnectingLine(
69 | int xStart, int yStart, int xEnd, int yEnd, Paint paint, Canvas canvas) {
70 | canvas.drawLine(xStart, yStart, xEnd, yEnd, paint);
71 | }
72 |
73 | public void setPaint(HashMap paints) {
74 | this.paints = paints;
75 | }
76 |
77 | private void updateWithNewCoordinates() {
78 | this.yOffset = OffsetHelper.getYOffset(this.view);
79 | this.setCoordinates();
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/npm-wrapper/README.md:
--------------------------------------------------------------------------------
1 | ## accessibility-insights-for-android-service-bin
2 |
3 | ### Notice
4 | The Accessibility Insights team is proud to provide accessibility scanning solutions across multiple platforms including Web, Windows Desktop, and Android. Unfortunately, the usage of Accessibility Insights for Android did not meet expectations and we will be **ending support of that product** effective June 1 2023, so we can focus on our more popular products for Web and Windows Desktop. The product download link will be removed to promote security and discourage use of an unmaintained version of the product. The source code will remain available under the MIT open-source license. We are grateful to the community who continue to use our accessibility testing products!
5 |
6 | This NPM package is CommonJS module that acts as a thin wrapper around the [Accessibility Insights for Android Service](../README.md) APK file. The package bundles a copy of the APK and exports the path and version of the bundled APK (and its associated NOTICE file).
7 |
8 | This wrapper package is intended for consumption by [Accessibility Insights for Android](https://github.com/microsoft/accessibility-insights-web); **we make no guarantees about its API stability or fitness for other purposes.**
9 |
10 | Note that the `apkPath` and `noticePath` exports are defined relative to `__dirname`, which means that they may not be usable in a bundled environment. For Accessibility Insights for Android, we read them during our build process and copy the files to a location our packaging setup understands.
11 |
12 | ### Versioning
13 |
14 | This wrapper package's version matches the version of the bundled APK. It **does not use semantic versioning**; we reserve the right to make breaking API changes to the wrapper package without a major version update.
15 |
16 | ### Typescript usage
17 |
18 | ```ts
19 | import { noticePath, apkPath, apkVersionName } from 'accessibility-insights-for-android-service-bin';
20 |
21 | console.log(`Absolute path of the APK bundled with the package: ${apkPath}`);
22 | console.log(`APK_VERSION_NAME of the bundled APK: ${apkVersionName}`);
23 | console.log(`Absolute path of the NOTICE file for the APK: ${noticePath}`);
24 | ```
25 |
26 | ### Reporting security vulnerabilities
27 |
28 | If you believe you have found a security vulnerability in this project, please follow [these steps](https://technet.microsoft.com/en-us/security/ff852094.aspx) to report it. For more information on how vulnerabilities are disclosed, see [Coordinated Vulnerability Disclosure](https://technet.microsoft.com/en-us/security/dn467923).
29 |
30 | ### Contributing
31 |
32 | All contributions are welcome! Please read through our guidelines on [contributions](../CONTRIBUTING.md) to this project.
33 |
34 | ### Code of Conduct
35 |
36 | Please read through our [Code of Conduct](../CODE_OF_CONDUCT.md) to this project.
37 |
38 | Android is a trademark of Google LLC.
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ATFAScanner.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.Context;
7 | import android.view.accessibility.AccessibilityNodeInfo;
8 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckPreset;
9 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResult;
10 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityCheckResultUtils;
11 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheck;
12 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
13 | import com.google.android.apps.common.testing.accessibility.framework.Parameters;
14 | import com.google.android.apps.common.testing.accessibility.framework.uielement.AccessibilityHierarchyAndroid;
15 | import com.google.android.apps.common.testing.accessibility.framework.utils.contrast.BitmapImage;
16 | import com.google.common.collect.ImmutableSet;
17 | import java.util.ArrayList;
18 | import java.util.Arrays;
19 | import java.util.HashSet;
20 | import java.util.List;
21 |
22 | public class ATFAScanner {
23 | private final Context context;
24 | private final AccessibilityCheckResult.AccessibilityCheckResultType[] relevantResultTypes = {
25 | AccessibilityCheckResult.AccessibilityCheckResultType.ERROR,
26 | AccessibilityCheckResult.AccessibilityCheckResultType.INFO,
27 | AccessibilityCheckResult.AccessibilityCheckResultType.WARNING,
28 | AccessibilityCheckResult.AccessibilityCheckResultType.RESOLVED,
29 | AccessibilityCheckResult.AccessibilityCheckResultType.NOT_RUN
30 | };
31 |
32 | public ATFAScanner(Context context) {
33 | this.context = context;
34 | }
35 |
36 | public List scanWithATFA(
37 | AccessibilityNodeInfo rootNode, BitmapImage screenshot) {
38 | Parameters parameters = new Parameters();
39 | parameters.setSaveViewImages(true);
40 | parameters.putCustomTouchTargetSize(44); // default is 48 but min size as defined by WCAG is 44
41 | parameters.putScreenCapture(screenshot);
42 |
43 | ImmutableSet checks =
44 | AccessibilityCheckPreset.getAccessibilityHierarchyChecksForPreset(
45 | AccessibilityCheckPreset.LATEST);
46 | AccessibilityHierarchyAndroid hierarchy =
47 | AccessibilityHierarchyAndroid.newBuilder(rootNode, this.context).build();
48 | List results = new ArrayList<>();
49 |
50 | for (AccessibilityHierarchyCheck check : checks) {
51 | results.addAll(check.runCheckOnHierarchy(hierarchy, null, parameters));
52 | }
53 |
54 | return AccessibilityCheckResultUtils.getResultsForTypes(
55 | results, new HashSet<>(Arrays.asList(relevantResultTypes)));
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
5 |
6 |
7 | ## Security
8 |
9 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/).
10 |
11 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below.
12 |
13 | ## Reporting Security Issues
14 |
15 | **Please do not report security vulnerabilities through public GitHub issues.**
16 |
17 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report).
18 |
19 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc).
20 |
21 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc).
22 |
23 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue:
24 |
25 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.)
26 | * Full paths of source file(s) related to the manifestation of the issue
27 | * The location of the affected source code (tag/branch/commit or direct URL)
28 | * Any special configuration required to reproduce the issue
29 | * Step-by-step instructions to reproduce the issue
30 | * Proof-of-concept or exploit code (if possible)
31 | * Impact of the issue, including how an attacker might exploit the issue
32 |
33 | This information will help us triage your report more quickly.
34 |
35 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs.
36 |
37 | ## Preferred Languages
38 |
39 | We prefer all communications to be in English.
40 |
41 | ## Policy
42 |
43 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd).
44 |
45 |
46 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo WARNING: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo Attempting to infer JAVA_HOME based on a default Android Studio
29 | echo installation.
30 | echo.
31 |
32 | set JAVA_HOME=%ProgramFiles%\Android\Android Studio\jre
33 | set JAVA_EXE=%JAVA_HOME%\bin\java.exe
34 | "%JAVA_EXE%" -version >NUL 2>&1
35 | if "%ERRORLEVEL%" == "0" goto init
36 | set JAVA_HOME=
37 |
38 | echo.
39 | echo ERROR: JAVA_HOME is not set, could not be inferred from your Android Studio
40 | echo installation, and no 'java' command could be found in your PATH.
41 | echo.
42 | echo Please set the JAVA_HOME variable in your environment to match the
43 | echo location of your Java installation.
44 |
45 | goto fail
46 |
47 | :findJavaFromJavaHome
48 | set JAVA_HOME=%JAVA_HOME:"=%
49 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
50 |
51 | if exist "%JAVA_EXE%" goto init
52 |
53 | echo.
54 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
55 | echo.
56 | echo Please set the JAVA_HOME variable in your environment to match the
57 | echo location of your Java installation.
58 |
59 | goto fail
60 |
61 | :init
62 | @rem Get command-line arguments, handling Windows variants
63 |
64 | if not "%OS%" == "Windows_NT" goto win9xME_args
65 |
66 | :win9xME_args
67 | @rem Slurp the command line arguments.
68 | set CMD_LINE_ARGS=
69 | set _SKIP=2
70 |
71 | :win9xME_args_slurp
72 | if "x%~1" == "x" goto execute
73 |
74 | set CMD_LINE_ARGS=%*
75 |
76 | :execute
77 | @rem Setup the command line
78 |
79 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
80 |
81 | @rem Execute Gradle
82 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
83 |
84 | :end
85 | @rem End local scope for the variables with windows NT shell
86 | if "%ERRORLEVEL%"=="0" goto mainEnd
87 |
88 | :fail
89 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
90 | rem the _cmd.exe /c_ return code!
91 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
92 | exit /b 1
93 |
94 | :mainEnd
95 | if "%OS%"=="Windows_NT" endlocal
96 |
97 | :omega
98 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/RequestDispatcher.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.os.CancellationSignal;
7 | import androidx.annotation.NonNull;
8 |
9 | public class RequestDispatcher {
10 | private static final String TAG = "RequestDispatcher";
11 |
12 | private final RootNodeFinder rootNodeFinder;
13 | private final ScreenshotController screenshotController;
14 | private final EventHelper eventHelper;
15 | private final AxeScanner axeScanner;
16 | private final ATFAScanner atfaScanner;
17 | private final DeviceConfigFactory deviceConfigFactory;
18 | private final FocusVisualizationStateManager focusVisualizationStateManager;
19 | private final ResultsV2ContainerSerializer resultsV2ContainerSerializer;
20 |
21 | public RequestDispatcher(
22 | @NonNull RootNodeFinder rootNodeFinder,
23 | @NonNull ScreenshotController screenshotController,
24 | @NonNull EventHelper eventHelper,
25 | @NonNull AxeScanner axeScanner,
26 | @NonNull ATFAScanner atfaScanner,
27 | @NonNull DeviceConfigFactory deviceConfigFactory,
28 | @NonNull FocusVisualizationStateManager focusVisualizationStateManager,
29 | @NonNull ResultsV2ContainerSerializer resultsV2ContainerSerializer) {
30 | this.rootNodeFinder = rootNodeFinder;
31 | this.screenshotController = screenshotController;
32 | this.eventHelper = eventHelper;
33 | this.axeScanner = axeScanner;
34 | this.atfaScanner = atfaScanner;
35 | this.deviceConfigFactory = deviceConfigFactory;
36 | this.focusVisualizationStateManager = focusVisualizationStateManager;
37 | this.resultsV2ContainerSerializer = resultsV2ContainerSerializer;
38 | }
39 |
40 | public String request(@NonNull String method, @NonNull CancellationSignal cancellationSignal)
41 | throws Exception {
42 | Logger.logVerbose(TAG, "Handling request for method " + method);
43 | return getRequestFulfiller(method).fulfillRequest(cancellationSignal);
44 | }
45 |
46 | public RequestFulfiller getRequestFulfiller(@NonNull String method) {
47 | switch (method) {
48 | case "/config":
49 | return new ConfigRequestFulfiller(rootNodeFinder, eventHelper, deviceConfigFactory);
50 | case "/result":
51 | return new ResultV2RequestFulfiller(
52 | rootNodeFinder,
53 | eventHelper,
54 | axeScanner,
55 | atfaScanner,
56 | screenshotController,
57 | resultsV2ContainerSerializer);
58 | case "/FocusTracking/Enable":
59 | return new TabStopsRequestFulfiller(focusVisualizationStateManager, true);
60 | case "/FocusTracking/Disable": // Intentional fallthrough
61 | case "/FocusTracking/Reset":
62 | return new TabStopsRequestFulfiller(focusVisualizationStateManager, false);
63 | default:
64 | return new UnrecognizedRequestFulfiller(method);
65 | }
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/ScreenshotAxeImageTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.times;
7 | import static org.mockito.Mockito.verify;
8 | import static org.mockito.Mockito.when;
9 |
10 | import android.graphics.Bitmap;
11 | import android.util.Base64;
12 | import com.deque.axe.android.colorcontrast.AxeColor;
13 | import com.deque.axe.android.wrappers.AxeRect;
14 | import java.io.ByteArrayOutputStream;
15 | import org.junit.Assert;
16 | import org.junit.Before;
17 | import org.junit.Test;
18 | import org.junit.runner.RunWith;
19 | import org.mockito.Mock;
20 | import org.mockito.MockedStatic;
21 | import org.mockito.Mockito;
22 | import org.mockito.junit.MockitoJUnitRunner;
23 |
24 | @RunWith(MockitoJUnitRunner.class)
25 | public class ScreenshotAxeImageTest {
26 |
27 | @Mock Bitmap bitmapMock;
28 | @Mock ByteArrayOutputStreamProvider byteArrayOutputStreamProviderMock;
29 | @Mock ByteArrayOutputStream byteArrayOutputStreamMock;
30 |
31 | int sampleWidth;
32 | int sampleHeight;
33 | ScreenshotAxeImage testSubject;
34 |
35 | @Before
36 | public void prepare() {
37 | sampleHeight = 100;
38 | sampleWidth = 50;
39 |
40 | when(bitmapMock.getWidth()).thenReturn(sampleWidth);
41 | when(bitmapMock.getHeight()).thenReturn(sampleHeight);
42 |
43 | testSubject = new ScreenshotAxeImage(bitmapMock, byteArrayOutputStreamProviderMock);
44 | }
45 |
46 | @Test
47 | public void screenShotAxeImageExists() {
48 | Assert.assertNotNull(testSubject);
49 | }
50 |
51 | @Test
52 | public void pixelReturnsCorrectColor() {
53 | int givenX = 10;
54 | int givenY = 20;
55 | int colorIntStub = 100;
56 |
57 | when(bitmapMock.getPixel(givenX, givenY)).thenReturn(colorIntStub);
58 |
59 | AxeColor returnedAxeColor = testSubject.pixel(givenX, givenY);
60 |
61 | Assert.assertEquals(returnedAxeColor, new AxeColor(colorIntStub));
62 | }
63 |
64 | @Test
65 | public void frameReturnsCorrectRect() {
66 | AxeRect expectedRect = new AxeRect(0, sampleWidth - 1, 0, sampleHeight - 1);
67 |
68 | Assert.assertEquals(testSubject.frame(), expectedRect);
69 | }
70 |
71 | @Test
72 | public void toBase64PngReturnsCorrectString() {
73 | byte[] byteArrayStub = new byte[1];
74 | String expectedString = "some string";
75 |
76 | try (MockedStatic base64StaticMock = Mockito.mockStatic(Base64.class)) {
77 | when(byteArrayOutputStreamProviderMock.get()).thenReturn(byteArrayOutputStreamMock);
78 | when(byteArrayOutputStreamMock.toByteArray()).thenReturn(byteArrayStub);
79 | base64StaticMock
80 | .when(() -> Base64.encodeToString(byteArrayStub, Base64.NO_WRAP))
81 | .thenReturn(expectedString);
82 |
83 | Assert.assertEquals(testSubject.toBase64Png(), expectedString);
84 |
85 | verify(bitmapMock, times(1))
86 | .compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStreamMock);
87 | }
88 | }
89 | }
90 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AccessibilityEventDispatcher.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityEvent;
7 | import android.view.accessibility.AccessibilityNodeInfo;
8 | import java.util.ArrayList;
9 | import java.util.Arrays;
10 | import java.util.List;
11 | import java.util.function.Consumer;
12 |
13 | public class AccessibilityEventDispatcher {
14 | private static final String TAG = "AccessibilityEventDispatcher";
15 | private CharSequence previousPackageName;
16 |
17 | private ArrayList> onAppChangedListeners;
18 | private ArrayList> onFocusEventListeners;
19 | private ArrayList> onRedrawEventListeners;
20 |
21 | public AccessibilityEventDispatcher() {
22 | onAppChangedListeners = new ArrayList>();
23 | onFocusEventListeners = new ArrayList>();
24 | onRedrawEventListeners = new ArrayList>();
25 | }
26 |
27 | public static List redrawEventTypes =
28 | Arrays.asList(
29 | AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED,
30 | AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED,
31 | AccessibilityEvent.TYPE_VIEW_SCROLLED,
32 | AccessibilityEvent.TYPE_WINDOWS_CHANGED);
33 |
34 | public void onAccessibilityEvent(AccessibilityEvent event, AccessibilityNodeInfo rootNode) {
35 | int eventType = event.getEventType();
36 |
37 | if (rootNode != null
38 | && (previousPackageName == null
39 | || !previousPackageName.equals(rootNode.getPackageName()))) {
40 | previousPackageName = rootNode.getPackageName();
41 | this.callListeners(onAppChangedListeners, rootNode);
42 | }
43 |
44 | if (isFocusEvent(eventType)) {
45 | this.callListeners(onFocusEventListeners, event);
46 | return;
47 | }
48 |
49 | if (isRedrawEvent(eventType)) {
50 | this.callListeners(onRedrawEventListeners, event);
51 | return;
52 | }
53 | }
54 |
55 | public void addOnFocusEventListener(Consumer listener) {
56 | onFocusEventListeners.add(listener);
57 | }
58 |
59 | public void addOnRedrawEventListener(Consumer listener) {
60 | onRedrawEventListeners.add(listener);
61 | }
62 |
63 | public void addOnAppChangedListener(Consumer listener) {
64 | onAppChangedListeners.add(listener);
65 | }
66 |
67 | private boolean isFocusEvent(int eventType) {
68 | return eventType == AccessibilityEvent.TYPE_VIEW_FOCUSED;
69 | }
70 |
71 | private boolean isRedrawEvent(int eventType) {
72 | return redrawEventTypes.contains(eventType);
73 | }
74 |
75 | private void callListeners(ArrayList> listeners, T newValue) {
76 | listeners.forEach(
77 | listener -> {
78 | listener.accept(newValue);
79 | });
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/NodeViewBuilder.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Rect;
7 | import android.view.accessibility.AccessibilityNodeInfo;
8 | import com.deque.axe.android.AxeView;
9 | import com.deque.axe.android.wrappers.AxeRect;
10 | import java.util.List;
11 |
12 | public class NodeViewBuilder implements AxeView.Builder {
13 | private final AccessibilityNodeInfo accessibilityNode;
14 | private final List children;
15 | private final AxeView labeledBy;
16 | private final AxeRect boundsRect;
17 |
18 | public AxeRect boundsInScreen() {
19 | return boundsRect;
20 | }
21 |
22 | public String className() {
23 | String rawClassName = safeToString(accessibilityNode.getClassName());
24 | return (rawClassName == null)
25 | ? "Class Name Not Specified--Inserted by Accessibility Insights"
26 | : rawClassName;
27 | }
28 |
29 | public String contentDescription() {
30 | return safeToString(accessibilityNode.getContentDescription());
31 | }
32 |
33 | public boolean isAccessibilityFocusable() {
34 | return accessibilityNode.isFocusable();
35 | }
36 |
37 | public boolean isClickable() {
38 | return accessibilityNode.isClickable();
39 | }
40 |
41 | public boolean isEnabled() {
42 | return accessibilityNode.isEnabled();
43 | }
44 |
45 | public boolean isImportantForAccessibility() {
46 | return accessibilityNode.isImportantForAccessibility();
47 | }
48 |
49 | public AxeView labeledBy() {
50 | return labeledBy;
51 | }
52 |
53 | public String packageName() {
54 | return safeToString(accessibilityNode.getPackageName());
55 | }
56 |
57 | public String paneTitle() {
58 | return null;
59 | }
60 |
61 | public String text() {
62 | return safeToString(accessibilityNode.getText());
63 | }
64 |
65 | public String viewIdResourceName() {
66 | return accessibilityNode.getViewIdResourceName();
67 | }
68 |
69 | public List children() {
70 | return children;
71 | }
72 |
73 | public String value() {
74 | return null;
75 | }
76 |
77 | public String hintText() {
78 | if (android.os.Build.VERSION.SDK_INT >= 26) {
79 | return safeToString(accessibilityNode.getHintText());
80 | }
81 | return null;
82 | }
83 |
84 | public NodeViewBuilder(
85 | AccessibilityNodeInfo node,
86 | List children,
87 | AxeView labeledBy,
88 | AxeRectProvider boundsRectProvider) {
89 | accessibilityNode = node;
90 | this.children = children;
91 | this.labeledBy = labeledBy;
92 |
93 | Rect rect = new Rect();
94 | accessibilityNode.getBoundsInScreen(rect);
95 | boundsRect = boundsRectProvider.createAxeRect(rect.left, rect.right, rect.top, rect.bottom);
96 | }
97 |
98 | private String safeToString(CharSequence chars) {
99 | if (chars == null) {
100 | return null;
101 | }
102 |
103 | return chars.toString();
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/LoggerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.util.Log;
7 | import org.junit.After;
8 | import org.junit.Before;
9 | import org.junit.Test;
10 | import org.junit.runner.RunWith;
11 | import org.mockito.MockedStatic;
12 | import org.mockito.Mockito;
13 | import org.mockito.junit.MockitoJUnitRunner;
14 |
15 | @RunWith(MockitoJUnitRunner.class)
16 | public class LoggerTest {
17 |
18 | final String logTag = "logTag";
19 | final String logMessage = "log message";
20 |
21 | boolean originalEnableLogging;
22 | MockedStatic logStaticMock;
23 |
24 | @Before
25 | public void prepare() {
26 | logStaticMock = Mockito.mockStatic(Log.class);
27 | originalEnableLogging = Logger.ENABLE_LOGGING;
28 | }
29 |
30 | @After
31 | public void cleanUp() {
32 | Logger.ENABLE_LOGGING = originalEnableLogging;
33 | logStaticMock.close();
34 | }
35 |
36 | @Test
37 | public void logVerboseDebugOn() {
38 | Logger.ENABLE_LOGGING = true;
39 |
40 | Logger.logVerbose(logTag, logMessage);
41 |
42 | logStaticMock.verify(() -> Log.v(logTag, logMessage));
43 | }
44 |
45 | @Test
46 | public void logVerboseDebugOff() {
47 | Logger.ENABLE_LOGGING = false;
48 |
49 | Logger.logVerbose(logTag, logMessage);
50 |
51 | logStaticMock.verifyNoMoreInteractions();
52 | }
53 |
54 | @Test
55 | public void logDebugDebugOn() {
56 | Logger.ENABLE_LOGGING = true;
57 |
58 | Logger.logDebug(logTag, logMessage);
59 |
60 | logStaticMock.verify(() -> Log.d(logTag, logMessage));
61 | }
62 |
63 | @Test
64 | public void logDebugDebugOff() {
65 | Logger.ENABLE_LOGGING = false;
66 |
67 | Logger.logDebug(logTag, logMessage);
68 |
69 | logStaticMock.verifyNoMoreInteractions();
70 | }
71 |
72 | @Test
73 | public void logErrorDebugOn() {
74 | Logger.ENABLE_LOGGING = true;
75 |
76 | Logger.logError(logTag, logMessage);
77 |
78 | logStaticMock.verify(() -> Log.e(logTag, logMessage));
79 | }
80 |
81 | @Test
82 | public void logErrorDebugOff() {
83 | Logger.ENABLE_LOGGING = false;
84 |
85 | Logger.logError(logTag, logMessage);
86 |
87 | logStaticMock.verifyNoMoreInteractions();
88 | }
89 |
90 | @Test
91 | public void logInfoDebugOn() {
92 | Logger.ENABLE_LOGGING = true;
93 |
94 | Logger.logInfo(logTag, logMessage);
95 |
96 | logStaticMock.verify(() -> Log.i(logTag, logMessage));
97 | }
98 |
99 | @Test
100 | public void logInfoDebugOff() {
101 | Logger.ENABLE_LOGGING = false;
102 |
103 | Logger.logInfo(logTag, logMessage);
104 |
105 | logStaticMock.verifyNoMoreInteractions();
106 | }
107 |
108 | @Test
109 | public void logWarningDebugOn() {
110 | Logger.ENABLE_LOGGING = true;
111 |
112 | Logger.logWarning(logTag, logMessage);
113 |
114 | logStaticMock.verify(() -> Log.w(logTag, logMessage));
115 | }
116 |
117 | @Test
118 | public void logWarningDebugOff() {
119 | Logger.ENABLE_LOGGING = false;
120 |
121 | Logger.logWarning(logTag, logMessage);
122 |
123 | logStaticMock.verifyNoMoreInteractions();
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
19 |
22 |
23 |
24 |
25 |
29 |
34 |
35 |
36 |
37 |
40 |
41 |
46 |
54 |
60 |
61 |
66 |
67 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/ConfigRequestFulfillerTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.junit.Assert.assertEquals;
7 | import static org.mockito.ArgumentMatchers.any;
8 | import static org.mockito.Mockito.never;
9 | import static org.mockito.Mockito.reset;
10 | import static org.mockito.Mockito.times;
11 | import static org.mockito.Mockito.verify;
12 | import static org.mockito.Mockito.verifyNoInteractions;
13 | import static org.mockito.Mockito.when;
14 |
15 | import android.os.CancellationSignal;
16 | import android.view.accessibility.AccessibilityNodeInfo;
17 | import org.junit.Assert;
18 | import org.junit.Before;
19 | import org.junit.Test;
20 | import org.junit.runner.RunWith;
21 | import org.mockito.Mock;
22 | import org.mockito.junit.MockitoJUnitRunner;
23 |
24 | @RunWith(MockitoJUnitRunner.class)
25 | public class ConfigRequestFulfillerTest {
26 |
27 | @Mock RootNodeFinder rootNodeFinder;
28 | @Mock EventHelper eventHelper;
29 | @Mock DeviceConfigFactory deviceConfigFactory;
30 | @Mock AccessibilityNodeInfo sourceNodeMock;
31 | @Mock AccessibilityNodeInfo rootNodeMock;
32 | @Mock DeviceConfig deviceConfig;
33 | @Mock CancellationSignal cancellationSignal;
34 |
35 | String configJson = "test config";
36 |
37 | ConfigRequestFulfiller testSubject;
38 |
39 | @Before
40 | public void prepare() {
41 | testSubject = new ConfigRequestFulfiller(rootNodeFinder, eventHelper, deviceConfigFactory);
42 | }
43 |
44 | @Test
45 | public void configRequestFulfillerExists() {
46 | Assert.assertNotNull(testSubject);
47 | }
48 |
49 | @Test
50 | public void writesCorrectResponse() {
51 | setupSuccessfulRequest();
52 |
53 | assertEquals(configJson, testSubject.fulfillRequest(cancellationSignal));
54 | }
55 |
56 | @Test
57 | public void recyclesNodes() {
58 | setupSuccessfulRequest();
59 |
60 | testSubject.fulfillRequest(cancellationSignal);
61 |
62 | verify(rootNodeMock, times(1)).recycle();
63 | verify(sourceNodeMock, times(1)).recycle();
64 | }
65 |
66 | @Test
67 | public void recyclesNodeOnceIfRootEqualsSource() {
68 | setupSuccessfulRequest();
69 | reset(rootNodeFinder);
70 | reset(deviceConfigFactory);
71 | when(rootNodeFinder.getRootNodeFromSource(any())).thenReturn(sourceNodeMock);
72 | when(deviceConfigFactory.getDeviceConfig(sourceNodeMock)).thenReturn(deviceConfig);
73 |
74 | testSubject.fulfillRequest(cancellationSignal);
75 |
76 | verifyNoInteractions(rootNodeMock);
77 | verify(sourceNodeMock, times(1)).recycle();
78 | }
79 |
80 | @Test
81 | public void doesNotRecycleSourceIfRestoreLastSourceSucceeds() {
82 | setupSuccessfulRequest();
83 | when(eventHelper.restoreLastSource(sourceNodeMock)).thenReturn(true);
84 |
85 | testSubject.fulfillRequest(cancellationSignal);
86 | verify(rootNodeMock, times(1)).recycle();
87 | verify(sourceNodeMock, never()).recycle();
88 | }
89 |
90 | private void setupSuccessfulRequest() {
91 | when(eventHelper.claimLastSource()).thenReturn(sourceNodeMock);
92 | when(rootNodeFinder.getRootNodeFromSource(sourceNodeMock)).thenReturn(rootNodeMock);
93 | when(deviceConfigFactory.getDeviceConfig(rootNodeMock)).thenReturn(deviceConfig);
94 | when(deviceConfig.toJson()).thenReturn(configJson);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/build.gradle:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
5 |
6 | buildscript {
7 | repositories {
8 | mavenCentral()
9 | exclusiveContent {
10 | forRepository {
11 | google()
12 | }
13 | filter {
14 | includeGroupByRegex("^androidx(\$|\\..*)")
15 | includeGroupByRegex("^com\\.android(\$|\\..*)")
16 | // Note: not all com.google.* cases come from google(), several are published
17 | // to mavenCentral only (eg, gson, protobuf, findbugs, guava, jimfs)
18 | includeGroup("com.google.android.apps.common.testing.accessibility.framework")
19 | includeGroup("com.google.android.material")
20 | includeGroup("com.google.test.platform")
21 | }
22 | }
23 | exclusiveContent {
24 | forRepository {
25 | gradlePluginPortal()
26 | }
27 | filter {
28 | includeGroup("org.jetbrains.trove4j")
29 | }
30 | }
31 | }
32 | dependencies {
33 | classpath 'com.android.tools.build:gradle:4.1.1'
34 |
35 | // NOTE: Do not place your application dependencies here; they belong
36 | // in the individual module build.gradle files
37 | }
38 | }
39 |
40 | plugins {
41 | id "com.diffplug.gradle.spotless" version "3.27.1"
42 | }
43 |
44 | allprojects {
45 | repositories {
46 | mavenCentral()
47 | exclusiveContent {
48 | forRepository {
49 | google()
50 | }
51 | filter {
52 | includeGroupByRegex("^androidx(\$|\\..*)")
53 | includeGroupByRegex("^com\\.android(\$|\\..*)")
54 | // Note: not all com.google.* cases come from google(), several are published
55 | // to mavenCentral only (eg, gson, protobuf, findbugs, guava, jimfs)
56 | includeGroup("com.google.android.apps.common.testing.accessibility.framework")
57 | includeGroup("com.google.android.material")
58 | includeGroup("com.google.test.platform")
59 | }
60 | }
61 | exclusiveContent {
62 | forRepository {
63 | gradlePluginPortal()
64 | }
65 | filter {
66 | includeGroup("org.jetbrains.trove4j")
67 | }
68 | }
69 | }
70 | }
71 |
72 | //task clean(type: Delete) {
73 | // delete rootProject.buildDir
74 | //}
75 |
76 | spotless {
77 | java {
78 | googleJavaFormat()
79 | target '**/*.java'
80 | }
81 | java {
82 | target '**/*.java'
83 | // These files have an APACHE license header, which we need to keep
84 | targetExclude '**/AccessibilityInsightsForAndroidService.java','**/AccessibilityNodeInfoSorter.java'
85 | licenseHeader '// Copyright (c) Microsoft Corporation.\n// Licensed under the MIT License.\n\n'
86 | }
87 | }
88 |
89 | task fastpass {
90 | dependsOn 'spotlessCheck'
91 | dependsOn 'app:lint'
92 | }
93 |
94 | task fastpassFix {
95 | gradle.startParameter.writeDependencyLocks = true
96 | dependsOn 'spotlessApply'
97 | dependsOn 'app:lintFix'
98 | }
99 |
100 | dependencyLocking {
101 | lockAllConfigurations()
102 | }
--------------------------------------------------------------------------------
/pipeline/validation-build.yaml:
--------------------------------------------------------------------------------
1 | # Copyright (c) Microsoft Corporation. All rights reserved.
2 | # Licensed under the MIT License.
3 |
4 | trigger:
5 | - main
6 |
7 | variables:
8 | Codeql.Enabled: true
9 |
10 | jobs:
11 | - job: 'gradlew_assemble'
12 |
13 | pool:
14 | vmImage: 'ubuntu-latest'
15 |
16 | steps:
17 | - task: Gradle@2
18 | displayName: build dev ai-android service
19 | inputs:
20 | workingDirectory: '$(system.defaultWorkingDirectory)/AccessibilityInsightsForAndroidService'
21 | gradleWrapperFile: 'AccessibilityInsightsForAndroidService/gradlew'
22 | gradleOptions: '-Xmx3072m'
23 | options: -S
24 | javaHomeOption: 'JDKVersion'
25 | jdkVersionOption: '1.11'
26 | jdkArchitectureOption: 'x64'
27 | tasks: 'assemble'
28 |
29 | - task: PublishPipelineArtifact@1
30 | displayName: publish artifact
31 | inputs:
32 | targetPath: '$(system.defaultWorkingDirectory)/AccessibilityInsightsForAndroidService/app/build/outputs/apk/debug/app-debug.apk'
33 | artifactName: debugApk
34 |
35 | - job: 'gradlew_build_test_lint'
36 |
37 | pool:
38 | vmImage: 'ubuntu-latest'
39 |
40 | steps:
41 | - task: Gradle@2
42 | displayName: build and test dev ai-android service
43 | inputs:
44 | workingDirectory: '$(system.defaultWorkingDirectory)/AccessibilityInsightsForAndroidService'
45 | gradleWrapperFile: 'AccessibilityInsightsForAndroidService/gradlew'
46 | gradleOptions: '-Xmx3072m'
47 | options: -S
48 | javaHomeOption: 'JDKVersion'
49 | jdkVersionOption: '1.11'
50 | jdkArchitectureOption: 'x64'
51 | publishJUnitResults: true
52 | testResultsFiles: '**/TEST-*.xml'
53 | tasks: 'build'
54 |
55 | - task: CopyFiles@2
56 | displayName: (lint-results) copy lint-results files to artifact staging
57 | condition: succeededOrFailed()
58 | inputs:
59 | contents: |
60 | lint-results.html
61 | lint-results.xml
62 | sourceFolder: '$(system.defaultWorkingDirectory)/AccessibilityInsightsForAndroidService/app/build/reports'
63 | targetFolder: '$(build.artifactstagingdirectory)/lint-results'
64 |
65 | - task: PublishPipelineArtifact@1
66 | displayName: (lint-results) publish lint-results artifact
67 | condition: succeededOrFailed()
68 | inputs:
69 | artifactName: 'lint-results'
70 | targetPath: '$(build.artifactstagingdirectory)/lint-results'
71 |
72 | - script: node $(system.defaultWorkingDirectory)/pipeline/verify-clean-lockfile.js
73 | displayName: verify that building did not change the lockfile unexpectedly
74 |
75 | - job: 'e2e_test'
76 |
77 | dependsOn: 'gradlew_assemble'
78 |
79 | pool:
80 | # per ADO docs, only macOS hosted agents support Android emulators. The emulator is installed on other agents, but didn't
81 | # function properly on Ubuntu when this was added.
82 | # https://docs.microsoft.com/en-us/azure/devops/pipelines/ecosystems/android?view=azure-devops#test-on-the-android-emulator
83 | vmImage: 'macOS-latest'
84 |
85 | strategy:
86 | matrix:
87 | API_24: { apiLevel: 24 } # minSdkVersion in build.gradle
88 | API_28: { apiLevel: 28 } # targetSdkVersion in build.gradle
89 | API_30: { apiLevel: 30 } # current most popular API level (Android 11)
90 | API_32: { apiLevel: 32 } # current latest API level
91 |
92 | steps:
93 | - template: ./e2e-emulator-template.yaml
94 | parameters:
95 | apiLevel: $(apiLevel)
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusElementHighlight.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Canvas;
7 | import android.graphics.Paint;
8 | import android.graphics.Rect;
9 | import android.view.View;
10 | import android.view.accessibility.AccessibilityNodeInfo;
11 | import java.util.HashMap;
12 |
13 | public class FocusElementHighlight {
14 | private AccessibilityNodeInfo eventSource;
15 | private int yOffset;
16 | private int tabStopCount;
17 | private int radius;
18 | private int xCoordinate;
19 | private int yCoordinate;
20 | private HashMap paints;
21 | private Rect rect;
22 | private View view;
23 | private boolean isCurrentElement;
24 | private static final String TAG = "FocusElementHighlight";
25 |
26 | public FocusElementHighlight(
27 | AccessibilityNodeInfo eventSource,
28 | HashMap currentPaints,
29 | int radius,
30 | int tabStopCount,
31 | View view) {
32 | this.view = view;
33 | this.eventSource = eventSource;
34 | this.tabStopCount = tabStopCount;
35 | this.radius = radius;
36 | this.rect = new Rect();
37 | this.paints = currentPaints;
38 | this.isCurrentElement = true;
39 | }
40 |
41 | private void setCoordinates() {
42 | this.eventSource.getBoundsInScreen(this.rect);
43 | this.rect.offset(0, this.yOffset);
44 | this.xCoordinate = rect.centerX();
45 | this.yCoordinate = rect.centerY();
46 | }
47 |
48 | public void drawElementHighlight(Canvas canvas) {
49 | if (this.eventSource == null) {
50 | return;
51 | }
52 |
53 | if (!this.eventSource.refresh()) {
54 | return;
55 | }
56 |
57 | this.updateWithNewCoordinates();
58 |
59 | if (isCurrentElement) {
60 | this.drawInnerCircle(
61 | this.xCoordinate,
62 | this.yCoordinate,
63 | this.radius,
64 | this.paints.get("transparentInnerCircle"),
65 | canvas);
66 | } else {
67 | this.drawInnerCircle(
68 | this.xCoordinate, this.yCoordinate, this.radius, this.paints.get("innerCircle"), canvas);
69 | this.drawNumberInCircle(
70 | this.xCoordinate, this.yCoordinate, this.tabStopCount, this.paints.get("number"), canvas);
71 | }
72 |
73 | this.drawOuterCircle(
74 | this.xCoordinate, this.yCoordinate, this.radius, this.paints.get("outerCircle"), canvas);
75 | }
76 |
77 | private void drawInnerCircle(
78 | int xCoordinate, int yCoordinate, int radius, Paint paint, Canvas canvas) {
79 | canvas.drawCircle(xCoordinate, yCoordinate, radius, paint);
80 | }
81 |
82 | private void drawOuterCircle(
83 | int xCoordinate, int yCoordinate, int radius, Paint paint, Canvas canvas) {
84 | canvas.drawCircle(xCoordinate, yCoordinate, radius + 3, paint);
85 | }
86 |
87 | private void drawNumberInCircle(
88 | int xCoordinate, int yCoordinate, int tabStopCount, Paint paint, Canvas canvas) {
89 | canvas.drawText(
90 | Integer.toString(tabStopCount),
91 | xCoordinate,
92 | yCoordinate - ((paint.descent() + paint.ascent()) / 2),
93 | paint);
94 | }
95 |
96 | public void setPaints(HashMap paints) {
97 | this.paints = paints;
98 | }
99 |
100 | public void setAsNonCurrentElement() {
101 | this.isCurrentElement = false;
102 | }
103 |
104 | public AccessibilityNodeInfo getEventSource() {
105 | return this.eventSource;
106 | }
107 |
108 | private void updateWithNewCoordinates() {
109 | this.yOffset = OffsetHelper.getYOffset(this.view);
110 | this.setCoordinates();
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ScreenshotController.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import android.graphics.PixelFormat;
8 | import android.hardware.display.DisplayManager;
9 | import android.hardware.display.VirtualDisplay;
10 | import android.media.ImageReader;
11 | import android.media.projection.MediaProjection;
12 | import android.os.Handler;
13 | import android.util.DisplayMetrics;
14 | import java.util.function.Consumer;
15 | import java.util.function.Supplier;
16 |
17 | public class ScreenshotController {
18 | private final OnScreenshotAvailableProvider onScreenshotAvailableProvider;
19 | private DisplayMetrics metrics;
20 | private Handler screenshotHandler;
21 | private ImageReader imageReader;
22 | private Supplier displayMetricsSupplier;
23 | private VirtualDisplay display;
24 | private BitmapProvider bitmapProvider;
25 | private Supplier mediaProjectionSupplier;
26 |
27 | public ScreenshotController(
28 | Supplier displayMetricsSupplier,
29 | Handler handler,
30 | OnScreenshotAvailableProvider onScreenshotAvailableProvider,
31 | BitmapProvider bitmapProvider,
32 | Supplier mediaProjectionSupplier) {
33 | this.displayMetricsSupplier = displayMetricsSupplier;
34 | this.screenshotHandler = handler;
35 | this.onScreenshotAvailableProvider = onScreenshotAvailableProvider;
36 | this.bitmapProvider = bitmapProvider;
37 | this.mediaProjectionSupplier = mediaProjectionSupplier;
38 | }
39 |
40 | public void getScreenshotWithMediaProjection(Consumer bitmapConsumer) {
41 | MediaProjection sharedMediaProjection = mediaProjectionSupplier.get();
42 |
43 | if (sharedMediaProjection == null) {
44 | bitmapConsumer.accept(null);
45 | return;
46 | }
47 |
48 | if (imageReader != null) {
49 | imageReader.close();
50 | }
51 |
52 | if (display != null) {
53 | display.release();
54 | }
55 |
56 | metrics = displayMetricsSupplier.get();
57 | imageReader = getImageReader(metrics, bitmapConsumer);
58 | display =
59 | sharedMediaProjection.createVirtualDisplay(
60 | "myDisplay",
61 | metrics.widthPixels,
62 | metrics.heightPixels,
63 | metrics.densityDpi,
64 | DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
65 | imageReader.getSurface(),
66 | null,
67 | null);
68 | }
69 |
70 | private ImageReader getImageReader(DisplayMetrics metrics, Consumer bitmapConsumer) {
71 | ImageReader imageReader =
72 | ImageReader.newInstance(
73 | metrics.widthPixels,
74 | metrics.heightPixels,
75 | // The linter gives a false positive here because it wants us to use one of the
76 | // ImageFormat.* constants, but ImageReader.newInstance documents the PixelFormat
77 | // constants as being acceptable, too. We don't control the choice of format; it's
78 | // determined by the input data we get from the system screenshot functionality.
79 | //
80 | // noinspection WrongConstant
81 | PixelFormat.RGBA_8888,
82 | 2);
83 |
84 | Consumer onBitmapAvailable =
85 | bitmap -> {
86 | display.release();
87 | bitmapConsumer.accept(bitmap);
88 | };
89 |
90 | OnScreenshotAvailable onScreenshotAvailable =
91 | onScreenshotAvailableProvider.getOnScreenshotAvailable(
92 | metrics, bitmapProvider, onBitmapAvailable);
93 | imageReader.setOnImageAvailableListener(onScreenshotAvailable, screenshotHandler);
94 |
95 | return imageReader;
96 | }
97 | }
98 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizer.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 | import java.util.ArrayList;
8 |
9 | public class FocusVisualizer {
10 | private ArrayList focusElementHighlights;
11 | private ArrayList focusElementLines;
12 | private int tabStopCount;
13 | private FocusVisualizerStyles styles;
14 | private FocusVisualizationCanvas focusVisualizationCanvas;
15 |
16 | public FocusVisualizer(
17 | FocusVisualizerStyles focusVisualizerStyles,
18 | FocusVisualizationCanvas focusVisualizationCanvas) {
19 | this.focusElementHighlights = new ArrayList<>();
20 | this.focusElementLines = new ArrayList<>();
21 | this.tabStopCount = 0;
22 | this.styles = focusVisualizerStyles;
23 | this.focusVisualizationCanvas = focusVisualizationCanvas;
24 | }
25 |
26 | public void refreshHighlights() {
27 | this.focusVisualizationCanvas.redraw();
28 | }
29 |
30 | public void addNewFocusedElement(AccessibilityNodeInfo eventSource) {
31 | tabStopCount++;
32 |
33 | AccessibilityNodeInfo previousEventSource = this.getPreviousEventSource();
34 |
35 | if (this.focusElementHighlights.size() > 0) {
36 | this.setPreviousElementHighlightNonCurrent(
37 | this.focusElementHighlights.get(this.focusElementHighlights.size() - 1));
38 | }
39 | if (focusElementLines.size() > 0) {
40 | this.setPreviousLineNonCurrent(this.focusElementLines.get(this.focusElementLines.size() - 1));
41 | }
42 |
43 | this.createFocusElementHighlight(eventSource);
44 | this.createFocusElementLine(eventSource, previousEventSource);
45 |
46 | this.setDrawItemsAndRedraw();
47 | }
48 |
49 | public void resetVisualizations() {
50 | this.tabStopCount = 0;
51 | this.focusElementHighlights.clear();
52 | this.focusElementLines.clear();
53 | this.setDrawItemsAndRedraw();
54 | }
55 |
56 | private void setPreviousLineNonCurrent(FocusElementLine line) {
57 | line.setPaint(this.styles.getNonCurrentLinePaints());
58 | }
59 |
60 | private void setPreviousElementHighlightNonCurrent(FocusElementHighlight focusElementHighlight) {
61 | focusElementHighlight.setAsNonCurrentElement();
62 | focusElementHighlight.setPaints(this.styles.getNonCurrentElementPaints());
63 | }
64 |
65 | private void createFocusElementLine(
66 | AccessibilityNodeInfo eventSource, AccessibilityNodeInfo previousEventSource) {
67 | FocusElementLine focusElementLine =
68 | new FocusElementLine(
69 | eventSource,
70 | previousEventSource,
71 | this.styles.getCurrentLinePaints(),
72 | this.focusVisualizationCanvas);
73 | this.focusElementLines.add(focusElementLine);
74 | }
75 |
76 | private void createFocusElementHighlight(AccessibilityNodeInfo eventSource) {
77 | FocusElementHighlight focusElementHighlight =
78 | new FocusElementHighlight(
79 | eventSource,
80 | this.styles.getCurrentElementPaints(),
81 | this.styles.focusElementHighlightRadius,
82 | this.tabStopCount,
83 | this.focusVisualizationCanvas);
84 | this.focusElementHighlights.add(focusElementHighlight);
85 | }
86 |
87 | private AccessibilityNodeInfo getPreviousEventSource() {
88 | if (this.focusElementHighlights.size() == 0) {
89 | return null;
90 | }
91 | return this.focusElementHighlights.get(this.focusElementHighlights.size() - 1).getEventSource();
92 | }
93 |
94 | private void setDrawItemsAndRedraw() {
95 | this.focusVisualizationCanvas.setDrawItems(this.focusElementHighlights, this.focusElementLines);
96 | this.focusVisualizationCanvas.redraw();
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AccessibilityInsightsContentProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.ContentProvider;
7 | import android.content.ContentValues;
8 | import android.database.Cursor;
9 | import android.net.Uri;
10 | import android.os.Binder;
11 | import android.os.Bundle;
12 | import android.os.CancellationSignal;
13 | import android.os.ParcelFileDescriptor;
14 | import androidx.annotation.NonNull;
15 | import androidx.annotation.Nullable;
16 |
17 | public class AccessibilityInsightsContentProvider extends ContentProvider {
18 | private SynchronizedRequestDispatcher requestDispatcher;
19 | private TempFileProvider tempFileProvider;
20 |
21 | @Override
22 | public boolean onCreate() {
23 | return onCreate(
24 | SynchronizedRequestDispatcher.SharedInstance, new TempFileProvider(this.getContext()));
25 | }
26 |
27 | public boolean onCreate(
28 | SynchronizedRequestDispatcher requestDispatcher, TempFileProvider tempFileProvider) {
29 | this.requestDispatcher = requestDispatcher;
30 | this.tempFileProvider = tempFileProvider;
31 | return true;
32 | }
33 |
34 | @Nullable
35 | @Override
36 | public Cursor query(
37 | @NonNull Uri uri,
38 | @Nullable String[] strings,
39 | @Nullable String s,
40 | @Nullable String[] strings1,
41 | @Nullable String s1) {
42 | return null;
43 | }
44 |
45 | @Nullable
46 | @Override
47 | public String getType(@NonNull Uri uri) {
48 | return null;
49 | }
50 |
51 | @Nullable
52 | @Override
53 | public Uri insert(@NonNull Uri uri, @Nullable ContentValues contentValues) {
54 | return null;
55 | }
56 |
57 | @Override
58 | public int delete(@NonNull Uri uri, @Nullable String s, @Nullable String[] strings) {
59 | return 0;
60 | }
61 |
62 | @Override
63 | public int update(
64 | @NonNull Uri uri,
65 | @Nullable ContentValues contentValues,
66 | @Nullable String s,
67 | @Nullable String[] strings) {
68 | return 0;
69 | }
70 |
71 | @Nullable
72 | @Override
73 | public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
74 | verifyCallerPermissions();
75 |
76 | Bundle output = new Bundle();
77 |
78 | try {
79 | String response = requestDispatcher.request("/" + method, new CancellationSignal());
80 | output.putString("response", response);
81 | } catch (Exception e) {
82 | output.putString("response", e.toString());
83 | }
84 |
85 | return output;
86 | }
87 |
88 | @Nullable
89 | @Override
90 | public ParcelFileDescriptor openFile(
91 | @NonNull Uri uri, @NonNull String mode, @Nullable CancellationSignal signal) {
92 | verifyCallerPermissions();
93 |
94 | if (signal == null) {
95 | signal = new CancellationSignal();
96 | }
97 |
98 | String method = uri.getPath();
99 |
100 | String response;
101 | try {
102 | response = requestDispatcher.request(method, signal);
103 | } catch (Exception e) {
104 | response = e.toString();
105 | }
106 |
107 | try {
108 | ParcelFileDescriptor file =
109 | ParcelFileDescriptor.open(
110 | tempFileProvider.createTempFileWithContents(response),
111 | ParcelFileDescriptor.MODE_READ_ONLY);
112 | return file;
113 | } catch (Exception e) {
114 | throw new RuntimeException(e);
115 | }
116 | }
117 |
118 | private final int AID_SHELL = 2000; // from android_filesystem_config.h
119 |
120 | private void verifyCallerPermissions() {
121 | if (Binder.getCallingUid() != AID_SHELL) {
122 | throw new SecurityException("This provider may only be queried via adb's shell user");
123 | }
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/TempFileProvider.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.content.Context;
7 | import androidx.annotation.NonNull;
8 | import androidx.work.Data;
9 | import androidx.work.OneTimeWorkRequest;
10 | import androidx.work.WorkManager;
11 | import androidx.work.Worker;
12 | import androidx.work.WorkerParameters;
13 | import java.io.BufferedWriter;
14 | import java.io.File;
15 | import java.io.FileOutputStream;
16 | import java.io.IOException;
17 | import java.io.OutputStreamWriter;
18 | import java.nio.charset.StandardCharsets;
19 | import java.util.Date;
20 | import java.util.concurrent.TimeUnit;
21 |
22 | public class TempFileProvider {
23 | // Avoid ever changing this; we want new versions of the app to be able to recognize and clean up
24 | // old versions'
25 | @NonNull
26 | private static final String tempDirName =
27 | "com.microsoft.accessibilityinsightsforandroidservice.TempFileProvider";
28 |
29 | @NonNull public static final int tempFileLifetimeMillis = 5 * 60 * 1000; // 5 minutes
30 | @NonNull private File tempDir;
31 | @NonNull private WorkManager workManager;
32 |
33 | public TempFileProvider(Context context) {
34 | this(context, WorkManagerHolder.getWorkManager(context));
35 | }
36 |
37 | public TempFileProvider(Context context, WorkManager workManager) {
38 | this.workManager = workManager;
39 | File cacheDir = context.getCacheDir();
40 | String tempDirPath = cacheDir.getAbsolutePath() + File.separator + tempDirName;
41 | this.tempDir = new File(tempDirPath);
42 | }
43 |
44 | public void cleanOldFilesBestEffort() {
45 | cleanOldFilesBestEffort(tempDir);
46 | }
47 |
48 | private static void cleanOldFilesBestEffort(File tempDir) {
49 | long cutoffTime = new Date().getTime() - tempFileLifetimeMillis;
50 | File[] files = tempDir.listFiles();
51 | if (files != null) {
52 | for (File file : files) {
53 | if (file.lastModified() < cutoffTime) {
54 | // We intentionally ignore failures (best-effort)
55 | // noinspection ResultOfMethodCallIgnored
56 | file.delete();
57 | }
58 | }
59 | }
60 | }
61 |
62 | public File createTempFileWithContents(String contents) throws IOException {
63 | ensureTempDirExists();
64 | File tempFile = File.createTempFile("TempFileProvider", "tmp", this.tempDir);
65 | try (BufferedWriter writer =
66 | new BufferedWriter(
67 | new OutputStreamWriter(new FileOutputStream(tempFile), StandardCharsets.UTF_8))) {
68 | writer.write(contents);
69 | writer.flush();
70 | }
71 | scheduleCleanOldFiles(tempDir.getAbsolutePath());
72 | return tempFile;
73 | }
74 |
75 | private void ensureTempDirExists() throws IOException {
76 | //noinspection ResultOfMethodCallIgnored
77 | this.tempDir.mkdir();
78 | }
79 |
80 | private void scheduleCleanOldFiles(String tempDir) {
81 | Data inputData = new Data.Builder().putString("tempDir", tempDir).build();
82 | OneTimeWorkRequest cleanFilesWorker =
83 | new OneTimeWorkRequest.Builder(CleanWorker.class)
84 | .setInitialDelay(tempFileLifetimeMillis, TimeUnit.MILLISECONDS)
85 | .setInputData(inputData)
86 | .build();
87 | workManager.enqueue(cleanFilesWorker);
88 | }
89 |
90 | public static class CleanWorker extends Worker {
91 | private String tempDir;
92 |
93 | public CleanWorker(@NonNull Context context, @NonNull WorkerParameters workerParams) {
94 | super(context, workerParams);
95 | tempDir = workerParams.getInputData().getString("tempDir");
96 | }
97 |
98 | @NonNull
99 | @Override
100 | public Result doWork() {
101 | cleanOldFilesBestEffort(new File(tempDir));
102 | return Result.success();
103 | }
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/AxeViewsFactory.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.accessibility.AccessibilityNodeInfo;
7 | import com.deque.axe.android.AxeView;
8 | import java.util.ArrayList;
9 | import java.util.HashSet;
10 | import java.util.Hashtable;
11 | import java.util.List;
12 | import java.util.Map;
13 | import java.util.Queue;
14 | import java.util.Set;
15 |
16 | public class AxeViewsFactory {
17 |
18 | private static final int maxRetries = 5;
19 |
20 | NodeViewBuilderFactory nodeViewBuilderFactory;
21 | AccessibilityNodeInfoQueueBuilder queueBuilder;
22 | Map axeMap;
23 |
24 | public AxeViewsFactory(
25 | NodeViewBuilderFactory nodeViewBuilderFactory,
26 | AccessibilityNodeInfoQueueBuilder queueBuilder) {
27 | this.nodeViewBuilderFactory = nodeViewBuilderFactory;
28 | this.queueBuilder = queueBuilder;
29 | }
30 |
31 | public AxeView createAxeViews(AccessibilityNodeInfo rootNode) throws ViewChangedException {
32 | return buildAxeViewsWithRetries(rootNode, maxRetries);
33 | }
34 |
35 | private AxeView buildAxeViewsWithRetries(AccessibilityNodeInfo rootNode, int retries)
36 | throws ViewChangedException {
37 | Queue> queue = queueBuilder.buildPriorityQueue(rootNode);
38 | axeMap = new Hashtable<>();
39 |
40 | try {
41 | return buildAxeViews(queue, rootNode);
42 | } catch (ViewChangedException e) {
43 | if (retries > 0) {
44 | rootNode.refresh();
45 | return buildAxeViewsWithRetries(rootNode, retries - 1);
46 | } else {
47 | throw new ViewChangedException("Failed after " + maxRetries + " attempts.");
48 | }
49 | } finally {
50 | recycleAllNodes(rootNode, queue);
51 | }
52 | }
53 |
54 | private AxeView buildAxeViews(
55 | Queue> queue, AccessibilityNodeInfo rootNode)
56 | throws ViewChangedException {
57 | OrderedValue nextOrderedNode;
58 |
59 | while ((nextOrderedNode = queue.poll()) != null) {
60 | AccessibilityNodeInfo node = nextOrderedNode.value;
61 | List children = getChildViews(node);
62 | AxeView labeledByView = getLabeledByView(node);
63 | AxeView nodeView =
64 | this.nodeViewBuilderFactory.createNodeViewBuilder(node, children, labeledByView).build();
65 | axeMap.put(node, nodeView);
66 | }
67 |
68 | return axeMap.get(rootNode);
69 | }
70 |
71 | private AxeView getLabeledByView(AccessibilityNodeInfo node) {
72 | AxeView labeledByView = null;
73 | AccessibilityNodeInfo labeledByNode = node.getLabeledBy();
74 | if (labeledByNode != null) {
75 | labeledByView = axeMap.get(labeledByNode);
76 | }
77 |
78 | return labeledByView;
79 | }
80 |
81 | private List getChildViews(AccessibilityNodeInfo node) throws ViewChangedException {
82 | int childCount = node.getChildCount();
83 | List children = new ArrayList<>(childCount);
84 |
85 | for (int loop = 0; loop < childCount; loop++) {
86 | AccessibilityNodeInfo child = node.getChild(loop);
87 | if (child == null) {
88 | throw new ViewChangedException();
89 | }
90 | children.add(axeMap.get(child));
91 | }
92 |
93 | return children;
94 | }
95 |
96 | private void recycleAllNodes(
97 | AccessibilityNodeInfo rootNode, Queue> queue) {
98 | Set allNodes = new HashSet<>(axeMap.keySet());
99 | for (OrderedValue orderedNode : queue) {
100 | allNodes.add(orderedNode.value);
101 | }
102 |
103 | for (AccessibilityNodeInfo node : allNodes) {
104 | if (node != rootNode && node.getClassName() != null) {
105 | node.recycle();
106 | }
107 | }
108 | }
109 | }
110 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/ResultV2RequestFulfiller.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.graphics.Bitmap;
7 | import android.os.CancellationSignal;
8 | import android.view.accessibility.AccessibilityNodeInfo;
9 | import com.deque.axe.android.AxeResult;
10 | import com.google.android.apps.common.testing.accessibility.framework.AccessibilityHierarchyCheckResult;
11 | import com.google.android.apps.common.testing.accessibility.framework.utils.contrast.BitmapImage;
12 | import java.util.List;
13 | import java.util.concurrent.CountDownLatch;
14 | import java.util.concurrent.atomic.AtomicReference;
15 |
16 | public class ResultV2RequestFulfiller implements RequestFulfiller {
17 | private final RootNodeFinder rootNodeFinder;
18 | private final EventHelper eventHelper;
19 | private final AxeScanner axeScanner;
20 | private final ATFAScanner atfaScanner;
21 | private final ScreenshotController screenshotController;
22 | private final ResultsV2ContainerSerializer resultsV2ContainerSerializer;
23 |
24 | public ResultV2RequestFulfiller(
25 | RootNodeFinder rootNodeFinder,
26 | EventHelper eventHelper,
27 | AxeScanner axeScanner,
28 | ATFAScanner atfaScanner,
29 | ScreenshotController screenshotController,
30 | ResultsV2ContainerSerializer resultsV2ContainerSerializer) {
31 | this.rootNodeFinder = rootNodeFinder;
32 | this.eventHelper = eventHelper;
33 | this.axeScanner = axeScanner;
34 | this.atfaScanner = atfaScanner;
35 | this.screenshotController = screenshotController;
36 | this.resultsV2ContainerSerializer = resultsV2ContainerSerializer;
37 | }
38 |
39 | public String fulfillRequest(CancellationSignal cancellationSignal) throws Exception {
40 | AtomicReference successResponse = new AtomicReference<>();
41 | AtomicReference errorResponse = new AtomicReference<>();
42 | CountDownLatch doneSignal = new CountDownLatch(1);
43 |
44 | screenshotController.getScreenshotWithMediaProjection(
45 | screenshot -> {
46 | try {
47 | cancellationSignal.throwIfCanceled();
48 |
49 | if (screenshot == null) {
50 | throw new Exception(
51 | "Could not acquire screenshot. Has the user granted screen recording permissions?");
52 | }
53 |
54 | AccessibilityNodeInfo source = eventHelper.claimLastSource();
55 | AccessibilityNodeInfo rootNode = rootNodeFinder.getRootNodeFromSource(source);
56 |
57 | successResponse.set(getScanContent(rootNode, screenshot, cancellationSignal));
58 |
59 | if (rootNode != null && rootNode != source) {
60 | rootNode.recycle();
61 | }
62 | if (source != null && !eventHelper.restoreLastSource(source)) {
63 | source.recycle();
64 | }
65 | } catch (Exception e) {
66 | errorResponse.set(e);
67 | }
68 | doneSignal.countDown();
69 | });
70 |
71 | doneSignal.await();
72 | if (errorResponse.get() != null) {
73 | throw errorResponse.get();
74 | }
75 | return successResponse.get();
76 | }
77 |
78 | private String getScanContent(
79 | AccessibilityNodeInfo rootNode, Bitmap screenshot, CancellationSignal cancellationSignal)
80 | throws ScanException, ViewChangedException {
81 | cancellationSignal.throwIfCanceled();
82 | if (rootNode == null) {
83 | throw new ScanException("Unable to locate root node to scan");
84 | }
85 | AxeResult axeResult = axeScanner.scanWithAxe(rootNode, screenshot);
86 | if (axeResult == null) {
87 | throw new ScanException("Scanner returned no data");
88 | }
89 |
90 | cancellationSignal.throwIfCanceled();
91 | List atfaResults =
92 | atfaScanner.scanWithATFA(rootNode, new BitmapImage(screenshot));
93 |
94 | return resultsV2ContainerSerializer.createResultsJson(axeResult, atfaResults);
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/main/java/com/microsoft/accessibilityinsightsforandroidservice/FocusVisualizerController.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import android.view.WindowManager;
7 | import android.view.accessibility.AccessibilityEvent;
8 | import android.view.accessibility.AccessibilityNodeInfo;
9 | import java.util.Date;
10 |
11 | public class FocusVisualizerController {
12 | private FocusVisualizer focusVisualizer;
13 | private FocusVisualizationStateManager focusVisualizationStateManager;
14 | private UIThreadRunner uiThreadRunner;
15 | private WindowManager windowManager;
16 | private LayoutParamGenerator layoutParamGenerator;
17 | private FocusVisualizationCanvas focusVisualizationCanvas;
18 | private AccessibilityNodeInfo lastEventSource;
19 | private DateProvider dateProvider;
20 | private Date lastOrientationChange;
21 | private long maximumOrientationChangeDelay = 1000;
22 |
23 | public FocusVisualizerController(
24 | FocusVisualizer focusVisualizer,
25 | FocusVisualizationStateManager focusVisualizationStateManager,
26 | UIThreadRunner uiThreadRunner,
27 | WindowManager windowManager,
28 | LayoutParamGenerator layoutParamGenerator,
29 | FocusVisualizationCanvas focusVisualizationCanvas,
30 | DateProvider dateProvider) {
31 | this.focusVisualizer = focusVisualizer;
32 | this.focusVisualizationStateManager = focusVisualizationStateManager;
33 | this.uiThreadRunner = uiThreadRunner;
34 | this.windowManager = windowManager;
35 | this.layoutParamGenerator = layoutParamGenerator;
36 | this.focusVisualizationCanvas = focusVisualizationCanvas;
37 | this.dateProvider = dateProvider;
38 | this.focusVisualizationStateManager.subscribe(this::onFocusVisualizationStateChange);
39 | this.lastOrientationChange = dateProvider.get();
40 | }
41 |
42 | public void onFocusEvent(AccessibilityEvent event) {
43 | lastEventSource = event.getSource();
44 | if (focusVisualizationStateManager.getState() == false
45 | || ignoreFocusEventDueToRecentOrientationChange()) {
46 | return;
47 | }
48 |
49 | focusVisualizer.addNewFocusedElement(event.getSource());
50 | }
51 |
52 | public void onRedrawEvent(AccessibilityEvent event) {
53 | if (focusVisualizationStateManager.getState() == false) {
54 | return;
55 | }
56 |
57 | focusVisualizer.refreshHighlights();
58 | }
59 |
60 | public void onAppChanged(AccessibilityNodeInfo nodeInfo) {
61 | if (focusVisualizationStateManager.getState() == false) {
62 | return;
63 | }
64 |
65 | focusVisualizer.resetVisualizations();
66 | }
67 |
68 | public void onOrientationChanged(Integer orientation) {
69 | if (focusVisualizationStateManager.getState() == false) {
70 | return;
71 | }
72 | lastOrientationChange = dateProvider.get();
73 | windowManager.updateViewLayout(focusVisualizationCanvas, layoutParamGenerator.get());
74 | focusVisualizer.resetVisualizations();
75 | }
76 |
77 | private void onFocusVisualizationStateChange(boolean enabled) {
78 | if (enabled) {
79 | uiThreadRunner.run(this::addFocusVisualizationToScreen);
80 | } else {
81 | uiThreadRunner.run(this::removeFocusVisualizationToScreen);
82 | }
83 | }
84 |
85 | private void addFocusVisualizationToScreen() {
86 | if (lastEventSource != null) {
87 | focusVisualizer.addNewFocusedElement(lastEventSource);
88 | }
89 | windowManager.addView(focusVisualizationCanvas, layoutParamGenerator.get());
90 | }
91 |
92 | private void removeFocusVisualizationToScreen() {
93 | focusVisualizer.resetVisualizations();
94 | windowManager.removeView(focusVisualizationCanvas);
95 | }
96 |
97 | private boolean ignoreFocusEventDueToRecentOrientationChange() {
98 | Date currentTime = dateProvider.get();
99 | long cur = currentTime.getTime();
100 | long last = lastOrientationChange.getTime();
101 | long timeSinceLastOrientationChange = cur - last;
102 | return timeSinceLastOrientationChange < maximumOrientationChangeDelay;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/AccessibilityInsightsForAndroidService/app/src/test/java/com/microsoft/accessibilityinsightsforandroidservice/AccessibilityNodeInfoQueueBuilderTest.java:
--------------------------------------------------------------------------------
1 | // Copyright (c) Microsoft Corporation.
2 | // Licensed under the MIT License.
3 |
4 | package com.microsoft.accessibilityinsightsforandroidservice;
5 |
6 | import static org.mockito.Mockito.when;
7 |
8 | import android.view.accessibility.AccessibilityNodeInfo;
9 | import java.util.Queue;
10 | import org.junit.Assert;
11 | import org.junit.Before;
12 | import org.junit.Test;
13 | import org.junit.runner.RunWith;
14 | import org.mockito.Mock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 |
17 | @RunWith(MockitoJUnitRunner.class)
18 | public class AccessibilityNodeInfoQueueBuilderTest {
19 |
20 | @Mock AccessibilityNodeInfo rootNode;
21 | @Mock AccessibilityNodeInfo childNode0;
22 | @Mock AccessibilityNodeInfo childNode1;
23 | @Mock AccessibilityNodeInfo grandchildNode;
24 |
25 | AccessibilityNodeInfoQueueBuilder testSubject;
26 |
27 | @Before
28 | public void prepare() {
29 | testSubject = new AccessibilityNodeInfoQueueBuilder();
30 | }
31 |
32 | @Test
33 | public void buildEmptyQueue() {
34 | Queue> queue = testSubject.buildPriorityQueue(null);
35 | Assert.assertNotNull(queue);
36 | Assert.assertTrue(queue.isEmpty());
37 | }
38 |
39 | @Test
40 | public void buildSingleNodeQueue() {
41 | Queue> queue = testSubject.buildPriorityQueue(rootNode);
42 | Assert.assertNotNull(queue);
43 | Assert.assertEquals(queue.size(), 1);
44 |
45 | assertNextQueueItemEquals(queue, rootNode, Long.MAX_VALUE);
46 | }
47 |
48 | @Test
49 | public void buildQueueWithChildren() {
50 | createChildren();
51 |
52 | Queue> queue = testSubject.buildPriorityQueue(rootNode);
53 |
54 | long rootOrder = Long.MAX_VALUE;
55 | long childOrder = rootOrder - 1;
56 | long grandchildOrder = rootOrder - 2;
57 |
58 | Assert.assertEquals(queue.size(), 4);
59 | assertNextQueueItemEquals(queue, grandchildNode, grandchildOrder);
60 | // Since the child nodes have the same priority, they can appear here in any order,
61 | // so check for both ordering possibilities
62 | if (queue.peek().value == childNode0) {
63 | assertNextQueueItemEquals(queue, childNode0, childOrder);
64 | assertNextQueueItemEquals(queue, childNode1, childOrder);
65 | } else {
66 | assertNextQueueItemEquals(queue, childNode1, childOrder);
67 | assertNextQueueItemEquals(queue, childNode0, childOrder);
68 | }
69 | assertNextQueueItemEquals(queue, rootNode, rootOrder);
70 | }
71 |
72 | @Test
73 | public void buildQueueWithLabelNodes() {
74 | createChildren();
75 |
76 | when(childNode0.getLabelFor()).thenReturn(childNode1);
77 |
78 | Queue> queue = testSubject.buildPriorityQueue(rootNode);
79 |
80 | long rootOrder = Long.MAX_VALUE;
81 | long child0Order = (rootOrder - 1) / 2;
82 | long child1Order = rootOrder - 1;
83 | long grandchildOrder = child0Order - 1;
84 |
85 | Assert.assertEquals(queue.size(), 4);
86 | assertNextQueueItemEquals(queue, grandchildNode, grandchildOrder);
87 | assertNextQueueItemEquals(queue, childNode0, child0Order);
88 | assertNextQueueItemEquals(queue, childNode1, child1Order);
89 | assertNextQueueItemEquals(queue, rootNode, rootOrder);
90 | }
91 |
92 | public void createChildren() {
93 | when(rootNode.getChildCount()).thenReturn(2);
94 | when(rootNode.getChild(0)).thenReturn(childNode0);
95 | when(rootNode.getChild(1)).thenReturn(childNode1);
96 |
97 | when(childNode0.getChildCount()).thenReturn(1);
98 | when(childNode0.getChild(0)).thenReturn(grandchildNode);
99 | }
100 |
101 | public void assertNextQueueItemEquals(
102 | Queue> queue, AccessibilityNodeInfo node, long priority) {
103 | OrderedValue nextItem = queue.poll();
104 | Assert.assertNotNull(nextItem);
105 | Assert.assertEquals(nextItem.value, node);
106 | Assert.assertEquals(nextItem.order, priority);
107 | }
108 | }
109 |
--------------------------------------------------------------------------------