/* * Copyright (C) 2015 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package com.android.switchaccess.builders; import android.annotation.TargetApi; import android.content.Context; import android.graphics.Rect; import android.os.Build; import android.view.accessibility.AccessibilityNodeInfo; import com.android.switchaccess.*; import com.android.switchaccess.test.ShadowAccessibilityNodeInfo; import com.android.switchaccess.treebuilding.HuffmanTreeBuilder; import com.android.talkback.BuildConfig; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.MockitoAnnotations; import org.robolectric.RobolectricGradleTestRunner; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; import org.robolectric.internal.ShadowExtractor; import java.util.*; import static org.junit.Assert.*; import static org.junit.Assert.assertFalse; import static org.mockito.Matchers.anySet; import static org.mockito.Matchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; /** * Robolectric tests for HuffmanTreeBuilder */ @Config( constants = BuildConfig.class, manifest = Config.NONE, sdk = 21, shadows = { ShadowAccessibilityNodeInfo.class, ShadowAccessibilityNodeInfo.ShadowAccessibilityAction.class}) @TargetApi(Build.VERSION_CODES.LOLLIPOP) @RunWith(RobolectricGradleTestRunner.class) public class HuffmanTreeBuilderTest { private static final Rect N0_BOUNDS = new Rect(10, 10, 30, 30); private static final Rect N00_BOUNDS = new Rect(10, 40, 30, 70); private static final Rect N01_BOUNDS = new Rect(40, 40, 70, 70); private static final Rect N1_BOUNDS = new Rect(80, 40, 110, 70); private static final Rect N10_BOUNDS = new Rect(10, 80, 30, 110); private static final Rect N11_BOUNDS = new Rect(40, 80, 70, 110); private final Context mContext = RuntimeEnvironment.application.getApplicationContext(); private SwitchAccessNodeCompat mWindowRoot0, mN0, mN1, mN10, mN11, mN00, mN01; private String mProbabilityContext = ""; private ProbabilityModelReader mMockProbabilityModelReader = mock(ProbabilityModelReader.class); /* * We build Huffman Tree from AccessibilityNodeInfosCompats: * * root0 * / \ * n0 n1 * / \ / \ * n00 n01 n10 n11 * * of which root, n0 and n1, are not clickable. Here's the content description of the * clickable nodes: n00: "a", n01 : "e", n10: "t", and n11: "!". Based on the English letter * frequency (which is the probability model used for the purpose of the following tests) the * ordering of these clickable nodes would be n11, n10, n00, n01 (ascending order). */ @Before public void setUp() { ShadowAccessibilityNodeInfo.resetObtainedInstances(); /* Build accessibility node tree */ mWindowRoot0 = new SwitchAccessNodeCompat(AccessibilityNodeInfo.obtain()); mWindowRoot0.setClickable(false); mWindowRoot0.setFocusable(false); mWindowRoot0.setContentDescription("mWindowRoot0"); mN0 = new SwitchAccessNodeCompat(AccessibilityNodeInfo.obtain()); mN0.setVisibleToUser(true); mN0.setClickable(false); mN0.setContentDescription("mN0"); mN0.setBoundsInScreen(N0_BOUNDS); AccessibilityNodeInfo n00Info = AccessibilityNodeInfo.obtain(); n00Info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); mN00 = new SwitchAccessNodeCompat(n00Info); mN00.setVisibleToUser(true); mN00.setClickable(true); mN00.setContentDescription("a"); mN00.setBoundsInScreen(N00_BOUNDS); AccessibilityNodeInfo n01Info = AccessibilityNodeInfo.obtain(); n01Info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); n01Info.setContentDescription("e"); n01Info.setBoundsInScreen(N01_BOUNDS); mN01 = new SwitchAccessNodeCompat(n01Info); mN01.setVisibleToUser(true); mN01.setClickable(true); mN1 = new SwitchAccessNodeCompat(AccessibilityNodeInfo.obtain()); mN1.setVisibleToUser(true); mN1.setClickable(false); mN1.setContentDescription("mN1"); mN1.setBoundsInScreen(N1_BOUNDS); AccessibilityNodeInfo n10Info = AccessibilityNodeInfo.obtain(); n10Info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); mN10 = new SwitchAccessNodeCompat(n10Info); mN10.setClickable(true); mN10.setVisibleToUser(true); mN10.setContentDescription("t"); mN10.setBoundsInScreen(N10_BOUNDS); AccessibilityNodeInfo n11Info = AccessibilityNodeInfo.obtain(); n11Info.setContentDescription("!"); n11Info.setBoundsInScreen(N11_BOUNDS); mN11 = new SwitchAccessNodeCompat(n11Info); mN11.setClickable(false); mN11.setVisibleToUser(true); ShadowAccessibilityNodeInfo shadowRoot = (ShadowAccessibilityNodeInfo) ShadowExtractor.extract(mWindowRoot0.getInfo()); shadowRoot.addChild((AccessibilityNodeInfo) mN0.getInfo()); shadowRoot.addChild((AccessibilityNodeInfo) mN1.getInfo()); final ShadowAccessibilityNodeInfo shadowN0 = (ShadowAccessibilityNodeInfo) ShadowExtractor.extract(mN0.getInfo()); shadowN0.addChild((AccessibilityNodeInfo) mN00.getInfo()); shadowN0.addChild((AccessibilityNodeInfo) mN01.getInfo()); final ShadowAccessibilityNodeInfo shadowN1 = (ShadowAccessibilityNodeInfo) ShadowExtractor.extract(mN1.getInfo()); shadowN1.addChild((AccessibilityNodeInfo) mN10.getInfo()); shadowN1.addChild((AccessibilityNodeInfo) mN11.getInfo()); /* Set up the mock probabilityModelReader */ MockitoAnnotations.initMocks(this); Map<SwitchAccessNodeCompat, Double> probabilityDistribution = new HashMap<>(); probabilityDistribution.put(mN00, 0.08167); probabilityDistribution.put(mN01, 0.12702); probabilityDistribution.put(mN10, 0.09056); probabilityDistribution.put(mN11, 0.0001); when(mMockProbabilityModelReader.getProbabilityDistribution(eq(""), anySet())) .thenReturn(probabilityDistribution); } @After public void tearDown() { mWindowRoot0.recycle(); mN0.recycle(); mN00.recycle(); mN01.recycle(); mN1.recycle(); mN10.recycle(); mN11.recycle(); assertFalse(ShadowAccessibilityNodeInfo.areThereUnrecycledNodes(true)); } @Test public void buildTreeWithNoActions_treeHasOnlyClearFocusNode() { AccessibilityNodeInfo info = AccessibilityNodeInfo.obtain(); info.setVisibleToUser(true); info.setContentDescription("buildTreeWithNoActions_treeHasOnlyClearFocusNode info"); HuffmanTreeBuilder treeBuilder = new HuffmanTreeBuilder(mContext, 2, mMockProbabilityModelReader); OptionScanNode tree = treeBuilder .buildTreeFromNodeTree(new SwitchAccessNodeCompat(info), null, mProbabilityContext); assertTrue(tree instanceof ClearFocusNode); info.recycle(); tree.recycle(); } @Test public void buildTreeWithDefaultProbabilityNode_includesNode() { makeN11Clickable(); HuffmanTreeBuilder treeBuilder = new HuffmanTreeBuilder(mContext, 2, mMockProbabilityModelReader); OptionScanSelectionNode treeRoot = (OptionScanSelectionNode) treeBuilder .buildTreeFromNodeTree(mWindowRoot0, null, mProbabilityContext); assertEquals(1, treeRoot.getChild(0).getRectsForNodeHighlight().size()); assertTrue(treeRoot.getChild(1).getRectsForNodeHighlight().contains(N11_BOUNDS)); treeRoot.recycle(); } @Test public void buildFullTreeDegreeTwo_hasExpectedStructure() { HuffmanTreeBuilder treeBuilder = new HuffmanTreeBuilder(mContext, 2, mMockProbabilityModelReader); OptionScanSelectionNode treeRoot = (OptionScanSelectionNode) treeBuilder .buildTreeFromNodeTree(mWindowRoot0, null, mProbabilityContext); assertTrue(treeRoot.getChild(0).getRectsForNodeHighlight().contains(N01_BOUNDS)); OptionScanSelectionNode firstLevelNode1 = (OptionScanSelectionNode)treeRoot.getChild(1); assertEquals(2, firstLevelNode1.getRectsForNodeHighlight().size()); assertTrue(firstLevelNode1.getChild(0).getRectsForNodeHighlight().contains(N00_BOUNDS)); OptionScanSelectionNode secondLevelNode0 = (OptionScanSelectionNode) firstLevelNode1.getChild(1); assertTrue(secondLevelNode0.getChild(1) instanceof ClearFocusNode); assertTrue(secondLevelNode0.getRectsForNodeHighlight().contains(N10_BOUNDS)); treeRoot.recycle(); } @Test public void buildFullTreeDegreeThree_hasExpectedStructure() { makeN11Clickable(); HuffmanTreeBuilder treeBuilder = new HuffmanTreeBuilder(mContext, 3, mMockProbabilityModelReader); OptionScanSelectionNode treeRoot = (OptionScanSelectionNode) treeBuilder .buildTreeFromNodeTree(mWindowRoot0, null, mProbabilityContext); assertEquals(3, treeRoot.getChildCount()); OptionScanSelectionNode firstChild = (OptionScanSelectionNode)treeRoot.getChild(0); assertTrue(firstChild.getRectsForNodeHighlight().contains(N00_BOUNDS)); assertEquals(2, firstChild.getRectsForNodeHighlight().size()); assertTrue(firstChild.getChild(2) instanceof ClearFocusNode); assertTrue(treeRoot.getChild(0).getRectsForNodeHighlight().contains(N11_BOUNDS)); assertTrue(treeRoot.getChild(1).getRectsForNodeHighlight().contains(N10_BOUNDS)); assertTrue(treeRoot.getChild(2).getRectsForNodeHighlight().contains(N01_BOUNDS)); treeRoot.recycle(); } private void makeN11Clickable() { AccessibilityNodeInfo n11Info = (AccessibilityNodeInfo) mN11.getInfo(); n11Info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK); n11Info.setClickable(true); } }