Developing a Digital Key App for Android

Andrey Mukamolov
Mobile Dev Blog by Bamboo Apps
6 min readJul 30, 2019
Photo by Mateusz Tworuszka on Unsplash

A lot of automotive companies are currently exploring the possibility to lock and unlock the vehicle, as well as to start the engine via smartphone without the physical key. However, the existing implementations of the digital key seemed to us not very convenient for the end-users.

Bamboo Apps’ team wanted to start an R&D project to enhance the expertise in automotive development and implement our ideas on how users can replace their regular car keys with smartphones.

In the automotive field, there are a lot of security and usability challenges. We tried to address these issues in our concept of digital key application during our R&D project, and I wanted to share with you some insights.

We developed a comprehensive solution that can be integrated into new or used vehicles with infotainment systems. In this article, I will focus on our Android digital key application. I’ll briefly describe some challenges we faced and our workarounds for them.

Concept

We wanted to create an application that is focused only on locking/unlocking vehicle doors. We used a BLE connection with the vehicle to trigger automatic door locking once the user is moving away from the car and unlocking when they’re approaching it.

We’ve also created a concept of zones for better security and UX. There are two zones for BLE connection with the vehicle: read and write.

The vehicle will unlock only when the user is in the read zone, close to it. The user also can manually lock or unlock the doors as long as they are in the write zone. Once the user moves away from the vehicle, doors will automatically lock, and the user will only see in the app if the doors were locked or if the operation has failed.

Source

Here is a demo of the animation that the user will see when moving between the zones.

Source

Architecture

During initial discussions, we decided to split the project into modules by layers, and we got 3 layers:

  • Data layer for managing access to the various data sources and processing data from them. Here we placed our repositories, data sources and Retrofit interfaces, and implementations of the use cases.
  • Domain layer houses our use cases (every single feature is contained in its own use case) with their base classes and entities that are sent to the presentation layer. This module doesn’t depend on anything.
  • The Presentation layer is the actual app. There we store our UI stuff and Dagger files.

We used the MVVM pattern with Google’s Architecture Components for the presentation layer and classical Clean Architecture principles for the data layer. However, we decided to implement MVVM without data binding, we used RxJava for linking view with ViewModel. It added a bit of boilerplate code to views but made all the bindings explicit and reactive.

Uber’s AutoDispose library helped us reduce the amount of boilerplate for subscriptions management. However, docs were slightly outdated, so we’ve implemented an old lifecycle binding initially and rewrote it to Android Architecture Lifecycle binding later.

For those who wonder how to do this I’ve highlighted necessary code:

tryAgainButton.clicks()
.debounce(300, TimeUnit.MILLISECONDS)
.observeOn(AndroidSchedulers.mainThread())
.autoDisposable(AndroidLifecycleScopeProvider.from(viewLifecycleOwner))
.subscribe {
// do stuff
}

To work with the camera we’ve used Fotoapparat library, remembering of painful Camera2 APIs.

Predictably, our language of choice was Kotlin. It’s now officially the language for developing Android apps, and our team had a pleasure to work with it. Kotlin saved us a lot of time and boilerplate code and was fun to learn it.

Security

Historically, the automotive industry has strict requirements for the security of software. We wanted to keep up with these requirements and make sure users’ data and resources of their vehicles will be safe.

We decided to secure connection by encrypting BLE payloads and adding session token for each payload. Session tokens expire immediately and are used to retrieve a token for the next message, so sessions are protected from replay attacks. Session tokens are also tamper-resistant because of encryption via TLS 1.2 protocol.

We decided not to use a database because it was mostly unnecessary for the use cases, but we stored needed data in encrypted storage baked by Armadillo library. In its README we described the way it works. Encryption and decryption were very fast for us, around 6–10 ms each, so it wasn’t a problem.

Also, we authenticate each request through the server, despite we only connect to it for requesting the latest vehicle state when we’re not connected via BLE.

In general, we followed OWASP recommendations for apps’ security to prevent major vulnerabilities and to protect our app from common vectors of attack.

Bluetooth Low Energy

Bluetooth Low Energy is a robust protocol, and it was a perfect match for our use case. (We’ve described in the detail our research on wireless protocols for digital key solutions here.) However, BLE doesn’t allow to detect an offset between device and vehicle, only an approximate value based on specification values and power of the signal.

We used RxAndroidBle library and developed wrapper around it to address our needs.

QR code

There is nothing special about scanning QR codes these days, there are a lot of libraries and guides. However, the main lib for working with QR codes, ZXing, appears to be deprecated, so we decided to use fresh new Firebase MLKit. It may seem like an overhead to scan QR codes using machine learning, but as per our internal testing, our MLKit scanner worked much faster than apps based on ZXing in the same conditions. Also, it fitted into RxJava pretty well, despite some hiccups.

It was needed to wrap Firebase’s Task<T> for detecting codes from the camera with RxJava’s Flowable. Like this:

override fun scanQRCode(frame: CameraFrame): Flowable<List<FirebaseVisionBarcode>> {
return Flowable.create({ emitter ->
detector.detectInImage(FirebaseVisionImage.fromByteArray(frame.image, metadataMapper.map(frame)))
.addOnSuccessListener { emitter.onNext(it) }
.addOnFailureListener { emitter.onError(it) }
.addOnCompleteListener { emitter.onComplete() }
}
, BackpressureStrategy.LATEST)
}

CameraFrame is a custom model based on Fotoapparat’s Frame. metadataMapper is a helper class that constructs info about image required by MLKit to detect something.

Final thoughts

The digital key app was our first inner R&D project, during which we learned a lot and tried some cool things we were unable to try on our main projects for different reasons.

However, in a retrospective session after finishing our project we found that some things in the development process could be better.

First of all, in the beginning, we agreed not to write tests. We thought that it’ll allow us to progress faster. However, in reality, we struggled with testing of the BLE feature, so manual tests took us a lot of time and effort. If we had invested that time into a couple of Espresso tests and a bunch of unit tests, we would have progressed much faster. So for the future projects, I would write tests for complicated logic or for some cumbersome scenarios that take much time to manually reproduce.

Also, we wanted to unify architecture between iOS and Android. And because we’ve developed native apps, we’ve inevitably failed at this. Platform-specific things made our architecture differ on each platform, and extra effort we invested in keeping our architecture aligned wasn’t worth it. Probably, it was enough just to make our architecture conceptually similar on both platforms in terms of using Rx and Clean architecture, but anything else that might be platform-specific at any level we could keep platform-specific and not waste time on sync-ups and discussions about aligning implementation details. Our apps should work similarly, but not be exactly the same in all the aspects, because it turns to be very hard to achieve.

Hope that this article will be interesting and helpful for you. Cheers!

--

--