diff --git a/src/com/android/settings/fuelgauge/BatteryChartView.java b/src/com/android/settings/fuelgauge/BatteryChartView.java index cd76d0e80c8..104801704d1 100644 --- a/src/com/android/settings/fuelgauge/BatteryChartView.java +++ b/src/com/android/settings/fuelgauge/BatteryChartView.java @@ -15,6 +15,7 @@ package com.android.settings.fuelgauge; import static java.lang.Math.round; +import android.accessibilityservice.AccessibilityServiceInfo; import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; @@ -28,23 +29,30 @@ import android.util.Log; import android.view.HapticFeedbackConstants; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityManager; import android.widget.TextView; import androidx.appcompat.widget.AppCompatImageView; +import androidx.annotation.VisibleForTesting; import com.android.settings.R; import com.android.settings.overlay.FeatureFactory; import com.android.settingslib.Utils; +import java.util.Arrays; +import java.util.List; import java.util.Locale; /** A widget component to draw chart graph. */ public class BatteryChartView extends AppCompatImageView implements View.OnClickListener { private static final String TAG = "BatteryChartView"; + private static final List ACCESSIBILITY_SERVICE_NAMES = + Arrays.asList("SwitchAccessService", "TalkBackService", "JustSpeakService"); // For drawing the percentage information. private static final String[] PERCENTAGES = new String[] {"100%", "50%", "0%"}; private static final int DEFAULT_TRAPEZOID_COUNT = 12; private static final int DEFAULT_TIMESTAMP_COUNT = 4; + /** Selects all trapezoid shapes. */ public static final int SELECTED_INDEX_ALL = -1; public static final int SELECTED_INDEX_INVALID = -2; @@ -57,10 +65,11 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick private int mDividerWidth; private int mDividerHeight; private int mTrapezoidCount; - private int mSelectedIndex; private float mTrapezoidVOffset; private float mTrapezoidHOffset; - private boolean mIsSlotsClickable; + private boolean mIsSlotsClickabled; + + @VisibleForTesting int mSelectedIndex; // Colors for drawing the trapezoid shape and dividers. private int mTrapezoidColor; @@ -247,26 +256,37 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick @Override public void onAttachedToWindow() { super.onAttachedToWindow(); + updateClickableState(); + } + + private void updateClickableState() { final Context context = mContext; - mIsSlotsClickable = + mIsSlotsClickabled = FeatureFactory.getFactory(context) - .getPowerUsageFeatureProvider(context) - .isChartGraphSlotsEnabled(context); - Log.d(TAG, "isChartGraphSlotsEnabled:" + mIsSlotsClickable); + .getPowerUsageFeatureProvider(context) + .isChartGraphSlotsEnabled(context) + && !isAccessibilityEnabled(context); + Log.d(TAG, "isChartGraphSlotsEnabled:" + mIsSlotsClickabled); setClickable(isClickable()); // Initializes the trapezoid curve paint for non-clickable case. - if (!mIsSlotsClickable && mTrapezoidCurvePaint == null) { + if (!mIsSlotsClickabled && mTrapezoidCurvePaint == null) { mTrapezoidCurvePaint = new Paint(); mTrapezoidCurvePaint.setAntiAlias(true); mTrapezoidCurvePaint.setColor(mTrapezoidSolidColor); mTrapezoidCurvePaint.setStyle(Paint.Style.STROKE); mTrapezoidCurvePaint.setStrokeWidth(mDividerWidth * 2); } + invalidate(); } @Override public void setClickable(boolean clickable) { - super.setClickable(mIsSlotsClickable && clickable); + super.setClickable(mIsSlotsClickabled && clickable); + } + + @VisibleForTesting + void setClickableForce(boolean clickable) { + super.setClickable(clickable); } private void initializeColors(Context context) { @@ -412,7 +432,7 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick } // Configures the trapezoid paint color. final int trapezoidColor = - !mIsSlotsClickable + !mIsSlotsClickabled ? mTrapezoidColor : mSelectedIndex == index || mSelectedIndex == SELECTED_INDEX_ALL ? mTrapezoidSolidColor : mTrapezoidColor; @@ -469,6 +489,29 @@ public class BatteryChartView extends AppCompatImageView implements View.OnClick && mLevels[trapezoidIndex + 1] != 0; } + @VisibleForTesting + static boolean isAccessibilityEnabled(Context context) { + final AccessibilityManager accessibilityManager = + context.getSystemService(AccessibilityManager.class); + if (!accessibilityManager.isEnabled()) { + return false; + } + final List serviceInfoList = + accessibilityManager.getEnabledAccessibilityServiceList( + AccessibilityServiceInfo.FEEDBACK_SPOKEN + | AccessibilityServiceInfo.FEEDBACK_GENERIC); + for (AccessibilityServiceInfo info : serviceInfoList) { + for (String serviceName : ACCESSIBILITY_SERVICE_NAMES) { + final String serviceId = info.getId(); + if (serviceId != null && serviceId.contains(serviceName)) { + Log.d(TAG, "acccessibilityEnabled:" + serviceId); + return true; + } + } + } + return false; + } + // A container class for each trapezoid left and right location. private static final class TrapezoidSlot { public float mLeft; diff --git a/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java new file mode 100644 index 00000000000..877ebc28734 --- /dev/null +++ b/tests/robotests/src/com/android/settings/fuelgauge/BatteryChartViewTest.java @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2021 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.settings.fuelgauge; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.accessibilityservice.AccessibilityServiceInfo; +import android.content.Context; +import android.view.accessibility.AccessibilityManager; + +import com.android.settings.testutils.FakeFeatureFactory; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; + +import java.util.Arrays; +import java.util.ArrayList; + +@RunWith(RobolectricTestRunner.class) +public final class BatteryChartViewTest { + + private Context mContext; + private BatteryChartView mBatteryChartView; + private FakeFeatureFactory mFeatureFactory; + private PowerUsageFeatureProvider mPowerUsageFeatureProvider; + + @Mock private AccessibilityServiceInfo mockAccessibilityServiceInfo; + @Mock private AccessibilityManager mockAccessibilityManager; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + mFeatureFactory = FakeFeatureFactory.setupForTest(); + mPowerUsageFeatureProvider = mFeatureFactory.powerUsageFeatureProvider; + mContext = spy(RuntimeEnvironment.application); + mBatteryChartView = new BatteryChartView(mContext); + doReturn(mockAccessibilityManager).when(mContext) + .getSystemService(AccessibilityManager.class); + doReturn("TalkBackService").when(mockAccessibilityServiceInfo).getId(); + doReturn(Arrays.asList(mockAccessibilityServiceInfo)) + .when(mockAccessibilityManager) + .getEnabledAccessibilityServiceList(anyInt()); + } + + @Test + public void testIsAccessibilityEnabled_disable_returnFalse() { + doReturn(false).when(mockAccessibilityManager).isEnabled(); + assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse(); + } + + @Test + public void testIsAccessibilityEnabled_emptyInfo_returnFalse() { + doReturn(true).when(mockAccessibilityManager).isEnabled(); + doReturn(new ArrayList()) + .when(mockAccessibilityManager) + .getEnabledAccessibilityServiceList(anyInt()); + + assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isFalse(); + } + + @Test + public void testIsAccessibilityEnabled_validServiceId_returnTrue() { + doReturn(true).when(mockAccessibilityManager).isEnabled(); + assertThat(BatteryChartView.isAccessibilityEnabled(mContext)).isTrue(); + } + + @Test + public void testSetSelectedIndex_invokesCallback() { + final int selectedIndex[] = new int[1]; + final int expectedIndex = 2; + mBatteryChartView.mSelectedIndex = 1; + mBatteryChartView.setOnSelectListener( + trapezoidIndex -> { + selectedIndex[0] = trapezoidIndex; + }); + + mBatteryChartView.setSelectedIndex(expectedIndex); + + assertThat(mBatteryChartView.mSelectedIndex) + .isEqualTo(expectedIndex); + assertThat(selectedIndex[0]).isEqualTo(expectedIndex); + } + + @Test + public void testSetSelectedIndex_sameIndex_notInvokesCallback() { + final int selectedIndex[] = new int[1]; + final int expectedIndex = 1; + mBatteryChartView.mSelectedIndex = expectedIndex; + mBatteryChartView.setOnSelectListener( + trapezoidIndex -> { + selectedIndex[0] = trapezoidIndex; + }); + + mBatteryChartView.setSelectedIndex(expectedIndex); + + assertThat(selectedIndex[0]).isNotEqualTo(expectedIndex); + } + + @Test + public void testClickable_isChartGraphSlotsEnabledIsFalse_notClickable() { + mBatteryChartView.setClickableForce(true); + when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) + .thenReturn(false); + + mBatteryChartView.onAttachedToWindow(); + assertThat(mBatteryChartView.isClickable()).isFalse(); + } + + @Test + public void testClickable_accessibilityIsDisabled_clickable() { + mBatteryChartView.setClickableForce(true); + when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) + .thenReturn(true); + doReturn(false).when(mockAccessibilityManager).isEnabled(); + + mBatteryChartView.onAttachedToWindow(); + assertThat(mBatteryChartView.isClickable()).isTrue(); + } + + @Test + public void testClickable_accessibilityIsEnabledWithoutValidId_clickable() { + mBatteryChartView.setClickableForce(true); + when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) + .thenReturn(true); + doReturn(true).when(mockAccessibilityManager).isEnabled(); + doReturn(new ArrayList()) + .when(mockAccessibilityManager) + .getEnabledAccessibilityServiceList(anyInt()); + + mBatteryChartView.onAttachedToWindow(); + assertThat(mBatteryChartView.isClickable()).isTrue(); + } + + @Test + public void testClickable_accessibilityIsEnabledWithValidId_notClickable() { + mBatteryChartView.setClickableForce(true); + when(mPowerUsageFeatureProvider.isChartGraphSlotsEnabled(mContext)) + .thenReturn(true); + doReturn(true).when(mockAccessibilityManager).isEnabled(); + + mBatteryChartView.onAttachedToWindow(); + assertThat(mBatteryChartView.isClickable()).isFalse(); + } +}