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:
roynatech2544 2022-02-28 18:05:41 +09:00
commit 9443bdc315
No known key found for this signature in database
GPG key ID: 9675C32163D88D30
17 changed files with 312 additions and 142 deletions

View file

@ -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",

View file

@ -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>

View file

@ -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"],
}

View 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;
}

View file

@ -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);
}
}
}

View file

@ -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"
}
}

View file

@ -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
}

View file

@ -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

View file

@ -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,
}

View file

@ -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
}

View file

@ -0,0 +1,11 @@
package com.eurekateam.fmradio.enums
/***
* [STATE_PLAYING] : Currently playing
*
* [STATE_STOPPED] : Stopped playing
*/
enum class PlayState {
STATE_PLAYING,
STATE_STOPPED
}

View file

@ -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")
}

View file

@ -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()
}
}
}
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}

View file

@ -4,7 +4,6 @@ android_app {
"src/**/*.kt",
],
platform_apis: true,
privileged: true,
certificate: "platform",
jni_libs: ["libsamsungparts_jni"],
static_libs: ["androidx.preference_preference"],

View file

@ -2,4 +2,3 @@
### DATA
/data/zram(/.*)? u:object_r:zram_data_file:s0