Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 1fbc702c authored by William Escande's avatar William Escande
Browse files

SystemServer: AutoOn: Timer can pause and resume

Some behavior may require timer to not be running (like satellite mode…)

Bug: 323060869
Bug: 316946334
Test: atest ServiceBluetoothRoboTests
Change-Id: Ibbf0a80f905244ff8a4f9b8462af6f7deb12ae64
parent 2912f2d5
Loading
Loading
Loading
Loading
+39 −4
Original line number Diff line number Diff line
@@ -54,7 +54,12 @@ public fun resetAutoOnTimerForUser(
        return
    }

    timer = Timer.start(looper, callback_on)
    timer = Timer.start(looper, context, callback_on)
}

public fun pause() {
    timer?.pause()
    timer = null
}

public fun notifyBluetoothOn(resolver: ContentResolver) {
@@ -104,6 +109,7 @@ public fun setUserEnabled(
internal class Timer
private constructor(
    looper: Looper,
    private val context: Context,
    callback_on: () -> Unit,
    private val now: LocalDateTime,
    private val target: LocalDateTime,
@@ -112,6 +118,7 @@ private constructor(
    private val handler = Handler(looper)

    init {
        writeDateToStorage(target, context.contentResolver)
        handler.postDelayed(
            {
                Log.i(TAG, "[${this}]: Bluetooth restarting now")
@@ -126,14 +133,35 @@ private constructor(
    }

    companion object {
        @VisibleForTesting internal val STORAGE_KEY = "bluetooth_internal_automatic_turn_on_timer"

        private fun writeDateToStorage(date: LocalDateTime, resolver: ContentResolver): Boolean {
            return Settings.Secure.putString(resolver, STORAGE_KEY, date.toString())
        }

        private fun getDateFromStorage(resolver: ContentResolver): LocalDateTime? {
            val date = Settings.Secure.getString(resolver, STORAGE_KEY)
            return date?.let { LocalDateTime.parse(it) }
        }

        fun start(looper: Looper, callback_on: () -> Unit): Timer {
        private fun resetStorage(resolver: ContentResolver) {
            Settings.Secure.putString(resolver, STORAGE_KEY, null)
        }

        fun start(looper: Looper, context: Context, callback_on: () -> Unit): Timer? {
            val now = LocalDateTime.now()
            val target = nextTimeout(now)
            val target = getDateFromStorage(context.contentResolver) ?: nextTimeout(now)
            val timeToSleep =
                now.until(target, ChronoUnit.NANOS).toDuration(DurationUnit.NANOSECONDS)

            return Timer(looper, callback_on, now, target, timeToSleep)
            if (timeToSleep.isNegative()) {
                Log.i(TAG, "Starting now (${now}) as it was scheduled for ${target}")
                callback_on()
                resetStorage(context.contentResolver)
                return null
            }

            return Timer(looper, context, callback_on, now, target, timeToSleep)
        }

        /** Return a LocalDateTime for tomorrow 5 am */
@@ -141,11 +169,18 @@ private constructor(
            LocalDateTime.of(now.toLocalDate(), LocalTime.of(5, 0)).plusDays(1)
    }

    /** Save timer to storage and stop it */
    internal fun pause() {
        Log.i(TAG, "[${this}]: Pausing timer")
        handler.removeCallbacksAndMessages(null)
    }

    /** Stop timer and reset storage */
    @VisibleForTesting
    internal fun cancel() {
        Log.i(TAG, "[${this}]: Cancelling timer")
        handler.removeCallbacksAndMessages(null)
        resetStorage(context.contentResolver)
    }

    override fun toString() = "Timer scheduled ${now} for target=${target} (=${timeToSleep} delay)."
+80 −0
Original line number Diff line number Diff line
@@ -22,15 +22,19 @@ import android.provider.Settings
import androidx.test.core.app.ApplicationProvider
import com.android.server.bluetooth.BluetoothAdapterState
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.isUserEnabled
import com.android.server.bluetooth.isUserSupported
import com.android.server.bluetooth.notifyBluetoothOn
import com.android.server.bluetooth.pause
import com.android.server.bluetooth.resetAutoOnTimerForUser
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
@@ -48,6 +52,8 @@ class AutoOnFeatureTest {
    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

@@ -66,6 +72,7 @@ class AutoOnFeatureTest {
        callback_count = 0
        timer?.cancel()
        timer = null
        restoreSavedTimer()
    }

    private fun setupTimer() {
@@ -91,6 +98,23 @@ class AutoOnFeatureTest {
        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++
    }
@@ -223,4 +247,60 @@ class AutoOnFeatureTest {

        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()
    }
}