| /* |
| * Copyright (C) 2020 The Android Open Source Project |
| * |
| * 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.car.ui; |
| |
| import static android.view.View.LAYOUT_DIRECTION_LTR; |
| import static android.view.View.LAYOUT_DIRECTION_RTL; |
| import static android.view.accessibility.AccessibilityNodeInfo.ACTION_FOCUS; |
| |
| import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_BOTTOM_BOUND_OFFSET; |
| import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_LEFT_BOUND_OFFSET; |
| import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_RIGHT_BOUND_OFFSET; |
| import static com.android.car.ui.utils.RotaryConstants.FOCUS_AREA_TOP_BOUND_OFFSET; |
| |
| import static com.google.common.truth.Truth.assertThat; |
| |
| import android.os.Bundle; |
| import android.view.View; |
| import android.view.accessibility.AccessibilityNodeInfo; |
| |
| import androidx.annotation.NonNull; |
| import androidx.test.rule.ActivityTestRule; |
| |
| import com.android.car.ui.test.R; |
| |
| import org.junit.Before; |
| import org.junit.Rule; |
| import org.junit.Test; |
| |
| import java.util.concurrent.CountDownLatch; |
| import java.util.concurrent.TimeUnit; |
| |
| /** Unit tests for {@link FocusArea}. */ |
| public class FocusAreaTest { |
| private static final long WAIT_TIME_MS = 3000; |
| |
| @Rule |
| public ActivityTestRule<FocusAreaTestActivity> mActivityRule = |
| new ActivityTestRule<>(FocusAreaTestActivity.class); |
| |
| private FocusAreaTestActivity mActivity; |
| private TestFocusArea mFocusArea; |
| private TestFocusArea mFocusArea2; |
| private View mChild; |
| private View mDefaultFocus; |
| private View mNonChild; |
| private View mChild1; |
| private View mChild2; |
| |
| @Before |
| public void setUp() { |
| mActivity = mActivityRule.getActivity(); |
| mFocusArea = mActivity.findViewById(R.id.focus_area); |
| mFocusArea.enableForegroundHighlight(); |
| mFocusArea2 = mActivity.findViewById(R.id.focus_area2); |
| mChild = mActivity.findViewById(R.id.child); |
| mDefaultFocus = mActivity.findViewById(R.id.default_focus); |
| mNonChild = mActivity.findViewById(R.id.non_child); |
| mChild1 = mActivity.findViewById(R.id.child1); |
| mChild2 = mActivity.findViewById(R.id.child2); |
| } |
| |
| @Test |
| public void testLoseFocus() throws Exception { |
| mChild.post(() -> { |
| mChild.requestFocus(); |
| }); |
| mFocusArea.setOnDrawCalled(false); |
| mFocusArea.setDrawCalled(false); |
| |
| // FocusArea lost focus. |
| CountDownLatch latch = new CountDownLatch(1); |
| mNonChild.post(() -> { |
| mNonChild.requestFocus(); |
| mNonChild.post(() -> { |
| latch.countDown(); |
| }); |
| }); |
| assertDrawMethodsCalled(latch); |
| } |
| |
| @Test |
| public void testGetFocus() throws Exception { |
| mNonChild.post(() -> { |
| mNonChild.requestFocus(); |
| }); |
| mFocusArea.setOnDrawCalled(false); |
| mFocusArea.setDrawCalled(false); |
| |
| // FocusArea got focus. |
| CountDownLatch latch = new CountDownLatch(1); |
| mChild.post(() -> { |
| mChild.requestFocus(); |
| mChild.post(() -> { |
| latch.countDown(); |
| }); |
| }); |
| assertDrawMethodsCalled(latch); |
| } |
| |
| @Test |
| public void testFocusOnDefaultFocus() throws Exception { |
| Bundle bundle = new Bundle(); |
| CountDownLatch latch = new CountDownLatch(1); |
| mFocusArea.post(() -> { |
| mFocusArea.performAccessibilityAction(ACTION_FOCUS, bundle); |
| latch.countDown(); |
| }); |
| latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS); |
| assertThat(mDefaultFocus.isFocused()).isTrue(); |
| } |
| |
| @Test |
| public void testBoundsOffset() { |
| assertThat(mFocusArea.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_LTR); |
| |
| // FocusArea's bounds offset specified in layout file: |
| // 10dp(start), 20dp(end), 30dp(top), 40dp(bottom). |
| int left = dp2Px(10); |
| int right = dp2Px(20); |
| int top = dp2Px(30); |
| int bottom = dp2Px(40); |
| AccessibilityNodeInfo node = mFocusArea.createAccessibilityNodeInfo(); |
| assertBoundsOffset(node, left, top, right, bottom); |
| node.recycle(); |
| } |
| |
| @Test |
| public void testBoundsOffsetWithRtl() throws Exception { |
| CountDownLatch latch = new CountDownLatch(1); |
| mFocusArea.post(() -> { |
| mFocusArea.setLayoutDirection(LAYOUT_DIRECTION_RTL); |
| latch.countDown(); |
| }); |
| latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS); |
| assertThat(mFocusArea.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_RTL); |
| |
| // FocusArea highlight padding specified in layout file: |
| // 10dp(start), 20dp(end), 30dp(top), 40dp(bottom). |
| int left = dp2Px(20); |
| int right = dp2Px(10); |
| int top = dp2Px(30); |
| int bottom = dp2Px(40); |
| AccessibilityNodeInfo node = mFocusArea.createAccessibilityNodeInfo(); |
| assertBoundsOffset(node, left, top, right, bottom); |
| node.recycle(); |
| } |
| |
| @Test |
| public void testSetBoundsOffset() { |
| mFocusArea.setBoundsOffset(50, 60, 70, 80); |
| AccessibilityNodeInfo node = mFocusArea.createAccessibilityNodeInfo(); |
| assertBoundsOffset(node, 50, 60, 70, 80); |
| node.recycle(); |
| } |
| |
| @Test |
| public void testHighlightPadding() { |
| assertThat(mFocusArea2.getLayoutDirection()).isEqualTo(LAYOUT_DIRECTION_LTR); |
| |
| int left = dp2Px(50); |
| int right = dp2Px(10); |
| int top = dp2Px(40); |
| int bottom = dp2Px(20); |
| AccessibilityNodeInfo node = mFocusArea2.createAccessibilityNodeInfo(); |
| assertBoundsOffset(node, left, top, right, bottom); |
| node.recycle(); |
| } |
| |
| @Test |
| public void testLastFocusedViewRemoved() { |
| mChild1.post(() -> { |
| // Focus on mChild1 in mFocusArea2, then mChild in mFocusArea . |
| mChild1.requestFocus(); |
| assertThat(mChild1.isFocused()).isTrue(); |
| mChild.requestFocus(); |
| assertThat(mChild.isFocused()).isTrue(); |
| |
| // Remove mChild1 in mFocusArea2, then Perform ACTION_FOCUS on mFocusArea2. |
| mFocusArea2.removeView(mChild1); |
| mFocusArea2.performAccessibilityAction(ACTION_FOCUS, null); |
| |
| // mChild2 in mFocusArea2 should get focused. |
| assertThat(mChild2.isFocused()).isTrue(); |
| }); |
| } |
| |
| private void assertBoundsOffset( |
| @NonNull AccessibilityNodeInfo node, int leftPx, int topPx, int rightPx, int bottomPx) { |
| Bundle extras = node.getExtras(); |
| assertThat(extras.getInt(FOCUS_AREA_LEFT_BOUND_OFFSET)).isEqualTo(leftPx); |
| assertThat(extras.getInt(FOCUS_AREA_RIGHT_BOUND_OFFSET)).isEqualTo(rightPx); |
| assertThat(extras.getInt(FOCUS_AREA_TOP_BOUND_OFFSET)).isEqualTo(topPx); |
| assertThat(extras.getInt(FOCUS_AREA_BOTTOM_BOUND_OFFSET)).isEqualTo(bottomPx); |
| } |
| |
| /** Converts dp unit to equivalent pixels. */ |
| private int dp2Px(int dp) { |
| return (int) (dp * mActivity.getResources().getDisplayMetrics().density + 0.5f); |
| } |
| |
| private void assertDrawMethodsCalled(CountDownLatch latch) throws Exception { |
| latch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS); |
| assertThat(mFocusArea.onDrawCalled()).isTrue(); |
| assertThat(mFocusArea.drawCalled()).isTrue(); |
| } |
| } |