/* * 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 java.util.function.Consumer 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.Mockito.`when` as whenever import org.mockito.MockitoAnnotations /** * 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() val listener2 = mock() // no interactions before adding listener verifyNoMoreInteractions(mFlagSettingsHelper) // adding the first listener registers the observer mFlagManager.addListener(ReleasedFlag("1", "test"), listener1) val observer = withArgCaptor { verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture()) } verifyNoMoreInteractions(mFlagSettingsHelper) // adding another listener does nothing mFlagManager.addListener(ReleasedFlag("2", "test"), 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() val clearCacheAction = mock>() mFlagManager.clearCacheAction = clearCacheAction mFlagManager.addListener(ReleasedFlag("1", "test"), listener) val observer = withArgCaptor { verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture()) } observer.onChange(false, flagUri(1)) verify(clearCacheAction).accept(eq("1")) } @Test fun testObserverInvokesListeners() { val listener1 = mock() val listener10 = mock() mFlagManager.addListener(ReleasedFlag("1", "test"), listener1) mFlagManager.addListener(ReleasedFlag("10", "test"), listener10) val observer = withArgCaptor { verify(mFlagSettingsHelper).registerContentObserver(any(), any(), capture()) } observer.onChange(false, flagUri(1)) val flagEvent1 = withArgCaptor { verify(listener1).onFlagChanged(capture()) } assertThat(flagEvent1.flagName).isEqualTo("1") verifyNoMoreInteractions(listener1, listener10) observer.onChange(false, flagUri(10)) val flagEvent10 = withArgCaptor { verify(listener10).onFlagChanged(capture()) } assertThat(flagEvent10.flagName).isEqualTo("10") verifyNoMoreInteractions(listener1, listener10) } fun flagUri(id: Int): Uri = Uri.parse("content://settings/system/systemui/flags/$id") @Test fun testOnlySpecificFlagListenerIsInvoked() { val listener1 = mock() val listener10 = mock() mFlagManager.addListener(ReleasedFlag("1", "test"), listener1) mFlagManager.addListener(ReleasedFlag("10", "test"), listener10) mFlagManager.dispatchListenersAndMaybeRestart("1", null) val flagEvent1 = withArgCaptor { verify(listener1).onFlagChanged(capture()) } assertThat(flagEvent1.flagName).isEqualTo("1") verifyNoMoreInteractions(listener1, listener10) mFlagManager.dispatchListenersAndMaybeRestart("10", null) val flagEvent10 = withArgCaptor { verify(listener10).onFlagChanged(capture()) } assertThat(flagEvent10.flagName).isEqualTo("10") verifyNoMoreInteractions(listener1, listener10) } @Test fun testSameListenerCanBeUsedForMultipleFlags() { val listener = mock() mFlagManager.addListener(ReleasedFlag("1", "test"), listener) mFlagManager.addListener(ReleasedFlag("10", "test"), listener) mFlagManager.dispatchListenersAndMaybeRestart("1", null) val flagEvent1 = withArgCaptor { verify(listener).onFlagChanged(capture()) } assertThat(flagEvent1.flagName).isEqualTo("1") verifyNoMoreInteractions(listener) mFlagManager.dispatchListenersAndMaybeRestart("10", null) val flagEvent10 = withArgCaptor { verify(listener, times(2)).onFlagChanged(capture()) } assertThat(flagEvent10.flagName).isEqualTo("10") verifyNoMoreInteractions(listener) } @Test fun testRestartWithNoListeners() { val restartAction = mock>() mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) verify(restartAction).accept(eq(false)) verifyNoMoreInteractions(restartAction) } @Test fun testListenerCanSuppressRestart() { val restartAction = mock>() mFlagManager.addListener(ReleasedFlag("1", "test")) { event -> event.requestNoRestart() } mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) verify(restartAction).accept(eq(true)) verifyNoMoreInteractions(restartAction) } @Test fun testListenerOnlySuppressesRestartForOwnFlag() { val restartAction = mock>() mFlagManager.addListener(ReleasedFlag("10", "test")) { event -> event.requestNoRestart() } mFlagManager.dispatchListenersAndMaybeRestart("1", restartAction) verify(restartAction).accept(eq(false)) verifyNoMoreInteractions(restartAction) } @Test fun testRestartWhenNotAllListenersRequestSuppress() { val restartAction = mock>() mFlagManager.addListener(ReleasedFlag("10", "test")) { event -> event.requestNoRestart() } mFlagManager.addListener(ReleasedFlag("10", "test")) { // 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\"}") } }