Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 52f7c029 authored by Chun-Ku Lin's avatar Chun-Ku Lin
Browse files

Notify AccessibilityManager when the tiles in SysUi changes

Only StatusBar can notify A11yManager tiles changed. StatusBar is the
application that has android.permission.STATUS_BAR_SERVICE.

Bug: 314843909
Flag: ACONFIG android.view.accessibility.a11y_qs_shortcut STAGING
Test: manual (view logs from AccessibilityManagerService)
Test: atest

Change-Id: Idb378226f596005b1143a4a02134b5dee0b94871
parent f23d375e
Loading
Loading
Loading
Loading
+25 −1
Original line number Original line Diff line number Diff line
@@ -2406,7 +2406,6 @@ public final class AccessibilityManager {
        }
        }
    }
    }



    /**
    /**
     * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
     * Attaches a {@link android.view.SurfaceControl} containing an accessibility overlay to the
     * specified display.
     * specified display.
@@ -2430,4 +2429,29 @@ public final class AccessibilityManager {
            throw re.rethrowFromSystemServer();
            throw re.rethrowFromSystemServer();
        }
        }
    }
    }

    /**
     * Notifies that the current a11y tiles in QuickSettings Panel has been changed
     *
     * @param userId            The userId of the user attempts to change the qs panel.
     * @param tileComponentNames A list of Accessibility feature's TileServices' component names
     *                           and the a11y platform tiles' component names
     * @hide
     */
    @RequiresPermission(Manifest.permission.STATUS_BAR_SERVICE)
    public void notifyQuickSettingsTilesChanged(
            @UserIdInt int userId, List<ComponentName> tileComponentNames) {
        final IAccessibilityManager service;
        synchronized (mLock) {
            service = getServiceLocked();
            if (service == null) {
                return;
            }
        }
        try {
            service.notifyQuickSettingsTilesChanged(userId, tileComponentNames);
        } catch (RemoteException re) {
            throw re.rethrowFromSystemServer();
        }
    }
}
}
+3 −0
Original line number Original line Diff line number Diff line
@@ -140,4 +140,7 @@ interface IAccessibilityManager {


    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)")
    void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);
    void attachAccessibilityOverlayToDisplay(int displayId, in SurfaceControl surfaceControl);

    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.STATUS_BAR_SERVICE)")
    oneway void notifyQuickSettingsTilesChanged(int userId, in List<ComponentName> tileComponentNames);
}
}
+2 −0
Original line number Original line Diff line number Diff line
@@ -109,6 +109,8 @@ public class AccessibilityShortcutController {
            new ComponentName("com.android.server.accessibility", "OneHandedModeTile");
            new ComponentName("com.android.server.accessibility", "OneHandedModeTile");
    public static final ComponentName REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME =
    public static final ComponentName REDUCE_BRIGHT_COLORS_TILE_SERVICE_COMPONENT_NAME =
            new ComponentName("com.android.server.accessibility", "ReduceBrightColorsTile");
            new ComponentName("com.android.server.accessibility", "ReduceBrightColorsTile");
    public static final ComponentName FONT_SIZE_TILE_COMPONENT_NAME =
            new ComponentName("com.android.server.accessibility", "FontSizeTile");


    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
    private static final AudioAttributes VIBRATION_ATTRIBUTES = new AudioAttributes.Builder()
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
            .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION)
+21 −2
Original line number Original line Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.data.repository
package com.android.systemui.accessibility.data.repository


import android.provider.Settings
import android.provider.Settings
import android.view.accessibility.AccessibilityManager
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.SysuiTestCase
@@ -26,13 +27,22 @@ import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runTest
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.junit.MockitoJUnit
import org.mockito.junit.MockitoRule


@SmallTest
@SmallTest
@RunWith(AndroidJUnit4::class)
@RunWith(AndroidJUnit4::class)
@android.platform.test.annotations.EnabledOnRavenwood
@android.platform.test.annotations.EnabledOnRavenwood
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
    @Rule @JvmField val mockitoRule: MockitoRule = MockitoJUnit.rule()

    // mocks
    @Mock private lateinit var a11yManager: AccessibilityManager
    private val testDispatcher = StandardTestDispatcher()
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)
    private val testScope = TestScope(testDispatcher)
    private val secureSettings = FakeSettings()
    private val secureSettings = FakeSettings()
@@ -49,8 +59,17 @@ class AccessibilityQsShortcutsRepositoryImplTest : SysuiTestCase() {
            }
            }
        }
        }


    private val underTest =
    private lateinit var underTest: AccessibilityQsShortcutsRepositoryImpl
        AccessibilityQsShortcutsRepositoryImpl(userA11yQsShortcutsRepositoryFactory)

    @Before
    fun setUp() {
        underTest =
            AccessibilityQsShortcutsRepositoryImpl(
                a11yManager,
                userA11yQsShortcutsRepositoryFactory,
                testDispatcher
            )
    }


    @Test
    @Test
    fun a11yQsShortcutTargetsForCorrectUsers() =
    fun a11yQsShortcutTargetsForCorrectUsers() =
+169 −0
Original line number Original line Diff line number Diff line
/*
 * Copyright (C) 2024 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.qs.pipeline.domain.interactor

import android.content.Context
import android.content.pm.UserInfo
import android.os.UserHandle
import android.platform.test.annotations.DisableFlags
import android.platform.test.annotations.EnableFlags
import android.view.accessibility.Flags
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.accessibility.data.repository.FakeAccessibilityQsShortcutsRepository
import com.android.systemui.qs.FakeQSFactory
import com.android.systemui.qs.pipeline.domain.model.TileModel
import com.android.systemui.qs.pipeline.shared.TileSpec
import com.android.systemui.qs.tiles.ColorCorrectionTile
import com.android.systemui.qs.tiles.ColorInversionTile
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.whenever
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.StandardTestDispatcher
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.runCurrent
import kotlinx.coroutines.test.runTest
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock

@SmallTest
@RunWith(AndroidJUnit4::class)
@OptIn(ExperimentalCoroutinesApi::class)
class AccessibilityTilesInteractorTest : SysuiTestCase() {
    private val USER_0_INFO =
        UserInfo(
            0,
            "zero",
            "",
            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
        )

    private val USER_1_INFO =
        UserInfo(
            1,
            "one",
            "",
            UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
        )

    private val USER_0_TILES = listOf(TileSpec.create(ColorInversionTile.TILE_SPEC))
    private val USER_1_TILES = listOf(TileSpec.create(ColorCorrectionTile.TILE_SPEC))
    private lateinit var currentTilesInteractor: CurrentTilesInteractor
    private lateinit var a11yQsShortcutsRepository: FakeAccessibilityQsShortcutsRepository
    private lateinit var underTest: AccessibilityTilesInteractor
    private lateinit var currentTiles: MutableStateFlow<List<TileModel>>
    private lateinit var userContext: MutableStateFlow<Context>
    private lateinit var qsFactory: FakeQSFactory
    private val testDispatcher = StandardTestDispatcher()
    private val testScope = TestScope(testDispatcher)

    @Before
    fun setUp() {
        a11yQsShortcutsRepository = FakeAccessibilityQsShortcutsRepository()

        qsFactory = FakeQSFactory { spec: String ->
            FakeQSTile(userContext.value.userId).also { it.setTileSpec(spec) }
        }
        currentTiles = MutableStateFlow(emptyList())
        userContext = MutableStateFlow(mock(Context::class.java))
        setUser(USER_0_INFO)

        currentTilesInteractor = mock()
        whenever(currentTilesInteractor.currentTiles).thenReturn(currentTiles)
        whenever(currentTilesInteractor.userContext).thenReturn(userContext)
    }

    @Test
    @DisableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
    fun currentTilesChanged_a11yQsShortcutFlagOff_nothingHappen() =
        testScope.runTest {
            underTest = createInteractor()

            setTiles(USER_0_TILES)
            runCurrent()

            assertThat(a11yQsShortcutsRepository.notifyA11yManagerTilesChangedRequests).isEmpty()
        }

    @Test
    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
    fun currentTilesChanged_a11yQsShortcutFlagOn_notifyAccessibilityRepository() =
        testScope.runTest {
            underTest = createInteractor()

            setTiles(USER_0_TILES)
            runCurrent()

            val requests = a11yQsShortcutsRepository.notifyA11yManagerTilesChangedRequests
            assertThat(requests).hasSize(1)
            with(requests[0]) {
                assertThat(this.userContext.userId).isEqualTo(USER_0_INFO.id)
                assertThat(this.tiles).isEqualTo(USER_0_TILES)
            }
        }

    @Test
    @EnableFlags(Flags.FLAG_A11Y_QS_SHORTCUT)
    fun userChanged_a11yQsShortcutFlagOn_notifyAccessibilityRepositoryWithCorrectTilesAndUser() =
        testScope.runTest {
            underTest = createInteractor()
            setTiles(USER_0_TILES)
            runCurrent()

            // Change User and updates corresponding tiles
            setUser(USER_1_INFO)
            runCurrent()
            setTiles(USER_1_TILES)
            runCurrent()

            val requestsForUser1 =
                a11yQsShortcutsRepository.notifyA11yManagerTilesChangedRequests.filter {
                    it.userContext.userId == USER_1_INFO.id
                }
            assertThat(requestsForUser1).hasSize(1)
            assertThat(requestsForUser1[0].tiles).isEqualTo(USER_1_TILES)
        }

    private fun setTiles(tiles: List<TileSpec>) {
        currentTiles.tryEmit(
            tiles.mapNotNull { qsFactory.createTile(it.spec)?.let { it1 -> TileModel(it, it1) } }
        )
    }

    private fun setUser(userInfo: UserInfo) {
        userContext.tryEmit(
            mock(Context::class.java).also {
                whenever(it.userId).thenReturn(userInfo.id)
                whenever(it.user).thenReturn(UserHandle.of(userInfo.id))
            }
        )
    }

    private fun createInteractor(): AccessibilityTilesInteractor {
        return AccessibilityTilesInteractor(
                a11yQsShortcutsRepository,
                testDispatcher,
                testScope.backgroundScope
            )
            .apply { init(currentTilesInteractor) }
    }
}
Loading