diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt index 7b915961c046b..9e3a77802d40c 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt +++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityLogger.kt @@ -16,8 +16,11 @@ package com.android.systemui.accessibility +import com.android.internal.annotations.GuardedBy import com.android.internal.logging.UiEvent import com.android.internal.logging.UiEventLogger +import com.android.internal.logging.UiEventLogger.UiEventEnum +import com.android.systemui.util.time.SystemClock import javax.inject.Inject /** @@ -25,16 +28,60 @@ import javax.inject.Inject * * See go/uievent */ -class AccessibilityLogger @Inject constructor(private val uiEventLogger: UiEventLogger) { +class AccessibilityLogger +@Inject +constructor(private val uiEventLogger: UiEventLogger, private val clock: SystemClock) { + + @GuardedBy("clock") private var lastTimeThrottledMs: Long = 0 + @GuardedBy("clock") private var lastEventThrottled: UiEventEnum? = null + + /** + * Logs the event, but any additional calls within the given delay window are ignored. The + * window resets every time a new event is received. i.e. it will only log one time until you + * wait at least [delayBeforeLoggingMs] before sending the next event. + * + *
Additionally, if a different type of event is passed in, the delay window for the previous + * one is forgotten. e.g. if you send two types of events interlaced all within the delay + * window, e.g. A->B->A within 1000ms, all three will be logged. + */ + @JvmOverloads fun logThrottled(event: UiEventEnum, delayBeforeLoggingMs: Int = 2000) { + synchronized(clock) { + val currentTimeMs = clock.elapsedRealtime() + val shouldThrottle = + event == lastEventThrottled && + currentTimeMs - lastTimeThrottledMs < delayBeforeLoggingMs + lastEventThrottled = event + lastTimeThrottledMs = currentTimeMs + if (shouldThrottle) { + return + } + } + log(event) + } + /** Logs the given event */ - fun log(event: UiEventLogger.UiEventEnum) { + fun log(event: UiEventEnum) { uiEventLogger.log(event) } + /** + * Logs the given event with an integer rank/position value. + * + * @param event the event to log + * @param position the rank or position value that the user interacted with in the UI + */ + fun logWithPosition(event: UiEventEnum, position: Int) { + uiEventLogger.logWithPosition(event, /* uid= */ 0, /* packageName= */ null, position) + } + /** Events regarding interaction with the magnifier settings panel */ enum class MagnificationSettingsEvent constructor(private val id: Int) : - UiEventLogger.UiEventEnum { - @UiEvent(doc = "Magnification settings panel opened.") + UiEventEnum { + @UiEvent( + doc = + "Magnification settings panel opened. The selection rank is from which " + + "magnifier mode it was opened (fullscreen or window)" + ) MAGNIFICATION_SETTINGS_PANEL_OPENED(1381), @UiEvent(doc = "Magnification settings panel closed") @@ -46,7 +93,14 @@ class AccessibilityLogger @Inject constructor(private val uiEventLogger: UiEvent @UiEvent(doc = "Magnification settings panel edit size save button clicked") MAGNIFICATION_SETTINGS_SIZE_EDITING_DEACTIVATED(1384), - @UiEvent(doc = "Magnification settings panel window size selected") + @UiEvent(doc = "Magnification settings panel zoom slider changed") + MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED(1385), + + @UiEvent( + doc = + "Magnification settings panel window size selected. The selection rank is " + + "which size was selected." + ) MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED(1386); override fun getId(): Int = this.id diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java index 2a14dc894d436..8f50af987ab30 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnification.java @@ -16,6 +16,7 @@ package com.android.systemui.accessibility; +import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN; import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW; import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_MAGNIFICATION_OVERLAY; @@ -346,7 +347,10 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback @Override public void onSetMagnifierSize(int displayId, int index) { mHandler.post(() -> onSetMagnifierSizeInternal(displayId, index)); - mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED); + mA11yLogger.logWithPosition( + MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED, + index + ); } @Override @@ -367,6 +371,9 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback if (mWindowMagnificationConnectionImpl != null) { mWindowMagnificationConnectionImpl.onPerformScaleAction(displayId, scale); } + mA11yLogger.logThrottled( + MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED + ); } @Override @@ -377,9 +384,6 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback @Override public void onSettingsPanelVisibilityChanged(int displayId, boolean shown) { mHandler.post(() -> onSettingsPanelVisibilityChangedInternal(displayId, shown)); - mA11yLogger.log(shown - ? MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED - : MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED); } }; @@ -433,8 +437,22 @@ public class WindowMagnification implements CoreStartable, CommandQueue.Callback private void onSettingsPanelVisibilityChangedInternal(int displayId, boolean shown) { final WindowMagnificationController windowMagnificationController = mMagnificationControllerSupplier.get(displayId); - if (windowMagnificationController != null && windowMagnificationController.isActivated()) { - windowMagnificationController.updateDragHandleResourcesIfNeeded(shown); + if (windowMagnificationController != null) { + boolean isWindowMagnifierActivated = windowMagnificationController.isActivated(); + if (isWindowMagnifierActivated) { + windowMagnificationController.updateDragHandleResourcesIfNeeded(shown); + } + + if (shown) { + mA11yLogger.logWithPosition( + MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED, + isWindowMagnifierActivated + ? ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW + : ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN + ); + } else { + mA11yLogger.log(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED); + } } } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt new file mode 100644 index 0000000000000..deacac39b5875 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/AccessibilityLoggerTest.kt @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2023 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.systemui.accessibility + +import android.testing.AndroidTestingRunner +import androidx.test.filters.SmallTest +import com.android.internal.logging.UiEventLogger +import com.android.systemui.SysuiTestCase +import com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_CLOSED +import com.android.systemui.accessibility.AccessibilityLogger.MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED +import com.android.systemui.util.time.FakeSystemClock +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.mockito.ArgumentMatchers.eq +import org.mockito.Mock +import org.mockito.Mockito.times +import org.mockito.Mockito.verify +import org.mockito.junit.MockitoJUnit + +@SmallTest +@RunWith(AndroidTestingRunner::class) +class AccessibilityLoggerTest : SysuiTestCase() { + @JvmField @Rule val mockito = MockitoJUnit.rule() + + private val fakeClock = FakeSystemClock() + @Mock private lateinit var fakeLogger: UiEventLogger + + private lateinit var a11yLogger: AccessibilityLogger + + @Before + fun setup() { + a11yLogger = AccessibilityLogger(fakeLogger, fakeClock) + } + + @Test + fun logThrottled_onceWithinWindow() { + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + fakeClock.advanceTime(100L) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + fakeClock.advanceTime(900L) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + fakeClock.advanceTime(1100L) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + + verify(fakeLogger, times(2)).log(eq(MAGNIFICATION_SETTINGS_PANEL_OPENED)) + } + + @Test + fun logThrottled_interlacedLogsAllWithinWindow() { + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_CLOSED, 1000) + fakeClock.advanceTime(100L) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_CLOSED, 1000) + fakeClock.advanceTime(200L) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + fakeClock.advanceTime(1100L) + a11yLogger.logThrottled(MAGNIFICATION_SETTINGS_PANEL_OPENED, 1000) + + verify(fakeLogger, times(3)).log(eq(MAGNIFICATION_SETTINGS_PANEL_OPENED)) + verify(fakeLogger).log(eq(MAGNIFICATION_SETTINGS_PANEL_CLOSED)) + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java index db580742a68f6..104ca6986f18e 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationTest.java @@ -117,6 +117,8 @@ public class WindowMagnificationTest extends SysuiTestCase { return null; }).when(mMagnificationSettingsController).closeMagnificationSettings(); + when(mWindowMagnificationController.isActivated()).thenReturn(true); + mCommandQueue = new CommandQueue(getContext(), mDisplayTracker); mWindowMagnification = new WindowMagnification(getContext(), getContext().getMainThreadHandler(), mCommandQueue, mModeSwitchesController, @@ -199,8 +201,10 @@ public class WindowMagnificationTest extends SysuiTestCase { waitForIdleSync(); verify(mMagnificationSettingsController).toggleSettingsPanelVisibility(); - verify(mA11yLogger).log( - eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED)); + verify(mA11yLogger).logWithPosition( + eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_PANEL_OPENED), + eq(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW) + ); } @Test @@ -211,8 +215,10 @@ public class WindowMagnificationTest extends SysuiTestCase { waitForIdleSync(); verify(mWindowMagnificationController).changeMagnificationSize(eq(index)); - verify(mA11yLogger).log( - eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED)); + verify(mA11yLogger).logWithPosition( + eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_WINDOW_SIZE_SELECTED), + eq(index) + ); } @Test @@ -253,6 +259,8 @@ public class WindowMagnificationTest extends SysuiTestCase { TEST_DISPLAY, scale); verify(mConnectionCallback).onPerformScaleAction(eq(TEST_DISPLAY), eq(scale)); + verify(mA11yLogger).logThrottled( + eq(MagnificationSettingsEvent.MAGNIFICATION_SETTINGS_ZOOM_SLIDER_CHANGED)); } @Test