1 /*
<lambda>null2  * Copyright 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 package com.android.server.bluetooth.test
17 
18 import android.app.AlarmManager
19 import android.app.Application
20 import android.bluetooth.BluetoothAdapter
21 import android.content.Context
22 import android.content.Intent
23 import android.os.Looper
24 import android.provider.Settings
25 import androidx.test.core.app.ApplicationProvider
26 import androidx.test.ext.truth.content.IntentSubject.assertThat
27 import com.android.server.bluetooth.BluetoothAdapterState
28 import com.android.server.bluetooth.Log
29 import com.android.server.bluetooth.Timer
30 import com.android.server.bluetooth.USER_SETTINGS_KEY
31 import com.android.server.bluetooth.airplane.isOnOverrode as isAirplaneModeOn
32 import com.android.server.bluetooth.airplane.test.ModeListenerTest as AirplaneListener
33 import com.android.server.bluetooth.isUserEnabled
34 import com.android.server.bluetooth.isUserSupported
35 import com.android.server.bluetooth.notifyBluetoothOn
36 import com.android.server.bluetooth.pause
37 import com.android.server.bluetooth.resetAutoOnTimerForUser
38 import com.android.server.bluetooth.satellite.isOn as isSatelliteModeOn
39 import com.android.server.bluetooth.satellite.test.ModeListenerTest as SatelliteListener
40 import com.android.server.bluetooth.setUserEnabled
41 import com.android.server.bluetooth.timer
42 import com.google.common.truth.Expect
43 import com.google.common.truth.Truth.assertThat
44 import java.time.LocalDateTime
45 import java.time.LocalTime
46 import kotlin.test.assertFailsWith
47 import org.junit.After
48 import org.junit.Before
49 import org.junit.Rule
50 import org.junit.Test
51 import org.junit.rules.TestName
52 import org.junit.runner.RunWith
53 import org.robolectric.RobolectricTestRunner
54 import org.robolectric.Shadows.shadowOf
55 
56 @RunWith(RobolectricTestRunner::class)
57 @kotlinx.coroutines.ExperimentalCoroutinesApi
58 class AutoOnFeatureTest {
59     @get:Rule val testName = TestName()
60     @get:Rule val expect = Expect.create()
61 
62     private val looper = Looper.getMainLooper()
63     private val state = BluetoothAdapterState()
64     private val context = ApplicationProvider.getApplicationContext<Context>()
65     private val resolver = context.contentResolver
66     private val now = LocalDateTime.now()
67     private val timerTarget = LocalDateTime.of(now.toLocalDate(), LocalTime.of(5, 0)).plusDays(1)
68 
69     private var callback_count = 0
70 
71     @Before
72     fun setUp() {
73         Log.i("AutoOnFeatureTest", "\t--> setUp(${testName.getMethodName()})")
74 
75         enableUserSettings()
76     }
77 
78     @After
79     fun tearDown() {
80         callback_count = 0
81         timer?.cancel()
82         timer = null
83         restoreSavedTimer()
84     }
85 
86     private fun setupTimer() {
87         resetAutoOnTimerForUser(looper, context, state, this::callback_on)
88     }
89 
90     private fun setUserEnabled(status: Boolean) {
91         setUserEnabled(looper, context, state, status, this::callback_on)
92     }
93 
94     private fun enableUserSettings() {
95         Settings.Secure.putInt(resolver, USER_SETTINGS_KEY, 1)
96         shadowOf(looper).idle()
97     }
98 
99     private fun disableUserSettings() {
100         Settings.Secure.putInt(resolver, USER_SETTINGS_KEY, 0)
101         shadowOf(looper).idle()
102     }
103 
104     private fun restoreSettings() {
105         Settings.Secure.putString(resolver, USER_SETTINGS_KEY, null)
106         shadowOf(looper).idle()
107     }
108 
109     private fun restoreSavedTimer() {
110         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, null)
111         shadowOf(looper).idle()
112     }
113 
114     private fun expectStorageTime() {
115         shadowOf(looper).idle()
116         expect
117             .that(Settings.Secure.getString(resolver, Timer.STORAGE_KEY))
118             .isEqualTo(timerTarget.toString())
119     }
120 
121     private fun expectNoStorageTime() {
122         shadowOf(looper).idle()
123         expect.that(Settings.Secure.getString(resolver, Timer.STORAGE_KEY)).isNull()
124     }
125 
126     private fun callback_on() {
127         callback_count++
128     }
129 
130     @Test
131     fun setupTimer_whenItWasNeverUsed_isNotScheduled() {
132         restoreSettings()
133 
134         setupTimer()
135 
136         expect.that(timer).isNull()
137         expect.that(callback_count).isEqualTo(0)
138     }
139 
140     @Test
141     fun setupTimer_whenBtOn_isNotScheduled() {
142         state.set(BluetoothAdapter.STATE_ON)
143 
144         setupTimer()
145 
146         state.set(BluetoothAdapter.STATE_OFF)
147         expect.that(timer).isNull()
148         expect.that(callback_count).isEqualTo(0)
149     }
150 
151     @Test
152     fun setupTimer_whenBtOffAndUserEnabled_isScheduled() {
153         setupTimer()
154 
155         expect.that(timer).isNotNull()
156     }
157 
158     @Test
159     fun setupTimer_whenBtOffAndUserEnabled_triggerCallback() {
160         setupTimer()
161 
162         val shadowAlarmManager = shadowOf(context.getSystemService(AlarmManager::class.java))
163         shadowAlarmManager.fireAlarm(shadowAlarmManager.peekNextScheduledAlarm())
164 
165         shadowOf(looper).runOneTask()
166 
167         expect.that(callback_count).isEqualTo(1)
168         expect.that(timer).isNull()
169     }
170 
171     @Test
172     fun setupTimer_whenAlreadySetup_triggerCallbackOnce() {
173         setupTimer()
174         setupTimer()
175         setupTimer()
176 
177         val shadowAlarmManager = shadowOf(context.getSystemService(AlarmManager::class.java))
178         shadowAlarmManager.fireAlarm(shadowAlarmManager.peekNextScheduledAlarm())
179 
180         shadowOf(looper).runOneTask()
181 
182         expect.that(callback_count).isEqualTo(1)
183         expect.that(timer).isNull()
184     }
185 
186     @Test
187     fun notifyBluetoothOn_whenNoTimer_noCrash() {
188         notifyBluetoothOn(context)
189 
190         assertThat(timer).isNull()
191     }
192 
193     @Test
194     fun notifyBluetoothOn_whenTimer_isNotScheduled() {
195         setupTimer()
196         notifyBluetoothOn(context)
197 
198         shadowOf(looper).runToEndOfTasks()
199         expect.that(callback_count).isEqualTo(0)
200         expect.that(timer).isNull()
201     }
202 
203     @Test
204     fun notifyBluetoothOn_whenItWasNeverUsed_enableSettings() {
205         restoreSettings()
206 
207         notifyBluetoothOn(context)
208 
209         assertThat(isUserSupported(resolver)).isTrue()
210     }
211 
212     @Test
213     fun notifyBluetoothOn_whenStorage_resetStorage() {
214         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, timerTarget.toString())
215         shadowOf(looper).idle()
216 
217         notifyBluetoothOn(context)
218 
219         expectNoStorageTime()
220     }
221 
222     @Test
223     fun apiIsUserEnable_whenItWasNeverUsed_throwException() {
224         restoreSettings()
225 
226         assertFailsWith<IllegalStateException> { isUserEnabled(context) }
227     }
228 
229     @Test
230     fun apiSetUserEnabled_whenItWasNeverUsed_throwException() {
231         restoreSettings()
232 
233         assertFailsWith<IllegalStateException> { setUserEnabled(true) }
234     }
235 
236     @Test
237     fun apiIsUserEnable_whenEnabled_isTrue() {
238         assertThat(isUserEnabled(context)).isTrue()
239     }
240 
241     @Test
242     fun apiIsUserEnable_whenDisabled_isFalse() {
243         disableUserSettings()
244         assertThat(isUserEnabled(context)).isFalse()
245     }
246 
247     @Test
248     fun apiSetUserEnableToFalse_whenScheduled_isNotScheduled() {
249         setupTimer()
250 
251         setUserEnabled(false)
252 
253         assertThat(isUserEnabled(context)).isFalse()
254         assertThat(callback_count).isEqualTo(0)
255         assertThat(timer).isNull()
256     }
257 
258     @Test
259     fun apiSetUserEnableToFalse_whenIdle_isNotScheduled() {
260         setUserEnabled(false)
261 
262         assertThat(isUserEnabled(context)).isFalse()
263         assertThat(callback_count).isEqualTo(0)
264         assertThat(timer).isNull()
265     }
266 
267     @Test
268     fun apiSetUserEnableToTrue_whenIdle_canSchedule() {
269         disableUserSettings()
270 
271         setUserEnabled(true)
272 
273         assertThat(timer).isNotNull()
274     }
275 
276     @Test
277     fun apiSetUserEnableToggle_whenScheduled_isRescheduled() {
278         val pastTime = timerTarget.minusDays(3)
279         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
280         shadowOf(looper).idle()
281 
282         setUserEnabled(false)
283         expectNoStorageTime()
284 
285         setUserEnabled(true)
286         expectStorageTime()
287 
288         assertThat(timer).isNotNull()
289     }
290 
291     @Test
292     fun apiSetUserEnableToFalse_whenEnabled_broadcastIntent() {
293         setUserEnabled(false)
294 
295         assertThat(shadowOf(context as Application).getBroadcastIntents().get(0)).run {
296             hasAction(BluetoothAdapter.ACTION_AUTO_ON_STATE_CHANGED)
297             hasFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
298             extras()
299                 .integer(BluetoothAdapter.EXTRA_AUTO_ON_STATE)
300                 .isEqualTo(BluetoothAdapter.AUTO_ON_STATE_DISABLED)
301         }
302     }
303 
304     @Test
305     fun apiSetUserEnableToTrue_whenDisabled_broadcastIntent() {
306         disableUserSettings()
307         setUserEnabled(true)
308 
309         assertThat(shadowOf(context as Application).getBroadcastIntents().get(0)).run {
310             hasAction(BluetoothAdapter.ACTION_AUTO_ON_STATE_CHANGED)
311             hasFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY)
312             extras()
313                 .integer(BluetoothAdapter.EXTRA_AUTO_ON_STATE)
314                 .isEqualTo(BluetoothAdapter.AUTO_ON_STATE_ENABLED)
315         }
316     }
317 
318     @Test
319     fun apiSetUserEnableToTrue_whenAlreadyEnabled_doNothing() {
320         setUserEnabled(true)
321 
322         assertThat(shadowOf(context as Application).getBroadcastIntents().size).isEqualTo(0)
323     }
324 
325     @Test
326     fun pause_whenIdle_noTimeSave() {
327         pause()
328 
329         expect.that(timer).isNull()
330         expect.that(callback_count).isEqualTo(0)
331         expectNoStorageTime()
332     }
333 
334     @Test
335     fun pause_whenTimer_timeIsSaved() {
336         setupTimer()
337 
338         pause()
339 
340         expect.that(timer).isNull()
341         expect.that(callback_count).isEqualTo(0)
342         expectStorageTime()
343     }
344 
345     @Test
346     fun setupTimer_whenIdle_timeIsSave() {
347         setupTimer()
348 
349         expect.that(timer).isNotNull()
350         expect.that(callback_count).isEqualTo(0)
351         expectStorageTime()
352     }
353 
354     @Test
355     fun setupTimer_whenPaused_isResumed() {
356         val now = LocalDateTime.now()
357         val alarmTime = LocalDateTime.of(now.toLocalDate(), LocalTime.of(5, 0)).plusDays(1)
358         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, alarmTime.toString())
359         shadowOf(looper).idle()
360 
361         setupTimer()
362 
363         expect.that(timer).isNotNull()
364         expect.that(callback_count).isEqualTo(0)
365         expectStorageTime()
366     }
367 
368     @Test
369     fun setupTimer_whenSaveTimerIsExpired_triggerCallback() {
370         val pastTime = timerTarget.minusDays(3)
371         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
372         shadowOf(looper).idle()
373 
374         setupTimer()
375 
376         expect.that(timer).isNull()
377         expect.that(callback_count).isEqualTo(1)
378         expectNoStorageTime()
379     }
380 
381     @Test
382     fun setupTimer_whenSatelliteIsOn_isNotScheduled() {
383         val satelliteCallback: (m: Boolean) -> Unit = { _: Boolean -> }
384 
385         SatelliteListener.setupSatelliteModeToOn(resolver, looper, satelliteCallback)
386         assertThat(isSatelliteModeOn).isTrue()
387 
388         setupTimer()
389 
390         SatelliteListener.setupSatelliteModeToOff(resolver, looper)
391         expect.that(timer).isNull()
392         expect.that(callback_count).isEqualTo(0)
393         expectNoStorageTime()
394     }
395 
396     @Test
397     fun updateTimezone_whenTimerSchedule_isReScheduled() {
398         setupTimer()
399 
400         // Fake storaged time so when receiving the intent, the test think we jump in the futur
401         val pastTime = timerTarget.minusDays(3)
402         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
403 
404         context.sendBroadcast(Intent(Intent.ACTION_TIMEZONE_CHANGED))
405         shadowOf(looper).idle()
406 
407         expect.that(timer).isNull()
408         expect.that(callback_count).isEqualTo(1)
409         expectNoStorageTime()
410     }
411 
412     @Test
413     fun updateTime_whenTimerSchedule_isReScheduled() {
414         setupTimer()
415 
416         // Fake stored time so when receiving the intent, the test think we jumped in the future
417         val pastTime = timerTarget.minusDays(3)
418         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
419 
420         context.sendBroadcast(Intent(Intent.ACTION_TIME_CHANGED))
421         shadowOf(looper).idle()
422 
423         expect.that(timer).isNull()
424         expect.that(callback_count).isEqualTo(1)
425         expectNoStorageTime()
426     }
427 
428     @Test
429     fun updateDate_whenTimerSchedule_isReScheduled() {
430         setupTimer()
431 
432         // Fake stored time so when receiving the intent, the test think we jumped in the future
433         val pastTime = timerTarget.minusDays(3)
434         Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
435 
436         context.sendBroadcast(Intent(Intent.ACTION_DATE_CHANGED))
437         shadowOf(looper).idle()
438 
439         expect.that(timer).isNull()
440         expect.that(callback_count).isEqualTo(1)
441         expectNoStorageTime()
442     }
443 
444     @Test
445     @kotlin.time.ExperimentalTime
446     fun setupTimer_whenLegacyAirplaneIsOn_isNotSchedule() {
447         val userCallback: () -> Context = { -> context }
448         AirplaneListener.setupAirplaneModeToOn(resolver, looper, userCallback, false)
449         assertThat(isAirplaneModeOn).isTrue()
450 
451         setupTimer()
452 
453         AirplaneListener.setupAirplaneModeToOff(resolver, looper)
454         expect.that(timer).isNull()
455         expect.that(callback_count).isEqualTo(0)
456         expectNoStorageTime()
457     }
458 
459     @Test
460     @kotlin.time.ExperimentalTime
461     fun setupTimer_whenApmAirplaneIsOn_isSchedule() {
462         val userCallback: () -> Context = { -> context }
463         AirplaneListener.setupAirplaneModeToOn(resolver, looper, userCallback, true)
464         assertThat(isAirplaneModeOn).isTrue()
465 
466         setupTimer()
467 
468         AirplaneListener.setupAirplaneModeToOff(resolver, looper)
469         expect.that(timer).isNotNull()
470         expect.that(callback_count).isEqualTo(0)
471         expectStorageTime()
472     }
473 }
474