/*
 * Copyright (C) 2022 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.systemui.stylus

import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
import android.hardware.BatteryState
import android.hardware.input.InputManager
import android.hardware.input.InputSettings
import android.os.Handler
import android.testing.AndroidTestingRunner
import android.view.InputDevice
import androidx.test.filters.SmallTest
import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
import com.android.dx.mockito.inline.extended.ExtendedMockito.never
import com.android.dx.mockito.inline.extended.ExtendedMockito.times
import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
import com.android.dx.mockito.inline.extended.StaticMockitoSession
import com.android.internal.logging.InstanceId
import com.android.internal.logging.UiEventLogger
import com.android.systemui.InstanceIdSequenceFake
import com.android.systemui.SysuiTestCase
import com.android.systemui.flags.FeatureFlags
import com.android.systemui.flags.Flags
import com.android.systemui.util.mockito.any
import com.android.systemui.util.mockito.whenever
import java.util.concurrent.Executor
import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.Mock
import org.mockito.Mockito.clearInvocations
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.verifyNoMoreInteractions
import org.mockito.Mockito.verifyZeroInteractions
import org.mockito.MockitoAnnotations
import org.mockito.quality.Strictness

@RunWith(AndroidTestingRunner::class)
@SmallTest
class StylusManagerTest : SysuiTestCase() {
    @Mock lateinit var inputManager: InputManager
    @Mock lateinit var stylusDevice: InputDevice
    @Mock lateinit var btStylusDevice: InputDevice
    @Mock lateinit var otherDevice: InputDevice
    @Mock lateinit var batteryState: BatteryState
    @Mock lateinit var bluetoothAdapter: BluetoothAdapter
    @Mock lateinit var bluetoothDevice: BluetoothDevice
    @Mock lateinit var handler: Handler
    @Mock lateinit var featureFlags: FeatureFlags
    @Mock lateinit var uiEventLogger: UiEventLogger
    @Mock lateinit var stylusCallback: StylusManager.StylusCallback
    @Mock lateinit var otherStylusCallback: StylusManager.StylusCallback

    private lateinit var mockitoSession: StaticMockitoSession
    private lateinit var stylusManager: StylusManager
    private val instanceIdSequenceFake = InstanceIdSequenceFake(10)

    @Before
    fun setUp() {
        MockitoAnnotations.initMocks(this)
        mockitoSession =
            mockitoSession()
                .mockStatic(InputSettings::class.java)
                .strictness(Strictness.LENIENT)
                .startMocking()

        whenever(handler.post(any())).thenAnswer {
            (it.arguments[0] as Runnable).run()
            true
        }

        stylusManager =
            StylusManager(
                mContext,
                inputManager,
                bluetoothAdapter,
                handler,
                EXECUTOR,
                featureFlags,
                uiEventLogger
            )

        stylusManager.instanceIdSequence = instanceIdSequenceFake

        whenever(otherDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(false)
        whenever(stylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)
        whenever(btStylusDevice.supportsSource(InputDevice.SOURCE_STYLUS)).thenReturn(true)

        whenever(btStylusDevice.isExternal).thenReturn(true)

        whenever(stylusDevice.bluetoothAddress).thenReturn(null)
        whenever(btStylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)

        whenever(btStylusDevice.batteryState).thenReturn(batteryState)
        whenever(stylusDevice.batteryState).thenReturn(batteryState)
        whenever(batteryState.capacity).thenReturn(0.5f)

        whenever(inputManager.getInputDevice(OTHER_DEVICE_ID)).thenReturn(otherDevice)
        whenever(inputManager.getInputDevice(STYLUS_DEVICE_ID)).thenReturn(stylusDevice)
        whenever(inputManager.getInputDevice(BT_STYLUS_DEVICE_ID)).thenReturn(btStylusDevice)
        whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(STYLUS_DEVICE_ID))

        whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(bluetoothDevice)
        whenever(bluetoothDevice.address).thenReturn(STYLUS_BT_ADDRESS)

        whenever(featureFlags.isEnabled(Flags.TRACK_STYLUS_EVER_USED)).thenReturn(true)

        whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(false)

        stylusManager.startListener()
        stylusManager.registerCallback(stylusCallback)
        clearInvocations(inputManager)
    }

    @After
    fun tearDown() {
        mockitoSession.finishMocking()
    }

    @Test
    fun startListener_hasNotStarted_registersInputDeviceListener() {
        stylusManager =
            StylusManager(
                mContext,
                inputManager,
                bluetoothAdapter,
                handler,
                EXECUTOR,
                featureFlags,
                uiEventLogger
            )

        stylusManager.startListener()

        verify(inputManager, times(1)).registerInputDeviceListener(any(), any())
    }

    @Test
    fun startListener_hasNotStarted_registersExistingBluetoothDevice() {
        whenever(inputManager.inputDeviceIds).thenReturn(intArrayOf(BT_STYLUS_DEVICE_ID))

        stylusManager =
            StylusManager(
                mContext,
                inputManager,
                bluetoothAdapter,
                handler,
                EXECUTOR,
                featureFlags,
                uiEventLogger
            )

        stylusManager.startListener()

        verify(bluetoothAdapter, times(1))
            .addOnMetadataChangedListener(bluetoothDevice, EXECUTOR, stylusManager)
    }

    @Test
    fun startListener_hasStarted_doesNothing() {
        stylusManager.startListener()

        verifyZeroInteractions(inputManager)
    }

    @Test
    fun onInputDeviceAdded_hasNotStarted_doesNothing() {
        stylusManager =
            StylusManager(
                mContext,
                inputManager,
                bluetoothAdapter,
                handler,
                EXECUTOR,
                featureFlags,
                uiEventLogger
            )

        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        verifyZeroInteractions(stylusCallback)
    }

    @Test
    fun onInputDeviceAdded_multipleRegisteredCallbacks_callsAll() {
        stylusManager.registerCallback(otherStylusCallback)

        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
        verifyNoMoreInteractions(stylusCallback)
        verify(otherStylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
        verifyNoMoreInteractions(otherStylusCallback)
    }

    @Test
    fun onInputDeviceAdded_internalStylus_registersBatteryListener() {
        whenever(stylusDevice.isExternal).thenReturn(false)

        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        verify(inputManager, times(1))
            .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager)
    }

    @Test
    fun onInputDeviceAdded_externalStylus_doesNotRegisterbatteryListener() {
        whenever(stylusDevice.isExternal).thenReturn(true)

        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        verify(inputManager, never())
            .addInputDeviceBatteryListener(STYLUS_DEVICE_ID, EXECUTOR, stylusManager)
    }

    @Test
    fun onInputDeviceAdded_stylus_callsCallbacksOnStylusAdded() {
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1)).onStylusAdded(STYLUS_DEVICE_ID)
        verifyNoMoreInteractions(stylusCallback)
    }

    @Test
    fun onInputDeviceAdded_btStylus_firstUsed_callsCallbacksOnStylusFirstUsed() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1)).onStylusFirstUsed()
    }

    @Test
    fun onInputDeviceAdded_btStylus_firstUsed_setsFlag() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
        verify({ InputSettings.setStylusEverUsed(mContext, true) }, times(1))
    }

    @Test
    fun onInputDeviceAdded_btStylus_callsCallbacksWithAddress() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        inOrder(stylusCallback).let {
            it.verify(stylusCallback, times(1)).onStylusAdded(BT_STYLUS_DEVICE_ID)
            it.verify(stylusCallback, times(1))
                .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
        }
    }

    @Test
    fun onInputDeviceAdded_notStylus_doesNotCallCallbacks() {
        stylusManager.onInputDeviceAdded(OTHER_DEVICE_ID)

        verifyNoMoreInteractions(stylusCallback)
    }

    @Test
    fun onInputDeviceChanged_hasNotStarted_doesNothing() {
        stylusManager =
            StylusManager(
                mContext,
                inputManager,
                bluetoothAdapter,
                handler,
                EXECUTOR,
                featureFlags,
                uiEventLogger
            )

        stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)

        verifyZeroInteractions(stylusCallback)
    }

    @Test
    fun onInputDeviceChanged_multipleRegisteredCallbacks_callsAll() {
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
        whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)
        stylusManager.registerCallback(otherStylusCallback)

        stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1))
            .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
        verify(otherStylusCallback, times(1))
            .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
    }

    @Test
    fun onInputDeviceChanged_stylusNewBtConnection_callsCallbacks() {
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
        whenever(stylusDevice.bluetoothAddress).thenReturn(STYLUS_BT_ADDRESS)

        stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1))
            .onStylusBluetoothConnected(STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
    }

    @Test
    fun onInputDeviceChanged_stylusLostBtConnection_callsCallbacks() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
        whenever(btStylusDevice.bluetoothAddress).thenReturn(null)

        stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1))
            .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
    }

    @Test
    fun onInputDeviceChanged_btConnection_stylusAlreadyBtConnected_onlyCallsListenersOnce() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        stylusManager.onInputDeviceChanged(BT_STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1))
            .onStylusBluetoothConnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
    }

    @Test
    fun onInputDeviceChanged_noBtConnection_stylusNeverBtConnected_doesNotCallCallbacks() {
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        stylusManager.onInputDeviceChanged(STYLUS_DEVICE_ID)

        verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
    }

    @Test
    fun onInputDeviceRemoved_hasNotStarted_doesNothing() {
        stylusManager =
            StylusManager(
                mContext,
                inputManager,
                bluetoothAdapter,
                handler,
                EXECUTOR,
                featureFlags,
                uiEventLogger
            )
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)

        verifyZeroInteractions(stylusCallback)
    }

    @Test
    fun onInputDeviceRemoved_multipleRegisteredCallbacks_callsAll() {
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)
        stylusManager.registerCallback(otherStylusCallback)

        stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
        verify(otherStylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
    }

    @Test
    fun onInputDeviceRemoved_stylus_callsCallbacks() {
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)

        verify(stylusCallback, times(1)).onStylusRemoved(STYLUS_DEVICE_ID)
        verify(stylusCallback, never()).onStylusBluetoothDisconnected(any(), any())
    }

    @Test
    fun onInputDeviceRemoved_unregistersBatteryListener() {
        stylusManager.onInputDeviceAdded(STYLUS_DEVICE_ID)

        stylusManager.onInputDeviceRemoved(STYLUS_DEVICE_ID)

        verify(inputManager, times(1))
            .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager)
    }

    @Test
    fun onInputDeviceRemoved_btStylus_callsCallbacks() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)

        inOrder(stylusCallback).let {
            it.verify(stylusCallback, times(1))
                .onStylusBluetoothDisconnected(BT_STYLUS_DEVICE_ID, STYLUS_BT_ADDRESS)
            it.verify(stylusCallback, times(1)).onStylusRemoved(BT_STYLUS_DEVICE_ID)
        }
    }

    @Test
    fun onStylusBluetoothConnected_registersMetadataListener() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        verify(bluetoothAdapter, times(1)).addOnMetadataChangedListener(any(), any(), any())
    }

    @Test
    fun onStylusBluetoothConnected_noBluetoothDevice_doesNotRegisterMetadataListener() {
        whenever(bluetoothAdapter.getRemoteDevice(STYLUS_BT_ADDRESS)).thenReturn(null)

        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        verify(bluetoothAdapter, never()).addOnMetadataChangedListener(any(), any(), any())
    }

    @Test
    fun onStylusBluetoothConnected_logsEvent() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        verify(uiEventLogger, times(1))
            .logWithInstanceId(
                StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED,
                0,
                null,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
            )
    }

    @Test
    fun onStylusBluetoothDisconnected_unregistersMetadataListener() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)

        verify(bluetoothAdapter, times(1)).removeOnMetadataChangedListener(any(), any())
    }

    @Test
    fun onStylusBluetoothDisconnected_logsEventInSameSession() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)
        val instanceId = InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)

        stylusManager.onInputDeviceRemoved(BT_STYLUS_DEVICE_ID)

        verify(uiEventLogger, times(1))
            .logWithInstanceId(StylusUiEvent.BLUETOOTH_STYLUS_CONNECTED, 0, null, instanceId)
        verify(uiEventLogger, times(1))
            .logWithInstanceId(StylusUiEvent.BLUETOOTH_STYLUS_DISCONNECTED, 0, null, instanceId)
    }

    @Test
    fun onMetadataChanged_chargingStateTrue_executesBatteryCallbacks() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        stylusManager.onMetadataChanged(
            bluetoothDevice,
            BluetoothDevice.METADATA_MAIN_CHARGING,
            "true".toByteArray()
        )

        verify(stylusCallback, times(1))
            .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, true)
    }

    @Test
    fun onMetadataChanged_chargingStateFalse_executesBatteryCallbacks() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        stylusManager.onMetadataChanged(
            bluetoothDevice,
            BluetoothDevice.METADATA_MAIN_CHARGING,
            "false".toByteArray()
        )

        verify(stylusCallback, times(1))
            .onStylusBluetoothChargingStateChanged(BT_STYLUS_DEVICE_ID, bluetoothDevice, false)
    }

    @Test
    fun onMetadataChanged_chargingStateNoDevice_doesNotExecuteBatteryCallbacks() {
        stylusManager.onMetadataChanged(
            bluetoothDevice,
            BluetoothDevice.METADATA_MAIN_CHARGING,
            "true".toByteArray()
        )

        verifyNoMoreInteractions(stylusCallback)
    }

    @Test
    fun onMetadataChanged_notChargingState_doesNotExecuteBatteryCallbacks() {
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        stylusManager.onMetadataChanged(
            bluetoothDevice,
            BluetoothDevice.METADATA_DEVICE_TYPE,
            "true".toByteArray()
        )

        verify(stylusCallback, never()).onStylusBluetoothChargingStateChanged(any(), any(), any())
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_updateEverUsedFlag() {
        whenever(batteryState.isPresent).thenReturn(true)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify({ InputSettings.setStylusEverUsed(mContext, true) }, times(1))
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_stylusNeverUsed_executesStylusFirstUsed() {
        whenever(batteryState.isPresent).thenReturn(true)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(stylusCallback, times(1)).onStylusFirstUsed()
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_notInUsiSession_logsSessionStart() {
        whenever(batteryState.isPresent).thenReturn(true)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, times(1))
            .logWithInstanceIdAndPosition(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
                0,
                null,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
                0,
            )
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_btStylusPresent_logsSessionStart() {
        whenever(batteryState.isPresent).thenReturn(true)
        stylusManager.onInputDeviceAdded(BT_STYLUS_DEVICE_ID)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, times(1))
            .logWithInstanceIdAndPosition(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
                0,
                null,
                InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId),
                1,
            )
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_inUsiSession_doesNotLogSessionStart() {
        whenever(batteryState.isPresent).thenReturn(true)
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
        clearInvocations(uiEventLogger)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verifyZeroInteractions(uiEventLogger)
    }

    @Test
    fun onBatteryStateChanged_batteryAbsent_notInUsiSession_doesNotLogSessionEnd() {
        whenever(batteryState.isPresent).thenReturn(false)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verifyZeroInteractions(uiEventLogger)
    }

    @Test
    fun onBatteryStateChanged_batteryAbsent_inUsiSession_logSessionEnd() {
        whenever(batteryState.isPresent).thenReturn(true)
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
        val instanceId = InstanceId.fakeInstanceId(instanceIdSequenceFake.lastInstanceId)
        whenever(batteryState.isPresent).thenReturn(false)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(uiEventLogger, times(1))
            .logWithInstanceIdAndPosition(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_FIRST_DETECTED,
                0,
                null,
                instanceId,
                0
            )

        verify(uiEventLogger, times(1))
            .logWithInstanceIdAndPosition(
                StylusUiEvent.USI_STYLUS_BATTERY_PRESENCE_REMOVED,
                0,
                null,
                instanceId,
                0
            )
    }

    @Test
    fun onBatteryStateChanged_batteryPresent_stylusUsed_doesNotUpdateEverUsedFlag() {
        whenever(InputSettings.isStylusEverUsed(mContext)).thenReturn(true)

        whenever(batteryState.isPresent).thenReturn(true)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify({ InputSettings.setStylusEverUsed(mContext, true) }, never())
    }

    @Test
    fun onBatteryStateChanged_batteryNotPresent_doesNotUpdateEverUsedFlag() {
        whenever(batteryState.isPresent).thenReturn(false)

        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(inputManager, never())
            .removeInputDeviceBatteryListener(STYLUS_DEVICE_ID, stylusManager)
    }

    @Test
    fun onBatteryStateChanged_hasNotStarted_doesNothing() {
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verifyZeroInteractions(inputManager)
    }

    @Test
    fun onBatteryStateChanged_executesBatteryCallbacks() {
        stylusManager.onBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)

        verify(stylusCallback, times(1))
            .onStylusUsiBatteryStateChanged(STYLUS_DEVICE_ID, 1, batteryState)
    }

    companion object {
        private val EXECUTOR = Executor { r -> r.run() }

        private const val OTHER_DEVICE_ID = 0
        private const val STYLUS_DEVICE_ID = 1
        private const val BT_STYLUS_DEVICE_ID = 2

        private const val STYLUS_BT_ADDRESS = "SOME:ADDRESS"
    }
}
