Home

Awesome

Basic Call Recorder

<img src="app/images/icon.svg" alt="app icon" width="72" />

latest release badge license badge

BCR is a simple Android call recording app for rooted devices or devices running custom firmware. Once enabled, it stays out of the way and automatically records incoming and outgoing calls in the background.

<img src="app/images/light.png" alt="light mode screenshot" width="200" /> <img src="app/images/dark.png" alt="dark mode screenshot" width="200" />

Features

Non-features

As the name alludes, BCR intends to be a basic as possible. The project will have succeeded at its goal if the only updates it ever needs are for compatibility with new Android versions. Thus, many potentially useful features will never be implemented, such as:

Usage

  1. Download the latest version from the releases page. To verify the digital signature, see the verifying digital signatures section.

  2. Install BCR as a system app.

    • For devices rooted with Magisk/KernelSU, simply flash the zip as a Magisk/KernelSU module from within the respective application.

      • For OnePlus and Realme devices running the stock firmware (or custom firmware based on the stock firmware), also extract the .apk from the zip and install it manually before rebooting. This is necessary to work around a bug in the firmware where the app data directory does not get created, causing BCR to open up to a blank screen.
    • For unrooted custom firmware, flash the zip while booted into recovery.

      • NOTE: The READ_CALL_LOG permission is hard restricted in Android 10+, which prevents it from being granted, even via Android's settings. To remove this restriction, run via adb after rebooting back into Android:
        # If rooted, run inside of `su`:
        CLASSPATH=/system/priv-app/com.chiller3.bcr/app-release.apk app_process / com.chiller3.bcr.standalone.RemoveHardRestrictionsKt
        
        # If unrooted, install BCR as both a user app and a system app:
        pm install /system/priv-app/com.chiller3.bcr/app-release.apk
        
      • NOTE: If the custom firmware's system partition is formatted with erofs, then the filesystem is read-only and it is not possible to use this method.
      • Manually extracting the files from the system/ folder in the zip will also work as long as the files have 644 permissions and the u:object_r:system_file:s0 SELinux label.
  3. Reboot and open BCR.

    If other call recorders are installed, make sure to disable their phone call recording functionality. On most devices, a phone call cannot be recorded by two apps at the same time. However, it is fine to have BCR record phone calls and another app record eg. VOIP calls.

  4. Enable call recording and pick an output directory.

    If no output directory is selected or if the output directory is no longer accessible, then recordings will be saved to /sdcard/Android/data/com.chiller3.bcr/files. Note that on Android 12+, /sdcard/Android/data/ is only accessible via USB or DocumentsUI (AOSP's built in file manager).

    When enabling call recording the first time, BCR will prompt for microphone, notification (Android 13+), call log, contacts, and phone permissions. Only microphone and notification permissions are required for basic call recording functionality. If additional permissions are granted, more information is added to the output filename. For example, the contacts permission will allow the contact name to be added to the filename.

    See the permissions section below for more details about the permissions.

  5. To install future updates, there are a couple methods:

    • If installed via Magisk, the module can be updated right from Magisk Manager's modules tab. Flashing the new version in Magisk manually also works just as well.
    • The .apk can also be extracted from the zip and be directly installed. With this method, the old version exists as a system app and the new version exists as a user-installed update to the system app. This method is more convenient if BCR is baked into the Android firmware image.

Recording announcement

Unlike the built-in call recording functionality of the preinstalled dialer app on some devices, BCR does not announce that the call is being recorded to the other party. BCR never outputs audio of any kind to the call audio stream.

When BCR is enabled, avoid using the the dialer's built-in call recorder at all. There's a very good chance that using it will cause unexpected behavior, like both recordings failing or the dialer announcing that the call is being recorded.

If you live in a jurisdiction where two-party consent is required, you are responsible for informing the other party that the call is being recorded. If needed, auto-record rules can be used to discard recordings by default. However, note that if you choose to preserve the recording during the middle of the call, the recording will contain full call, not just the portion after the other party consented.

Direct boot

BCR is direct boot aware, meaning that it's capable of running and recording calls before the device is initially unlocked following a reboot. In this state, most of BCR's functionality will still work, aside from features that require the contact list or call log. In practice, this means:

However, if the device is unlocked before the call ends, then none of these limitations apply.

Note that the output directory is not available before the device is unlocked for the first time. Recordings made while in the state are stored in an internal directory that's not accessible by the user. After the device is unlocked, BCR will move the files to the output directory. This may take a few moments to complete.

Permissions

Note that INTERNET is not in the list. BCR does not and will never access the network. BCR will never communicate with other apps either, except if the user explicitly taps on the Open or Share buttons in the notification shown when a recording completes. In that scenario, the target app is granted access to that single recording only.

Call redirection

BCR has limited support for call redirection apps, like Google Voice. Redirected calls can be recorded only if the call redirection service uses standard telephone calls behind the scenes (instead of VOIP).

There are several limitations when recording redirected calls compared to regular calls:

These limitations exist because when a call is redirected, only the dialer app itself is aware of the original phone number. The Android telephony system is not aware of it. BCR can only find the original phone number by searching the system call log when the dialer adds the entry at the end of the call.

Filename template

BCR supports customizing the template used for determining the output filenames of recordings. The default template is:

{date}[_{direction}|][_sim{sim_slot}|][_{phone_number}|][_[{contact_name}|{caller_name}|{call_log_name}]|]

Template syntax

Template variables

Subdirectories

The filename template supports specifying subdirectories using the / character. Slashes are allowed anywhere inside the filename template, including {date} (eg. {date:yyyy/MM/dd}). However, any slashes that appear after expanding other variables will be replaced with underscores. For example, if the caller ID for a call is First/Last, then {caller_name} is expanded to First_Last.

Note that due to Android Storage Access Framework's poor performance, using subdirectories may significantly slow down the saving of the recording on some devices. On Android builds with a good SAF implementation, this may only be a few seconds. On the OEM Android build with the worst known SAF implementation, this could take several minutes. The delay is proportional to the number of files in the output directory.

Metadata file

If the Write metadata file option is enabled, BCR will write a JSON file to the output directory containing all of the details that BCR knows about the call as well as information about the recorded audio. The file has the same name as the audio file, except with a .json extension.

The JSON structure is shown in the following example. Note that only timestamp_unix_ms, timestamp, and output.format.* are guaranteed to exist. If the value for a field can't be determined (eg. when an error occurs or a required permission is denied), then it is set to null.

{
    // The timestamp represented as milliseconds since the Unix epoch in UTC.
    "timestamp_unix_ms": 1689817988931,

    // The timestamp represented as ISO8601 (+ offset) in the local time zone.
    "timestamp": "2023-07-19T21:53:08.931-04:00",

    // The call direction ("in", "out", or "conference").
    // [Android 10+ only]
    "direction": "in",

    // The SIM slot used for the call.
    // [Android 11+ only; requires the Phone permission]
    "sim_slot": 1,

    // The name shown in the dialer's call log. This may include the business'
    // name for dialers that perform reverse lookups.
    // [Requires the Call Log permission]
    "call_log_name": "John Doe",

    // Details about the other party or parties in the call. There will be
    // multiple entries for conference calls.
    "calls": [
        {
            // The raw phone number as reported by Android. For outgoing calls,
            // this is usually what the user typed. For incoming calls, this is
            // usually E.164 formatted. This will be null for private calls.
            "phone_number": "+11234567890",

            // The phone number formatted using the country-specific style. This
            // will be null for private calls or if Android cannot determine the
            // country.
            "phone_number_formatted": "+1 123-456-7890",

            // The caller name/ID as reported by CNAP from the carrier.
            "caller_name": "John Doe",

            // The contact name associated with the phone number.
            // [Requires the Contacts permission]
            "contact_name": "John Doe"
        }
    ],

    // Details about the output file.
    "output": {
        // Details about the output file format.
        "format": {
            // The audio encoding format.
            "type": "OGG/Opus",

            // The MIME type of the container format (eg. OGG).
            "mime_type_container": "audio/ogg",

            // The MIME type of the raw audio stream (eg. Opus).
            "mime_type_audio": "audio/opus",

            // The type of the parameter value below. Either "bitrate",
            // "compression_level", or "none".
            "parameter_type": "bitrate",

            // The encoder quality/size parameter.
            "parameter": 48000,
        },

        // Details about the recording and encoding process. If the recording
        // process fails, this is set to null.
        "recording": {
            // The total number of audio frames that BCR read from the audio
            // device. This includes the periods of time when the recording was
            // paused or on hold.
            // (Number of frames == number of samples * channel count)
            "frames_total": 96000,

            // The number of audio frames that were actually saved to the output
            // file. This excludes the periods of time when the recording was
            // paused or on hold.
            // (Number of frames == number of samples * channel count)
            "frames_encoded": 48000,

            // The number of samples per second of audio.
            "sample_rate": 48000,

            // The number of channels in the audio. This is currently always 1
            // because no device supports stereo call audio.
            "channel_count": 1,

            // The total time in seconds that BCR read from the audio device.
            // (Equal to: frames_total / sample_rate / channel_count)
            "duration_secs_total": 2.0,

            // The time in seconds of audio actually saved to the output file.
            // (Equal to: frames_encoded / sample_rate / channel_count)
            "duration_secs_encoded": 1.0,

            // The size of the recording buffer in frames. This is the maximum
            // number of audio frames read from the audio driver before it is
            // passed to the audio encoder.
            "buffer_frames": 640,

            // The number of buffer overruns. This is the number of times that
            // the CPU or storage couldn't keep up while encoding the raw audio,
            // resulting in skips (loss of audio).
            "buffer_overruns": 0,

            // Whether the call was ever paused by the user.
            "was_ever_paused": false,

            // Whether the call was ever placed on hold (call waiting).
            "was_ever_holding": false
        }
    }
}

Advanced features

This section describes BCR's hidden advanced features.

Debug mode

BCR has a hidden debug mode that can be enabled or disabled by long pressing the version number.

When debug mode is enabled, BCR will write a log file to the output directory after a call recording completes. It is named the same way as the audio file. The log file contains the same messages as what adb logcat would show, except messages not relevant to BCR are filtered out (BCR does not have permission to access those messages anyway).

Within the log file, BCR aims to never log any sensitive information. Information about the current call, like the phone number, are replaced with placeholders instead, like <phone number>. However, other information can't be easily redacted this way will be truncated instead. For example, when the file retention feature cleans up old files, filenames like 20230101_010203.456+0000_out_1234567890_John_Doe.oga are logged as 20<...>ga.

When reporting bugs, please include the log file as it is extremely helpful for identifying what might be wrong. (But please double check the log file to ensure there's no sensitive information!)

How it works

BCR relies heavily on system app permissions in order to function properly. This is primarily because of two permissions:

With these two permissions, BCR can reliably detect phone calls and record from the call's audio stream. The recording process pulls PCM s16le raw audio and uses Android's built-in encoders to produce the compressed output file.

Verifying digital signatures

Both the zip file and the APK contained within are digitally signed. NOTE: The zip file signing mechanism switched from GPG to SSH as of version 1.31. To verify signatures for old versions, see version 1.30's README.md.

Verifying zip file signature

To verify the digital signatures of the downloads, follow the steps here.

Verifying apk signature

First, extract the apk from the zip and then run:

apksigner verify --print-certs system/priv-app/com.chiller3.bcr/app-release.apk

Then, check that the SHA-256 digest of the APK signing certificate is:

d16f9b375df668c58ef4bb855eae959713d6d02e45f7f2c05ce2c27ae944f4f9

Building from source

BCR can be built like most other Android apps using Android Studio or the gradle command line.

To build the APK:

./gradlew assembleDebug

To build the Magisk module zip (which automatically runs the assembleDebug task if needed):

./gradlew zipDebug

The output file is written to app/build/distributions/debug/. The APK will be signed with the default autogenerated debug key.

To create a release build with a specific signing key, set up the following environment variables:

export RELEASE_KEYSTORE=/path/to/keystore.jks
export RELEASE_KEY_ALIAS=alias_name

read -r -s RELEASE_KEYSTORE_PASSPHRASE
read -r -s RELEASE_KEY_PASSPHRASE
export RELEASE_KEYSTORE_PASSPHRASE
export RELEASE_KEY_PASSPHRASE

and then build the release zip:

./gradlew zipRelease

Contributing

Bug fix and translation pull requests are welcome and much appreciated!

If you are interested in implementing a new feature and would like to see it included in BCR, please open an issue to discuss it first. I intend for BCR to be as simple and low-maintenance as possible, so I am not too inclined to add any new features, but I could be convinced otherwise.

License

BCR is licensed under GPLv3. Please see LICENSE for the full license text.