mirror of
https://github.com/chararomandroid/android_device_samsung_a20
synced 2026-08-21 11:55:31 -04:00
universal7885: FMRadio: Futher improve code
* Using enum classes for various statuses for good readability * Implement favorite channel saving which was missing * Use Kotlin coroutines again for refreshing channel list button, for faster UI response * Added javadoc for better functions description * Also, code native audio routing system on jni (fm_route.cpp) Change-Id: I993c88fdb580248d8a55efd8e9b6fed0e36d9b24 Signed-off-by: roynatech2544 <whiteshell2544@naver.com>
This commit is contained in:
parent
2648c647fc
commit
9443bdc315
17 changed files with 312 additions and 142 deletions
|
|
@ -6,7 +6,7 @@ android_app {
|
|||
platform_apis: true,
|
||||
privileged: true,
|
||||
certificate: "platform",
|
||||
jni_libs: ["libfmioctl_jni"],
|
||||
jni_libs: ["libfmnative_jni"],
|
||||
static_libs: [
|
||||
"androidx.core_core",
|
||||
"androidx.appcompat_appcompat",
|
||||
|
|
|
|||
|
|
@ -21,11 +21,11 @@
|
|||
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.OUTPUT"/>
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
cc_library_shared {
|
||||
name: "libfmioctl_jni",
|
||||
name: "libfmnative_jni",
|
||||
cflags: [
|
||||
"-Wno-unused-parameter",
|
||||
],
|
||||
srcs: ["fm_ioctl.cpp"],
|
||||
srcs: [
|
||||
"fm_ioctl.cpp",
|
||||
"fm_route.cpp",
|
||||
],
|
||||
shared_libs: [
|
||||
"libhidlbase",
|
||||
"liblog",
|
||||
|
|
@ -11,7 +14,19 @@ cc_library_shared {
|
|||
"vendor.eureka.hardware.fmradio@1.0",
|
||||
"vendor.eureka.hardware.fmradio@1.1",
|
||||
"vendor.eureka.hardware.fmradio@1.2",
|
||||
"audioflinger-aidl-cpp",
|
||||
"audiopolicy-aidl-cpp",
|
||||
"audiopolicy-types-aidl-cpp",
|
||||
"libaudiofoundation",
|
||||
"libaudioutils",
|
||||
"libaudioclient",
|
||||
"libutils",
|
||||
"libaudiopolicy",
|
||||
"libaudiomanager",
|
||||
],
|
||||
header_libs: [
|
||||
"libaudioclient_headers",
|
||||
"jni_headers",
|
||||
],
|
||||
header_libs: ["jni_headers"],
|
||||
}
|
||||
|
||||
|
|
|
|||
37
universal7885-common/apps/FMRadio/jni/fm_route.cpp
Normal file
37
universal7885-common/apps/FMRadio/jni/fm_route.cpp
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
/*
|
||||
* Copyright 2021 Soo Hwan Na "Royna"
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
#include <jni.h>
|
||||
#include <media/IAudioFlinger.h>
|
||||
#include <media/AudioSystem.h>
|
||||
|
||||
#define FM_FAILURE -1
|
||||
#define FM_SUCCESS 0
|
||||
#define IOHANDLE 13
|
||||
using namespace android;
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_eurekateam_fmradio_NativeFMInterface_setAudioRoute
|
||||
(__unused JNIEnv *env, __unused jobject thiz, jboolean speaker) {
|
||||
const sp<IAudioFlinger>& af = AudioSystem::get_audio_flinger();
|
||||
if (af == 0) return PERMISSION_DENIED;
|
||||
if (speaker){
|
||||
af->setParameters(IOHANDLE, String8("routing=2"));
|
||||
}else{
|
||||
af->setParameters(IOHANDLE, String8("routing=8"));
|
||||
}
|
||||
return FM_SUCCESS;
|
||||
}
|
||||
|
|
@ -3,14 +3,16 @@ package com.eurekateam.fmradio
|
|||
import android.app.*
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.BitmapFactory
|
||||
import android.graphics.drawable.Icon
|
||||
import android.media.AudioManager
|
||||
import android.media.MediaMetadata
|
||||
import android.media.session.MediaSession
|
||||
import android.media.session.PlaybackState
|
||||
import android.os.IBinder
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import android.graphics.BitmapFactory
|
||||
import com.eurekateam.fmradio.enums.OutputState
|
||||
import com.eurekateam.fmradio.enums.PlayState
|
||||
import com.eurekateam.fmradio.enums.PowerState
|
||||
import com.eurekateam.fmradio.fragments.MainFragment
|
||||
import com.eurekateam.fmradio.utils.Log
|
||||
import java.io.File
|
||||
|
|
@ -20,15 +22,12 @@ class FMRadioService : Service() {
|
|||
private lateinit var mContext: Context
|
||||
private lateinit var mAudioManager: AudioManager
|
||||
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 mFilePath : File
|
||||
override fun onBind(intent: Intent?): IBinder? {
|
||||
return null
|
||||
}
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
Log.i("--- FMRadio Background (IN) ---")
|
||||
fd = MainFragment.fd
|
||||
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
mTracks = MainFragment.mTracks
|
||||
|
|
@ -40,62 +39,61 @@ class FMRadioService : Service() {
|
|||
if (intent != null) {
|
||||
intent.action?.let { Log.i(it) }
|
||||
when (intent.action) {
|
||||
BEGIN_BG_SERVICE -> {
|
||||
mContext = this
|
||||
mFilePath = mContext.filesDir
|
||||
mNativeFMInterface = NativeFMInterface()
|
||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000} Mhz"
|
||||
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
mIsPlaying = true
|
||||
}
|
||||
ACTION_PLAY -> {
|
||||
mIsPlaying = !mIsPlaying
|
||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
|
||||
if (mIsPlaying) {
|
||||
mAudioManager.setParameters(FM_RADIO_ON)
|
||||
} else {
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
if (mPlayState == PlayState.STATE_PLAYING) {
|
||||
mPlayState = PlayState.STATE_STOPPED
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||
}else if (mPlayState == PlayState.STATE_STOPPED){
|
||||
mPlayState = PlayState.STATE_PLAYING
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_ON.mAudioParam)
|
||||
}
|
||||
}
|
||||
ACTION_BEFORE -> {
|
||||
mIsPlaying = true
|
||||
Log.i("onStartCommand: mCurrentIndex $mIndex")
|
||||
mPlayState = PlayState.STATE_PLAYING
|
||||
Log.i("mCurrentIndex $mIndex")
|
||||
if (mIndex > 0)
|
||||
mIndex -= 1
|
||||
if (mIndex >= 1)
|
||||
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
|
||||
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
||||
MainFragment.mFreqCurrent = mTracks[mIndex].toInt()
|
||||
}
|
||||
ACTION_NEXT -> {
|
||||
mIsPlaying = true
|
||||
Log.i("onStartCommand: mCurrentIndex $mIndex")
|
||||
mPlayState = PlayState.STATE_PLAYING
|
||||
Log.i("mCurrentIndex $mIndex")
|
||||
if (mIndex < mTracks.size - 1)
|
||||
mIndex += 1
|
||||
if (mIndex < mTracks.size - 1)
|
||||
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
|
||||
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
||||
MainFragment.mFreqCurrent = mTracks[mIndex].toInt()
|
||||
}
|
||||
ACTION_QUIT -> {
|
||||
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
Log.i("--- FMRadio Background (OUT) ---")
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||
mMediaSession.release()
|
||||
stopSelf()
|
||||
}
|
||||
else -> {
|
||||
throw IllegalAccessException("Unexpected Intent!")
|
||||
ACTION_OUTPUT -> {
|
||||
changeOutputDevice(mOutput)
|
||||
if (mOutput == OutputState.OUTPUT_HEADSET){
|
||||
mOutput = OutputState.OUTPUT_SPEAKER
|
||||
}else if (mOutput == OutputState.OUTPUT_SPEAKER){
|
||||
mOutput = OutputState.OUTPUT_HEADSET
|
||||
}
|
||||
}
|
||||
ACTION_START -> {
|
||||
mMediaSession = MediaSession(this, "FMRadio")
|
||||
mNativeFMInterface = NativeFMInterface()
|
||||
mContext = this
|
||||
setPlaybackState()
|
||||
mMediaSession.isActive = true
|
||||
}
|
||||
}
|
||||
}
|
||||
mContext = this
|
||||
mFilePath = mContext.filesDir
|
||||
mNativeFMInterface = NativeFMInterface()
|
||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000} Mhz"
|
||||
Log.i("onStartCommand: mTitle=$mTitle")
|
||||
mMediaSession = MediaSession(this, "FMRadio")
|
||||
setPlaybackState()
|
||||
sendMetaData("FM ${mTracks[mIndex].toFloat() / 1000}Mhz")
|
||||
mMediaSession.isActive = true
|
||||
startForeground(51,pushNotification())
|
||||
sendMetaData("FM ${mTracks[mIndex].toFloat() / 1000} Mhz")
|
||||
startForeground(51, pushNotification())
|
||||
if (mOutput == OutputState.OUTPUT_SPEAKER){
|
||||
changeOutputDevice(OutputState.OUTPUT_HEADSET)
|
||||
}
|
||||
Log.i("--- FMRadio Background (OUT) ---")
|
||||
return START_STICKY
|
||||
}
|
||||
private fun setPlaybackState() {
|
||||
|
|
@ -112,7 +110,8 @@ class FMRadioService : Service() {
|
|||
val metadata = MediaMetadata.Builder()
|
||||
.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle)
|
||||
.putString(MediaMetadata.METADATA_KEY_ARTIST, resources.getString(R.string.app_name))
|
||||
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART, BitmapFactory.decodeResource(mContext.resources, R.drawable.ic_radio))
|
||||
.putBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART,
|
||||
BitmapFactory.decodeResource(mContext.resources, R.drawable.ic_radio))
|
||||
.build()
|
||||
mMediaSession.setMetadata(metadata)
|
||||
}
|
||||
|
|
@ -132,16 +131,31 @@ class FMRadioService : Service() {
|
|||
builder.setColor(resources.getColor(android.R.color.system_accent1_200, mContext.theme))
|
||||
builder.setColorized(true)
|
||||
builder.style = mediaStyle
|
||||
builder.setOnlyAlertOnce(true)
|
||||
builder.setChannelId(mContext.packageName)
|
||||
val togglePlay = PendingIntent.getService(mContext, 0, Intent(ACTION_PLAY), PendingIntent.FLAG_IMMUTABLE)
|
||||
val forward = PendingIntent.getService(mContext, 0, Intent(ACTION_NEXT), PendingIntent.FLAG_IMMUTABLE)
|
||||
val rewind = PendingIntent.getService(mContext, 0, Intent(ACTION_BEFORE), PendingIntent.FLAG_IMMUTABLE)
|
||||
val close = PendingIntent.getService(mContext, 0, Intent(ACTION_QUIT), PendingIntent.FLAG_IMMUTABLE)
|
||||
val output = PendingIntent.getService(mContext, 0, Intent(ACTION_OUTPUT), PendingIntent.FLAG_IMMUTABLE)
|
||||
val mOutputAction : Notification.Action = Notification.Action.Builder(
|
||||
when (mOutput) {
|
||||
OutputState.OUTPUT_HEADSET -> {
|
||||
Icon.createWithResource(this, R.drawable.ic_volume_up)
|
||||
}
|
||||
OutputState.OUTPUT_SPEAKER -> {
|
||||
Icon.createWithResource(this, R.drawable.ic_headphones)
|
||||
}
|
||||
}, "Output Configuration", output
|
||||
).build()
|
||||
val mPausePlayAction: Notification.Action = Notification.Action.Builder(
|
||||
if (mIsPlaying) {
|
||||
Icon.createWithResource(this, R.drawable.ic_pause)
|
||||
}else{
|
||||
Icon.createWithResource(this, R.drawable.ic_play)
|
||||
when (mPlayState) {
|
||||
PlayState.STATE_PLAYING -> {
|
||||
Icon.createWithResource(this, R.drawable.ic_pause)
|
||||
}
|
||||
PlayState.STATE_STOPPED -> {
|
||||
Icon.createWithResource(this, R.drawable.ic_play)
|
||||
}
|
||||
}, "Start/Stop", togglePlay
|
||||
|
||||
).build()
|
||||
|
|
@ -157,6 +171,7 @@ class FMRadioService : Service() {
|
|||
Icon.createWithResource(this, R.drawable.ic_close),
|
||||
"Close", close
|
||||
).build()
|
||||
builder.addAction(mOutputAction)
|
||||
builder.addAction(mRewindAction)
|
||||
builder.addAction(mPausePlayAction)
|
||||
builder.addAction(mForwardAction)
|
||||
|
|
@ -165,15 +180,23 @@ class FMRadioService : Service() {
|
|||
}
|
||||
companion object {
|
||||
private const val PACKAGENAME = "com.eurekateam.fmradio"
|
||||
private const val BEGIN_BG_SERVICE = "$PACKAGENAME.STARTBG"
|
||||
private const val ACTION_START = "$PACKAGENAME.START"
|
||||
private const val ACTION_PLAY = "$PACKAGENAME.PLAY_FM"
|
||||
private const val ACTION_NEXT = "$PACKAGENAME.NEXT"
|
||||
private var mIsPlaying = true
|
||||
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 ACTION_OUTPUT = "$PACKAGENAME.OUTPUT"
|
||||
private var mPlayState : PlayState = PlayState.STATE_PLAYING
|
||||
private var mOutput : OutputState = MainFragment.mHeadset
|
||||
private var fd : Int = -1
|
||||
private var mIndex = -1
|
||||
lateinit var mMediaSession : MediaSession
|
||||
}
|
||||
private fun changeOutputDevice(output: OutputState){
|
||||
if (output == OutputState.OUTPUT_SPEAKER){
|
||||
mNativeFMInterface.setAudioRoute(false);
|
||||
}else if (output == OutputState.OUTPUT_HEADSET){
|
||||
mNativeFMInterface.setAudioRoute(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ import androidx.appcompat.app.AppCompatActivity
|
|||
import androidx.appcompat.widget.AppCompatImageView
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.fragment.app.FragmentManager
|
||||
import com.eurekateam.fmradio.enums.HeadsetState
|
||||
import com.eurekateam.fmradio.enums.PowerState
|
||||
import com.eurekateam.fmradio.fragments.ChannelListFragment
|
||||
import com.eurekateam.fmradio.fragments.FavouriteFragment
|
||||
import com.eurekateam.fmradio.fragments.MainFragment
|
||||
|
|
@ -25,6 +27,12 @@ import com.google.android.material.textview.MaterialTextView
|
|||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
private lateinit var mIntent : Intent
|
||||
|
||||
/**
|
||||
* Function to change current [Fragment] to other [Fragment]
|
||||
* @param [fragment] [Fragment] to change to
|
||||
* @param [tagFragmentName] Tag for the changing [fragment]
|
||||
*/
|
||||
private fun changeFragment(fragment: Fragment?, tagFragmentName: String) {
|
||||
supportFragmentManager.beginTransaction().apply {
|
||||
replace(R.id.container_view, fragment!!, tagFragmentName)
|
||||
|
|
@ -40,7 +48,7 @@ class MainActivity : AppCompatActivity() {
|
|||
private lateinit var mAlertImage : AppCompatImageView
|
||||
private lateinit var mAudioManager : AudioManager
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
System.loadLibrary("fmioctl_jni")
|
||||
System.loadLibrary("fmnative_jni")
|
||||
MainFragment.fd = mFMInterface.openFMDevice()
|
||||
mAlertView = (getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater)
|
||||
.inflate(R.layout.alertdialog, null)
|
||||
|
|
@ -65,15 +73,19 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
DynamicColors.applyToActivitiesIfAvailable(application)
|
||||
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||
/**
|
||||
* Detects whether wired headphones is connected to this device or no
|
||||
* @see AudioManager.getDevices
|
||||
*/
|
||||
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) {
|
||||
MainFragment.mHeadSetPlugged = true
|
||||
MainFragment.mHeadSetPlugged = HeadsetState.HEADSET_STATE_CONNECTED
|
||||
Log.i("Wired Headphones detected")
|
||||
}
|
||||
}
|
||||
if (!MainFragment.mHeadSetPlugged){
|
||||
if (MainFragment.mHeadSetPlugged != HeadsetState.HEADSET_STATE_CONNECTED){
|
||||
mAlertTitle.text = getString(R.string.no_headphones_error)
|
||||
mAlertDesc.text = getString(R.string.no_headphones_error_desc)
|
||||
mAlertImage.setImageIcon(Icon.createWithResource(mAlertView.context,
|
||||
|
|
@ -83,7 +95,7 @@ class MainActivity : AppCompatActivity() {
|
|||
.setCancelable(false)
|
||||
.setView(mAlertView)
|
||||
.setNegativeButton(R.string.ok) { v: DialogInterface, _: Int ->
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||
v.dismiss()
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
finish()
|
||||
|
|
@ -98,8 +110,6 @@ class MainActivity : AppCompatActivity() {
|
|||
val mFavouriteFragment = FavouriteFragment()
|
||||
changeFragment(mRadioMainFragment, MainFragment::class.java.name)
|
||||
mIntent = Intent(this, FMRadioService::class.java)
|
||||
mIntent.action = BEGIN_BG_SERVICE
|
||||
startService(mIntent)
|
||||
findViewById<BottomNavigationView>(R.id.bottom_nav_bar).setOnItemSelectedListener {
|
||||
when (it.itemId){
|
||||
R.id.radio_main -> changeFragment(mRadioMainFragment, MainFragment::class.java.name)
|
||||
|
|
@ -110,18 +120,21 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a [BroadcastReceiver] for listening to headset events
|
||||
*/
|
||||
private val mWiredHeadsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
if (intent.action == AudioManager.ACTION_HEADSET_PLUG) {
|
||||
MainFragment.mHeadSetPlugged = false
|
||||
MainFragment.mHeadSetPlugged = HeadsetState.HEADSET_STATE_DISCONNECTED
|
||||
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){
|
||||
MainFragment.mHeadSetPlugged = true
|
||||
MainFragment.mHeadSetPlugged = HeadsetState.HEADSET_STATE_CONNECTED
|
||||
}
|
||||
}
|
||||
if (!MainFragment.mHeadSetPlugged){
|
||||
if (MainFragment.mHeadSetPlugged != HeadsetState.HEADSET_STATE_CONNECTED){
|
||||
Log.w("onReceive: Headset Unplugged")
|
||||
mAlertTitle.text = getString(R.string.no_headphones_error)
|
||||
mAlertDesc.text = getString(R.string.no_headphones_error_desc)
|
||||
|
|
@ -134,7 +147,7 @@ class MainActivity : AppCompatActivity() {
|
|||
.setView(mAlertView)
|
||||
.setNegativeButton(R.string.ok) { v: DialogInterface, _: Int ->
|
||||
v.dismiss()
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||
Handler(Looper.getMainLooper()).postDelayed({
|
||||
finish()
|
||||
},500)
|
||||
|
|
@ -144,13 +157,19 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get [MainActivity]'s [FragmentManager] to sub [Fragment]
|
||||
* @return [FragmentManager] supplied to this activity
|
||||
*/
|
||||
fun getMySupportFragmentManager(): FragmentManager {
|
||||
return supportFragmentManager
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
mIntent.action = BEGIN_BG_SERVICE
|
||||
Log.i("Application onPause")
|
||||
mIntent.action = ACTION_START
|
||||
startService(mIntent)
|
||||
}
|
||||
|
||||
|
|
@ -158,10 +177,7 @@ class MainActivity : AppCompatActivity() {
|
|||
super.onRestart()
|
||||
stopService(mIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val PACKAGENAME = "com.eurekateam.fmradio"
|
||||
private const val BEGIN_BG_SERVICE = "$PACKAGENAME.STARTBG"
|
||||
private const val FM_RADIO_OFF = "l_fmradio_mode=off"
|
||||
private const val ACTION_START = "com.eurekateam.fmradio.START"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,4 +21,5 @@ class NativeFMInterface {
|
|||
external fun setFMRSSI(fd: Int, rssi: Long): Int
|
||||
external fun closeFMDevice(fd: Int)
|
||||
external fun getSysfsSupport(): Boolean
|
||||
}
|
||||
external fun setAudioRoute(speaker: Boolean): Int
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import com.eurekateam.fmradio.NativeFMInterface
|
|||
import com.eurekateam.fmradio.PebbleTextView
|
||||
import com.eurekateam.fmradio.R
|
||||
import com.eurekateam.fmradio.fragments.MainFragment
|
||||
import com.eurekateam.fmradio.utils.FileUtilities
|
||||
|
||||
|
||||
class PebbleLayoutAdapter (private val mContext: Context) : BaseAdapter() {
|
||||
|
|
@ -21,6 +22,11 @@ class PebbleLayoutAdapter (private val mContext: Context) : BaseAdapter() {
|
|||
}
|
||||
}
|
||||
mFavoriteList.sort()
|
||||
var mData = ""
|
||||
for (i in mFavoriteList){
|
||||
mData += "$i\n"
|
||||
}
|
||||
FileUtilities.writeToFile(FileUtilities.mFavouriteChannelFileName, mData, mContext)
|
||||
}
|
||||
override fun getCount(): Int {
|
||||
return mFavoriteList.size
|
||||
|
|
@ -47,6 +53,10 @@ class PebbleLayoutAdapter (private val mContext: Context) : BaseAdapter() {
|
|||
setOnClickListener {
|
||||
mFMInterface.setFMFreq(MainFragment.fd, mFavoriteList[id])
|
||||
MainFragment.mFreqCurrent = mFavoriteList[id]
|
||||
FileUtilities.writeToFile(
|
||||
FileUtilities.mFMFreqFileName,
|
||||
MainFragment.mFreqCurrent.toString(), mContext
|
||||
)
|
||||
}
|
||||
}
|
||||
return mAnotherConvertView
|
||||
|
|
|
|||
|
|
@ -0,0 +1,11 @@
|
|||
package com.eurekateam.fmradio.enums
|
||||
|
||||
/**
|
||||
* [HEADSET_STATE_CONNECTED] Headset is connected
|
||||
*
|
||||
* [HEADSET_STATE_DISCONNECTED] Headset is disconnected
|
||||
*/
|
||||
enum class HeadsetState {
|
||||
HEADSET_STATE_CONNECTED,
|
||||
HEADSET_STATE_DISCONNECTED,
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.eurekateam.fmradio.enums
|
||||
|
||||
/***
|
||||
* [OUTPUT_SPEAKER] : Output to speaker
|
||||
*
|
||||
* [OUTPUT_HEADSET] : Output to headset
|
||||
*/
|
||||
enum class OutputState {
|
||||
OUTPUT_SPEAKER,
|
||||
OUTPUT_HEADSET
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.eurekateam.fmradio.enums
|
||||
|
||||
/***
|
||||
* [STATE_PLAYING] : Currently playing
|
||||
*
|
||||
* [STATE_STOPPED] : Stopped playing
|
||||
*/
|
||||
enum class PlayState {
|
||||
STATE_PLAYING,
|
||||
STATE_STOPPED
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.eurekateam.fmradio.enums
|
||||
|
||||
/**
|
||||
* [FM_POWER_OFF] : Turns off fm audio
|
||||
*
|
||||
* [FM_POWER_ON] : Turns on fm audio
|
||||
*
|
||||
* This enum includes audio parameter that can be written to A-BOX
|
||||
*/
|
||||
enum class PowerState(val mAudioParam: String) {
|
||||
FM_POWER_ON("l_fmradio_mode=on"),
|
||||
FM_POWER_OFF("l_fmradio_mode=off")
|
||||
}
|
||||
|
|
@ -12,6 +12,10 @@ import com.eurekateam.fmradio.MainActivity
|
|||
import com.eurekateam.fmradio.R
|
||||
import com.eurekateam.fmradio.adapters.ListViewAdapter
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
class ChannelListFragment : Fragment(R.layout.activity_channel_list),
|
||||
View.OnClickListener {
|
||||
|
|
@ -45,11 +49,17 @@ class ChannelListFragment : Fragment(R.layout.activity_channel_list),
|
|||
}
|
||||
|
||||
override fun onClick(v: View?) {
|
||||
MainFragment.mRefreshTracks()
|
||||
(requireActivity() as MainActivity).getMySupportFragmentManager().apply {
|
||||
beginTransaction().remove(this@ChannelListFragment).commit()
|
||||
executePendingTransactions()
|
||||
beginTransaction().add(R.id.container_view, this@ChannelListFragment).commit()
|
||||
GlobalScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
MainFragment.mRefreshTracks()
|
||||
}
|
||||
withContext(Dispatchers.Main){
|
||||
(requireActivity() as MainActivity).getMySupportFragmentManager().apply {
|
||||
beginTransaction().remove(this@ChannelListFragment).commit()
|
||||
executePendingTransactions()
|
||||
beginTransaction().add(R.id.container_view, this@ChannelListFragment).commit()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +1,27 @@
|
|||
package com.eurekateam.fmradio.fragments
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.Configuration
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.Icon
|
||||
import android.media.AudioManager
|
||||
import android.os.Bundle
|
||||
import android.view.Display
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.SeekBar
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.AppCompatSeekBar
|
||||
import androidx.core.content.res.ResourcesCompat
|
||||
import androidx.fragment.app.Fragment
|
||||
import com.eurekateam.fmradio.utils.FileUtilities
|
||||
import com.eurekateam.fmradio.utils.Log
|
||||
import com.eurekateam.fmradio.NativeFMInterface
|
||||
import com.eurekateam.fmradio.R
|
||||
import com.eurekateam.fmradio.enums.HeadsetState
|
||||
import com.eurekateam.fmradio.enums.OutputState
|
||||
import com.eurekateam.fmradio.enums.PowerState
|
||||
import com.eurekateam.fmradio.utils.FileUtilities
|
||||
import com.eurekateam.fmradio.utils.Log
|
||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -56,7 +58,7 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
R.drawable.ic_star, requireContext().theme)!!
|
||||
mStarFilled = ResourcesCompat.getDrawable(requireContext().resources,
|
||||
R.drawable.ic_star_filled, requireContext().theme)!!
|
||||
mAudioManager = requireContext().getSystemService(AppCompatActivity.AUDIO_SERVICE) as AudioManager
|
||||
mAudioManager = requireContext().getSystemService(Context.AUDIO_SERVICE) as AudioManager
|
||||
mVolumeUp = mRootView.findViewById(R.id.volume_up)
|
||||
mVolumeDown = mRootView.findViewById(R.id.volume_down)
|
||||
mSeekBar = mRootView.findViewById(R.id.volume_seekbar)
|
||||
|
|
@ -97,15 +99,24 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
else
|
||||
setBackgroundColor(resources.getColor(android.R.color.system_accent1_700, requireContext().theme))
|
||||
}
|
||||
var mFMInit = false
|
||||
GlobalScope.launch {
|
||||
withContext(Dispatchers.IO){
|
||||
if (FileUtilities.checkIfExistFile(FileUtilities.mFavouriteChannelFileName, requireContext())) {
|
||||
val mFavData = FileUtilities.readFromFile(
|
||||
FileUtilities.mFavouriteChannelFileName, requireContext()
|
||||
)
|
||||
for (mItem in mFavData.split("\\r?\\n".toRegex())) {
|
||||
if (mItem.isNotBlank())
|
||||
mFavStats[mItem.toInt()] = true
|
||||
}
|
||||
}
|
||||
var mMute = false
|
||||
if (mFreqCurrent == -1) {
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||
withContext(Dispatchers.Main){
|
||||
mUpdateEnableDisable(false, mRootView)
|
||||
}
|
||||
mFMInit = true
|
||||
mMute = true
|
||||
}
|
||||
|
||||
if (FileUtilities.checkIfExistFile(FileUtilities.mFMFreqFileName, requireContext())){
|
||||
|
|
@ -141,27 +152,8 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
}
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.IO){
|
||||
if (FileUtilities.checkIfExistFile(FileUtilities.mHeadsetFileName, requireContext())){
|
||||
mHeadset = true
|
||||
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||
mAudioManager.isSpeakerphoneOn = mHeadset
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
mAudioManager.setParameters(FM_RADIO_ON)
|
||||
mAudioManager.mode = AudioManager.MODE_NORMAL
|
||||
withContext(Dispatchers.Main){
|
||||
if (mHeadset) {
|
||||
mOutputSwitch.setImageIcon(
|
||||
Icon.createWithResource(
|
||||
requireContext(),
|
||||
R.drawable.ic_volume_up
|
||||
)
|
||||
)
|
||||
FileUtilities.createFile(FileUtilities.mHeadsetFileName, requireContext())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!mMute)
|
||||
mFMInterface.setFMMute(fd, true)
|
||||
mFMInterface.setFMFreq(fd, mFMInterface.getFMLower(fd))
|
||||
mRefreshTracks()
|
||||
if (mFreqCurrent != -1){
|
||||
|
|
@ -170,14 +162,13 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
mFreqCurrent = mFMInterface.getFMLower(fd)
|
||||
}
|
||||
mFMInterface.setFMBoot(fd)
|
||||
if (!mFMInit && isAdded) {
|
||||
mFreqCurrent = mFMInterface.getFMFreq(fd).toInt()
|
||||
mFMInterface.setFMFreq(fd, mFreqCurrent)
|
||||
requireActivity().mainExecutor.execute {
|
||||
mFMFreq.text = mCleanFormat.format( mFreqCurrent.toFloat() / 1000)
|
||||
}
|
||||
mFreqCurrent = mFMInterface.getFMFreq(fd).toInt()
|
||||
mFMInterface.setFMFreq(fd, mFreqCurrent)
|
||||
withContext(Dispatchers.Main) {
|
||||
mFMFreq.text = mCleanFormat.format( mFreqCurrent.toFloat() / 1000)
|
||||
}
|
||||
if (mTracks.isEmpty())
|
||||
if (!mMute)
|
||||
mFMInterface.setFMMute(fd, false)
|
||||
if (mFreqCurrent == -1)
|
||||
mFMInterface.setFMThread(fd, true)
|
||||
withContext(Dispatchers.Main){
|
||||
|
|
@ -196,14 +187,34 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
}
|
||||
return mRootView
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension function for [View], for Grayed out, disabled View
|
||||
* @param mTextView Whether the target view is touchable [FloatingActionButton] or
|
||||
* [MaterialTextView] with useless touch attr
|
||||
*/
|
||||
private fun View.disable(mTextView : Boolean = false) {
|
||||
alpha = .7f
|
||||
if(!mTextView) isEnabled = false
|
||||
}
|
||||
/**
|
||||
* Extension function for [View], for reverting Grayed out, disabled View
|
||||
* @param mTextView Whether the target view is touchable [FloatingActionButton] or
|
||||
* [MaterialTextView] with useless touch attr
|
||||
*/
|
||||
private fun View.enable(mTextView : Boolean = false) {
|
||||
alpha = 1.0f
|
||||
if(!mTextView) isEnabled = true
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses array of [R] to make the [View] look disabled or enabled
|
||||
* @param mEnabled Should we enable the Views or not
|
||||
* @param mView The root view we should find views such as [FloatingActionButton].
|
||||
* Defaults to [requireView]
|
||||
* @see [enable]
|
||||
* @see [disable]
|
||||
*/
|
||||
private fun mUpdateEnableDisable(mEnabled: Boolean, mView: View = requireView()){
|
||||
val mTextViewList = listOf(R.id.fm_freq, R.id.freq_misc)
|
||||
val mFloatButtonList = listOf(
|
||||
|
|
@ -227,29 +238,30 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
override fun onClick(v: View) {
|
||||
when (v.id) {
|
||||
mOutputSwitch.id -> {
|
||||
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
||||
mAudioManager.isSpeakerphoneOn = mHeadset
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
mAudioManager.setParameters(FM_RADIO_ON)
|
||||
mAudioManager.mode = AudioManager.MODE_NORMAL
|
||||
if (mHeadset) {
|
||||
if (mHeadset == OutputState.OUTPUT_HEADSET) {
|
||||
mOutputSwitch.setImageIcon(
|
||||
Icon.createWithResource(
|
||||
requireContext(),
|
||||
R.drawable.ic_volume_up
|
||||
)
|
||||
)
|
||||
FileUtilities.createFile(FileUtilities.mHeadsetFileName, requireContext())
|
||||
}else{
|
||||
val ret = mFMInterface.setAudioRoute(true);
|
||||
Log.i("mFMInterface.setAudioRoute return $ret");
|
||||
}else if (mHeadset == OutputState.OUTPUT_SPEAKER){
|
||||
mOutputSwitch.setImageIcon(
|
||||
Icon.createWithResource(
|
||||
requireContext(),
|
||||
R.drawable.ic_headphones
|
||||
)
|
||||
)
|
||||
FileUtilities.removeFile(FileUtilities.mHeadsetFileName, requireContext())
|
||||
val ret = mFMInterface.setAudioRoute(false);
|
||||
Log.i("mFMInterface.setAudioRoute return $ret");
|
||||
}
|
||||
if (mHeadset == OutputState.OUTPUT_HEADSET){
|
||||
mHeadset = OutputState.OUTPUT_SPEAKER
|
||||
}else if (mHeadset == OutputState.OUTPUT_SPEAKER){
|
||||
mHeadset = OutputState.OUTPUT_HEADSET
|
||||
}
|
||||
mHeadset = !mHeadset
|
||||
}
|
||||
mVolumeUp.id -> {
|
||||
if (mVolume < 15)
|
||||
|
|
@ -302,10 +314,10 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
}
|
||||
mPowerBtn.id -> {
|
||||
if (mFMPower){
|
||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||
mFMFreq.text = getText(R.string.inital_freq)
|
||||
}else{
|
||||
mAudioManager.setParameters(FM_RADIO_ON)
|
||||
mAudioManager.setParameters(PowerState.FM_POWER_ON.mAudioParam)
|
||||
mFMFreq.text = mCleanFormat.format(mFreqCurrent.toFloat() / 1000)
|
||||
}
|
||||
mUpdateEnableDisable(!mFMPower)
|
||||
|
|
@ -372,13 +384,11 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
companion object {
|
||||
private var mVolume = -1
|
||||
var fd = -1
|
||||
private var mHeadset = false
|
||||
var mHeadset = OutputState.OUTPUT_SPEAKER
|
||||
var mFreqCurrent = -1
|
||||
private var mFMPower = false
|
||||
var mHeadSetPlugged = false
|
||||
var mHeadSetPlugged : HeadsetState = HeadsetState.HEADSET_STATE_DISCONNECTED
|
||||
var mTracks : LongArray = emptyArray<Long>().toLongArray()
|
||||
private const val FM_RADIO_ON = "l_fmradio_mode=on"
|
||||
private const val FM_RADIO_OFF = "l_fmradio_mode=off"
|
||||
fun mRefreshTracks(){
|
||||
NativeFMInterface().setFMFreq(fd, NativeFMInterface().getFMLower(fd))
|
||||
mTracks = NativeFMInterface().getFMTracks(fd)
|
||||
|
|
@ -391,9 +401,18 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
val mFavStats = HashMap<Int, Boolean>(30)
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to remove zero values on a [LongArray].
|
||||
* Since we don't know the channel count, we initialize it with size 30.
|
||||
* But most likely the channel count is less then 30, so remaining values
|
||||
* are filled with zero.
|
||||
*
|
||||
* @param array The target Long array
|
||||
* @return Long array with zeros removed
|
||||
*/
|
||||
private fun removeZeros(array : LongArray): LongArray{
|
||||
val mArray : MutableList<Long> = emptyList<Long>().toMutableList()
|
||||
Log.d("removeZeros: Got array")
|
||||
Log.d("removeZeros: Got array size ${array.size}")
|
||||
for (i in array.indices){
|
||||
if(array[i] > 0L){
|
||||
mArray.add(array[i])
|
||||
|
|
@ -402,4 +421,4 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
|||
return mArray.toLongArray()
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import java.io.*
|
|||
object FileUtilities {
|
||||
const val mFMFreqFileName = "fm_freq_current"
|
||||
const val mFMVolumeFileName = "fm_volume_current"
|
||||
const val mHeadsetFileName = "fm_using_headset"
|
||||
const val mFavouriteChannelFileName = "fm_fav_freqs"
|
||||
|
||||
fun writeToFile(fileName : String, data: String, mContext: Context){
|
||||
var os: OutputStream? = null
|
||||
|
|
@ -41,12 +41,7 @@ object FileUtilities {
|
|||
}
|
||||
return String(mByteArray)
|
||||
}
|
||||
fun createFile(fileName: String, mContext: Context){
|
||||
File(mContext.filesDir.absolutePath + "/" + fileName).createNewFile()
|
||||
}
|
||||
fun removeFile(fileName: String, mContext: Context){
|
||||
File(mContext.filesDir.absolutePath + "/" + fileName).delete()
|
||||
}
|
||||
|
||||
fun checkIfExistFile(fileName: String, mContext: Context): Boolean {
|
||||
return File(mContext.filesDir.absolutePath + "/" + fileName).exists()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ android_app {
|
|||
"src/**/*.kt",
|
||||
],
|
||||
platform_apis: true,
|
||||
privileged: true,
|
||||
certificate: "platform",
|
||||
jni_libs: ["libsamsungparts_jni"],
|
||||
static_libs: ["androidx.preference_preference"],
|
||||
|
|
|
|||
|
|
@ -2,4 +2,3 @@
|
|||
|
||||
### DATA
|
||||
/data/zram(/.*)? u:object_r:zram_data_file:s0
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue