1 /* <lambda>null2 * Copyright 2023 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 package com.android.server.bluetooth.airplane.test 17 18 import android.app.ActivityManager 19 import android.bluetooth.BluetoothAdapter 20 import android.content.ContentResolver 21 import android.content.Context 22 import android.content.res.Resources 23 import android.os.Looper 24 import android.os.UserHandle 25 import android.platform.test.flag.junit.FlagsParameterization 26 import android.platform.test.flag.junit.SetFlagsRule 27 import android.provider.Settings 28 import androidx.test.core.app.ApplicationProvider 29 import com.android.bluetooth.flags.Flags 30 import com.android.server.bluetooth.BluetoothAdapterState 31 import com.android.server.bluetooth.Log 32 import com.android.server.bluetooth.airplane.APM_BT_ENABLED_NOTIFICATION 33 import com.android.server.bluetooth.airplane.APM_BT_NOTIFICATION 34 import com.android.server.bluetooth.airplane.APM_ENHANCEMENT 35 import com.android.server.bluetooth.airplane.APM_USER_TOGGLED_BLUETOOTH 36 import com.android.server.bluetooth.airplane.APM_WIFI_BT_NOTIFICATION 37 import com.android.server.bluetooth.airplane.BLUETOOTH_APM_STATE 38 import com.android.server.bluetooth.airplane.WIFI_APM_STATE 39 import com.android.server.bluetooth.airplane.initialize 40 import com.android.server.bluetooth.airplane.isOn 41 import com.android.server.bluetooth.airplane.isOnOverrode 42 import com.android.server.bluetooth.airplane.notifyUserToggledBluetooth 43 import com.android.server.bluetooth.test.disableMode 44 import com.android.server.bluetooth.test.disableSensitive 45 import com.android.server.bluetooth.test.enableMode 46 import com.android.server.bluetooth.test.enableSensitive 47 import com.google.common.truth.Truth.assertThat 48 import kotlin.time.Duration.Companion.minutes 49 import kotlin.time.TestTimeSource 50 import kotlin.time.TimeSource 51 import org.junit.Before 52 import org.junit.Rule 53 import org.junit.Test 54 import org.junit.rules.TestName 55 import org.junit.runner.RunWith 56 import org.robolectric.ParameterizedRobolectricTestRunner 57 import org.robolectric.ParameterizedRobolectricTestRunner.Parameters 58 import org.robolectric.shadows.ShadowToast 59 60 @RunWith(ParameterizedRobolectricTestRunner::class) 61 @kotlin.time.ExperimentalTime 62 class ModeListenerTest(flags: FlagsParameterization) { 63 companion object { 64 @JvmStatic 65 @Parameters(name = "{0}") 66 fun getParams() = 67 FlagsParameterization.allCombinationsOf(Flags.FLAG_GET_STATE_FROM_SYSTEM_SERVER) 68 69 internal fun setupAirplaneModeToOn( 70 resolver: ContentResolver, 71 looper: Looper, 72 user: () -> Context, 73 enableEnhancedMode: Boolean 74 ) { 75 enableSensitive(resolver, looper, Settings.Global.AIRPLANE_MODE_RADIOS) 76 enableMode(resolver, looper, Settings.Global.AIRPLANE_MODE_ON) 77 val mode: (m: Boolean) -> Unit = { _: Boolean -> } 78 val notif: (m: String) -> Unit = { _: String -> } 79 val media: () -> Boolean = { -> false } 80 if (enableEnhancedMode) { 81 Settings.Secure.putInt(resolver, APM_USER_TOGGLED_BLUETOOTH, 1) 82 } 83 84 initialize( 85 looper, 86 resolver, 87 BluetoothAdapterState(), 88 mode, 89 notif, 90 media, 91 user, 92 TimeSource.Monotonic, 93 ) 94 } 95 96 internal fun setupAirplaneModeToOff(resolver: ContentResolver, looper: Looper) { 97 disableSensitive(resolver, looper, Settings.Global.AIRPLANE_MODE_RADIOS) 98 disableMode(resolver, looper, Settings.Global.AIRPLANE_MODE_ON) 99 } 100 } 101 102 @get:Rule val testName = TestName() 103 @get:Rule val setFlagsRule = SetFlagsRule() 104 105 init { 106 setFlagsRule.setFlagsParameterization(flags) 107 } 108 109 private val looper: Looper = Looper.getMainLooper() 110 private val state = BluetoothAdapterState() 111 private val mContext = ApplicationProvider.getApplicationContext<Context>() 112 private val resolver: ContentResolver = mContext.contentResolver 113 114 private val userContext = 115 mContext.createContextAsUser(UserHandle.of(ActivityManager.getCurrentUser()), 0) 116 117 private var isMediaProfileConnected = false 118 private lateinit var mode: ArrayList<Boolean> 119 private lateinit var notification: ArrayList<String> 120 121 @Before 122 public fun setup() { 123 Log.i("AirplaneModeListenerTest", "\t--> setup of " + testName.getMethodName()) 124 125 // Most test will expect the system to be sensitive + off 126 enableSensitive() 127 disableMode() 128 129 isMediaProfileConnected = false 130 mode = ArrayList() 131 notification = ArrayList() 132 } 133 134 private fun initializeAirplane() { 135 initialize( 136 looper, 137 resolver, 138 state, 139 this::callback, 140 this::notificationCallback, 141 this::mediaCallback, 142 this::userCallback, 143 TimeSource.Monotonic, 144 ) 145 } 146 147 private fun enableSensitive() { 148 enableSensitive(resolver, looper, Settings.Global.AIRPLANE_MODE_RADIOS) 149 } 150 151 private fun disableSensitive() { 152 disableSensitive(resolver, looper, Settings.Global.AIRPLANE_MODE_RADIOS) 153 } 154 155 private fun disableMode() { 156 disableMode(resolver, looper, Settings.Global.AIRPLANE_MODE_ON) 157 } 158 159 private fun enableMode() { 160 enableMode(resolver, looper, Settings.Global.AIRPLANE_MODE_ON) 161 } 162 163 private fun callback(newMode: Boolean) = mode.add(newMode) 164 165 private fun notificationCallback(state: String) = notification.add(state) 166 167 private fun mediaCallback() = isMediaProfileConnected 168 169 private fun userCallback() = userContext 170 171 @Test 172 fun initialize_whenNullSensitive_isOff() { 173 Settings.Global.putString(resolver, Settings.Global.AIRPLANE_MODE_RADIOS, null) 174 enableMode() 175 176 initializeAirplane() 177 178 assertThat(isOn).isFalse() 179 assertThat(isOnOverrode).isFalse() 180 assertThat(mode).isEmpty() 181 } 182 183 @Test 184 fun initialize_whenNotSensitive_isOff() { 185 disableSensitive() 186 enableMode() 187 188 initializeAirplane() 189 190 assertThat(isOn).isFalse() 191 assertThat(isOnOverrode).isFalse() 192 assertThat(mode).isEmpty() 193 } 194 195 @Test 196 fun enable_whenNotSensitive_isOff() { 197 disableSensitive() 198 disableMode() 199 200 initializeAirplane() 201 202 enableMode() 203 204 assertThat(isOn).isFalse() 205 assertThat(isOnOverrode).isFalse() 206 assertThat(mode).isEmpty() 207 } 208 209 @Test 210 fun initialize_whenSensitive_isOff() { 211 initializeAirplane() 212 213 assertThat(isOn).isFalse() 214 assertThat(isOnOverrode).isFalse() 215 assertThat(mode).isEmpty() 216 } 217 218 @Test 219 fun initialize_whenSensitive_isOnOverrode() { 220 enableSensitive() 221 enableMode() 222 223 initializeAirplane() 224 225 assertThat(isOn).isTrue() 226 assertThat(isOnOverrode).isTrue() 227 assertThat(mode).isEmpty() 228 } 229 230 @Test 231 fun initialize_whenApmToggled_isOnOverrode() { 232 enableSensitive() 233 enableMode() 234 Settings.Secure.putInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 1) 235 Settings.Secure.putInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 1) 236 237 initializeAirplane() 238 239 assertThat(isOn).isTrue() 240 assertThat(isOnOverrode).isFalse() 241 assertThat(mode).isEmpty() 242 } 243 244 @Test 245 fun toggleSensitive_whenEnabled_isOnOverrode() { 246 enableSensitive() 247 enableMode() 248 249 initializeAirplane() 250 251 disableSensitive() 252 enableSensitive() 253 254 assertThat(isOnOverrode).isTrue() 255 assertThat(mode).containsExactly(false, true) 256 } 257 258 @Test 259 fun toggleEnable_whenSensitive_isOffOnOff() { 260 initializeAirplane() 261 262 enableMode() 263 disableMode() 264 265 assertThat(isOnOverrode).isFalse() 266 assertThat(mode).containsExactly(true, false) 267 } 268 269 @Test 270 fun disable_whenDisabled_discardUpdate() { 271 initializeAirplane() 272 273 disableMode() 274 275 assertThat(isOnOverrode).isFalse() 276 assertThat(mode).isEmpty() 277 } 278 279 @Test 280 fun disable_whenBluetoothOn_discardUpdate() { 281 initializeAirplane() 282 enableMode() 283 284 state.set(BluetoothAdapter.STATE_ON) 285 disableMode() 286 287 assertThat(isOnOverrode).isFalse() 288 assertThat(mode).containsExactly(true) 289 } 290 291 @Test 292 fun enabled_whenEnabled_discardOnChange() { 293 enableSensitive() 294 enableMode() 295 296 initializeAirplane() 297 298 enableMode() 299 300 assertThat(isOnOverrode).isTrue() 301 assertThat(mode).isEmpty() 302 } 303 304 @Test 305 fun changeContent_whenDisabled_discard() { 306 initializeAirplane() 307 308 disableSensitive() 309 enableMode() 310 311 assertThat(isOnOverrode).isFalse() 312 // As opposed to the bare RadioModeListener, similar consecutive event are discarded 313 assertThat(mode).isEmpty() 314 } 315 316 @Test 317 fun triggerOverride_whenNoOverride_turnOff() { 318 initializeAirplane() 319 320 state.set(BluetoothAdapter.STATE_ON) 321 322 enableMode() 323 324 assertThat(isOnOverrode).isTrue() 325 assertThat(mode).containsExactly(true) 326 assertThat(ShadowToast.shownToastCount()).isEqualTo(0) 327 } 328 329 @Test 330 fun triggerOverride_whenMedia_staysOn() { 331 initializeAirplane() 332 333 state.set(BluetoothAdapter.STATE_ON) 334 isMediaProfileConnected = true 335 336 enableMode() 337 338 assertThat(isOnOverrode).isFalse() 339 assertThat(mode).isEmpty() 340 341 assertThat(ShadowToast.shownToastCount()).isEqualTo(1) 342 assertThat(ShadowToast.getTextOfLatestToast()) 343 .isEqualTo( 344 mContext.getString( 345 Resources.getSystem() 346 .getIdentifier("bluetooth_airplane_mode_toast", "string", "android") 347 ) 348 ) 349 } 350 351 @Test 352 fun triggerOverride_whenApmEnhancementNotTrigger_turnOff() { 353 initializeAirplane() 354 355 state.set(BluetoothAdapter.STATE_ON) 356 Settings.Global.putInt(resolver, APM_ENHANCEMENT, 0) 357 358 enableMode() 359 360 assertThat(isOnOverrode).isTrue() 361 assertThat(isOn).isTrue() 362 assertThat(mode).containsExactly(true) 363 } 364 365 @Test 366 fun triggerOverride_whenApmEnhancementNotTriggerButMedia_staysOn() { 367 initializeAirplane() 368 369 state.set(BluetoothAdapter.STATE_ON) 370 Settings.Global.putInt(resolver, APM_ENHANCEMENT, 0) 371 isMediaProfileConnected = true 372 373 enableMode() 374 375 assertThat(isOnOverrode).isFalse() 376 assertThat(isOn).isTrue() 377 assertThat(mode).isEmpty() 378 } 379 380 @Test 381 fun triggerOverride_whenApmEnhancementWasToggled_turnOff() { 382 initializeAirplane() 383 384 state.set(BluetoothAdapter.STATE_ON) 385 Settings.Secure.putInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 1) 386 387 enableMode() 388 389 assertThat(isOnOverrode).isTrue() 390 assertThat(isOn).isTrue() 391 assertThat(mode).containsExactly(true) 392 } 393 394 @Test 395 fun triggerOverride_whenApmEnhancementWasToggled_staysOnWithBtNotification() { 396 initializeAirplane() 397 398 state.set(BluetoothAdapter.STATE_ON) 399 Settings.Secure.putInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 1) 400 Settings.Secure.putInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 1) 401 402 enableMode() 403 404 assertThat(isOnOverrode).isFalse() 405 assertThat(isOn).isTrue() 406 assertThat(mode).isEmpty() 407 assertThat(notification).containsExactly(APM_BT_NOTIFICATION) 408 } 409 410 @Test 411 fun triggerOverride_whenApmEnhancementWasToggledAndWifiOn_staysOnWithBtWifiNotification() { 412 initializeAirplane() 413 414 state.set(BluetoothAdapter.STATE_ON) 415 Settings.Secure.putInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 1) 416 Settings.Secure.putInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 1) 417 418 Settings.Global.putInt(resolver, Settings.Global.WIFI_ON, 1) 419 Settings.Secure.putInt(userContext.contentResolver, WIFI_APM_STATE, 1) 420 421 enableMode() 422 423 assertThat(isOnOverrode).isFalse() 424 assertThat(mode).isEmpty() 425 assertThat(notification).containsExactly(APM_WIFI_BT_NOTIFICATION) 426 } 427 428 @Test 429 fun triggerOverride_whenApmEnhancementWasToggledAndWifiNotOn_staysOnWithBtNotification() { 430 initializeAirplane() 431 432 state.set(BluetoothAdapter.STATE_ON) 433 Settings.Secure.putInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 1) 434 Settings.Secure.putInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 1) 435 436 Settings.Global.putInt(resolver, Settings.Global.WIFI_ON, 1) 437 438 enableMode() 439 440 assertThat(isOnOverrode).isFalse() 441 assertThat(mode).isEmpty() 442 assertThat(notification).containsExactly(APM_BT_NOTIFICATION) 443 } 444 445 @Test 446 fun showToast_inLoop_stopNotifyWhenMaxToastReached() { 447 initializeAirplane() 448 449 state.set(BluetoothAdapter.STATE_ON) 450 isMediaProfileConnected = true 451 452 repeat(30) { 453 enableMode() 454 disableMode() 455 } 456 457 assertThat(isOnOverrode).isFalse() 458 assertThat(mode).isEmpty() 459 assertThat(notification).isEmpty() 460 461 assertThat(ShadowToast.shownToastCount()) 462 .isEqualTo(com.android.server.bluetooth.airplane.ToastNotification.MAX_TOAST_COUNT) 463 } 464 465 @Test 466 fun userToggleBluetooth_whenNoSession_nothingHappen() { 467 initializeAirplane() 468 469 notifyUserToggledBluetooth(resolver, userContext, false) 470 471 assertThat(isOnOverrode).isFalse() 472 assertThat(mode).isEmpty() 473 assertThat(notification).isEmpty() 474 assertThat(ShadowToast.shownToastCount()).isEqualTo(0) 475 } 476 477 @Test 478 fun userToggleBluetooth_whenSessionButNoApm_noNotificationAndNoSettingSave() { 479 initializeAirplane() 480 Settings.Global.putInt(resolver, APM_ENHANCEMENT, 0) 481 482 enableMode() 483 notifyUserToggledBluetooth(resolver, userContext, true) 484 485 assertThat(isOnOverrode).isTrue() 486 assertThat(mode).containsExactly(true) 487 assertThat(notification).isEmpty() 488 assertThat(ShadowToast.shownToastCount()).isEqualTo(0) 489 assertThat(Settings.Secure.getInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 0)) 490 .isEqualTo(0) 491 assertThat( 492 Settings.Secure.getInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 0) 493 ) 494 .isEqualTo(0) 495 } 496 497 @Test 498 fun userToggleBluetooth_whenSession_noNotificationAndSettingSaved() { 499 initializeAirplane() 500 501 enableMode() 502 notifyUserToggledBluetooth(resolver, userContext, false) 503 504 assertThat(isOnOverrode).isTrue() 505 assertThat(mode).containsExactly(true) 506 assertThat(notification).isEmpty() 507 assertThat(ShadowToast.shownToastCount()).isEqualTo(0) 508 assertThat(Settings.Secure.getInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 0)) 509 .isEqualTo(0) 510 assertThat( 511 Settings.Secure.getInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 0) 512 ) 513 .isEqualTo(1) 514 } 515 516 @Test 517 fun userToggleBluetooth_whenSession_notificationAndSettingSaved() { 518 initializeAirplane() 519 520 enableMode() 521 notifyUserToggledBluetooth(resolver, userContext, true) 522 523 assertThat(isOnOverrode).isTrue() 524 assertThat(mode).containsExactly(true) 525 assertThat(notification).containsExactly(APM_BT_ENABLED_NOTIFICATION) 526 assertThat(ShadowToast.shownToastCount()).isEqualTo(0) 527 assertThat(Settings.Secure.getInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 0)) 528 .isEqualTo(1) 529 assertThat( 530 Settings.Secure.getInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 0) 531 ) 532 .isEqualTo(1) 533 } 534 535 @Test 536 fun userToggleTwiceBluetooth_whenSession_notificationAndSettingSaved() { 537 initializeAirplane() 538 539 enableMode() 540 notifyUserToggledBluetooth(resolver, userContext, true) 541 notifyUserToggledBluetooth(resolver, userContext, false) 542 543 assertThat(isOnOverrode).isTrue() 544 assertThat(mode).containsExactly(true) 545 assertThat(notification).containsExactly(APM_BT_ENABLED_NOTIFICATION) 546 assertThat(ShadowToast.shownToastCount()).isEqualTo(0) 547 assertThat(Settings.Secure.getInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 0)) 548 .isEqualTo(0) 549 assertThat( 550 Settings.Secure.getInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 0) 551 ) 552 .isEqualTo(1) 553 } 554 555 @Test 556 fun userToggleBluetooth_whenSessionButNoApm_noNotificationAndNoSettingSave_skipTime() { 557 val timesource = TestTimeSource() 558 initialize( 559 looper, 560 resolver, 561 state, 562 this::callback, 563 this::notificationCallback, 564 this::mediaCallback, 565 this::userCallback, 566 timesource, 567 ) 568 Settings.Global.putInt(resolver, APM_ENHANCEMENT, 0) 569 570 enableMode() 571 timesource += 2.minutes 572 notifyUserToggledBluetooth(resolver, userContext, true) 573 574 assertThat(isOnOverrode).isTrue() 575 assertThat(mode).containsExactly(true) 576 assertThat(notification).isEmpty() 577 assertThat(ShadowToast.shownToastCount()).isEqualTo(0) 578 assertThat(Settings.Secure.getInt(userContext.contentResolver, BLUETOOTH_APM_STATE, 0)) 579 .isEqualTo(0) 580 assertThat( 581 Settings.Secure.getInt(userContext.contentResolver, APM_USER_TOGGLED_BLUETOOTH, 0) 582 ) 583 .isEqualTo(0) 584 } 585 586 @Test 587 fun initialize_firstTime_apmSettingIsSet() { 588 initializeAirplane() 589 assertThat(Settings.Global.getInt(resolver, APM_ENHANCEMENT, 0)).isEqualTo(1) 590 } 591 592 @Test 593 fun initialize_secondTime_apmSettingIsNotOverride() { 594 val settingValue = 42 595 Settings.Global.putInt(resolver, APM_ENHANCEMENT, settingValue) 596 597 initializeAirplane() 598 599 assertThat(Settings.Global.getInt(resolver, APM_ENHANCEMENT, 0)).isEqualTo(settingValue) 600 } 601 } 602