This commit introduces a new helper function `fromHex()` that converts a HEX string (with optional separators like spaces or hyphens) into a byte array. It also adds a corresponding `toHex()` function for converting a byte array to a HEX string.
These helpers are now used in various parts of the codebase, including:
- Fake BLE data generation
- AirPod key input dialog
- RPA checker tests
- BaseAirPodsTest
Additionally, a unit test for these conversion functions has been added.
This commit adds a test case to `AirPodsProTest` to verify the decryption of AirPods Pro data with and without encryption/IRK keys. It also introduces a new test class `RPACheckerTest` to validate the RPA checking mechanism.
Helper functions for converting hex strings to byte arrays and setting mock keys have been added to `BaseAirPodsTest` to simplify test setup.
The encryption key setting for the main device is now disabled if the identity key is not set. This prevents users from trying to set an encryption key for a device that doesn't have an identity key.
This change introduces the capability to decrypt the last 16 bytes of the proximity pairing message if an encryption key is available.
The decrypted payload provides more precise battery information (0-100% instead of 0-10%) and charging status for each pod and the case.
This information is now used by `DualApplePods` to enhance battery reporting accuracy.
The `RPAChecker` has also been updated with improved error logging.
Replace `values()` with `entries` for enum iteration, as recommended by Kotlin for improved performance and consistency.
Also:
- Suppress unused warning in `ByteArrayAdapter`
- Improve logging in `WebpageTool` by including the exception details.
This commit introduces settings for users to input their AirPods' Identity Resolving Key (IRK) and Encryption Key. These keys, obtainable via a MacBook, allow CAPod to reliably identify the AirPods and decrypt detailed status information.
Key changes:
- Added new preferences in General Settings for IRK and Encryption Key.
- Implemented a custom dialog for key input, including validation and a link to a guide.
- Added a Moshi adapter for serializing/deserializing `ByteArray` to/from Base64 for storing the keys.
- Included new string resources for labels, descriptions, and explanations related to the keys.
- Added new icons for the key preferences.
```
Caused by java.lang.IllegalStateException: WorkManager is not initialized properly. You have explicitly disabled WorkManagerInitializer in your manifest, have not manually called WorkManager#initialize at this point, and your Application does not implement Configuration.Provider.
at androidx.work.impl.WorkManagerImpl.getInstance (WorkManagerImpl.java)
at androidx.work.impl.foreground.SystemForegroundDispatcher.<init> (SystemForegroundDispatcher.java)
at androidx.work.impl.foreground.SystemForegroundService.initializeDispatcher (SystemForegroundService.java)
at androidx.work.impl.foreground.SystemForegroundService.onCreate (SystemForegroundService.java)
at android.app.ActivityThread.handleCreateService (ActivityThread.java:4726)
```
```java
12:49:29.433 AndroidRuntime E FATAL EXCEPTION: main
Process: eu.darken.capod, PID: 15164
java.lang.SecurityException: Starting FGS with type connectedDevice callerApp=ProcessRecord{cb03698 15164:eu.darken.capod/u0a352} targetSDK=34 requires permissions: all of the permissions allOf=true [android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE] any of the permissions allOf=false [android.permission.BLUETOOTH_ADVERTISE, android.permission.BLUETOOTH_CONNECT, android.permission.BLUETOOTH_SCAN, android.permission.CHANGE_NETWORK_STATE, android.permission.CHANGE_WIFI_STATE, android.permission.CHANGE_WIFI_MULTICAST_STATE, android.permission.NFC, android.permission.TRANSMIT_IR, android.permission.UWB_RANGING, USB Device, USB Accessory]
at android.os.Parcel.createExceptionOrNull(Parcel.java:3242)
at android.os.Parcel.createException(Parcel.java:3226)
at android.os.Parcel.readException(Parcel.java:3209)
at android.os.Parcel.readException(Parcel.java:3151)
at android.app.IActivityManager$Stub$Proxy.setServiceForeground(IActivityManager.java:7167)
at android.app.Service.startForeground(Service.java:863)
at androidx.work.impl.foreground.SystemForegroundService$Api31Impl.startForeground(SystemForegroundService.java:194)
at androidx.work.impl.foreground.SystemForegroundService$1.run(SystemForegroundService.java:130)
at android.os.Handler.handleCallback(Handler.java:959)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loopOnce(Looper.java:232)
at android.os.Looper.loop(Looper.java:317)
at android.app.ActivityThread.main(ActivityThread.java:8699)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:580)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:886)