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>
21
universal7885-common/apps/FMRadio/Android.bp
Normal 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",
|
||||
}
|
||||
48
universal7885-common/apps/FMRadio/AndroidManifest.xml
Normal 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>
|
||||
12
universal7885-common/apps/FMRadio/jni/Android.bp
Normal 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"],
|
||||
}
|
||||
|
||||
65
universal7885-common/apps/FMRadio/jni/fm.h
Normal 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,
|
||||
};
|
||||
|
||||
|
||||
484
universal7885-common/apps/FMRadio/jni/fm_ioctl.cpp
Normal 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);
|
||||
}
|
||||
125
universal7885-common/apps/FMRadio/jni/kernel_internal.h
Normal 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)
|
||||
|
||||
|
||||
|
|
@ -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>
|
||||
BIN
universal7885-common/apps/FMRadio/res/drawable/left.png
Normal file
|
After Width: | Height: | Size: 452 B |
12
universal7885-common/apps/FMRadio/res/drawable/power.xml
Normal 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>
|
||||
BIN
universal7885-common/apps/FMRadio/res/drawable/radio.png
Normal file
|
After Width: | Height: | Size: 22 KiB |
BIN
universal7885-common/apps/FMRadio/res/drawable/radio_text.png
Normal file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
universal7885-common/apps/FMRadio/res/drawable/right.png
Normal file
|
After Width: | Height: | Size: 434 B |
BIN
universal7885-common/apps/FMRadio/res/drawable/start.png
Normal file
|
After Width: | Height: | Size: 457 B |
BIN
universal7885-common/apps/FMRadio/res/drawable/stop.png
Normal file
|
After Width: | Height: | Size: 305 B |
BIN
universal7885-common/apps/FMRadio/res/drawable/warning.jpg
Normal file
|
After Width: | Height: | Size: 144 KiB |
310
universal7885-common/apps/FMRadio/res/layout/activity_main.xml
Normal 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>
|
||||
|
|
@ -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>
|
||||
|
|
@ -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>
|
||||
|
After Width: | Height: | Size: 2.5 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.6 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 2.2 KiB |
|
After Width: | Height: | Size: 3 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 5.3 KiB |
|
After Width: | Height: | Size: 7.4 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 14 KiB |
34
universal7885-common/apps/FMRadio/res/values/strings.xml
Normal 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>
|
||||
|
|
@ -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"
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||