Disable All Apps keyboard shortcut during user setup.

Flag: EXEMPT bugfix
Fix: 394238663
Test: go/testedequals
Change-Id: I4a9175613686a358bd62800789b30262d9dc39cb
This commit is contained in:
Brian Isganitis
2025-02-04 19:43:30 -05:00
parent 2b6f14b621
commit 6dec35f18e
4 changed files with 135 additions and 18 deletions
@@ -612,6 +612,7 @@ public class TaskbarManager {
*/
public void setSetupUIVisible(boolean isVisible) {
mSharedState.setupUIVisible = isVisible;
mAllAppsActionManager.setSetupUiVisible(isVisible);
TaskbarActivityContext taskbar = getTaskbarForDisplay(getDefaultDisplayId());
if (taskbar != null) {
taskbar.setSetupUIVisible(isVisible);
@@ -21,10 +21,16 @@ import android.app.PendingIntent
import android.app.RemoteAction
import android.content.Context
import android.graphics.drawable.Icon
import android.provider.Settings
import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import android.view.accessibility.AccessibilityManager
import com.android.launcher3.R
import com.android.launcher3.util.SettingsCache
import com.android.launcher3.util.SettingsCache.OnChangeListener
import java.util.concurrent.Executor
private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
/**
* Registers a [RemoteAction] for toggling All Apps if needed.
*
@@ -38,6 +44,12 @@ class AllAppsActionManager(
private val createAllAppsPendingIntent: () -> PendingIntent,
) {
private val onSettingsChangeListener = OnChangeListener { v -> isUserSetupComplete = v }
init {
SettingsCache.INSTANCE[context].register(USER_SETUP_COMPLETE_URI, onSettingsChangeListener)
}
/** `true` if home and overview are the same Activity. */
var isHomeAndOverviewSame = false
set(value) {
@@ -52,12 +64,27 @@ class AllAppsActionManager(
updateSystemAction()
}
/** `true` if the setup UI is visible. */
var isSetupUiVisible = false
set(value) {
field = value
updateSystemAction()
}
private var isUserSetupComplete =
SettingsCache.INSTANCE[context].getValue(USER_SETUP_COMPLETE_URI, 0)
set(value) {
field = value
updateSystemAction()
}
/** `true` if the action should be registered. */
var isActionRegistered = false
private set
private fun updateSystemAction() {
val shouldRegisterAction = isHomeAndOverviewSame || isTaskbarPresent
val isInSetupFlow = isSetupUiVisible || !isUserSetupComplete
val shouldRegisterAction = (isHomeAndOverviewSame || isTaskbarPresent) && !isInSetupFlow
if (isActionRegistered == shouldRegisterAction) return
isActionRegistered = shouldRegisterAction
@@ -84,8 +111,10 @@ class AllAppsActionManager(
isActionRegistered = false
context
.getSystemService(AccessibilityManager::class.java)
?.unregisterSystemAction(
GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS,
)
?.unregisterSystemAction(GLOBAL_ACTION_ACCESSIBILITY_ALL_APPS)
SettingsCache.INSTANCE[context].unregister(
USER_SETUP_COMPLETE_URI,
onSettingsChangeListener,
)
}
}
@@ -17,19 +17,22 @@
package com.android.launcher3.util
import android.net.Uri
import com.android.launcher3.util.SettingsCache.OnChangeListener
import org.mockito.kotlin.any
import org.mockito.kotlin.doAnswer
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever
/**
* Provides a sandboxed [SettingsCache] for testing.
*
* Note that listeners registered to [cache] will never be invoked.
*/
/** Provides [SettingsCache] sandboxed from system settings for testing. */
class SettingsCacheSandbox {
private val values = mutableMapOf<Uri, Int>()
private val listeners = mutableMapOf<Uri, MutableSet<OnChangeListener>>()
/** Fake cache that delegates [SettingsCache.getValue] to [values]. */
/**
* Fake cache that delegates:
* - [SettingsCache.getValue] to [values]
* - [SettingsCache.mListenerMap] to [listeners].
*/
val cache =
mock<SettingsCache> {
on { getValue(any<Uri>()) } doAnswer { mock.getValue(it.getArgument(0), 1) }
@@ -37,11 +40,22 @@ class SettingsCacheSandbox {
{
values.getOrDefault(it.getArgument(0), it.getArgument(1)) == 1
}
doAnswer {
listeners.getOrPut(it.getArgument(0)) { mutableSetOf() }.add(it.getArgument(1))
}
.whenever(mock)
.register(any(), any())
doAnswer { listeners[it.getArgument(0)]?.remove(it.getArgument(1)) }
.whenever(mock)
.unregister(any(), any())
}
operator fun get(key: Uri): Int? = values[key]
operator fun set(key: Uri, value: Int) {
if (value == values[key]) return
values[key] = value
listeners[key]?.forEach { it.onSettingsChanged(value == 1) }
}
}
@@ -18,32 +18,59 @@ package com.android.quickstep
import android.app.PendingIntent
import android.content.IIntentSender
import android.provider.Settings
import android.provider.Settings.Secure.USER_SETUP_COMPLETE
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.platform.app.InstrumentationRegistry
import com.android.launcher3.dagger.LauncherAppComponent
import com.android.launcher3.dagger.LauncherAppSingleton
import com.android.launcher3.util.AllModulesForTest
import com.android.launcher3.util.Executors.UI_HELPER_EXECUTOR
import com.android.launcher3.util.SandboxApplication
import com.android.launcher3.util.SettingsCache
import com.android.launcher3.util.SettingsCacheSandbox
import com.android.launcher3.util.TestUtil
import com.google.common.truth.Truth.assertThat
import dagger.BindsInstance
import dagger.Component
import java.util.concurrent.Semaphore
import java.util.concurrent.TimeUnit.SECONDS
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
private const val TIMEOUT = 5L
private val USER_SETUP_COMPLETE_URI = Settings.Secure.getUriFor(USER_SETUP_COMPLETE)
@RunWith(AndroidJUnit4::class)
class AllAppsActionManagerTest {
private val callbackSemaphore = Semaphore(0)
private val bgExecutor = UI_HELPER_EXECUTOR
private val allAppsActionManager =
AllAppsActionManager(
InstrumentationRegistry.getInstrumentation().targetContext,
bgExecutor,
) {
callbackSemaphore.release()
PendingIntent(IIntentSender.Default())
@get:Rule val context = SandboxApplication()
private val settingsCacheSandbox =
SettingsCacheSandbox().also { it[USER_SETUP_COMPLETE_URI] = 1 }
private val allAppsActionManager by
lazy(LazyThreadSafetyMode.NONE) {
AllAppsActionManager(context, bgExecutor) {
callbackSemaphore.release()
PendingIntent(IIntentSender.Default())
}
}
@Before
fun initDaggerComponent() {
context.initDaggerComponent(
DaggerAllAppsActionManagerTestComponent.builder()
.bindSettingsCache(settingsCacheSandbox.cache)
)
}
@After fun destroyManager() = allAppsActionManager.onDestroy()
@Test
fun taskbarPresent_actionRegistered() {
allAppsActionManager.isTaskbarPresent = true
@@ -88,4 +115,50 @@ class AllAppsActionManagerTest {
assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
assertThat(allAppsActionManager.isActionRegistered).isTrue()
}
@Test
fun taskbarPresent_userSetupIncomplete_actionUnregistered() {
settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
allAppsActionManager.isTaskbarPresent = true
assertThat(allAppsActionManager.isActionRegistered).isFalse()
}
@Test
fun taskbarPresent_setupUiVisible_actionUnregistered() {
allAppsActionManager.isSetupUiVisible = true
allAppsActionManager.isTaskbarPresent = true
assertThat(allAppsActionManager.isActionRegistered).isFalse()
}
@Test
fun taskbarPresent_userSetupCompleted_actionRegistered() {
settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 0
allAppsActionManager.isTaskbarPresent = true
settingsCacheSandbox[USER_SETUP_COMPLETE_URI] = 1
assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
assertThat(allAppsActionManager.isActionRegistered).isTrue()
}
@Test
fun taskbarPresent_setupUiDismissed_actionRegistered() {
allAppsActionManager.isSetupUiVisible = true
allAppsActionManager.isTaskbarPresent = true
allAppsActionManager.isSetupUiVisible = false
assertThat(callbackSemaphore.tryAcquire(TIMEOUT, SECONDS)).isTrue()
assertThat(allAppsActionManager.isActionRegistered).isTrue()
}
}
@LauncherAppSingleton
@Component(modules = [AllModulesForTest::class])
interface AllAppsActionManagerTestComponent : LauncherAppComponent {
@Component.Builder
interface Builder : LauncherAppComponent.Builder {
@BindsInstance fun bindSettingsCache(settingsCache: SettingsCache): Builder
override fun build(): AllAppsActionManagerTestComponent
}
}