Merge "Adds ChooserSelector" into tm-qpr-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
dcc64534db
@@ -0,0 +1,67 @@
|
|||||||
|
package com.android.systemui
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import com.android.systemui.dagger.SysUISingleton
|
||||||
|
import com.android.systemui.dagger.qualifiers.Application
|
||||||
|
import com.android.systemui.dagger.qualifiers.Background
|
||||||
|
import com.android.systemui.flags.FeatureFlags
|
||||||
|
import com.android.systemui.flags.FlagListenable
|
||||||
|
import com.android.systemui.flags.Flags
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlinx.coroutines.CoroutineDispatcher
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.suspendCancellableCoroutine
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
@SysUISingleton
|
||||||
|
class ChooserSelector @Inject constructor(
|
||||||
|
context: Context,
|
||||||
|
private val featureFlags: FeatureFlags,
|
||||||
|
@Application private val coroutineScope: CoroutineScope,
|
||||||
|
@Background private val bgDispatcher: CoroutineDispatcher
|
||||||
|
) : CoreStartable(context) {
|
||||||
|
|
||||||
|
private val packageManager = context.packageManager
|
||||||
|
private val chooserComponent = ComponentName.unflattenFromString(
|
||||||
|
context.resources.getString(ChooserSelectorResourceHelper.CONFIG_CHOOSER_ACTIVITY))
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
coroutineScope.launch {
|
||||||
|
val listener = FlagListenable.Listener { event ->
|
||||||
|
if (event.flagId == Flags.CHOOSER_UNBUNDLED.id) {
|
||||||
|
launch { updateUnbundledChooserEnabled() }
|
||||||
|
event.requestNoRestart()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
featureFlags.addListener(Flags.CHOOSER_UNBUNDLED, listener)
|
||||||
|
updateUnbundledChooserEnabled()
|
||||||
|
|
||||||
|
awaitCancellationAndThen { featureFlags.removeListener(listener) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun updateUnbundledChooserEnabled() {
|
||||||
|
setUnbundledChooserEnabled(withContext(bgDispatcher) {
|
||||||
|
featureFlags.isEnabled(Flags.CHOOSER_UNBUNDLED)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setUnbundledChooserEnabled(enabled: Boolean) {
|
||||||
|
val newState = if (enabled) {
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_ENABLED
|
||||||
|
} else {
|
||||||
|
PackageManager.COMPONENT_ENABLED_STATE_DISABLED
|
||||||
|
}
|
||||||
|
packageManager.setComponentEnabledSetting(chooserComponent, newState, /* flags = */ 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend inline fun awaitCancellation(): Nothing = suspendCancellableCoroutine { }
|
||||||
|
suspend inline fun awaitCancellationAndThen(block: () -> Unit): Nothing = try {
|
||||||
|
awaitCancellation()
|
||||||
|
} finally {
|
||||||
|
block()
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,31 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2022 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;
|
||||||
|
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
|
|
||||||
|
import com.android.internal.R;
|
||||||
|
|
||||||
|
/** Helper class for referencing resources */
|
||||||
|
class ChooserSelectorResourceHelper {
|
||||||
|
|
||||||
|
private ChooserSelectorResourceHelper() {
|
||||||
|
}
|
||||||
|
|
||||||
|
@StringRes
|
||||||
|
static final int CONFIG_CHOOSER_ACTIVITY = R.string.config_chooserActivity;
|
||||||
|
}
|
||||||
@@ -17,6 +17,7 @@
|
|||||||
package com.android.systemui.dagger
|
package com.android.systemui.dagger
|
||||||
|
|
||||||
import com.android.keyguard.KeyguardBiometricLockoutLogger
|
import com.android.keyguard.KeyguardBiometricLockoutLogger
|
||||||
|
import com.android.systemui.ChooserSelector
|
||||||
import com.android.systemui.CoreStartable
|
import com.android.systemui.CoreStartable
|
||||||
import com.android.systemui.LatencyTester
|
import com.android.systemui.LatencyTester
|
||||||
import com.android.systemui.ScreenDecorations
|
import com.android.systemui.ScreenDecorations
|
||||||
@@ -60,6 +61,12 @@ abstract class SystemUICoreStartableModule {
|
|||||||
@ClassKey(AuthController::class)
|
@ClassKey(AuthController::class)
|
||||||
abstract fun bindAuthController(service: AuthController): CoreStartable
|
abstract fun bindAuthController(service: AuthController): CoreStartable
|
||||||
|
|
||||||
|
/** Inject into ChooserCoreStartable. */
|
||||||
|
@Binds
|
||||||
|
@IntoMap
|
||||||
|
@ClassKey(ChooserSelector::class)
|
||||||
|
abstract fun bindChooserSelector(sysui: ChooserSelector): CoreStartable
|
||||||
|
|
||||||
/** Inject into ClipboardListener. */
|
/** Inject into ClipboardListener. */
|
||||||
@Binds
|
@Binds
|
||||||
@IntoMap
|
@IntoMap
|
||||||
|
|||||||
@@ -253,6 +253,9 @@ public class Flags {
|
|||||||
// 1400 - columbus, b/242800729
|
// 1400 - columbus, b/242800729
|
||||||
public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
|
public static final UnreleasedFlag QUICK_TAP_IN_PCC = new UnreleasedFlag(1400);
|
||||||
|
|
||||||
|
// 1500 - chooser
|
||||||
|
public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500);
|
||||||
|
|
||||||
// Pay no attention to the reflection behind the curtain.
|
// Pay no attention to the reflection behind the curtain.
|
||||||
// ========================== Curtain ==========================
|
// ========================== Curtain ==========================
|
||||||
// | |
|
// | |
|
||||||
|
|||||||
@@ -0,0 +1,179 @@
|
|||||||
|
package com.android.systemui
|
||||||
|
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.content.res.Resources
|
||||||
|
import android.testing.AndroidTestingRunner
|
||||||
|
import androidx.test.filters.SmallTest
|
||||||
|
import com.android.systemui.flags.FeatureFlags
|
||||||
|
import com.android.systemui.flags.Flag
|
||||||
|
import com.android.systemui.flags.FlagListenable
|
||||||
|
import com.android.systemui.flags.Flags
|
||||||
|
import com.android.systemui.flags.UnreleasedFlag
|
||||||
|
import com.android.systemui.util.mockito.any
|
||||||
|
import com.android.systemui.util.mockito.eq
|
||||||
|
import com.android.systemui.util.mockito.kotlinArgumentCaptor
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.cancel
|
||||||
|
import kotlinx.coroutines.test.TestCoroutineDispatcher
|
||||||
|
import org.junit.After
|
||||||
|
import org.junit.Before
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.mockito.Mock
|
||||||
|
import org.mockito.Mockito.anyInt
|
||||||
|
import org.mockito.Mockito.clearInvocations
|
||||||
|
import org.mockito.Mockito.never
|
||||||
|
import org.mockito.Mockito.verify
|
||||||
|
import org.mockito.Mockito.verifyZeroInteractions
|
||||||
|
import org.mockito.Mockito.`when`
|
||||||
|
import org.mockito.MockitoAnnotations
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
@RunWith(AndroidTestingRunner::class)
|
||||||
|
@SmallTest
|
||||||
|
class ChooserSelectorTest : SysuiTestCase() {
|
||||||
|
|
||||||
|
private val flagListener = kotlinArgumentCaptor<FlagListenable.Listener>()
|
||||||
|
|
||||||
|
private val testDispatcher = TestCoroutineDispatcher()
|
||||||
|
private val testScope = CoroutineScope(testDispatcher)
|
||||||
|
|
||||||
|
private lateinit var chooserSelector: ChooserSelector
|
||||||
|
|
||||||
|
@Mock private lateinit var mockContext: Context
|
||||||
|
@Mock private lateinit var mockPackageManager: PackageManager
|
||||||
|
@Mock private lateinit var mockResources: Resources
|
||||||
|
@Mock private lateinit var mockFeatureFlags: FeatureFlags
|
||||||
|
|
||||||
|
@Before
|
||||||
|
fun setup() {
|
||||||
|
MockitoAnnotations.initMocks(this)
|
||||||
|
|
||||||
|
`when`(mockContext.packageManager).thenReturn(mockPackageManager)
|
||||||
|
`when`(mockContext.resources).thenReturn(mockResources)
|
||||||
|
`when`(mockResources.getString(anyInt())).thenReturn(
|
||||||
|
ComponentName("TestPackage", "TestClass").flattenToString())
|
||||||
|
|
||||||
|
chooserSelector = ChooserSelector(mockContext, mockFeatureFlags, testScope, testDispatcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
fun tearDown() {
|
||||||
|
testDispatcher.cleanupTestCoroutines()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun initialize_registersFlagListenerUntilScopeCancelled() {
|
||||||
|
// Arrange
|
||||||
|
|
||||||
|
// Act
|
||||||
|
chooserSelector.start()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(mockFeatureFlags).addListener(
|
||||||
|
eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
|
||||||
|
verify(mockFeatureFlags, never()).removeListener(any())
|
||||||
|
|
||||||
|
// Act
|
||||||
|
testScope.cancel()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(mockFeatureFlags).removeListener(eq(flagListener.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun initialize_enablesUnbundledChooser_whenFlagEnabled() {
|
||||||
|
// Arrange
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
chooserSelector.start()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(mockPackageManager).setComponentEnabledSetting(
|
||||||
|
eq(ComponentName("TestPackage", "TestClass")),
|
||||||
|
eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
|
||||||
|
anyInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun initialize_disablesUnbundledChooser_whenFlagDisabled() {
|
||||||
|
// Arrange
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
chooserSelector.start()
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(mockPackageManager).setComponentEnabledSetting(
|
||||||
|
eq(ComponentName("TestPackage", "TestClass")),
|
||||||
|
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
|
||||||
|
anyInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun enablesUnbundledChooser_whenFlagBecomesEnabled() {
|
||||||
|
// Arrange
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
|
||||||
|
chooserSelector.start()
|
||||||
|
verify(mockFeatureFlags).addListener(
|
||||||
|
eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
|
||||||
|
verify(mockPackageManager, never()).setComponentEnabledSetting(
|
||||||
|
any(), eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED), anyInt())
|
||||||
|
|
||||||
|
// Act
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
|
||||||
|
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(mockPackageManager).setComponentEnabledSetting(
|
||||||
|
eq(ComponentName("TestPackage", "TestClass")),
|
||||||
|
eq(PackageManager.COMPONENT_ENABLED_STATE_ENABLED),
|
||||||
|
anyInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun disablesUnbundledChooser_whenFlagBecomesDisabled() {
|
||||||
|
// Arrange
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(true)
|
||||||
|
chooserSelector.start()
|
||||||
|
verify(mockFeatureFlags).addListener(
|
||||||
|
eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
|
||||||
|
verify(mockPackageManager, never()).setComponentEnabledSetting(
|
||||||
|
any(), eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED), anyInt())
|
||||||
|
|
||||||
|
// Act
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
|
||||||
|
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verify(mockPackageManager).setComponentEnabledSetting(
|
||||||
|
eq(ComponentName("TestPackage", "TestClass")),
|
||||||
|
eq(PackageManager.COMPONENT_ENABLED_STATE_DISABLED),
|
||||||
|
anyInt())
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun doesNothing_whenAnotherFlagChanges() {
|
||||||
|
// Arrange
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
|
||||||
|
chooserSelector.start()
|
||||||
|
verify(mockFeatureFlags).addListener(
|
||||||
|
eq<Flag<*>>(Flags.CHOOSER_UNBUNDLED), flagListener.capture())
|
||||||
|
clearInvocations(mockPackageManager)
|
||||||
|
|
||||||
|
// Act
|
||||||
|
`when`(mockFeatureFlags.isEnabled(any<UnreleasedFlag>())).thenReturn(false)
|
||||||
|
flagListener.value.onFlagChanged(TestFlagEvent(Flags.CHOOSER_UNBUNDLED.id + 1))
|
||||||
|
|
||||||
|
// Assert
|
||||||
|
verifyZeroInteractions(mockPackageManager)
|
||||||
|
}
|
||||||
|
|
||||||
|
private class TestFlagEvent(override val flagId: Int) : FlagListenable.FlagEvent {
|
||||||
|
override fun requestNoRestart() {}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user