Building an Audio ad-blocker for Android

#android #programming

This post covers some of the insights I’ve gotten while building ad-free, a modularized audio ad-blocker for Android I started in 2016. Ad-free is a proof-of-concept application to show limitations in the way how audio advertisement is implemented on Android. It features a small Kotlin application with a plugin-based design and a simplistic user interface.


Table Of Contents

Introduction

The Android OS provides powerful mechanisms to intercept and monitor events on the system. A Notification Listener Service is an application that can monitor calls from the operating system when new notifications are posted or removed. Other services such as Accessibility Services monitor and enhance user interaction to support impaired users to interact with the device. These services typically expose much information about the current user activity. Android’s UI security has been undermined in the past by these services because they break otherwise strong principles of sandboxing between applications. A malicious application, when granted permission by the user, may sniff sensitive data from other apps including passwords. See Kalysch et al.1 for an overview of some security implications of Android’s UI services.

Goals

With a proof-of-concept application, I aim to evaluate the expressiveness of some of these mechanisms in the domain of audio advertisement.

The proof-of-concept is to lower system volume if the app detects advertisements and to restore it back to normal afterward. This does not alter other apps on the system and does not require system modifications such as rooting. However, the effectiveness of this approach directly depends on what data is exposed through some of the system services aforementioned.

The Application

The Android application is written in Kotlin. It consists of three components.

You can find the source-code on GitHub.

User Interface

The application implements some derivation of Model-View-Presenter with the following goals:

mvp

A simple separation between presenters and view/ activities.

ModPresenter and ModActivity demonstrate some of these ideas in the code base of ad-free. Some design assets of the interface are shown below.

ad-freeapp

The minimalistic user interface of ad-free. The crash-report screen features a quote by Winston Churchill.

Voting Mechanism

Ad-free implements various detectors. A detector receives some execution context and then parses the context for anomalies that are present if an advertisement is playing. The execution context is provided by a Notification Listener or Accessibility service. An anomaly may be some payload in a bundle or a UI widget always present. A detector then votes whether it thinks it found an advertisement. A manager subsequently collects these votes and decides what to do. This idea is sometimes referred to in literature as Chain of Responsibility. Ad-free currently implements ca. 20 of these detectors.

interface AdDetectable 
{
    fun getMeta(): AdDetectorMeta
    fun canHandle(p: AdPayload): Boolean
    fun flagAsAdvertisement(payload: AdPayload): Boolean
    fun flagAsMusic(payload: AdPayload): Boolean
}

data class AdDetectorMeta(val title: String, 
     val description: String, var enabledByDef: Boolean = true,
     var debugOnly: Boolean = false, var category: String = "General")
All detectors of ad-free implement this contract. Given some payload, a detector can flag an event as music or advertisement.

Replacing Audio with nonsensical Advertisements

As an Easter egg, I added a feature to replace the audio advertisement with nonsensical advertisements from the television show Rick and Morty. The series features interdimensional cable: cable television with absurd and yet creative advertisements from fictional alternative universes.

This Easter egg quickly received some attention on the interwebs. However, I only briefly included it in the app.

Traffic

This Easter egg got much attention on Reddit. Within a few days, I received traffic of over 10k users on the landing page.

AudioManager, Android’s audio system, provides several streams on which audio is played. Music players play audio on the stream STREAM_MUSIC. There are alternative streams available, such as STREAM_VOICE_CALL. This stream targets the ear speaker in the phone and is used for phone calls. However, if the phone is connected to external speakers such as headphones, this stream is forwarded to the headphones as well. Hence, by lowering the volume on STREAM_MUSIC and playing sound on STREAM_VOICE_CALL we can play arbitrary audio during advertisements.

Based on this idea, ad-free features music plugins that perform some action during ads. For instance, the local music plugin plays audio tracks on the device.

plugins

Plugin to play local music.

Conclusion

Notification and Accessibility services expose much sensitive data to non-privileged apps on the system. Hence, it is of utter importance to carefully decide if a given app should be granted permission to this data.

Ad-free is solely a proof-of-concept and not published in Google’s Play Store. However, the source code is available on F-Droid and GitHub.

Tackling down some of the technical challenges has been much fun. The implemented design is modular and works suprisingly well. Even if this app only lowers the phone volume, muting ads may not be supported and may be against terms of services of your music player. Advertisements can be dubious by wiring desires, morals, and unwanted default values into our brains. Nonetheless, audio streaming companies offer awesome services for non-paying users. If too many users block audio advertisements, the industry may be forced to re-imagine its funding model or free services would be much more limited.

Thanks for reading.
– bean


  1. Anatoli Kalysch, Davide Bove, and Tilo Müller. 2018. How Android’s UI Security is Undermined by Accessibility. In Proceedings of the 2nd Reversing and Offensive-oriented Trends Symposium (ROOTS ‘18). Association for Computing Machinery, New York, NY, USA, Article 2, 1–10. ↩︎

Found a typo or a mistake? Edit this Page on Github and submit a Pull Request. Thank you.