blob: a2eca81b04ed1f81d3f43385f6e4141a356a6af1 [file] [log] [blame]
/*
* Copyright (C) 2021 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.flags
import android.content.Context
import android.database.ContentObserver
import android.net.Uri
import android.os.Handler
import android.test.suitebuilder.annotation.SmallTest
import com.android.systemui.SysuiTestCase
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.eq
import com.android.systemui.util.mockito.mock
import com.android.systemui.util.mockito.withArgCaptor
import com.google.common.truth.Truth.assertThat
import org.junit.Assert.assertThrows
import org.junit.Before
import org.junit.Test
import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.MockitoAnnotations
import java.util.function.Consumer
import org.mockito.Mockito.`when` as whenever
/**
* NOTE: This test is for the version of FeatureFlagManager in src-release, which should not allow
* overriding, and should never return any value other than the one provided as the default.
*/
@SmallTest
class FlagManagerTest : SysuiTestCase() {
private lateinit var mFlagManager: FlagManager
@Mock private lateinit var mMockContext: Context
@Mock private lateinit var mFlagSettingsHelper: FlagSettingsHelper
@Mock private lateinit var mHandler: Handler
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
mFlagManager = FlagManager(mMockContext, mFlagSettingsHelper, mHandler)
}
@Test
fun testContentObserverAddedAndRemoved() {
val listener1 = mock<FlagListenable.Listener>()
val listener2 = mock<FlagListenable.Listener>()
// no interactions before adding listener
verifyNoMoreInteractions(mFlagSettingsHelper)
// adding the first listener registers the observer
mFlagManager.addListener(BooleanFlag(1, true), listener1)
val observer = withArgCaptor<ContentObserver> {
verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
}
verifyNoMoreInteractions(mFlagSettingsHelper)
// adding another listener does nothing
mFlagManager.addListener(BooleanFlag(2, true), listener2)
verifyNoMoreInteractions(mFlagSettingsHelper)
// removing the original listener does nothing with second one still present
mFlagManager.removeListener(listener1)
verifyNoMoreInteractions(mFlagSettingsHelper)
// removing the final listener unregisters the observer
mFlagManager.removeListener(listener2)
verify(mFlagSettingsHelper).unregisterContentObserver(eq(observer))
verifyNoMoreInteractions(mFlagSettingsHelper)
}
@Test
fun testObserverClearsCache() {
val listener = mock<FlagListenable.Listener>()
val clearCacheAction = mock<Consumer<Int>>()
mFlagManager.clearCacheAction = clearCacheAction
mFlagManager.addListener(BooleanFlag(1, true), listener)
val observer = withArgCaptor<ContentObserver> {
verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
}
observer.onChange(false, flagUri(1))
verify(clearCacheAction).accept(eq(1))
}
@Test
fun testObserverInvokesListeners() {
val listener1 = mock<FlagListenable.Listener>()
val listener10 = mock<FlagListenable.Listener>()
mFlagManager.addListener(BooleanFlag(1, true), listener1)
mFlagManager.addListener(BooleanFlag(10, true), listener10)
val observer = withArgCaptor<ContentObserver> {
verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture())
}
observer.onChange(false, flagUri(1))
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener1).onFlagChanged(capture())
}
assertThat(flagEvent1.flagId).isEqualTo(1)
verifyNoMoreInteractions(listener1, listener10)
observer.onChange(false, flagUri(10))
val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener10).onFlagChanged(capture())
}
assertThat(flagEvent10.flagId).isEqualTo(10)
verifyNoMoreInteractions(listener1, listener10)
}
fun flagUri(id: Int): Uri = Uri.parse("content://settings/system/systemui/flags/$id")
@Test
fun testOnlySpecificFlagListenerIsInvoked() {
val listener1 = mock<FlagListenable.Listener>()
val listener10 = mock<FlagListenable.Listener>()
mFlagManager.addListener(BooleanFlag(1, true), listener1)
mFlagManager.addListener(BooleanFlag(10, true), listener10)
mFlagManager.dispatchListenersAndMaybeRestart(1, null)
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener1).onFlagChanged(capture())
}
assertThat(flagEvent1.flagId).isEqualTo(1)
verifyNoMoreInteractions(listener1, listener10)
mFlagManager.dispatchListenersAndMaybeRestart(10, null)
val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener10).onFlagChanged(capture())
}
assertThat(flagEvent10.flagId).isEqualTo(10)
verifyNoMoreInteractions(listener1, listener10)
}
@Test
fun testSameListenerCanBeUsedForMultipleFlags() {
val listener = mock<FlagListenable.Listener>()
mFlagManager.addListener(BooleanFlag(1, true), listener)
mFlagManager.addListener(BooleanFlag(10, true), listener)
mFlagManager.dispatchListenersAndMaybeRestart(1, null)
val flagEvent1 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener).onFlagChanged(capture())
}
assertThat(flagEvent1.flagId).isEqualTo(1)
verifyNoMoreInteractions(listener)
mFlagManager.dispatchListenersAndMaybeRestart(10, null)
val flagEvent10 = withArgCaptor<FlagListenable.FlagEvent> {
verify(listener, times(2)).onFlagChanged(capture())
}
assertThat(flagEvent10.flagId).isEqualTo(10)
verifyNoMoreInteractions(listener)
}
@Test
fun testRestartWithNoListeners() {
val restartAction = mock<Consumer<Boolean>>()
mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
verify(restartAction).accept(eq(false))
verifyNoMoreInteractions(restartAction)
}
@Test
fun testListenerCanSuppressRestart() {
val restartAction = mock<Consumer<Boolean>>()
mFlagManager.addListener(BooleanFlag(1, true)) { event ->
event.requestNoRestart()
}
mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
verify(restartAction).accept(eq(true))
verifyNoMoreInteractions(restartAction)
}
@Test
fun testListenerOnlySuppressesRestartForOwnFlag() {
val restartAction = mock<Consumer<Boolean>>()
mFlagManager.addListener(BooleanFlag(10, true)) { event ->
event.requestNoRestart()
}
mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
verify(restartAction).accept(eq(false))
verifyNoMoreInteractions(restartAction)
}
@Test
fun testRestartWhenNotAllListenersRequestSuppress() {
val restartAction = mock<Consumer<Boolean>>()
mFlagManager.addListener(BooleanFlag(10, true)) { event ->
event.requestNoRestart()
}
mFlagManager.addListener(BooleanFlag(10, true)) {
// do not request
}
mFlagManager.dispatchListenersAndMaybeRestart(1, restartAction)
verify(restartAction).accept(eq(false))
verifyNoMoreInteractions(restartAction)
}
@Test
fun testReadBooleanFlag() {
// test that null string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
// test that empty string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
// test false
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"boolean\",\"value\":false}")
assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isFalse()
// test true
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"boolean\",\"value\":true}")
assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isTrue()
// Reading a value of a different type should just return null
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
assertThat(mFlagManager.readFlagValue(1, BooleanFlagSerializer)).isNull()
// Reading a value that isn't json should throw an exception
assertThrows(InvalidFlagStorageException::class.java) {
whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
mFlagManager.readFlagValue(1, BooleanFlagSerializer)
}
}
@Test
fun testSerializeBooleanFlag() {
// test false
assertThat(BooleanFlagSerializer.toSettingsData(false))
.isEqualTo("{\"type\":\"boolean\",\"value\":false}")
// test true
assertThat(BooleanFlagSerializer.toSettingsData(true))
.isEqualTo("{\"type\":\"boolean\",\"value\":true}")
}
@Test
fun testReadStringFlag() {
// test that null string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn(null)
assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
// test that empty string returns null
whenever(mFlagSettingsHelper.getString(any())).thenReturn("")
assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
// test json with the empty string value returns empty string
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"string\",\"value\":\"\"}")
assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("")
// test string with value is returned
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"string\",\"value\":\"foo\"}")
assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isEqualTo("foo")
// Reading a value of a different type should just return null
whenever(mFlagSettingsHelper.getString(any()))
.thenReturn("{\"type\":\"boolean\",\"value\":false}")
assertThat(mFlagManager.readFlagValue(1, StringFlagSerializer)).isNull()
// Reading a value that isn't json should throw an exception
assertThrows(InvalidFlagStorageException::class.java) {
whenever(mFlagSettingsHelper.getString(any())).thenReturn("1")
mFlagManager.readFlagValue(1, StringFlagSerializer)
}
}
@Test
fun testSerializeStringFlag() {
// test empty string
assertThat(StringFlagSerializer.toSettingsData(""))
.isEqualTo("{\"type\":\"string\",\"value\":\"\"}")
// test string "foo"
assertThat(StringFlagSerializer.toSettingsData("foo"))
.isEqualTo("{\"type\":\"string\",\"value\":\"foo\"}")
}
}