1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.settings.bluetooth.ui.view 18 19 import android.app.settings.SettingsEnums; 20 import android.bluetooth.BluetoothAdapter 21 import android.content.Context 22 import android.content.Intent 23 import android.graphics.Bitmap 24 import androidx.fragment.app.FragmentActivity 25 import androidx.preference.Preference 26 import androidx.preference.PreferenceManager 27 import androidx.preference.PreferenceScreen 28 import androidx.test.core.app.ApplicationProvider 29 import com.android.settings.bluetooth.ui.model.DeviceSettingPreferenceModel 30 import com.android.settings.bluetooth.ui.model.FragmentTypeModel 31 import com.android.settings.dashboard.DashboardFragment 32 import com.android.settings.testutils.FakeFeatureFactory 33 import com.android.settingslib.bluetooth.CachedBluetoothDevice 34 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId 35 import com.android.settingslib.bluetooth.devicesettings.data.repository.DeviceSettingRepository 36 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel 37 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel 38 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon 39 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel 40 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel 41 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel 42 import com.android.settingslib.core.AbstractPreferenceController 43 import com.google.common.truth.Truth.assertThat 44 import kotlinx.coroutines.ExperimentalCoroutinesApi 45 import kotlinx.coroutines.delay 46 import kotlinx.coroutines.flow.flowOf 47 import kotlinx.coroutines.flow.launchIn 48 import kotlinx.coroutines.flow.onEach 49 import kotlinx.coroutines.test.TestScope 50 import kotlinx.coroutines.test.runCurrent 51 import kotlinx.coroutines.test.runTest 52 import org.junit.Before 53 import org.junit.Rule 54 import org.junit.Test 55 import org.junit.runner.RunWith 56 import org.mockito.ArgumentMatchers.eq 57 import org.mockito.Mock 58 import org.mockito.Mockito.any 59 import org.mockito.Mockito.verify 60 import org.mockito.Mockito.`when` 61 import org.mockito.junit.MockitoJUnit 62 import org.mockito.junit.MockitoRule 63 import org.robolectric.Robolectric 64 import org.robolectric.RobolectricTestRunner 65 import org.robolectric.shadows.ShadowLooper 66 import org.robolectric.shadows.ShadowLooper.shadowMainLooper 67 68 69 @ExperimentalCoroutinesApi 70 @RunWith(RobolectricTestRunner::class) 71 class DeviceDetailsFragmentFormatterTest { 72 @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule() 73 74 @Mock private lateinit var cachedDevice: CachedBluetoothDevice 75 @Mock private lateinit var bluetoothAdapter: BluetoothAdapter 76 @Mock private lateinit var repository: DeviceSettingRepository 77 @Mock private lateinit var profileController: AbstractPreferenceController 78 @Mock private lateinit var headerController: AbstractPreferenceController 79 @Mock private lateinit var buttonController: AbstractPreferenceController 80 81 private lateinit var context: Context 82 private lateinit var fragment: TestFragment 83 private lateinit var underTest: DeviceDetailsFragmentFormatter 84 private lateinit var featureFactory: FakeFeatureFactory 85 private lateinit var fragmentActivity: FragmentActivity 86 private val testScope = TestScope() 87 88 @Before setUpnull89 fun setUp() { 90 context = ApplicationProvider.getApplicationContext() 91 featureFactory = FakeFeatureFactory.setupForTest() 92 `when`( 93 featureFactory.bluetoothFeatureProvider.getDeviceSettingRepository( 94 eq(context), eq(bluetoothAdapter), any())) 95 .thenReturn(repository) 96 fragmentActivity = Robolectric.setupActivity(FragmentActivity::class.java) 97 assertThat(fragmentActivity.applicationContext).isNotNull() 98 fragment = TestFragment(context) 99 fragmentActivity.supportFragmentManager.beginTransaction().add(fragment, null).commit() 100 shadowMainLooper().idle() 101 102 fragment.preferenceScreen.run { 103 addPreference(Preference(context).apply { key = "bluetooth_device_header" }) 104 addPreference(Preference(context).apply { key = "action_buttons" }) 105 addPreference(Preference(context).apply { key = "bluetooth_profiles" }) 106 } 107 `when`(profileController.preferenceKey).thenReturn("bluetooth_profiles") 108 `when`(headerController.preferenceKey).thenReturn("bluetooth_device_header") 109 `when`(buttonController.preferenceKey).thenReturn("action_buttons") 110 111 underTest = 112 DeviceDetailsFragmentFormatterImpl( 113 context, 114 fragment, 115 listOf(profileController, headerController, buttonController), 116 bluetoothAdapter, 117 cachedDevice, 118 testScope.testScheduler) 119 } 120 121 @Test getMenuItem_returnItemnull122 fun getMenuItem_returnItem() { 123 testScope.runTest { 124 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 125 .thenReturn( 126 DeviceSettingConfigModel( 127 listOf(), listOf(), DeviceSettingConfigItemModel.AppProvidedItem(12345, false))) 128 val intent = Intent().apply { 129 setAction(Intent.ACTION_VIEW) 130 setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 131 } 132 `when`(repository.getDeviceSetting(cachedDevice, 12345)) 133 .thenReturn( 134 flowOf( 135 DeviceSettingModel.HelpPreference( 136 cachedDevice = cachedDevice, 137 id = 12345, 138 intent = intent, 139 ))) 140 141 var helpPreference: DeviceSettingPreferenceModel.HelpPreference? = null 142 underTest.getMenuItem(FragmentTypeModel.DeviceDetailsMoreSettingsFragment).onEach { 143 helpPreference = it 144 }.launchIn(testScope.backgroundScope) 145 delay(100) 146 runCurrent() 147 ShadowLooper.idleMainLooper() 148 149 assertThat(helpPreference?.intent).isSameInstanceAs(intent) 150 } 151 } 152 153 @Test updateLayout_configIsNull_notChangenull154 fun updateLayout_configIsNull_notChange() { 155 testScope.runTest { 156 `when`(repository.getDeviceSettingsConfig(cachedDevice)).thenReturn(null) 157 158 underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment) 159 160 assertThat(getDisplayedPreferences().mapNotNull { it.key }) 161 .containsExactly("bluetooth_device_header", "action_buttons", "bluetooth_profiles") 162 } 163 } 164 165 @Test updateLayout_itemsNotInConfig_hidenull166 fun updateLayout_itemsNotInConfig_hide() { 167 testScope.runTest { 168 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 169 .thenReturn( 170 DeviceSettingConfigModel( 171 listOf( 172 DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( 173 DeviceSettingId.DEVICE_SETTING_ID_HEADER, 174 highlighted = false, preferenceKey = "bluetooth_device_header"), 175 DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( 176 DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES, 177 highlighted = false, preferenceKey = "bluetooth_profiles"), 178 ), 179 listOf(), 180 null)) 181 182 underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment) 183 runCurrent() 184 185 assertThat(getDisplayedPreferences().mapNotNull { it.key }) 186 .containsExactly("bluetooth_device_header", "bluetooth_profiles") 187 verify(featureFactory.metricsFeatureProvider) 188 .action( 189 SettingsEnums.PAGE_UNKNOWN, 190 SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN, 191 0, 192 "bluetooth_device_header", 1) 193 verify(featureFactory.metricsFeatureProvider) 194 .action( 195 SettingsEnums.PAGE_UNKNOWN, 196 SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN, 197 0, 198 "bluetooth_profiles", 1) 199 } 200 } 201 202 @Test updateLayout_newItems_displayNewItemsnull203 fun updateLayout_newItems_displayNewItems() { 204 testScope.runTest { 205 `when`(repository.getDeviceSettingsConfig(cachedDevice)) 206 .thenReturn( 207 DeviceSettingConfigModel( 208 listOf( 209 DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( 210 DeviceSettingId.DEVICE_SETTING_ID_HEADER, 211 highlighted = false, 212 preferenceKey = "bluetooth_device_header"), 213 DeviceSettingConfigItemModel.AppProvidedItem( 214 DeviceSettingId.DEVICE_SETTING_ID_ANC, highlighted = false), 215 DeviceSettingConfigItemModel.BuiltinItem.CommonBuiltinItem( 216 DeviceSettingId.DEVICE_SETTING_ID_BLUETOOTH_PROFILES, 217 highlighted = false, 218 preferenceKey = "bluetooth_profiles"), 219 ), 220 listOf(), 221 null)) 222 `when`(repository.getDeviceSetting(cachedDevice, DeviceSettingId.DEVICE_SETTING_ID_ANC)) 223 .thenReturn( 224 flowOf( 225 DeviceSettingModel.MultiTogglePreference( 226 cachedDevice, 227 DeviceSettingId.DEVICE_SETTING_ID_ANC, 228 "title", 229 toggles = 230 listOf( 231 ToggleModel( 232 "", 233 DeviceSettingIcon.BitmapIcon( 234 Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)))), 235 isActive = true, 236 state = DeviceSettingStateModel.MultiTogglePreferenceState(0), 237 isAllowedChangingState = true, 238 updateState = {}))) 239 240 underTest.updateLayout(FragmentTypeModel.DeviceDetailsMainFragment) 241 runCurrent() 242 243 assertThat(getDisplayedPreferences().mapNotNull { it.key }) 244 .containsExactly( 245 "bluetooth_device_header", 246 "DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}", 247 "bluetooth_profiles") 248 verify(featureFactory.metricsFeatureProvider) 249 .action( 250 SettingsEnums.PAGE_UNKNOWN, 251 SettingsEnums.ACTION_BLUETOOTH_DEVICE_DETAILS_ITEM_SHOWN, 252 0, 253 "DEVICE_SETTING_${DeviceSettingId.DEVICE_SETTING_ID_ANC}", 1 254 ) 255 } 256 } 257 getDisplayedPreferencesnull258 private fun getDisplayedPreferences(): List<Preference> { 259 val prefs = mutableListOf<Preference>() 260 for (i in 0..<fragment.preferenceScreen.preferenceCount) { 261 prefs.add(fragment.preferenceScreen.getPreference(i)) 262 } 263 return prefs 264 } 265 266 class TestFragment(context: Context) : DashboardFragment() { 267 private val mPreferenceManager: PreferenceManager = PreferenceManager(context) 268 269 init { 270 mPreferenceManager.setPreferences(mPreferenceManager.createPreferenceScreen(context)) 271 } 272 getPreferenceScreenResIdnull273 public override fun getPreferenceScreenResId(): Int = 0 274 275 override fun getLogTag(): String = "TestLogTag" 276 277 override fun getPreferenceScreen(): PreferenceScreen { 278 return mPreferenceManager.preferenceScreen 279 } 280 getMetricsCategorynull281 override fun getMetricsCategory(): Int = 0 282 283 override fun getPreferenceManager(): PreferenceManager { 284 return mPreferenceManager 285 } 286 } 287 288 private companion object {} 289 } 290