blob: b18314ec52c5a0910029cb2533c0f841a6139b7a [file] [log] [blame]
/*
* Copyright 2024 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.server.bluetooth.test
import android.bluetooth.BluetoothAdapter
import android.content.Context
import android.content.Intent
import android.os.Looper
import android.provider.Settings
import androidx.test.core.app.ApplicationProvider
import com.android.server.bluetooth.BluetoothAdapterState
import com.android.server.bluetooth.HiddenApiListener
import com.android.server.bluetooth.Log
import com.android.server.bluetooth.Timer
import com.android.server.bluetooth.USER_SETTINGS_KEY
import com.android.server.bluetooth.airplane.isOn as isAirplaneModeOn
import com.android.server.bluetooth.airplane.test.ModeListenerTest as AirplaneListener
import com.android.server.bluetooth.isUserEnabled
import com.android.server.bluetooth.isUserSupported
import com.android.server.bluetooth.notifyBluetoothOn
import com.android.server.bluetooth.pause
import com.android.server.bluetooth.registerHiddenApiListener
import com.android.server.bluetooth.resetAutoOnTimerForUser
import com.android.server.bluetooth.satellite.isOn as isSatelliteModeOn
import com.android.server.bluetooth.satellite.test.ModeListenerTest as SatelliteListener
import com.android.server.bluetooth.setUserEnabled
import com.android.server.bluetooth.timer
import com.google.common.truth.Expect
import com.google.common.truth.Truth.assertThat
import java.time.LocalDateTime
import java.time.LocalTime
import kotlin.test.assertFailsWith
import org.junit.After
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.rules.TestName
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.Shadows.shadowOf
@RunWith(RobolectricTestRunner::class)
@kotlinx.coroutines.ExperimentalCoroutinesApi
class AutoOnFeatureTest {
private val SETTING_URI = Settings.Secure.getUriFor(USER_SETTINGS_KEY)
private val looper = Looper.getMainLooper()
private val state = BluetoothAdapterState()
private val context = ApplicationProvider.getApplicationContext<Context>()
private val resolver = context.contentResolver
private val now = LocalDateTime.now()
private val timerTarget = LocalDateTime.of(now.toLocalDate(), LocalTime.of(5, 0)).plusDays(1)
private var callback_count = 0
@JvmField @Rule val testName = TestName()
@JvmField @Rule val expect = Expect.create()
@Before
fun setUp() {
Log.i("AutoOnFeatureTest", "\t--> setUp(${testName.getMethodName()})")
enableUserSettings()
}
@After
fun tearDown() {
HiddenApiListener.listener?.let { it.remove() }
HiddenApiListener.listener = null
callback_count = 0
timer?.cancel()
timer = null
restoreSavedTimer()
}
private fun setupTimer() {
resetAutoOnTimerForUser(looper, context, state, this::callback_on)
}
private fun setUserEnabled(status: Boolean) {
setUserEnabled(looper, context, state, status, this::callback_on)
}
private fun enableUserSettings() {
Settings.Secure.putInt(resolver, USER_SETTINGS_KEY, 1)
shadowOf(looper).idle()
}
private fun disableUserSettings() {
Settings.Secure.putInt(resolver, USER_SETTINGS_KEY, 0)
shadowOf(looper).idle()
}
private fun restoreSettings() {
Settings.Secure.putString(resolver, USER_SETTINGS_KEY, null)
shadowOf(looper).idle()
}
private fun restoreSavedTimer() {
Settings.Secure.putString(resolver, Timer.STORAGE_KEY, null)
shadowOf(looper).idle()
}
private fun expectStorageTime() {
shadowOf(looper).idle()
expect
.that(Settings.Secure.getString(resolver, Timer.STORAGE_KEY))
.isEqualTo(timerTarget.toString())
}
private fun expectNoStorageTime() {
shadowOf(looper).idle()
expect.that(Settings.Secure.getString(resolver, Timer.STORAGE_KEY)).isNull()
}
private fun callback_on() {
callback_count++
}
@Test
fun setupTimer_whenItWasNeverUsed_isNotScheduled() {
restoreSettings()
setupTimer()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
}
@Test
fun setupTimer_whenBtOn_isNotScheduled() {
state.set(BluetoothAdapter.STATE_ON)
setupTimer()
state.set(BluetoothAdapter.STATE_OFF)
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
}
@Test
fun setupTimer_whenBtOffAndUserEnabled_isScheduled() {
setupTimer()
expect.that(timer).isNotNull()
}
@Test
fun setupTimer_whenBtOffAndUserEnabled_triggerCallback() {
setupTimer()
shadowOf(looper).runToEndOfTasks()
expect.that(callback_count).isEqualTo(1)
expect.that(timer).isNull()
}
@Test
fun setupTimer_whenAlreadySetup_triggerCallbackOnce() {
setupTimer()
setupTimer()
setupTimer()
shadowOf(looper).runToEndOfTasks()
expect.that(callback_count).isEqualTo(1)
expect.that(timer).isNull()
}
@Test
fun notifyBluetoothOn_whenNoTimer_noCrash() {
notifyBluetoothOn(resolver)
assertThat(timer).isNull()
}
@Test
fun notifyBluetoothOn_whenTimer_isNotScheduled() {
setupTimer()
notifyBluetoothOn(resolver)
shadowOf(looper).runToEndOfTasks()
expect.that(callback_count).isEqualTo(0)
expect.that(timer).isNull()
}
@Test
fun notifyBluetoothOn_whenItWasNeverUsed_enableSettings() {
restoreSettings()
notifyBluetoothOn(resolver)
assertThat(isUserSupported(resolver)).isTrue()
}
@Test
fun apiIsUserEnable_whenItWasNeverUsed_throwException() {
restoreSettings()
assertFailsWith<IllegalStateException> { isUserEnabled(context) }
}
@Test
fun apiSetUserEnabled_whenItWasNeverUsed_throwException() {
restoreSettings()
assertFailsWith<IllegalStateException> { setUserEnabled(true) }
}
@Test
fun apiIsUserEnable_whenEnabled_isTrue() {
assertThat(isUserEnabled(context)).isTrue()
}
@Test
fun apiIsUserEnable_whenDisabled_isFalse() {
disableUserSettings()
assertThat(isUserEnabled(context)).isFalse()
}
@Test
fun apiSetUserEnableToFalse_whenScheduled_isNotScheduled() {
setupTimer()
setUserEnabled(false)
assertThat(isUserEnabled(context)).isFalse()
assertThat(callback_count).isEqualTo(0)
assertThat(timer).isNull()
}
@Test
fun apiSetUserEnableToFalse_whenIdle_isNotScheduled() {
setUserEnabled(false)
assertThat(isUserEnabled(context)).isFalse()
assertThat(callback_count).isEqualTo(0)
assertThat(timer).isNull()
}
@Test
fun apiSetUserEnableToTrue_whenIdle_canSchedule() {
disableUserSettings()
setUserEnabled(true)
setupTimer()
assertThat(timer).isNotNull()
}
@Test
fun pause_whenIdle_noTimeSave() {
pause()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
@Test
fun pause_whenTimer_timeIsSaved() {
setupTimer()
pause()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectStorageTime()
}
@Test
fun setupTimer_whenIdle_timeIsSave() {
setupTimer()
expect.that(timer).isNotNull()
expect.that(callback_count).isEqualTo(0)
expectStorageTime()
}
@Test
fun setupTimer_whenPaused_isResumed() {
val now = LocalDateTime.now()
val alarmTime = LocalDateTime.of(now.toLocalDate(), LocalTime.of(5, 0)).plusDays(1)
Settings.Secure.putString(resolver, Timer.STORAGE_KEY, alarmTime.toString())
shadowOf(looper).idle()
setupTimer()
expect.that(timer).isNotNull()
expect.that(callback_count).isEqualTo(0)
expectStorageTime()
}
@Test
fun setupTimer_whenSaveTimerIsExpired_triggerCallback() {
val pastTime = timerTarget.minusDays(3)
Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
shadowOf(looper).idle()
setupTimer()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(1)
expectNoStorageTime()
}
@Test
fun setupTimer_whenSatelliteIsOn_isNotScheduled() {
val satelliteCallback: (m: Boolean) -> Unit = { _: Boolean -> }
SatelliteListener.setupSatelliteModeToOn(resolver, looper, satelliteCallback)
assertThat(isSatelliteModeOn).isTrue()
setupTimer()
SatelliteListener.setupSatelliteModeToOff(resolver, looper)
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
@Test
fun updateTimezone_whenTimerSchedule_isReScheduled() {
setupTimer()
// Fake storaged time so when receiving the intent, the test think we jump in the futur
val pastTime = timerTarget.minusDays(3)
Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
context.sendBroadcast(Intent(Intent.ACTION_TIMEZONE_CHANGED))
shadowOf(looper).idle()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(1)
expectNoStorageTime()
}
@Test
fun updateTime_whenTimerSchedule_isReScheduled() {
setupTimer()
// Fake stored time so when receiving the intent, the test think we jumped in the future
val pastTime = timerTarget.minusDays(3)
Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
context.sendBroadcast(Intent(Intent.ACTION_TIME_CHANGED))
shadowOf(looper).idle()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(1)
expectNoStorageTime()
}
@Test
fun updateDate_whenTimerSchedule_isReScheduled() {
setupTimer()
// Fake stored time so when receiving the intent, the test think we jumped in the future
val pastTime = timerTarget.minusDays(3)
Settings.Secure.putString(resolver, Timer.STORAGE_KEY, pastTime.toString())
context.sendBroadcast(Intent(Intent.ACTION_DATE_CHANGED))
shadowOf(looper).idle()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(1)
expectNoStorageTime()
}
@Test
@kotlin.time.ExperimentalTime
fun setupTimer_whenLegacyAirplaneIsOn_isNotSchedule() {
val userCallback: () -> Context = { -> context }
AirplaneListener.setupAirplaneModeToOn(resolver, looper, userCallback, false)
assertThat(isAirplaneModeOn).isTrue()
setupTimer()
AirplaneListener.setupAirplaneModeToOff(resolver, looper)
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
@Test
@kotlin.time.ExperimentalTime
fun setupTimer_whenApmAirplaneIsOn_isSchedule() {
val userCallback: () -> Context = { -> context }
AirplaneListener.setupAirplaneModeToOn(resolver, looper, userCallback, true)
assertThat(isAirplaneModeOn).isTrue()
setupTimer()
AirplaneListener.setupAirplaneModeToOff(resolver, looper)
expect.that(timer).isNotNull()
expect.that(callback_count).isEqualTo(0)
expectStorageTime()
}
@Test
fun registerHiddenListener_whenNothing_isRegistered() {
registerHiddenApiListener(looper, context, state, this::callback_on)
assertThat(HiddenApiListener.listener).isNotNull()
}
@Test
fun unregisterHiddenListener_whenRegistered_isNotRegistered() {
registerHiddenApiListener(looper, context, state, this::callback_on)
HiddenApiListener.listener?.let { it.remove() }
assertThat(shadowOf(resolver).getContentObservers(SETTING_URI).size).isEqualTo(0)
}
@Test
fun registerHiddenListener_whenAlreadyRegistered_isRegisteredOnce() {
registerHiddenApiListener(looper, context, state, this::callback_on)
registerHiddenApiListener(looper, context, state, this::callback_on)
expect.that(shadowOf(resolver).getContentObservers(SETTING_URI).size).isEqualTo(1)
expect.that(HiddenApiListener.listener).isNotNull()
}
@Test
fun changeSettingsToDisabled_whenHiddenApiIsRegisteredandNotScheduled_isNotSchedule() {
registerHiddenApiListener(looper, context, state, this::callback_on)
disableUserSettings()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
@Test
fun changeSettingsToDisabled_whenHiddenApiIsRegisteredandScheduled_isNotSchedule() {
setupTimer()
registerHiddenApiListener(looper, context, state, this::callback_on)
disableUserSettings()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
@Test
fun changeSettingsToEnabled_whenHiddenApiIsRegisteredandNotScheduled_isSchedule() {
disableUserSettings()
registerHiddenApiListener(looper, context, state, this::callback_on)
enableUserSettings()
expect.that(timer).isNotNull()
expect.that(callback_count).isEqualTo(0)
expectStorageTime()
}
@Test
fun setSettingsToSameValue_whenHiddenApiIsRegisteredandNotScheduled_isNotSchedule() {
restoreSettings()
registerHiddenApiListener(looper, context, state, this::callback_on)
Settings.Secure.putInt(resolver, USER_SETTINGS_KEY, -1)
shadowOf(looper).idle()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
@Test
fun setSettingsToEnabled_whenHiddenApiIsRegisteredandNotSupported_isNotSchedule() {
restoreSettings()
registerHiddenApiListener(looper, context, state, this::callback_on)
enableUserSettings()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
@Test
fun setSettingsToDisable_whenHiddenApiIsRegisteredandNotSupported_isNotSchedule() {
// Current design will set the feature to enabled, but there is no reason to not support
// having a default value to disabled
restoreSettings()
registerHiddenApiListener(looper, context, state, this::callback_on)
disableUserSettings()
expect.that(timer).isNull()
expect.that(callback_count).isEqualTo(0)
expectNoStorageTime()
}
}