diff options
| author | 2022-08-29 21:44:51 +0000 | |
|---|---|---|
| committer | 2022-08-29 21:44:51 +0000 | |
| commit | 764a223c91f1a39059f51e5a3b51922edc7a51f7 (patch) | |
| tree | 4c207fd67b81d2c934d222e0624abf002c791533 | |
| parent | 66c35d7e8294be667f05d614dbc3ce144fd83ae5 (diff) | |
| parent | af62f5fdae6d51cafd07a5925f0f793b36e34a62 (diff) | |
Merge "Adds ChooserSelector" into tm-qpr-dev am: dcc64534db am: af62f5fdae
Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/19758717
Change-Id: I8bced9006b781bec1bee31ed2a65c1953cad3da9
Signed-off-by: Automerger Merge Worker <android-build-automerger-merge-worker@system.gserviceaccount.com>
5 files changed, 287 insertions, 0 deletions
diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt new file mode 100644 index 000000000000..109be40ce10f --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ChooserSelector.kt @@ -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() + } +} diff --git a/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java b/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java new file mode 100644 index 000000000000..7a2de7b6a78d --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/ChooserSelectorResourceHelper.java @@ -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; +} diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt index 6db3e82a77b0..8bb27a7bc217 100644 --- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt +++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt @@ -17,6 +17,7 @@ package com.android.systemui.dagger import com.android.keyguard.KeyguardBiometricLockoutLogger +import com.android.systemui.ChooserSelector import com.android.systemui.CoreStartable import com.android.systemui.LatencyTester import com.android.systemui.ScreenDecorations @@ -60,6 +61,12 @@ abstract class SystemUICoreStartableModule { @ClassKey(AuthController::class) abstract fun bindAuthController(service: AuthController): CoreStartable + /** Inject into ChooserCoreStartable. */ + @Binds + @IntoMap + @ClassKey(ChooserSelector::class) + abstract fun bindChooserSelector(sysui: ChooserSelector): CoreStartable + /** Inject into ClipboardListener. */ @Binds @IntoMap diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 45237a358700..c4553c4cb299 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -252,6 +252,9 @@ public class Flags { // 1400 - columbus, b/242800729 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. // ========================== Curtain ========================== // | | diff --git a/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt new file mode 100644 index 000000000000..6b1ef389a98e --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/ChooserSelectorTest.kt @@ -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() {} + } +} |