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