refactor: remove deprecated user token and device ID code

Complete removal of deprecated fields and methods:
- Remove userApiToken and deviceId from TrmnlDeviceConfig
- Remove deprecated DataStore methods (saveUserApiToken, getUserApiToken, saveDeviceId, getDeviceId)
- Remove userApiToken from AppSettingsScreen state and UI
- Remove USER_API_TOKEN_KEY and DEVICE_ID_KEY constants
- Add context comments for battery percentage reporting

This simplifies the codebase by fully removing unused code paths rather
than keeping them as deprecated.
This commit is contained in:
Hossain Khan
2026-02-05 19:54:43 -05:00
parent 0614f59ff9
commit b0958b8564
4 changed files with 7 additions and 375 deletions
@@ -99,8 +99,6 @@ class TrmnlDeviceConfigDataStore
private val CONFIG_JSON_KEY = stringPreferencesKey("config_json")
private val DEVICE_MAC_ID_KEY = stringPreferencesKey("device_mac_id")
private val IS_MASTER_DEVICE_KEY = stringPreferencesKey("is_master_device")
private val USER_API_TOKEN_KEY = stringPreferencesKey("user_api_token")
private val DEVICE_ID_KEY = stringPreferencesKey("device_id")
private val DEVICE_MODEL_PREFERENCES_KEY = stringPreferencesKey("device_model_preferences")
}
@@ -238,7 +236,7 @@ class TrmnlDeviceConfigDataStore
try {
val config: TrmnlDeviceConfig? = deviceConfigAdapter.fromJson(configJson)
Timber.tag(TAG).d(
"Loading device config (JSON): type=${config?.type}, userApiToken=${config?.userApiToken.obfuscated()}",
"Loading device config (JSON): type=${config?.type}",
)
config
} catch (e: Exception) {
@@ -261,11 +259,9 @@ class TrmnlDeviceConfigDataStore
val refreshRate = preferences[REFRESH_RATE_SEC_KEY] ?: DEFAULT_REFRESH_INTERVAL_SEC
val deviceMacId = preferences[DEVICE_MAC_ID_KEY]
val isMasterDevice = preferences[IS_MASTER_DEVICE_KEY]?.toBoolean()
val userApiToken = preferences[USER_API_TOKEN_KEY]
val deviceId = preferences[DEVICE_ID_KEY]?.toIntOrNull()
Timber.tag(TAG).d(
"Loading device config (legacy): type=$type, deviceApiToken=${token.obfuscated()}, deviceId=$deviceId",
"Loading device config (legacy): type=$type, deviceApiToken=${token.obfuscated()}",
)
if (token != null) {
@@ -276,8 +272,6 @@ class TrmnlDeviceConfigDataStore
deviceMacId = deviceMacId,
refreshRateSecs = refreshRate,
isMasterDevice = isMasterDevice,
userApiToken = userApiToken,
deviceId = deviceId,
)
} else {
null
@@ -311,7 +305,7 @@ class TrmnlDeviceConfigDataStore
suspend fun saveDeviceConfig(config: TrmnlDeviceConfig) {
try {
Timber.tag(TAG).d(
"Saving device config: type=${config.type}, userApiToken=${config.userApiToken.obfuscated()}",
"Saving device config: type=${config.type}",
)
val configJson = deviceConfigAdapter.toJson(config)
context.deviceConfigStore.edit { preferences ->
@@ -333,16 +327,6 @@ class TrmnlDeviceConfigDataStore
config.isMasterDevice?.let { isMaster ->
preferences[IS_MASTER_DEVICE_KEY] = isMaster.toString()
} ?: preferences.remove(IS_MASTER_DEVICE_KEY)
// Save userApiToken if available
config.userApiToken?.let { userToken ->
preferences[USER_API_TOKEN_KEY] = userToken
} ?: preferences.remove(USER_API_TOKEN_KEY)
// Save deviceId if available
config.deviceId?.let { deviceId ->
preferences[DEVICE_ID_KEY] = deviceId.toString()
} ?: preferences.remove(DEVICE_ID_KEY)
}
Timber.tag(TAG).d("Device config saved successfully")
} catch (e: Exception) {
@@ -359,84 +343,6 @@ class TrmnlDeviceConfigDataStore
}
}
/**
* Saves the user-level API token (Account API key)
*
* **DEPRECATED:** User API token is no longer needed for battery reporting.
* Battery percentage is now sent via the Percent-Charged header in /api/display call.
*/
@Deprecated(
message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.",
level = DeprecationLevel.WARNING,
)
suspend fun saveUserApiToken(token: String) {
Timber.tag(TAG).d("Saving user API token: ${token.obfuscated()}")
context.deviceConfigStore.edit { preferences ->
preferences[USER_API_TOKEN_KEY] = token
}
Timber.tag(TAG).d("User API token saved successfully")
}
/**
* Gets the user-level API token
*
* **DEPRECATED:** User API token is no longer needed for battery reporting.
* Battery percentage is now sent via the Percent-Charged header in /api/display call.
*/
@Deprecated(
message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.",
level = DeprecationLevel.WARNING,
)
suspend fun getUserApiToken(): String? {
val token =
context.deviceConfigStore.data
.map { preferences -> preferences[USER_API_TOKEN_KEY] }
.first()
Timber.tag(TAG).d("Retrieved user API token: ${token.obfuscated()}")
return token
}
/**
* Saves the device ID (TRMNL device ID from /api/devices/me).
*
* **DEPRECATED:** Device ID is no longer needed for battery reporting.
* Battery percentage is now sent via the Percent-Charged header in /api/display call.
*
* **Note:** This is only applicable for BYOD device types.
*/
@Deprecated(
message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.",
level = DeprecationLevel.WARNING,
)
suspend fun saveDeviceId(deviceId: Int) {
Timber.tag(TAG).d("Saving device ID: $deviceId")
context.deviceConfigStore.edit { preferences ->
preferences[DEVICE_ID_KEY] = deviceId.toString()
}
Timber.tag(TAG).d("Device ID saved successfully")
}
/**
* Gets the device ID.
*
* **DEPRECATED:** Device ID is no longer needed for battery reporting.
* Battery percentage is now sent via the Percent-Charged header in /api/display call.
*
* **Note:** This is only applicable for BYOD device types.
*/
@Deprecated(
message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.",
level = DeprecationLevel.WARNING,
)
suspend fun getDeviceId(): Int? {
val deviceId =
context.deviceConfigStore.data
.map { preferences -> preferences[DEVICE_ID_KEY]?.toIntOrNull() }
.first()
Timber.tag(TAG).d("Retrieved device ID: $deviceId")
return deviceId
}
/**
* Saves the access token
*/
@@ -82,6 +82,10 @@ class TrmnlDisplayRepository
percentCharged =
if (trmnlDeviceConfig.type == TrmnlDeviceType.BYOD) {
// Send battery percentage if available for BYOD devices only
// See following for context:
// https://github.com/usetrmnl/trmnl-android/issues/252
// https://github.com/usetrmnl/trmnl-android/issues/239
// https://discord.com/channels/1281055965508141100/1466030731770855434/1469103763846463620
androidDeviceInfoProvider.getBatteryLevel()?.toDouble()
} else {
null
@@ -33,43 +33,4 @@ data class TrmnlDeviceConfig constructor(
* See https://github.com/usetrmnl/trmnl-android/issues/190
*/
val isMasterDevice: Boolean? = null,
/**
* User-level API token (Account API key) for user-level endpoints.
*
* **DEPRECATED:** This field is no longer needed for battery reporting.
* Battery percentage is now sent via the Percent-Charged header in /api/display call,
* which only requires device-level authentication ([apiAccessToken]).
*
* This field is kept for backward compatibility and may be removed in a future version.
*
* Required for BYOD devices to access user-level API endpoints like /api/me and /api/devices.
*
* This is separate from [apiAccessToken] which is the device-level API key.
*/
@Deprecated(
message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.",
level = DeprecationLevel.WARNING,
)
val userApiToken: String? = null,
/**
* TRMNL device ID extracted from /api/devices/me endpoint.
*
* **DEPRECATED:** This field is no longer needed for battery reporting.
* Battery percentage is now sent via the Percent-Charged header in /api/display call,
* which doesn't require device ID or user-level authentication.
*
* This field is kept for backward compatibility and may be removed in a future version.
*
* Used for making user-level API calls to /api/devices/{id}.
*
* This ID is fetched during BYOD device validation and is required for
* reporting battery status and other device-specific updates.
*
* **Note:** This field is only applicable for BYOD device types.
*/
@Deprecated(
message = "No longer needed for battery reporting. Battery is now sent via Percent-Charged header.",
level = DeprecationLevel.WARNING,
)
val deviceId: Int? = null,
)
@@ -143,7 +143,6 @@ data class AppSettingsScreen(
val accessToken: String,
val deviceMacId: String,
val isByodMasterDevice: Boolean,
val userApiToken: String,
val usesFakeApiData: Boolean,
val isLoading: Boolean = false,
val validationResult: ValidationResult? = null,
@@ -197,18 +196,6 @@ data class AppSettingsScreen(
val token: String,
) : Event()
/**
* Event triggered when the user API token is changed.
*/
data class UserApiTokenChanged(
val token: String,
) : Event()
/**
* Event triggered to validate the current user API token.
*/
data object ValidateUserToken : Event()
/**
* Event triggered to validate the current access token.
*/
@@ -289,7 +276,6 @@ class AppSettingsPresenter
var accessToken by remember { mutableStateOf("") }
var deviceMacId by remember { mutableStateOf("") }
var isByodMasterDevice by remember { mutableStateOf(true) }
var userApiToken by remember { mutableStateOf("") }
var isLoading by remember { mutableStateOf(false) }
var validationResult by remember { mutableStateOf<ValidationResult?>(null) }
var isDeviceSetupLoading by remember { mutableStateOf(false) }
@@ -352,7 +338,6 @@ class AppSettingsPresenter
// Load BYOD-specific settings
if (it.type == TrmnlDeviceType.BYOD) {
isByodMasterDevice = it.isMasterDevice ?: true
userApiToken = it.userApiToken ?: ""
}
}
}
@@ -363,7 +348,6 @@ class AppSettingsPresenter
accessToken = accessToken,
deviceMacId = deviceMacId,
isByodMasterDevice = isByodMasterDevice,
userApiToken = userApiToken,
usesFakeApiData = usesFakeApiData,
isLoading = isLoading,
validationResult = validationResult,
@@ -380,73 +364,6 @@ class AppSettingsPresenter
deviceSetupMessage = null
}
is AppSettingsScreen.Event.UserApiTokenChanged -> {
userApiToken = event.token
// Clear previous validation when user token changes
if (validationResult is ValidationResult.UserTokenSuccess ||
validationResult is ValidationResult.InvalidUserToken
) {
validationResult = null
}
}
AppSettingsScreen.Event.ValidateUserToken -> {
// DEPRECATED: User API token validation is no longer needed
// Battery reporting now uses Percent-Charged header instead of user-level API
Timber.d("User token validation skipped - no longer needed for battery reporting")
/* DISABLED - User token no longer needed for battery reporting
scope.launch {
focusManager.clearFocus()
isLoading = true
// Clear previous user token validation
if (validationResult is ValidationResult.UserTokenSuccess ||
validationResult is ValidationResult.InvalidUserToken
) {
validationResult = null
}
// Validate user API token by calling /api/me
val result =
displayRepository.validateUserApiToken(
apiBaseUrl = serverBaseUrl.forDevice(deviceType),
userApiToken = userApiToken,
)
validationResult =
when {
result.isSuccess -> {
val user = result.getOrNull()
if (user != null) {
// Token is valid - user will save it via "Save and Continue"
ValidationResult.UserTokenSuccess(
userName = user.name,
userEmail = user.email,
)
} else {
Timber.e(
"validateUserApiToken succeeded but returned null user. " +
"apiBaseUrl=%s, deviceType=%s",
serverBaseUrl.forDevice(deviceType),
deviceType,
)
ValidationResult.InvalidUserToken(
"Unexpected error: API returned success but no user data was received",
)
}
}
else -> {
ValidationResult.InvalidUserToken(
result.exceptionOrNull()?.message ?: "Invalid user API token",
)
}
}
isLoading = false
}
*/
}
AppSettingsScreen.Event.ValidateToken -> {
scope.launch {
focusManager.clearFocus()
@@ -518,28 +435,6 @@ class AppSettingsPresenter
response.imageUrl,
response.refreshIntervalSeconds ?: DEFAULT_REFRESH_INTERVAL_SEC,
)
// DEPRECATED: Device ID fetching no longer needed
// Battery reporting now uses Percent-Charged header instead of user-level API
/* DISABLED - Device ID no longer needed for battery reporting
// For BYOD devices, also fetch and save the device ID
if (deviceType == TrmnlDeviceType.BYOD) {
val deviceIdResult = displayRepository.getDeviceIdFromApi(deviceConfig)
if (deviceIdResult.isSuccess) {
val deviceId = deviceIdResult.getOrNull()
if (deviceId != null) {
deviceConfigStore.saveDeviceId(deviceId)
Timber.d("Device ID saved successfully for BYOD device: $deviceId")
}
} else {
Timber.w(
"Failed to fetch device ID for BYOD device. Error: %s",
deviceIdResult.exceptionOrNull(),
)
}
}
*/
} else {
// No error but also no image URL
val errorMessage = response.error ?: ""
@@ -562,14 +457,6 @@ class AppSettingsPresenter
TrmnlDeviceType.TRMNL -> false
}
// For BYOD devices, retrieve the device ID that was fetched during validation
val deviceId =
if (deviceType == TrmnlDeviceType.BYOD) {
deviceConfigStore.getDeviceId()
} else {
null
}
deviceConfigStore.saveDeviceConfig(
TrmnlDeviceConfig(
type = deviceType,
@@ -579,13 +466,6 @@ class AppSettingsPresenter
// Normalize the MAC address to standard format if provided in different format
deviceMacId = normalizeMacAddress(deviceMacId)?.ifBlank { null },
isMasterDevice = isMaster,
// Save user API token if provided (for BYOD).
// Note: user token validation is optional and may be skipped by the user.
// We still persist the token here; any invalid or expired token will be
// detected and surfaced via downstream API error handling.
userApiToken = userApiToken.ifBlank { null },
// Include device ID for BYOD devices (fetched during validation)
deviceId = deviceId,
),
)
trmnlWorkScheduler.updateRefreshInterval(result.refreshRateSecs)
@@ -823,116 +703,6 @@ fun AppSettingsContent(
deviceIdError = (state.validationResult as? ValidationResult.InvalidDeviceMacId)?.message,
)
//
// DEPRECATED: User API Token field is no longer needed
//
// Battery reporting now uses the Percent-Charged header in /api/display call,
// which only requires device-level authentication (Access-Token).
// User-level authentication is no longer needed for BYOD device battery reporting.
//
// This UI has been disabled but kept in code for reference.
//
/* DISABLED - User API token no longer needed for battery reporting
// User API Token field (only for BYOD)
AnimatedVisibility(
visible = state.deviceType == TrmnlDeviceType.BYOD,
enter = expandVertically() + fadeIn(),
exit = shrinkVertically() + fadeOut(),
) {
Column {
Spacer(modifier = Modifier.height(16.dp))
var userTokenVisible by remember { mutableStateOf(false) }
OutlinedTextField(
value = state.userApiToken,
onValueChange = { state.eventSink(AppSettingsScreen.Event.UserApiTokenChanged(it)) },
label = { Text("User API Token (Account Key)") },
modifier = Modifier.fillMaxWidth(),
singleLine = true,
visualTransformation = if (userTokenVisible) VisualTransformation.None else PasswordVisualTransformation(),
keyboardOptions =
KeyboardOptions(
keyboardType = KeyboardType.Password,
imeAction = ImeAction.Done,
),
keyboardActions =
KeyboardActions(
onDone = {
state.eventSink(AppSettingsScreen.Event.ValidateUserToken)
},
),
supportingText = {
Text(
"Optional: This token is needed for device management features like battery reporting. Get this from your TRMNL user account settings.",
)
},
trailingIcon = {
IconButton(onClick = { userTokenVisible = !userTokenVisible }) {
Icon(
painter =
painterResource(
if (userTokenVisible) R.drawable.visibility_off_24dp else R.drawable.visibility_24dp,
),
contentDescription = if (userTokenVisible) "Hide user token" else "Show user token",
)
}
},
isError = state.validationResult is ValidationResult.InvalidUserToken,
)
Spacer(modifier = Modifier.height(8.dp))
// Determine button state based on validation result
val isValidationSuccess = state.validationResult is ValidationResult.UserTokenSuccess
val isValidationError = state.validationResult is ValidationResult.InvalidUserToken
Button(
onClick = { state.eventSink(AppSettingsScreen.Event.ValidateUserToken) },
enabled = state.userApiToken.isNotBlank() && !state.isLoading,
modifier = Modifier.fillMaxWidth(),
colors =
when {
isValidationSuccess ->
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
contentColor = MaterialTheme.colorScheme.onPrimaryContainer,
)
isValidationError ->
ButtonDefaults.buttonColors(
containerColor = MaterialTheme.colorScheme.errorContainer,
contentColor = MaterialTheme.colorScheme.onErrorContainer,
)
else -> ButtonDefaults.buttonColors()
},
) {
Text("Validate User Token")
when {
isValidationSuccess -> {
Spacer(modifier = Modifier.width(8.dp))
Icon(
painter = painterResource(R.drawable.check_circle_24dp),
contentDescription = "Validation successful",
modifier = Modifier.size(20.dp),
)
}
isValidationError -> {
Spacer(modifier = Modifier.width(8.dp))
Icon(
painter = painterResource(R.drawable.error_24dp),
contentDescription = "Validation failed",
modifier = Modifier.size(20.dp),
)
}
}
}
}
}
*/
Spacer(modifier = Modifier.height(16.dp))
// Password field with toggle visibility button
OutlinedTextField(
value = state.accessToken,
@@ -1556,7 +1326,6 @@ private fun PreviewAppSettingsContentInitial() {
accessToken = "",
deviceMacId = "aa:bb:cc:dd:ee:ff",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = true,
isLoading = false,
validationResult = null,
@@ -1579,7 +1348,6 @@ private fun PreviewAppSettingsContentLoading() {
accessToken = "some-token",
deviceMacId = "aa:bb:cc:dd:ee:ff",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = false,
isLoading = true,
validationResult = null,
@@ -1602,7 +1370,6 @@ private fun PreviewAppSettingsContentSuccess() {
accessToken = "valid-token-123",
deviceMacId = "aa:bb:cc:dd:ee:ff",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = false,
isLoading = false,
validationResult =
@@ -1629,7 +1396,6 @@ private fun PreviewAppSettingsContentFailure() {
accessToken = "invalid-token",
deviceMacId = "aa:bb:cc:dd:ee:ff",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = false,
isLoading = false,
validationResult =
@@ -1659,7 +1425,6 @@ private fun PreviewAppSettingsContentWithWork() {
accessToken = "valid-token-123",
deviceMacId = "aa:bb:cc:dd:ee:ff",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = false,
isLoading = false,
validationResult = null, // Can also be Success state
@@ -1692,7 +1457,6 @@ private fun PreviewWorkScheduleStatusCardScheduled() {
accessToken = "some-token",
deviceMacId = "AA:BB:CC:DD:EE:FF",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = false,
nextRefreshJobInfo =
NextImageRefreshDisplayInfo(
@@ -1719,7 +1483,6 @@ private fun PreviewWorkScheduleStatusCardNoWork() {
accessToken = "some-token",
deviceMacId = "aa:bb:cc:dd:ee:ff",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = false,
nextRefreshJobInfo = null,
eventSink = {},
@@ -1748,7 +1511,6 @@ private fun PreviewAppSettingsContentByod() {
accessToken = "byod-access-token-here",
deviceMacId = "",
isByodMasterDevice = false,
userApiToken = "user_test123",
usesFakeApiData = false,
isLoading = false,
validationResult = null,
@@ -1772,7 +1534,6 @@ private fun PreviewAppSettingsContentByos() {
accessToken = "byos-access-token-here",
deviceMacId = "AA:BB:CC:DD:EE:FF",
isByodMasterDevice = true,
userApiToken = "",
usesFakeApiData = false,
isLoading = false,
validationResult = null,