universal7885: Add FMRadio app

* Created from scratch by me (Royna) with help of LineageOS repo [1], Sony Xperia Dev repo [2]
* Basic input/output, channel selection is working

[1]: https://github.com/LineageOS/android_hardware_samsung_slsi_fm
[2]: https://github.com/sonyxperiadev/vendor-qcom-opensource-fm

Change-Id: I922bbeb9f86e6a4c7bc993d27d2f9eeb2a712186
Signed-off-by: roynatech2544 <whiteshell2544@naver.com>
This commit is contained in:
roynatech2544 2022-02-18 15:13:52 +09:00
commit 0519231d7b
No known key found for this signature in database
GPG key ID: 9675C32163D88D30
38 changed files with 2164 additions and 0 deletions

View file

@ -0,0 +1,21 @@
android_app {
name: "FMRadio",
srcs: [
"src/**/*.kt",
],
platform_apis: true,
privileged: true,
certificate: "platform",
jni_libs: ["libfmioctl_jni"],
static_libs: [
"androidx.core_core",
"androidx.appcompat_appcompat",
],
required: ["privapp_whitelist_com.eurekateam.fmradio.xml"],
}
prebuilt_etc {
name: "privapp_whitelist_com.eurekateam.fmradio.xml",
src: "privapp_whitelist_com.eurekateam.fmradio.xml",
sub_dir: "permissions",
}

View file

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.eurekateam.fmradio"
android:sharedUserId="android.uid.system">
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<application
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/OneUI4Theme">
<activity
android:name=".ErrorHandler"
android:exported="false">
<intent-filter>
<action android:name="android.intent.action.HEADSET_PLUG"/>
<action android:name="com.eurekateam.fmradio.RADIO.IO" />
<action android:name="com.eurekateam.fmradio.TRACKS" />
<action android:name="com.eurekateam.fmradio.NO_HEADPHONE" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.HEADSET_PLUG"/>
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".FMRadioService"
android:exported="false">
<intent-filter>
<action android:name="com.eurekateam.fmradio.STARTBG" />
<action android:name="com.eurekateam.fmradio.PLAY_FM"/>
<action android:name="com.eurekateam.fmradio.NEXT"/>
<action android:name="com.eurekateam.fmradio.BEFORE"/>
<action android:name="com.eurekateam.fmradio.QUIT"/>
<action android:name="com.eurekateam.fmradio.STOPBG"/>
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</service>
</application>
</manifest>

View file

@ -0,0 +1,12 @@
cc_library_shared {
name: "libfmioctl_jni",
cflags: [
"-Wall",
"-Wextra",
"-Wno-unused-parameter",
"-Werror",
],
srcs: ["fm_ioctl.cpp"],
header_libs: ["jni_headers"],
}

View file

@ -0,0 +1,65 @@
#define FM_FAILURE -1
#define FM_SUCCESS 0
#define V4L2_CID_USER_S610_BASE (0x00980900 + 0x1070)
enum s610_ctrl_id {
V4L2_CID_S610_CH_SPACING = (V4L2_CID_USER_S610_BASE + 0x01),
V4L2_CID_S610_CH_BAND = (V4L2_CID_USER_S610_BASE + 0x02),
V4L2_CID_S610_SOFT_STEREO_BLEND = (V4L2_CID_USER_S610_BASE + 0x03),
V4L2_CID_S610_SOFT_STEREO_BLEND_COEFF = (V4L2_CID_USER_S610_BASE+0x04),
V4L2_CID_S610_SOFT_MUTE_COEFF = (V4L2_CID_USER_S610_BASE + 0x5),
V4L2_CID_S610_RSSI_CURR = (V4L2_CID_USER_S610_BASE + 0x06),
V4L2_CID_S610_SNR_CURR = (V4L2_CID_USER_S610_BASE + 0x07),
V4L2_CID_S610_SEEK_CANCEL = (V4L2_CID_USER_S610_BASE + 0x08),
V4L2_CID_S610_SEEK_MODE = (V4L2_CID_USER_S610_BASE + 0x09),
V4L2_CID_S610_RDS_ON = (V4L2_CID_USER_S610_BASE + 0x0A),
V4L2_CID_S610_IF_COUNT1 = (V4L2_CID_USER_S610_BASE + 0x0B),
V4L2_CID_S610_IF_COUNT2 = (V4L2_CID_USER_S610_BASE + 0x0C),
V4L2_CID_S610_RSSI_TH = (V4L2_CID_USER_S610_BASE + 0x0D),
V4L2_CID_S610_KERNEL_VER = (V4L2_CID_USER_S610_BASE + 0x0E),
V4L2_CID_S610_SOFT_STEREO_BLEND_REF = (V4L2_CID_USER_S610_BASE+0x0F),
V4L2_CID_S610_REG_RW_ADDR = (V4L2_CID_USER_S610_BASE + 0x10),
V4L2_CID_S610_REG_RW = (V4L2_CID_USER_S610_BASE + 0x11),
};
/* Tunner modes */
enum fm_tuner_mode {
FM_TUNER_STOP_SEARCH_MODE = 0,
FM_TUNER_PRESET_MODE = 1,
FM_TUNER_AUTONOMOUS_SEARCH_MODE = 2,
FM_TUNER_AUTONOMOUS_SEARCH_MODE_NEXT = 10
};
/* channel spacing */
enum fm_channel_spacing {
FM_CHANNEL_SPACING_50KHZ = 1,
FM_CHANNEL_SPACING_100KHZ = 2,
FM_CHANNEL_SPACING_200KHZ = 4
};
/* Mute modes */
enum fm_mute_mode {
FM_MUTE_ON = 0,
FM_MUTE_OFF = 1,
FM_MUTE_ATTENUATE = 2
};
/* FM RDS modes */
enum fm_rds_mode {
FM_RDS_DISABLE = 0,
FM_RDS_ENABLE = 1
};
#define FM_RADIO_RDS_DATA_MAX 48
enum s610_freq_bands {
S610_BAND_FM = 0,
S610_BAND_AM = 1,
};
enum s610_aud_mode {
S610_AUD_ENABLE = 1,
S610_AUD_DISABLE = 0,
};

View file

@ -0,0 +1,484 @@
#include <sys/ioctl.h>
#include <sys/poll.h>
#include <cstdio>
#include <cstdlib>
#include <fcntl.h>
#include <cstring>
#include <unistd.h>
#include <cerrno>
#include <iostream>
#include "fm.h"
#include "kernel_internal.h"
#include <algorithm>
#include <jni.h>
//#define DEBUG
#define TRACK_SIZE 30
long tracks[TRACK_SIZE] = { 0 };
bool FMThread = false;
int open_fm_device(){
int fd;
if ((fd = open("/dev/radio0", O_RDWR)) < 0){
printf("Cannot open /dev/radio0.\n");
return -1;
}
return fd;
}
static int fm_radio_get_frequency(int fd, long *channel)
{
struct v4l2_frequency freq{};
int ret;
freq.tuner = 0;
freq.type = V4L2_TUNER_RADIO;
ret = ioctl(fd, VIDIOC_G_FREQUENCY, &freq);
if (ret < 0) {
printf("FmRadioController: failed to get frequency\n");
return FM_FAILURE;
}
*channel = (long)freq.frequency/16000;
return FM_SUCCESS;
}
static int fm_radio_set_frequency(int fd, long channel)
{
struct v4l2_frequency freq{};
int ret;
freq.tuner = 0;
freq.type = V4L2_TUNER_RADIO;
freq.frequency = (unsigned int) channel * 16000;
ret = ioctl(fd, VIDIOC_S_FREQUENCY, &freq);
if (ret < 0) {
printf("FmRadioController: failed to set frequency\n");
return FM_FAILURE;
}
return FM_SUCCESS;
}
static int fm_radio_get_control(int fd, unsigned int id, long *val)
{
struct v4l2_control ctrl{};
int ret;
#ifdef DEBUG
printf("FmRadioController:fm_radio_get_control: id(%d)\n", id);
#endif
ctrl.id = id;
ret = ioctl(fd, VIDIOC_G_CTRL, &ctrl);
if (ret < 0) {
printf("FmRadioController: failed to get control\n");
return FM_FAILURE;
}
*val = (long)ctrl.value;
return FM_SUCCESS;
}
static int fm_radio_set_control(int fd, unsigned int id, long val)
{
struct v4l2_control ctrl{};
int ret;
#ifdef DEBUG
printf("FmRadioController:fm_radio_set_control: id(%d) val(%ld)\n", id, val);
#endif
ctrl.id = id;
if (val)
ctrl.value = (unsigned int)val;
else
ctrl.value = 0;
ret = ioctl(fd, VIDIOC_S_CTRL, &ctrl);
if (ret < 0) {
printf("FmRadioController: failed to set control\n");
return FM_FAILURE;
}
return FM_SUCCESS;
}
static int fm_radio_seek_frequency(int fd, unsigned int upward, unsigned int wrap_around, unsigned int spacing)
{
struct v4l2_hw_freq_seek seek{};
int ret;
seek.tuner = 0;
seek.type = V4L2_TUNER_RADIO;
seek.seek_upward = upward;
seek.wrap_around = wrap_around;
seek.spacing = spacing;
ret = ioctl(fd, VIDIOC_S_HW_FREQ_SEEK, &seek);
if (ret < 0) {
printf("FmRadioController: failed to seek frequency\n");
return FM_FAILURE;
}
return FM_SUCCESS;
}
static int fm_radio_channel_searching(int fd, unsigned int upward, unsigned int wrap_around, unsigned int spacing, long *channel)
{
int ret;
ret = fm_radio_set_control(fd, V4L2_CID_S610_SEEK_MODE, FM_TUNER_AUTONOMOUS_SEARCH_MODE);
if (ret < 0)
return ret;
ret = fm_radio_seek_frequency(fd, upward, wrap_around, spacing);
if (ret < 0)
return ret;
ret = fm_radio_get_frequency(fd, channel);
if (ret < 0)
return ret;
return ret;
}
static int fm_radio_set_tuner(int fd, unsigned int mode)
{
struct v4l2_tuner tuner{};
int ret;
tuner.index = 0;
tuner.audmode = mode;
tuner.type = 1;
ret = ioctl(fd, VIDIOC_S_TUNER, &tuner);
if (ret < 0) {
printf("FmRadioController: failed to set tuner\n");
return FM_FAILURE;
}
return FM_SUCCESS;
}
static int fm_radio_get_tuner(int fd)
{
struct v4l2_tuner tuner{};
int ret;
tuner.index = 0;
tuner.type = 1;
ret = ioctl(fd, VIDIOC_G_TUNER, &tuner);
if (ret < 0) {
printf("FmRadioController: failed to set tuner\n");
return FM_FAILURE;
}
return tuner.audmode;
}
static int fm_radio_set_mute(int fd, bool mute)
{
int muteint = 1;
if (mute) muteint = 0;
int ret = fm_radio_set_control(fd, V4L2_CID_AUDIO_MUTE, muteint);
if (ret < 0) {
printf("FmRadioController: failed to set mute\n");
return FM_FAILURE;
}
return FM_SUCCESS;
}
static int fm_radio_set_volume(int fd, int volume /* 1 ~ 15 */)
{
int ret = fm_radio_set_control(fd, V4L2_CID_AUDIO_VOLUME, volume);
if (ret < 0) {
printf("FmRadioController: failed to set volume\n");
return FM_FAILURE;
}
return FM_SUCCESS;
}
template<class C, typename T>
bool contains(C&& c, T e) { return std::find(std::begin(c), std::end(c), e) != std::end(c); }
static long fm_radio_get_freqs(int fd){
long ret = 0;
fm_radio_set_mute(fd, true);
for (long & track : tracks){
fm_radio_channel_searching(fd, 1, 0, FM_CHANNEL_SPACING_50KHZ, &ret);
if (contains(tracks, ret)) break;
track = ret;
printf("Found Freq %ld\n", ret);
}
fm_radio_set_mute(fd, false);
return ret;
}
static int fm_radio_poll(int fd, struct pollfd *poll_fd)
{
int ret;
poll_fd->fd = fd;
poll_fd->events = POLLIN;
poll_fd->revents = 0;
ret = poll(poll_fd, 1, 360);
if (ret > 0) {
if (poll_fd->revents & POLLIN) {
printf("FmRadioController: ready to read\n");
return FM_SUCCESS;
}
printf("FmRadioController: cannot read yet\n");
return FM_FAILURE;
}
if (!ret) {
printf("FmRadioController: polling timeout\n");
return FM_FAILURE;
}
printf("FmRadioController: pollig fail: %d\n", ret);
return FM_FAILURE - 1;
}
static int fm_radio_read(int fd, unsigned char *buf)
{
int ret;
ret = read(fd, buf, FM_RADIO_RDS_DATA_MAX);
if (ret < 0) {
printf("FmRadioController: failed to read\n");
return FM_FAILURE;
}
return ret;
}
static int fm_radio_thread(int fd)
{
struct pollfd radio_poll{};
unsigned char read_buf[FM_RADIO_RDS_DATA_MAX];
int ret;
while (FMThread) {
ret = fm_radio_poll(fd, &radio_poll);
if (ret < 0) {
if (ret < FM_FAILURE)
break;
else
continue;
}
ret = fm_radio_read(fd, read_buf);
if (ret < 0)
break;
}
return 0;
}
static unsigned int fm_radio_get_upperband_limit(int fd)
{
int ret;
struct v4l2_tuner tuner{};
unsigned int freq;
tuner.index = 0;
ret = ioctl(fd, VIDIOC_G_TUNER, &tuner);
if(ret < 0) {
return FM_FAILURE;
}else {
freq = (tuner.rangehigh / 16000);
return freq;
}
}
static unsigned int fm_radio_get_lowerband_limit(int fd)
{
int ret;
unsigned int freq;
struct v4l2_tuner tuner{};
tuner.index = 0;
ret = ioctl(fd, VIDIOC_G_TUNER, &tuner);
if(ret < 0) {
return FM_FAILURE;
}else {
freq = (tuner.rangelow / 16000);
return freq;
}
}
static long fm_radio_get_rmssi(int fd)
{
struct v4l2_tuner tuner{};
int ret;
long rmssi;
tuner.index = 0;
tuner.signal = 0;
ret = ioctl(fd, VIDIOC_G_TUNER, &tuner);
if(ret < 0) {
ret = FM_FAILURE;
}else {
rmssi = tuner.signal;
ret = rmssi;
}
return ret;
}
static int fm_radio_set_rssi(int fd, long rssi){
int ret = fm_radio_set_control(fd, V4L2_CID_S610_RSSI_TH, rssi);
if (ret < 0){
return FM_FAILURE;
}
return FM_SUCCESS;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_openFMDevice
(__unused JNIEnv *env, __unused jobject thiz) {
return open_fm_device();
}
extern "C"
JNIEXPORT jlong JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getFMFreq
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
long freq;
fm_radio_get_frequency(fd, &freq);
return freq;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMFreq
(__unused JNIEnv *env, __unused jobject thiz, jint fd, jint freq) {
return fm_radio_set_frequency(fd, freq);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMVolume
(__unused JNIEnv *env, __unused jobject thiz, jint fd, jint volume) {
return fm_radio_set_volume(fd, volume);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMMute
(__unused JNIEnv *env, __unused jobject thiz, jint fd, jboolean mute) {
return fm_radio_set_mute(fd, mute);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getFmUpper
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
return fm_radio_get_upperband_limit(fd);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getFMLower
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
return fm_radio_get_lowerband_limit(fd);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getRMSSI
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
return fm_radio_get_rmssi(fd);
}
extern "C"
JNIEXPORT jlongArray JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getFMTracks
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
fm_radio_get_freqs(fd);
jlongArray result;
result = (*env).NewLongArray(TRACK_SIZE);
if (result == nullptr) {
return nullptr; /* out of memory error thrown */
}
int i;
// fill a temp structure to use to populate the java int array
jlong fill[TRACK_SIZE];
for (i = 0; i < TRACK_SIZE; i++) {
fill[i] = tracks[i]; // put whatever logic you want to populate the values here.
}
// move from the temp structure to the java structure
(*env).SetLongArrayRegion(result, 0, TRACK_SIZE, fill);
return result;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMStereo
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
return fm_radio_set_tuner(fd, 1);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMMono
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
return fm_radio_set_tuner(fd, 0);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMThread
(__unused JNIEnv *env, __unused jobject thiz, jint fd, jboolean run) {
if (run){
FMThread = true;
fm_radio_thread(fd);
}else{
FMThread = false;
}
return FM_SUCCESS;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMBoot
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
fm_radio_set_control(fd, V4L2_CID_S610_IF_COUNT1, 4800); // SetIFCount 1
fm_radio_set_control(fd, V4L2_CID_S610_IF_COUNT2, 5600); // SetIFCount 2
fm_radio_set_control(fd, V4L2_CID_S610_SOFT_STEREO_BLEND, 3172); // Set Soft Stereo Blend
fm_radio_set_control(fd, V4L2_CID_S610_SOFT_MUTE_COEFF, 16); // SetSoftMuteCoeff
fm_radio_set_control(fd, V4L2_CID_S610_CH_BAND, S610_BAND_FM); // Set Band (To FM)
fm_radio_set_control(fd, V4L2_CID_S610_CH_SPACING, FM_CHANNEL_SPACING_50KHZ); // Spacing 5kHz
fm_radio_set_control(fd, V4L2_CID_S610_RDS_ON, FM_RDS_ENABLE); // RDS on
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getNextChannel
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
long ret;
fm_radio_channel_searching(fd, 1, 0, FM_CHANNEL_SPACING_100KHZ, &ret);
return ret;
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getBeforeChannel
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
long ret;
fm_radio_channel_searching(fd, 0, 0, FM_CHANNEL_SPACING_100KHZ, &ret);
return ret;
}
extern "C"
JNIEXPORT jboolean JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_getAudioChannel
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
int channel = fm_radio_get_tuner(fd);
if (channel == V4L2_TUNER_MODE_STEREO){
return true; // Is EarPhones
}else{
return false; // Is Speaker
}
}
extern "C"
JNIEXPORT void JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_stopSearching
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
fm_radio_set_control(fd, V4L2_CID_S610_SEEK_CANCEL, 1);
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_setFMRSSI
(__unused JNIEnv *env, __unused jobject thiz, jint fd, jlong rssi) {
return fm_radio_set_rssi(fd, rssi);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_eurekateam_fmradio_NativeFMInterface_closeFMDevice
(__unused JNIEnv *env, __unused jobject thiz, jint fd) {
close(fd);
}

View file

@ -0,0 +1,125 @@
typedef u_int8_t __u8;
typedef int32_t __s32;
typedef u_int32_t __u32;
struct v4l2_tuner {
__u32 index;
__u8 name[32];
__u32 type; /* enum v4l2_tuner_type */
__u32 capability;
__u32 rangelow;
__u32 rangehigh;
__u32 rxsubchans;
__u32 audmode;
__s32 signal;
__s32 afc;
__u32 reserved[4];
};
struct v4l2_frequency {
__u32 tuner;
__u32 type; /* enum v4l2_tuner_type */
__u32 frequency;
__u32 reserved[8];
};
struct v4l2_hw_freq_seek {
__u32 tuner;
__u32 type; /* enum v4l2_tuner_type */
__u32 seek_upward;
__u32 wrap_around;
__u32 spacing;
__u32 rangelow;
__u32 rangehigh;
__u32 reserved[5];
};
enum v4l2_tuner_type {
V4L2_TUNER_RADIO = 1,
V4L2_TUNER_ANALOG_TV = 2,
V4L2_TUNER_DIGITAL_TV = 3,
V4L2_TUNER_SDR = 4,
V4L2_TUNER_RF = 5,
};
struct v4l2_control {
__u32 id;
__s32 value;
};
/**
* struct v4l2_capability - Describes V4L2 device caps returned by VIDIOC_QUERYCAP
*
* @driver: name of the driver module (e.g. "bttv")
* @card: name of the card (e.g. "Hauppauge WinTV")
* @bus_info: name of the bus (e.g. "PCI:" + pci_name(pci_dev) )
* @version: KERNEL_VERSION
* @capabilities: capabilities of the physical device as a whole
* @device_caps: capabilities accessed via this particular device (node)
* @reserved: reserved fields for future extensions
*/
struct v4l2_capability {
__u8 driver[16];
__u8 card[32];
__u8 bus_info[32];
__u32 version;
__u32 capabilities;
__u32 device_caps;
__u32 reserved[3];
};
/*
* T I M E C O D E
*/
struct v4l2_timecode {
__u32 type;
__u32 flags;
__u8 frames;
__u8 seconds;
__u8 minutes;
__u8 hours;
__u8 userbits[4];
};
struct v4l2_buffer {
__u32 index;
__u32 type;
__u32 bytesused;
__u32 flags;
__u32 field;
struct timeval timestamp;
struct v4l2_timecode timecode;
__u32 sequence;
/* memory location */
__u32 memory;
union {
__u32 offset;
unsigned long userptr;
struct v4l2_plane *planes;
__s32 fd;
} m;
__u32 length;
__u32 reserved2;
__u32 reserved;
};
#define V4L2_TUNER_MODE_MONO 0x0000
#define V4L2_TUNER_MODE_STEREO 0x0001
// Ioctl
#define VIDIOC_G_FREQUENCY _IOWR('V', 56, struct v4l2_frequency)
#define VIDIOC_S_FREQUENCY _IOW('V', 57, struct v4l2_frequency)
#define VIDIOC_G_TUNER _IOWR('V', 29, struct v4l2_tuner)
#define VIDIOC_S_HW_FREQ_SEEK _IOW('V', 82, struct v4l2_hw_freq_seek)
#define VIDIOC_S_TUNER _IOW('V', 30, struct v4l2_tuner)
#define VIDIOC_S_CTRL _IOWR('V', 28, struct v4l2_control)
#define VIDIOC_QUERYCAP _IOR('V', 0, struct v4l2_capability)
#define VIDIOC_G_CTRL _IOWR('V', 27, struct v4l2_control)
#define V4L2_CTRL_CLASS_USER 0x00980000 /* Old-style 'user' controls */
#define V4L2_CID_BASE (V4L2_CTRL_CLASS_USER | 0x900)
#define V4L2_CID_AUDIO_VOLUME (V4L2_CID_BASE+5)
#define V4L2_CID_AUDIO_MUTE (V4L2_CID_BASE+9)

View file

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2021 Eureka Team
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<permissions>
<privapp-permissions package="com.eurekateam.fmradio">
<permission name="android.permission.MODIFY_AUDIO_SETTINGS"/>
</privapp-permissions>
</permissions>

Binary file not shown.

After

Width:  |  Height:  |  Size: 452 B

View file

@ -0,0 +1,12 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24.0dp"
android:height="24.0dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11,2h2v10h-2z"/>
<path
android:fillColor="#FF000000"
android:pathData="M18.37,5.64l-1.41,1.41c2.73,2.73 2.72,7.16 -0.01,9.89c-2.73,2.73 -7.17,2.73 -9.89,0.01c-2.73,-2.73 -2.74,-7.18 -0.01,-9.91L5.64,5.64c-3.51,3.51 -3.51,9.21 0.01,12.73c3.51,3.51 9.21,3.51 12.72,-0.01C21.88,14.85 21.88,9.16 18.37,5.64z"/>
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 457 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 305 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 144 KiB

View file

@ -0,0 +1,310 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="top|center"
android:fitsSystemWindows="true"
tools:context=".MainActivity">
<TextView
android:id="@+id/debug_info"
android:layout_width="190dp"
android:layout_height="30dp"
android:gravity="center" />
<Space
android:layout_width="match_parent"
android:layout_height="10dp" />
<de.dlyt.yanndroid.oneui.layout.RoundLinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/primary_color"
android:gravity="center">
<Space
android:layout_width="10dp"
android:layout_height="match_parent"/>
<ImageView
android:layout_width="100dp"
android:layout_height="100dp"
android:src="@drawable/radio"/>
<TextView
android:id="@+id/fm_radio_title"
android:layout_width="240dp"
android:layout_height="120dp"
android:gravity="center"
android:text="@string/fm_radio"
android:textColor="#263238"
android:textSize="48sp"
android:textStyle="bold" />
<Space
android:layout_width="10dp"
android:layout_height="match_parent" />
</de.dlyt.yanndroid.oneui.layout.RoundLinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="15dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center">
<de.dlyt.yanndroid.oneui.layout.RoundLinearLayout
android:layout_width="wrap_content"
android:layout_height="140dp"
android:orientation="vertical"
android:background="@color/primary_color"
android:gravity="center|top">
<TextView
android:id="@+id/output_modes"
android:layout_width="185dp"
android:layout_height="70dp"
android:gravity="center"
android:text="@string/output_modes"
android:textColor="#263238"
android:textSize="25sp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="10dp"/>
<Button
android:id="@+id/fm_output"
android:layout_width="175dp"
android:layout_height="50dp"
android:text="@string/wired_earphones"
android:textColor="#263238"
android:theme="@style/ButtonStyle.Colored"
android:textSize="18sp" />
</de.dlyt.yanndroid.oneui.layout.RoundLinearLayout>
<Space
android:layout_width="10dp"
android:layout_height="match_parent"/>
<de.dlyt.yanndroid.oneui.layout.RoundLinearLayout
android:layout_width="wrap_content"
android:layout_height="140dp"
android:orientation="vertical"
android:background="@color/primary_color"
android:gravity="center|top">
<TextView
android:id="@+id/enable_fm"
android:layout_width="185dp"
android:layout_height="70dp"
android:gravity="center"
android:text="@string/turn_on_fm"
android:textColor="#263238"
android:textSize="25sp"
android:textStyle="bold" />
<Space
android:layout_width="match_parent"
android:layout_height="10dp"/>
<Button
android:id="@+id/fmswitch"
android:layout_width="175dp"
android:layout_height="50dp"
android:text="@string/disabled"
android:textColor="#263238"
android:textSize="18sp"
android:theme="@style/ButtonStyle.Colored" />
</de.dlyt.yanndroid.oneui.layout.RoundLinearLayout>
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="20dp" />
<de.dlyt.yanndroid.oneui.layout.RoundLinearLayout
android:layout_width="wrap_content"
android:layout_height="230dp"
android:background="@color/primary_color"
android:orientation="vertical"
android:gravity="center|top">
<Space
android:layout_width="match_parent"
android:layout_height="20dp"/>
<TextView
android:id="@+id/freq_title"
android:layout_width="375dp"
android:layout_height="50dp"
android:text="@string/fm_radio_frequency"
android:textColor="#263238"
android:textSize="35sp"
android:textStyle="bold"
android:gravity="center"/>
<Space
android:layout_width="match_parent"
android:layout_height="10dp"/>
<TextView
android:id="@+id/band_select"
android:layout_width="375dp"
android:layout_height="50dp"
android:text="@string/select_channel"
android:textColor="#263238"
android:textSize="25sp"
android:gravity="center"/>
<Space
android:layout_width="match_parent"
android:layout_height="20dp"/>
<FrameLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@color/secondary_color">
<de.dlyt.yanndroid.oneui.view.SeekBar
android:id="@+id/fm_freq_seekbar"
android:layout_width="325dp"
android:layout_height="50dp"
android:progressBackgroundTint="@color/white"
android:thumbTint="#263238"
android:progressTint="#263238"/>
</FrameLayout>
</de.dlyt.yanndroid.oneui.layout.RoundLinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="20dp" />
<de.dlyt.yanndroid.oneui.layout.RoundLinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center"
android:orientation="vertical"
android:background="@color/primary_color">
<Space
android:layout_width="match_parent"
android:layout_height="15dp" />
<TextView
android:id="@+id/radio_ctrl"
android:layout_width="375dp"
android:layout_height="50dp"
android:text="@string/radio_controls"
android:textColor="#263238"
android:textSize="35sp"
android:textStyle="bold"
android:gravity="center"/>
<Space
android:layout_width="match_parent"
android:layout_height="15dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center">
<Space
android:layout_width="15dp"
android:layout_height="match_parent" />
<Button
android:id="@+id/scan_through"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="@string/scan"
android:textColor="#263238"
android:theme="@style/ButtonStyle.Colored"/>
<Space
android:layout_width="15dp"
android:layout_height="match_parent" />
<Button
android:id="@+id/stop_scan"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="@string/stop"
android:textColor="#263238"
android:theme="@style/ButtonStyle.Colored" />
<Space
android:layout_width="15dp"
android:layout_height="match_parent" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="15dp" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="top|center">
<Space
android:layout_width="15dp"
android:layout_height="match_parent" />
<Button
android:id="@+id/seek_down"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="@string/seek_down"
android:textColor="#263238"
android:theme="@style/ButtonStyle.Colored"/>
<Space
android:layout_width="15dp"
android:layout_height="match_parent" />
<Button
android:id="@+id/quit_fm"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="@string/quit"
android:textColor="#263238"
android:theme="@style/ButtonStyle.Colored"/>
<Space
android:layout_width="15dp"
android:layout_height="match_parent" />
<Button
android:id="@+id/seek_up"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:text="@string/seek_up"
android:textColor="#263238"
android:theme="@style/ButtonStyle.Colored" />
<Space
android:layout_width="15dp"
android:layout_height="match_parent" />
</LinearLayout>
<Space
android:layout_width="match_parent"
android:layout_height="15dp" />
</de.dlyt.yanndroid.oneui.layout.RoundLinearLayout>
</LinearLayout>

View file

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:background="@drawable/warning">
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:layout_gravity="center">
<ImageView
android:id="@+id/error_img"
android:layout_width="75dp"
android:layout_height="75dp"
android:layout_gravity="start|center_vertical"
android:background="@drawable/ic_samsung_error"
android:translationZ="0dp" />
<Space
android:layout_width="25dp"
android:layout_height="match_parent"/>
<TextView
android:id="@+id/error_simple"
android:layout_width="300dp"
android:layout_height="75dp"
android:gravity="center"
android:textColor="#00838F"
android:textSize="18sp"/>
</LinearLayout>
<TextView
android:id="@+id/error_detailed"
android:layout_width="match_parent"
android:layout_height="202dp"
android:textColor="#00838F"
android:textSize="16sp"
android:gravity="center"/>
</LinearLayout>

View file

@ -0,0 +1,58 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="64dp"
android:gravity="center_vertical"
android:orientation="horizontal">
<TextView
android:id="@+id/txt_title"
android:layout_width="0dp"
android:layout_height="50dp"
android:layout_marginStart="5dp"
android:layout_weight="1"
android:gravity="center"
android:background="@android:color/white"
android:textColor="@android:color/black"
android:textSize="20sp"/>
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white">
<ImageButton
android:id="@+id/btn_rewind"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:background="@null"
android:contentDescription="@string/previous_radio_channel"
android:src="@drawable/left"/>
<ImageButton
android:id="@+id/btn_play_pause"
android:layout_width="wrap_content"
android:layout_height="48dp"
android:background="@null"
android:contentDescription="@string/play_pause"
android:src="@drawable/stop" />
<ImageButton
android:id="@+id/btn_forward"
android:layout_width="wrap_content"
android:layout_height="50dp"
android:background="@null"
android:contentDescription="@string/next_radio_channel"
android:src="@drawable/right"/>
</LinearLayout>
<ImageButton
android:id="@+id/btn_close"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@android:color/white"
android:contentDescription="@string/close_radio"
android:minWidth="32dp"
android:minHeight="48dp"
android:src="@drawable/power" />
</LinearLayout>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View file

@ -0,0 +1,34 @@
<resources>
<string name="app_name">FM Radio</string>
<string name="fm_radio">FM Radio</string>
<string name="output_modes">Output Modes</string>
<string name="turn_on_fm">FM Power</string>
<string name="disabled">Disabled</string>
<string name="enabled">Enabled</string>
<string name="phone_speaker">Speaker</string>
<string name="wired_earphones">Wired Earphones</string>
<string name="select_channel">Radio Channel</string>
<string name="refresh">Refresh</string>
<string name="seek_down">Seek Down</string>
<string name="seek_up">Seek Up</string>
<string name="scan">Scan</string>
<string name="stop">Stop</string>
<string name="mhz">%s Mhz</string>
<string name="quit">Quit</string>
<string name="album_art">Album Art</string>
<string name="previous_radio_channel">Previous Radio Channel</string>
<string name="next_radio_channel">Next Radio Channel</string>
<string name="play_pause">Play Pause</string>
<string name="close_radio">Close Radio</string>
<string name="radio_io_error">Failed to open radio chr device!!</string>
<string name="radio_io_error_desc">The FM radio app needs to call ioctl()
to radio chr device (/dev/radio0). If SELinux is enforcing, try making
it permissive</string>
<string name="tracks_empty_error">Failed to check if array != null!!</string>
<string name="tracks_empty_error_desc">Most likely because FM device couldn\'t
find radio channels, if you are sure it should find channels, contact the devs</string>
<string name="no_headphones_error">Failed to find wired headset</string>
<string name="no_headphones_error_desc">Insert Headphones and try again</string>
<string name="radio_controls">Radio Controls</string>
<string name="fm_radio_frequency">FM Radio Station</string>
</resources>

View file

@ -0,0 +1,93 @@
package com.eurekateam.fmradio
import android.app.Activity
import android.app.Service
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioDeviceInfo
import android.media.AudioManager
import android.os.Bundle
import android.widget.TextView
import android.widget.Toast
class ErrorHandler: Activity() {
private lateinit var mErrorSimple: TextView
private lateinit var mErrorDetail: TextView
private var mHeadPhonesPresent = false
private var mRegistered = false
private var fd = 0
private lateinit var mContext: Context
private lateinit var mAudioManager : AudioManager
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.error_screen)
mContext = this
fd = intent.getIntExtra("fd", -1)
mAudioManager = getSystemService(Service.AUDIO_SERVICE) as AudioManager
mErrorDetail = findViewById(R.id.error_detailed)
mErrorSimple = findViewById(R.id.error_simple)
val mAudioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
for (i in mAudioDeviceInfo.indices){
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES){
mHeadPhonesPresent = true
}
}
when (intent?.action){
RADIO_DEVICE_IO_FAIL -> {
mErrorSimple.text = getString(R.string.radio_io_error)
mErrorDetail.text = getString(R.string.radio_io_error_desc)
}
TRACKS_EMPTY -> {
mErrorSimple.text = getString(R.string.tracks_empty_error)
mErrorDetail.text = getString(R.string.tracks_empty_error_desc)
}
NO_WIRED_HEADPHONES -> {
mErrorSimple.text = getString(R.string.no_headphones_error)
mErrorDetail.text = getString(R.string.no_headphones_error_desc)
mHeadPhonesPresent = false
val receiverFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
val receiver = mWiredHeadsetReceiver
mHeadPhonesPresent = false
registerReceiver(receiver, receiverFilter)
mRegistered = true
}
}
}
private val mWiredHeadsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AudioManager.ACTION_HEADSET_PLUG) {
mHeadPhonesPresent = false
val mAudioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
for (i in mAudioDeviceInfo.indices){
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES){
mHeadPhonesPresent = true
}
}
if (mHeadPhonesPresent){
Toast.makeText(mContext, "Wired headphones inserted", Toast.LENGTH_SHORT).show()
startActivity(Intent(mContext, MainActivity::class.java))
}
}
}
}
override fun onPause() {
super.onPause()
if (mRegistered)
unregisterReceiver(mWiredHeadsetReceiver)
}
companion object {
private const val PACKAGENAME = "com.eurekateam.fmradio"
// Failures
private const val RADIO_DEVICE_IO_FAIL = "${PACKAGENAME}.RADIO.IO"
private const val TRACKS_EMPTY = "${PACKAGENAME}.TRACKS"
private const val NO_WIRED_HEADPHONES = "${PACKAGENAME}.NO_HEADPHONE"
}
}

View file

@ -0,0 +1,207 @@
package com.eurekateam.fmradio
import android.app.*
import android.content.Context
import android.content.Intent
import android.media.AudioManager
import android.os.IBinder
import android.util.Log
import android.widget.RemoteViews
import androidx.core.app.NotificationCompat
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import java.io.OutputStream
class FMRadioService : Service() {
private lateinit var mContext: Context
private lateinit var mAudioManager: AudioManager
private var mIsPlaying = true
private lateinit var mTracks : LongArray
private var mTitle : String = ""
private var fd : Int = -1
private var mIndex = -1
private lateinit var mNativeFMInterface: NativeFMInterface
private lateinit var pendingIntent: PendingIntent
private lateinit var contentView : RemoteViews
private lateinit var mFilePath : File
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
if (intent != null) {
var mShutDown = false
Log.i(TAG, "onStartCommand: Received ${intent.action}")
when (intent.action) {
BEGIN_BG_SERVICE -> {
mContext = this
mFilePath = mContext.filesDir
contentView = RemoteViews(mContext.packageName, R.layout.notification)
mNativeFMInterface = NativeFMInterface()
fd = intent.getIntExtra("fd", -1)
mTracks = intent.getLongArrayExtra("tracks")!!
mIndex = intent.getIntExtra("CurrentIndex", -1)
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000} Mhz"
contentView.setImageViewResource(
R.id.btn_play_pause,
R.drawable.stop
)
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
setupRemoteView()
// Create an explicit intent for an Activity in your app
val onClickIntent = Intent(this, FMRadioService::class.java).apply {
Intent.FLAG_ACTIVITY_NEW_TASK
}
pendingIntent = PendingIntent.getActivity(
this,
0,
onClickIntent,
PendingIntent.FLAG_IMMUTABLE
)
mIsPlaying = true
startForeground(51, pushNotification())
}
ACTION_PLAY -> {
mIsPlaying = !mIsPlaying
mTitle =
if (mIsPlaying) {
"FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
} else {
"FM ${mTracks[mIndex].toFloat() / 1000}Mhz - Stopped"
}
if (mIsPlaying) {
contentView.setImageViewResource(
R.id.btn_play_pause,
R.drawable.stop
)
writeToFile("fm_radio_state", "1")
mAudioManager.setParameters(FM_RADIO_ON)
} else {
mAudioManager.setParameters(FM_RADIO_OFF)
contentView.setImageViewResource(
R.id.btn_play_pause,
R.drawable.start
)
writeToFile("fm_radio_state", "0")
}
}
ACTION_BEFORE -> {
mIsPlaying = true
contentView.setImageViewResource(
R.id.btn_play_pause,
R.drawable.stop
)
Log.i(TAG, "onStartCommand: mCurrentIndex $mIndex")
if (mIndex > 0)
mIndex -= 1
if (mIndex >= 1)
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
mTitle = if (mIsPlaying) {
"FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
} else {
"FM ${mTracks[mIndex].toFloat() / 1000}Mhz - Stopped"
}
}
ACTION_NEXT -> {
mIsPlaying = true
contentView.setImageViewResource(
R.id.btn_play_pause,
R.drawable.stop
)
Log.i(TAG, "onStartCommand: mCurrentIndex $mIndex")
if (mIndex < mTracks.size - 1)
mIndex += 1
if (mIndex < mTracks.size - 1)
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
mTitle = if (mIsPlaying) {
"FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
} else {
"FM ${mTracks[mIndex].toFloat() / 1000}Mhz - Stopped"
}
}
ACTION_QUIT -> {
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
mAudioManager.setParameters(FM_RADIO_OFF)
stopForeground(true)
mShutDown = true
}
STOP_BG_SERVICE -> {
val mFreqToWrite = mTracks[mIndex].toString()
writeToFile(
"fm_radio_freq_current",
mFreqToWrite
)
}
else -> {
throw IllegalAccessException("Unexpected Intent!")
}
}
Log.i(TAG, "onStartCommand: mTitle=$mTitle")
if (!mShutDown) {
contentView.setTextViewText(R.id.txt_title, mTitle)
startForeground(51,pushNotification())
}
}
return START_STICKY
}
private fun writeToFile(fileName : String, data: String){
var os: OutputStream? = null
val mData = data.padStart(6,'0')
try {
os = FileOutputStream(File(mFilePath.absolutePath + "/" + fileName))
os.write(mData.toByteArray(), 0, mData.length)
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
os?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun setupRemoteView() {
val actionTogglePlay = Intent(ACTION_PLAY)
val actionForward = Intent(ACTION_NEXT)
val actionRewind = Intent(ACTION_BEFORE)
val actionClose = Intent(ACTION_QUIT)
val togglePlay = PendingIntent.getService(mContext, 0, actionTogglePlay, PendingIntent.FLAG_IMMUTABLE)
val forward = PendingIntent.getService(mContext, 0, actionForward, PendingIntent.FLAG_IMMUTABLE)
val rewind = PendingIntent.getService(mContext, 0, actionRewind, PendingIntent.FLAG_IMMUTABLE)
val close = PendingIntent.getService(mContext, 0, actionClose, PendingIntent.FLAG_IMMUTABLE)
contentView.setOnClickPendingIntent(R.id.btn_play_pause, togglePlay)
contentView.setOnClickPendingIntent(R.id.btn_forward, forward)
contentView.setOnClickPendingIntent(R.id.btn_rewind, rewind)
contentView.setOnClickPendingIntent(R.id.btn_close, close)
}
private fun pushNotification(): Notification {
val nm = mContext.getSystemService(NOTIFICATION_SERVICE) as NotificationManager
val channel = NotificationChannel(
mContext.packageName, "FM Radio Playing",
NotificationManager.IMPORTANCE_NONE
)
nm.createNotificationChannel(channel)
val builder = NotificationCompat.Builder(mContext, mContext.packageName)
builder.setSmallIcon(R.drawable.radio_text)
builder.setOngoing(true)
builder.setCustomBigContentView(contentView)
builder.setContentIntent(pendingIntent)
builder.setChannelId(mContext.packageName)
return builder.build()
}
companion object {
private const val PACKAGENAME = "com.eurekateam.fmradio"
private const val BEGIN_BG_SERVICE = "$PACKAGENAME.STARTBG"
private const val ACTION_PLAY = "$PACKAGENAME.PLAY_FM"
private const val ACTION_NEXT = "$PACKAGENAME.NEXT"
private const val ACTION_BEFORE = "$PACKAGENAME.BEFORE"
private const val ACTION_QUIT = "$PACKAGENAME.QUIT"
private const val FM_RADIO_ON = "l_fmradio_mode=on"
private const val FM_RADIO_OFF = "l_fmradio_mode=off"
private const val TAG = "FMRadioService"
private const val STOP_BG_SERVICE = "${PACKAGENAME}.STOPBG"
}
}

View file

@ -0,0 +1,610 @@
package com.eurekateam.fmradio
import android.bluetooth.BluetoothAdapter
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.media.AudioAttributes
import android.media.AudioDeviceInfo
import android.media.AudioFocusRequest
import android.media.AudioManager
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.os.SystemClock
import android.util.Log
import android.view.KeyEvent
import android.view.View
import android.widget.Button
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import de.dlyt.yanndroid.oneui.view.SeekBar
import java.io.*
import java.text.DecimalFormat
class MainActivity : AppCompatActivity(),
View.OnClickListener, SeekBar.OnSeekBarChangeListener, AudioManager.OnAudioFocusChangeListener {
private lateinit var mAudioManager: AudioManager
private lateinit var mStreamSwitch : Button
private lateinit var mFMSwitch : Button
private lateinit var mSeekUpBtn: Button
private lateinit var mSeekDownBtn : Button
private lateinit var mBandSelect : TextView
private var mFMThread: Thread? = null
private var fd = -1
private var mCurrentFreq = 0
private lateinit var mNativeFMInterface: NativeFMInterface
private lateinit var tracks : LongArray
private lateinit var mContext : Context
private lateinit var mTitle : TextView
private lateinit var mScanBtn : Button
private lateinit var mScanStopBtn : Button
private lateinit var mSeekBar: SeekBar
private var mCurrentIndex = 0
private val format: DecimalFormat = DecimalFormat("0.#")
private var mVolume : Int = 0
private lateinit var scanThread: Thread
private var mScanThread: Thread? = null
private lateinit var mIntent: Intent
private var mServiceRegistered = false
private lateinit var mFilePath : File
private var mLastBackPress: Long = 0L
private var mScanning = false
private lateinit var mErrorIntent : Intent
private var mFreqInitialized = false
private lateinit var mDebugStringThread: Thread
private lateinit var mDebugInfo : TextView
private var mDebugThread : Thread? = null
private lateinit var mQuitButton: Button
private var mQuit = false
private var mHeadSetPlugged : Boolean = false
private val mMainHandler : Handler = Handler(Looper.getMainLooper())
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
System.loadLibrary("fmradioioctl_jni")
setContentView(R.layout.activity_main)
mContext = this
mFilePath = mContext.filesDir
mNativeFMInterface = NativeFMInterface()
mIntent = Intent(mContext, FMRadioService::class.java)
mErrorIntent = Intent(mContext, ErrorHandler::class.java)
mErrorIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
mCurrentFreq = mNativeFMInterface.getFMLower(fd)
mTitle = findViewById(R.id.fm_radio_title)
mStreamSwitch = findViewById(R.id.fm_output)
mFMSwitch = findViewById(R.id.fmswitch)
mSeekBar = findViewById(R.id.fm_freq_seekbar)
mSeekDownBtn = findViewById(R.id.seek_down)
mSeekUpBtn = findViewById(R.id.seek_up)
mBandSelect = findViewById(R.id.band_select)
mScanBtn = findViewById(R.id.scan_through)
mScanStopBtn = findViewById(R.id.stop_scan)
mDebugInfo = findViewById(R.id.debug_info)
mQuitButton = findViewById(R.id.quit_fm)
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
mStreamSwitch.setOnClickListener(this)
mFMSwitch.setOnClickListener(this)
mSeekDownBtn.setOnClickListener(this)
mSeekUpBtn.setOnClickListener(this)
mQuitButton.setOnClickListener(this)
mScanBtn.setOnClickListener(this)
mScanStopBtn.isEnabled = false
mStreamSwitch.text = if(!mAudioManager.isSpeakerphoneOn)
getString(R.string.wired_earphones) else getString(R.string.phone_speaker)
val receiverFilter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
val mBtFilter = IntentFilter(BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED)
registerReceiver(mBtHeadsetReceiver, mBtFilter)
registerReceiver(mWiredHeadsetReceiver, receiverFilter)
val mAudioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
for (i in mAudioDeviceInfo.indices){
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
mHeadSetPlugged = true
Log.i(TAG, "onCreate: Wired Headphones detected")
}
}
fd = mNativeFMInterface.openFMDevice()
if (fd == -1){
Log.e(TAG, "onCreate: CANNOT OPEN /dev/radio0!!!")
mErrorIntent.action = RADIO_DEVICE_IO_FAIL
startActivity(mErrorIntent)
finish()
}
mErrorIntent.putExtra("fd", fd)
if (!mHeadSetPlugged){
Toast.makeText(mContext, "Wired Headphones not detected", Toast.LENGTH_SHORT).show()
Thread{
Thread.sleep(1500)
mainExecutor.execute {
mErrorIntent.action = NO_WIRED_HEADPHONES
mNativeFMInterface.setFMMute(fd, true)
startActivity(mErrorIntent)
finish()
}
}.start()
}
mScanStopBtn.setOnClickListener(this)
if (File(mFilePath.absolutePath + "/fm_radio_volume").exists()) {
mVolume = if (readFromFile("fm_radio_volume").isEmpty()){
10
}else{
readFromFile("fm_radio_volume").toInt()
}
}
Thread{
tracks = mNativeFMInterface.getFMTracks(fd)
tracks = removeZeros(tracks)
val mPreFreq = if (File(mFilePath.absolutePath + "/fm_radio_freq_current").exists()){
readFromFile("fm_radio_freq_current")
}else{
tracks[0].toString()
}
if (DEBUG) Log.i(TAG, "onCreate: file value = $mPreFreq")
for (i in tracks.indices){
if (tracks[i] == mPreFreq.toLong()){
mNativeFMInterface.setFMFreq(fd,mPreFreq.toInt())
mBandSelect.text = String.format(getString(R.string.mhz), mPreFreq.toFloat() / 1000)
mCurrentFreq = mPreFreq.toInt()
mCurrentIndex = i
break
}
}
if (File(mFilePath.absolutePath + "/fm_radio_state").exists()){
if(readFromFile("fm_radio_state") == "1"){
mainExecutor.execute {
mFMSwitch.text = getString(R.string.enabled)
}
mAudioManager.setParameters(FM_RADIO_ON)
mNativeFMInterface.setFMBoot(fd)
mNativeFMInterface.setFMFreq(fd, mCurrentFreq)
mFMThread = Thread {
mNativeFMInterface.setFMThread(fd,true)
}
mFMThread!!.start()
mDebugThread = Thread(mDebugStringThread)
mDebugThread!!.start()
}else{
mainExecutor.execute {
mFMSwitch.text = getString(R.string.disabled)
}
mFMThread = null
mDebugThread = null
mAudioManager.setParameters(FM_RADIO_OFF)
mNativeFMInterface.setFMThread(fd,false)
}
}
mSeekBar.max = tracks.size - 1
mSeekBar.min = 0
mSeekBar.progress = mCurrentIndex
mSeekBar.setOnSeekBarChangeListener(this)
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
if (DEBUG)
Log.d(TAG, "onCreate: reading $mFilePath/headphones")
if (File("$mFilePath/headphones").exists()){
mAudioManager.isSpeakerphoneOn = false
mStreamSwitch.text = getString(R.string.wired_earphones)
}else{
mAudioManager.isSpeakerphoneOn = true
mStreamSwitch.text = getString(R.string.phone_speaker)
}
mFreqInitialized = true
}.start()
mFMThread = Thread {
mNativeFMInterface.setFMThread(fd,true)
}
scanThread = Thread {
mFreqInitialized = false
val mOriginalIndex = mCurrentIndex
mainExecutor.execute {
mScanStopBtn.isEnabled = true
mSeekBar.isEnabled = false
}
var count = 0
mNativeFMInterface.setFMMute(fd, false)
while (count < tracks.size && mScanning) {
mNativeFMInterface.setFMFreq(fd, tracks[count].toInt())
mainExecutor.execute {
mBandSelect.text = String.format(getString(R.string.mhz), format.format(tracks[count].toFloat() / 1000))
mSeekBar.progress = mCurrentIndex
}
mCurrentFreq = tracks[count].toInt()
mCurrentIndex = count
Thread.sleep(1500)
count++
}
mainExecutor.execute {
mScanBtn.isEnabled = true
mScanStopBtn.isEnabled = false
mSeekBar.isEnabled = true
}
if (mScanning)
mCurrentIndex = mOriginalIndex
mNativeFMInterface.setFMFreq(fd, tracks[mCurrentIndex].toInt())
mBandSelect.text = String.format(getString(R.string.mhz), format.format(tracks[mCurrentIndex].toFloat() / 1000))
mCurrentFreq = tracks[mCurrentIndex].toInt()
mSeekBar.progress = mCurrentIndex
mScanning = false
mFreqInitialized = true
}
mAudioManager.requestAudioFocus(mFocusRequest)
mNativeFMInterface.setFMVolume(fd, mVolume)
mDebugStringThread = Thread{
while (true){
mainExecutor.execute {
mDebugInfo.text = String.format(DEBUG_STRING, fd, mNativeFMInterface.getRMSSI(fd))
}
Thread.sleep(1000)
}
}
}
private val mWiredHeadsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == AudioManager.ACTION_HEADSET_PLUG) {
mHeadSetPlugged = false
val mAudioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
for (i in mAudioDeviceInfo.indices){
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES){
mHeadSetPlugged = true
}
}
if (!mHeadSetPlugged){
Log.w(TAG, "onReceive: Headset Unplugged")
Toast.makeText(mContext, "HeadSet disconnected", Toast.LENGTH_SHORT).show()
mErrorIntent.action = NO_WIRED_HEADPHONES
unregisterReceiver(this)
mNativeFMInterface.setFMMute(fd, true)
startActivity(mErrorIntent)
finish()
}
}
}
}
private val mBtHeadsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
if (intent.action == BluetoothAdapter.ACTION_CONNECTION_STATE_CHANGED) {
when (intent.getIntExtra(BluetoothAdapter.EXTRA_CONNECTION_STATE, BluetoothAdapter.ERROR)){
BluetoothAdapter.STATE_CONNECTED -> {
Log.i(TAG, "onReceive: BT Connected, SCO ${mAudioManager.isBluetoothScoOn}")
Toast.makeText(mContext, "Warning: FM via BT not supported", Toast.LENGTH_SHORT).show()
}
BluetoothAdapter.STATE_DISCONNECTED -> {
Log.i(TAG, "onReceive: BT Disconnected SCO ${mAudioManager.isBluetoothScoOn}")
}
}
}
}
}
override fun dispatchKeyEvent(event: KeyEvent): Boolean {
val action = event.action
val mHandler = Handler(Looper.getMainLooper())
return when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
if (action == KeyEvent.ACTION_UP) {
if (mVolume < 15)
mVolume += 1
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_RAISE, AudioManager.FLAG_SHOW_UI)
mNativeFMInterface.setFMVolume(fd, mVolume)
mTitle.text = String.format("Volume %d", mVolume)
mHandler.postDelayed({
mTitle.text = getString(R.string.fm_radio)
},1500)
}
true
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
if (action == KeyEvent.ACTION_UP) {
if (mVolume > 0)
mVolume -= 1
mAudioManager.adjustStreamVolume(AudioManager.STREAM_MUSIC, AudioManager.ADJUST_LOWER, AudioManager.FLAG_SHOW_UI)
mNativeFMInterface.setFMVolume(fd, mVolume)
mTitle.text = String.format("Volume %d", mVolume)
mHandler.postDelayed({
mTitle.text = getString(R.string.fm_radio)
},1500)
}
true
}
else -> super.dispatchKeyEvent(event)
}
}
override fun onClick(v: View) {
if (v.id == R.id.fmswitch){
if(mDebugThread == null){
mAudioManager.setParameters(FM_RADIO_ON)
mNativeFMInterface.setFMBoot(fd)
mNativeFMInterface.setFMFreq(fd, mCurrentFreq)
mFMSwitch.text = getString(R.string.enabled)
writeToFile("fm_radio_state", "1")
mFMThread = Thread {
mNativeFMInterface.setFMThread(fd,true)
}
mFMThread!!.start()
mDebugThread = Thread(mDebugStringThread)
mDebugThread!!.start()
}else{
mFMThread = null
mDebugThread = null
mAudioManager.setParameters(FM_RADIO_OFF)
mNativeFMInterface.setFMThread(fd,false)
writeToFile("fm_radio_state", "0")
mFMSwitch.text = getString(R.string.disabled)
}
}else if (v.id == R.id.fm_output) {
/***
* Some Notes about ABOX
* service call media.audio_policy 5 i32 <mode> i32 2
* when mode 1 -> normal
* mode 2 -> telephony mode
* mode 3 -> terminate telephony mode
* mode 4 -> nothing
* from 5 -> doesn't exist
*/
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
if (mStreamSwitch.text == getString(R.string.wired_earphones)) {
Log.i(TAG, "New Output: Speaker")
mAudioManager.isSpeakerphoneOn = true
File("$mFilePath/headphones").delete()
mStreamSwitch.text = getString(R.string.phone_speaker)
} else {
Log.i(TAG, "New Output: Headset")
mAudioManager.isSpeakerphoneOn = false
File("$mFilePath/headphones").createNewFile()
mStreamSwitch.text = getString(R.string.wired_earphones)
}
mAudioManager.setParameters(FM_RADIO_OFF)
mAudioManager.setParameters(FM_RADIO_ON)
mAudioManager.mode = AudioManager.MODE_NORMAL
}else {
var mExit = false
if (!mFreqInitialized && v.id != R.id.stop_scan) {
Toast.makeText(mContext, "FM Radio is still scanning...", Toast.LENGTH_SHORT).show()
} else {
if (tracks.isEmpty()) {
Log.e(TAG, "onClick: tracks is empty!!!")
mErrorIntent.action = TRACKS_EMPTY
startActivity(mErrorIntent)
}
mNativeFMInterface.setFMMute(fd, true)
when (v.id) {
R.id.seek_up -> mCurrentIndex += 1
R.id.seek_down -> mCurrentIndex -= 1
R.id.scan_through -> {
mScanning = true
mScanBtn.isEnabled = false
mScanThread = null
mScanThread = Thread(scanThread)
mScanThread!!.start()
mExit = true
}
R.id.stop_scan -> {
mScanning = false
mNativeFMInterface.setFMMute(fd, false)
mExit = true
}
R.id.quit_fm -> {
mQuit = true
finish()
}
else -> throw IllegalArgumentException("Unexpected Onclick Event!!")
}
if (!mExit)
updateFreq()
}
}
}
private fun updateFreq(){
if (mCurrentIndex < 0 || mCurrentIndex >= tracks.size) mCurrentIndex = 0
mCurrentFreq = tracks[mCurrentIndex].toInt()
writeToFile("fm_radio_freq_current", mCurrentFreq.toString())
mNativeFMInterface.setFMFreq(fd, mCurrentFreq)
mainExecutor.execute {
mBandSelect.text = String.format("%s Mhz", format.format(mCurrentFreq.toFloat() / 1000))
mSeekBar.progress = mCurrentIndex
}
mNativeFMInterface.setFMMute(fd, false)
}
companion object {
// Abox flags
private const val FM_RADIO_ON = "l_fmradio_mode=on"
private const val FM_RADIO_OFF = "l_fmradio_mode=off"
private const val TAG = "FMRadio"
private const val DEBUG = true
// Backgroud service intents
private const val PACKAGENAME = "com.eurekateam.fmradio"
private const val BEGIN_BG_SERVICE = "$PACKAGENAME.STARTBG"
private const val STOP_BG_SERVICE = "$PACKAGENAME.STOPBG"
private const val QUIT = "$PACKAGENAME.QUIT"
// Failures
private const val RADIO_DEVICE_IO_FAIL = "$PACKAGENAME.RADIO.IO"
private const val TRACKS_EMPTY = "$PACKAGENAME.TRACKS"
private const val NO_WIRED_HEADPHONES = "$PACKAGENAME.NO_HEADPHONE"
private const val DEBUG_STRING = "fd = %d, rssi = %d"
}
private fun removeZeros(array : LongArray): LongArray{
var count = 0
if (DEBUG) Log.d(TAG, "removeZeros: Got array")
for (i in array.indices){
if(array[i] != 0L){
count++
}
}
if (DEBUG) Log.d(TAG, "removeZeros: Removed ${array.size - count} of zeros")
return array.sliceArray(0 until count)
}
override fun onBackPressed() {
if (SystemClock.uptimeMillis() - mLastBackPress in 0..1500){
super.onBackPressed()
}else{
Toast.makeText(mContext, "Press back again to quit", Toast.LENGTH_SHORT).show()
}
mLastBackPress = SystemClock.uptimeMillis()
}
override fun onPause() {
super.onPause()
if (mQuit){
mNativeFMInterface.closeFMDevice(fd)
mIntent.action = QUIT
startService(mIntent)
mAudioManager.abandonAudioFocusRequest(mFocusRequest)
writeToFile("fm_radio_state","0")
}else {
if (readFromFile("fm_radio_state") == "1" && mFreqInitialized) {
Log.i(TAG, "onPause: Starting background service")
for (i in tracks.indices){
if (tracks[i] == mCurrentFreq.toLong()){
mCurrentIndex = i
}
}
Log.d(TAG, "onPause: params: fd=$fd, " +
"tracks.size=${tracks.size}, mCurrentIndex: $mCurrentIndex")
mIntent.action = BEGIN_BG_SERVICE
mIntent.putExtra("fd", fd)
mIntent.putExtra("tracks", tracks)
mIntent.putExtra("freq", mCurrentFreq )
mIntent.putExtra("CurrentIndex", mCurrentIndex )
startService(mIntent)
mServiceRegistered = true
}
}
writeToFile("fm_radio_volume", mVolume.toString())
}
private val mFocusRequest: AudioFocusRequest = AudioFocusRequest.Builder(AudioManager.AUDIOFOCUS_GAIN).run {
setAudioAttributes(AudioAttributes.Builder().run {
setUsage(AudioAttributes.USAGE_MEDIA)
setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
build()
})
setAcceptsDelayedFocusGain(true)
setOnAudioFocusChangeListener(this@MainActivity, mMainHandler)
build()
}
// implementing OnAudioFocusChangeListener to react to focus changes
override fun onAudioFocusChange(focusChange: Int) {
when (focusChange) {
AudioManager.AUDIOFOCUS_GAIN -> {
if (DEBUG)
Log.i(TAG, "onAudioFocusChange: AudioFocus Gain")
mNativeFMInterface.setFMVolume(fd, mVolume)
mAudioManager.setParameters(FM_RADIO_ON)
mNativeFMInterface.setFMFreq(fd, mCurrentFreq)
mFMSwitch.text = getString(R.string.enabled)
writeToFile("fm_radio_state", "1")
mFMThread = Thread {
mNativeFMInterface.setFMThread(fd,true)
}
mFMThread!!.start()
mDebugThread = Thread(mDebugStringThread)
mDebugThread!!.start()
}
AudioManager.AUDIOFOCUS_LOSS -> {
if (DEBUG)
Log.i(TAG, "onAudioFocusChange: AudioFocus Loss")
}
AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {
if (DEBUG)
Log.i(TAG, "onAudioFocusChange: AudioFocus Loss (Can Duck)")
mNativeFMInterface.setFMVolume(fd, 2)
}
}
}
override fun onRestart() {
super.onRestart()
if (mServiceRegistered) {
mAudioManager.requestAudioFocus(mFocusRequest)
Log.i(TAG, "onRestart: Stopping service")
mIntent.action = STOP_BG_SERVICE
startService(mIntent)
mCurrentFreq = readFromFile("fm_radio_freq_current").toInt()
if(readFromFile("fm_radio_state") == "1"){
mFMThread = Thread {
mNativeFMInterface.setFMThread(fd,true)
}
mFMThread!!.start()
mDebugThread = null
mDebugThread = Thread(mDebugStringThread)
mDebugThread!!.start()
mFMSwitch.text = getString(R.string.enabled)
}else{
mFMThread = null
mDebugThread = null
mNativeFMInterface.setFMThread(fd,false)
mFMSwitch.text = getString(R.string.disabled)
}
mServiceRegistered = false
}
}
private fun writeToFile(fileName : String, data: String){
var os: OutputStream? = null
val mData = data.padStart(6,'0')
try {
os = FileOutputStream(File(mFilePath.absolutePath + "/" + fileName))
if (DEBUG) Log.i(TAG, "writeToFile: Writing $mData to $fileName")
os.write(mData.toByteArray(), 0, mData.length)
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
os?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
}
private fun readFromFile(fileName: String): String{
var os: InputStream? = null
val mByteArray = ByteArray(6)
try {
os = FileInputStream(File(mFilePath.absolutePath + "/" + fileName))
if (DEBUG) Log.i(TAG, "readFromFile: Reading from $fileName")
os.read(mByteArray)
} catch (e: IOException) {
e.printStackTrace()
} finally {
try {
os?.close()
} catch (e: IOException) {
e.printStackTrace()
}
}
return String(mByteArray).trimStart('0')
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (DEBUG)
Log.i(TAG, "onProgressChanged: Value changed to $progress, matching FM freq is ${tracks[progress]}")
mBandSelect.text = String.format(getString(R.string.mhz),
format.format(tracks[seekBar.progress].toFloat()/1000))
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
mNativeFMInterface.setFMMute(fd, true)
if (DEBUG)
Log.i(TAG, "onStartTrackingTouch: user touched.")
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
mCurrentIndex = seekBar.progress
mCurrentFreq = tracks[seekBar.progress].toInt()
Log.d(TAG, "onStopTrackingTouch: user stopped touching")
writeToFile("fm_radio_freq_current", mCurrentFreq.toString())
mNativeFMInterface.setFMFreq(fd, mCurrentFreq)
mBandSelect.text = String.format("%s Mhz", format.format(mCurrentFreq.toFloat() / 1000))
mNativeFMInterface.setFMMute(fd, false)
}
}

View file

@ -0,0 +1,23 @@
package com.eurekateam.fmradio
class NativeFMInterface {
external fun openFMDevice() : Int
external fun getFMFreq(fd : Int): Long
external fun setFMFreq(fd : Int, freq : Int): Int
external fun setFMVolume(fd: Int, volume: Int /* 1 - 15 */): Int
external fun setFMMute(fd: Int, mute: Boolean): Int
external fun getFmUpper(fd: Int): Int
external fun getFMLower(fd: Int): Int
external fun getRMSSI(fd: Int): Int
external fun getFMTracks(fd: Int) : LongArray
external fun setFMStereo(fd: Int): Int
external fun setFMMono(fd: Int): Int
external fun setFMThread(fd: Int, run: Boolean): Int
external fun setFMBoot(fd: Int)
external fun getNextChannel(fd: Int): Int
external fun getBeforeChannel(fd: Int): Int
external fun getAudioChannel(fd: Int): Boolean // Stereo == true, Mono == false
external fun stopSearching(fd: Int)
external fun setFMRSSI(fd: Int, rssi: Long): Int
external fun closeFMDevice(fd: Int)
}