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

Commit 2de042f2 authored by Wenbo Jie's avatar Wenbo Jie
Browse files

[DocsUI M3 a11y] Hide toolbar when selection bar shows

When selection bar shows, it completely covers the original toolbar,
but the original toolbar still respond to the tab focus and Talkback.
If both bars have buttons in the same location, the button actually
focused is the button user sees (e.g. the button on the toolbar is
focused but the selection bar covers the toolbar, users assume the
button on the selection bar is focused). We should only show one bar
and hide the other one in any circumstances.

Note: ActionModeController (which handles selection bar for pre-M3)
has a similar logic but it uses setImportantForAccessibility()
instead of the visibility show/hide, which somehow doesn't work with
the search bar (i.e. the search bar inside the app bar still gets
the focus even if the app bar has
IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS), that's why
visibility solution is chosen eventually.

Check the attached bug for the demo.

Bug: 439514402
Test: m DocumentsUIGoogle && tab navigation with selected files
Flag: com.android.documentsui.flags.use_material3
Change-Id: Idd83f45467860f86a9e2ec5f0ad50d07859c3924
parent 49afeb9e
Loading
Loading
Loading
Loading
+4 −1
Original line number Diff line number Diff line
@@ -30,6 +30,7 @@ import com.google.android.material.appbar.MaterialToolbar
 * enabled.
 */
class SelectionBarController(
    private val appBar: MaterialToolbar,
    private val selectionBar: MaterialToolbar,
    private val menuManager: MenuManager,
    private val selectionManager: DocsSelectionHelper
@@ -71,10 +72,13 @@ class SelectionBarController(

    private fun updateSelectionBar() {
        if (selectedItems.isEmpty) {
            appBar.visibility = View.VISIBLE
            selectionBar.visibility = View.GONE
            return
        }
        selectionBar.visibility = View.VISIBLE
        appBar.visibility = View.GONE

        val quantity: Int = selectedItems.size()
        val title: String =
            selectionBar.context
@@ -98,7 +102,6 @@ class SelectionBarController(
        if (!isMenuInflated) {
            selectionBar.inflateMenu(getRes(R.menu.action_mode_menu))
        }
        selectionBar.visibility = View.VISIBLE
        menuManager.updateActionMenu(selectionBar.menu, selectionDetails)
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -170,6 +170,7 @@ public class FilesActivity extends BaseActivity implements AbstractActionHandler
        if (isUseMaterial3FlagEnabled()) {
            mInjector.selectionBarController =
                    new SelectionBarController(
                            findViewById(getRes(R.id.toolbar)),
                            findViewById(getRes(R.id.selection_bar)),
                            mInjector.menuManager,
                            mInjector.selectionMgr);
+1 −0
Original line number Diff line number Diff line
@@ -152,6 +152,7 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons {
        if (isUseMaterial3FlagEnabled()) {
            mInjector.selectionBarController =
                    new SelectionBarController(
                            findViewById(getRes(R.id.toolbar)),
                            findViewById(getRes(R.id.selection_bar)),
                            mInjector.menuManager,
                            mInjector.selectionMgr);
+1 −0
Original line number Diff line number Diff line
@@ -86,6 +86,7 @@ class DirectoryFragmentTest {
        // SelectionBarController itself can't be mocked, initialize a real one.
        injector.selectionBarController =
            SelectionBarController(
                MaterialToolbar(context),
                MaterialToolbar(context),
                injector.menuManager,
                injector.selectionMgr,
+79 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 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.documentsui

import android.view.View
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import androidx.test.platform.app.InstrumentationRegistry
import com.android.documentsui.util.Material3Config.Companion.getRes
import com.google.android.material.appbar.MaterialToolbar
import com.google.common.truth.Truth.assertThat
import junit.framework.TestCase.assertEquals
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mockito.mock

@RunWith(AndroidJUnit4::class)
@SmallTest
class SelectionBarControllerTest {
    private lateinit var appBar: MaterialToolbar
    private lateinit var selectionBar: MaterialToolbar
    private lateinit var selectionManager: DocsSelectionHelper
    private lateinit var selectionBarController: SelectionBarController

    @Before
    fun setUp() {
        val context = InstrumentationRegistry.getInstrumentation().targetContext
        context.setTheme(getRes(R.style.DocumentsTheme))
        context.theme.applyStyle(getRes(R.style.DocumentsDefaultTheme), false)

        appBar = MaterialToolbar(context)
        selectionBar = MaterialToolbar(context)
        // By default toolbar is visible but selection bar is not.
        appBar.visibility = View.VISIBLE
        selectionBar.visibility = View.GONE
        val menuManager = mock(MenuManager::class.java)
        selectionManager = SelectionHelpers.createTestInstance()
        selectionBarController =
            SelectionBarController(appBar, selectionBar, menuManager, selectionManager)
    }

    @Test
    fun testOnSelectionChanged_showHideSelectionBar() {
        assertThat(selectionManager.selection).hasSize(0)

        // Let selection bar controller observe selection manager.
        selectionManager.addObserver(selectionBarController)

        // Select one file and it should notify selection bar controller.
        selectionManager.select("file1")
        assertThat(selectionManager.selection).hasSize(1)

        // Assert selection bar should show and app bar should hide.
        assertEquals(selectionBar.visibility, View.VISIBLE)
        assertEquals(appBar.visibility, View.GONE)

        // Remove all selections.
        selectionManager.clearSelection()
        assertThat(selectionManager.selection).hasSize(0)

        // Assert selection bar should hide and app bar should show.
        assertEquals(selectionBar.visibility, View.GONE)
        assertEquals(appBar.visibility, View.VISIBLE)
    }
}