mirror of
https://github.com/chararomandroid/android_device_samsung_a20
synced 2026-08-22 20:15:32 -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,
|
platform_apis: true,
|
||||||
privileged: true,
|
privileged: true,
|
||||||
certificate: "platform",
|
certificate: "platform",
|
||||||
jni_libs: ["libfmioctl_jni"],
|
jni_libs: ["libfmnative_jni"],
|
||||||
static_libs: [
|
static_libs: [
|
||||||
"androidx.core_core",
|
"androidx.core_core",
|
||||||
"androidx.appcompat_appcompat",
|
"androidx.appcompat_appcompat",
|
||||||
|
|
|
||||||
|
|
@ -21,11 +21,11 @@
|
||||||
android:name=".FMRadioService"
|
android:name=".FMRadioService"
|
||||||
android:exported="false">
|
android:exported="false">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="com.eurekateam.fmradio.STARTBG" />
|
|
||||||
<action android:name="com.eurekateam.fmradio.PLAY_FM"/>
|
<action android:name="com.eurekateam.fmradio.PLAY_FM"/>
|
||||||
<action android:name="com.eurekateam.fmradio.NEXT"/>
|
<action android:name="com.eurekateam.fmradio.NEXT"/>
|
||||||
<action android:name="com.eurekateam.fmradio.BEFORE"/>
|
<action android:name="com.eurekateam.fmradio.BEFORE"/>
|
||||||
<action android:name="com.eurekateam.fmradio.QUIT"/>
|
<action android:name="com.eurekateam.fmradio.QUIT"/>
|
||||||
|
<action android:name="com.eurekateam.fmradio.OUTPUT"/>
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
<category android:name="android.intent.category.DEFAULT" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,12 @@
|
||||||
cc_library_shared {
|
cc_library_shared {
|
||||||
name: "libfmioctl_jni",
|
name: "libfmnative_jni",
|
||||||
cflags: [
|
cflags: [
|
||||||
"-Wno-unused-parameter",
|
"-Wno-unused-parameter",
|
||||||
],
|
],
|
||||||
srcs: ["fm_ioctl.cpp"],
|
srcs: [
|
||||||
|
"fm_ioctl.cpp",
|
||||||
|
"fm_route.cpp",
|
||||||
|
],
|
||||||
shared_libs: [
|
shared_libs: [
|
||||||
"libhidlbase",
|
"libhidlbase",
|
||||||
"liblog",
|
"liblog",
|
||||||
|
|
@ -11,7 +14,19 @@ cc_library_shared {
|
||||||
"vendor.eureka.hardware.fmradio@1.0",
|
"vendor.eureka.hardware.fmradio@1.0",
|
||||||
"vendor.eureka.hardware.fmradio@1.1",
|
"vendor.eureka.hardware.fmradio@1.1",
|
||||||
"vendor.eureka.hardware.fmradio@1.2",
|
"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.app.*
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.media.MediaMetadata
|
import android.media.MediaMetadata
|
||||||
import android.media.session.MediaSession
|
import android.media.session.MediaSession
|
||||||
import android.media.session.PlaybackState
|
import android.media.session.PlaybackState
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import com.eurekateam.fmradio.enums.OutputState
|
||||||
import android.graphics.BitmapFactory
|
import com.eurekateam.fmradio.enums.PlayState
|
||||||
|
import com.eurekateam.fmradio.enums.PowerState
|
||||||
import com.eurekateam.fmradio.fragments.MainFragment
|
import com.eurekateam.fmradio.fragments.MainFragment
|
||||||
import com.eurekateam.fmradio.utils.Log
|
import com.eurekateam.fmradio.utils.Log
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
@ -20,15 +22,12 @@ class FMRadioService : Service() {
|
||||||
private lateinit var mContext: Context
|
private lateinit var mContext: Context
|
||||||
private lateinit var mAudioManager: AudioManager
|
private lateinit var mAudioManager: AudioManager
|
||||||
private lateinit var mTracks : LongArray
|
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 mNativeFMInterface: NativeFMInterface
|
||||||
private lateinit var mFilePath : File
|
|
||||||
override fun onBind(intent: Intent?): IBinder? {
|
override fun onBind(intent: Intent?): IBinder? {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
Log.i("--- FMRadio Background (IN) ---")
|
||||||
fd = MainFragment.fd
|
fd = MainFragment.fd
|
||||||
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
||||||
mTracks = MainFragment.mTracks
|
mTracks = MainFragment.mTracks
|
||||||
|
|
@ -40,62 +39,61 @@ class FMRadioService : Service() {
|
||||||
if (intent != null) {
|
if (intent != null) {
|
||||||
intent.action?.let { Log.i(it) }
|
intent.action?.let { Log.i(it) }
|
||||||
when (intent.action) {
|
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 -> {
|
ACTION_PLAY -> {
|
||||||
mIsPlaying = !mIsPlaying
|
if (mPlayState == PlayState.STATE_PLAYING) {
|
||||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
|
mPlayState = PlayState.STATE_STOPPED
|
||||||
if (mIsPlaying) {
|
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||||
mAudioManager.setParameters(FM_RADIO_ON)
|
}else if (mPlayState == PlayState.STATE_STOPPED){
|
||||||
} else {
|
mPlayState = PlayState.STATE_PLAYING
|
||||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
mAudioManager.setParameters(PowerState.FM_POWER_ON.mAudioParam)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ACTION_BEFORE -> {
|
ACTION_BEFORE -> {
|
||||||
mIsPlaying = true
|
mPlayState = PlayState.STATE_PLAYING
|
||||||
Log.i("onStartCommand: mCurrentIndex $mIndex")
|
Log.i("mCurrentIndex $mIndex")
|
||||||
if (mIndex > 0)
|
if (mIndex > 0)
|
||||||
mIndex -= 1
|
mIndex -= 1
|
||||||
if (mIndex >= 1)
|
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
||||||
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
MainFragment.mFreqCurrent = mTracks[mIndex].toInt()
|
||||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
|
|
||||||
}
|
}
|
||||||
ACTION_NEXT -> {
|
ACTION_NEXT -> {
|
||||||
mIsPlaying = true
|
mPlayState = PlayState.STATE_PLAYING
|
||||||
Log.i("onStartCommand: mCurrentIndex $mIndex")
|
Log.i("mCurrentIndex $mIndex")
|
||||||
if (mIndex < mTracks.size - 1)
|
if (mIndex < mTracks.size - 1)
|
||||||
mIndex += 1
|
mIndex += 1
|
||||||
if (mIndex < mTracks.size - 1)
|
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
||||||
mNativeFMInterface.setFMFreq(fd, mTracks[mIndex].toInt())
|
MainFragment.mFreqCurrent = mTracks[mIndex].toInt()
|
||||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000}Mhz"
|
|
||||||
}
|
}
|
||||||
ACTION_QUIT -> {
|
ACTION_QUIT -> {
|
||||||
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
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()
|
mMediaSession.release()
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
else -> {
|
ACTION_OUTPUT -> {
|
||||||
throw IllegalAccessException("Unexpected Intent!")
|
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
|
sendMetaData("FM ${mTracks[mIndex].toFloat() / 1000} Mhz")
|
||||||
mFilePath = mContext.filesDir
|
startForeground(51, pushNotification())
|
||||||
mNativeFMInterface = NativeFMInterface()
|
if (mOutput == OutputState.OUTPUT_SPEAKER){
|
||||||
mTitle = "FM ${mTracks[mIndex].toFloat() / 1000} Mhz"
|
changeOutputDevice(OutputState.OUTPUT_HEADSET)
|
||||||
Log.i("onStartCommand: mTitle=$mTitle")
|
}
|
||||||
mMediaSession = MediaSession(this, "FMRadio")
|
Log.i("--- FMRadio Background (OUT) ---")
|
||||||
setPlaybackState()
|
|
||||||
sendMetaData("FM ${mTracks[mIndex].toFloat() / 1000}Mhz")
|
|
||||||
mMediaSession.isActive = true
|
|
||||||
startForeground(51,pushNotification())
|
|
||||||
return START_STICKY
|
return START_STICKY
|
||||||
}
|
}
|
||||||
private fun setPlaybackState() {
|
private fun setPlaybackState() {
|
||||||
|
|
@ -112,7 +110,8 @@ class FMRadioService : Service() {
|
||||||
val metadata = MediaMetadata.Builder()
|
val metadata = MediaMetadata.Builder()
|
||||||
.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle)
|
.putString(MediaMetadata.METADATA_KEY_TITLE, mTitle)
|
||||||
.putString(MediaMetadata.METADATA_KEY_ARTIST, resources.getString(R.string.app_name))
|
.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()
|
.build()
|
||||||
mMediaSession.setMetadata(metadata)
|
mMediaSession.setMetadata(metadata)
|
||||||
}
|
}
|
||||||
|
|
@ -132,16 +131,31 @@ class FMRadioService : Service() {
|
||||||
builder.setColor(resources.getColor(android.R.color.system_accent1_200, mContext.theme))
|
builder.setColor(resources.getColor(android.R.color.system_accent1_200, mContext.theme))
|
||||||
builder.setColorized(true)
|
builder.setColorized(true)
|
||||||
builder.style = mediaStyle
|
builder.style = mediaStyle
|
||||||
|
builder.setOnlyAlertOnce(true)
|
||||||
builder.setChannelId(mContext.packageName)
|
builder.setChannelId(mContext.packageName)
|
||||||
val togglePlay = PendingIntent.getService(mContext, 0, Intent(ACTION_PLAY), PendingIntent.FLAG_IMMUTABLE)
|
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 forward = PendingIntent.getService(mContext, 0, Intent(ACTION_NEXT), PendingIntent.FLAG_IMMUTABLE)
|
||||||
val rewind = PendingIntent.getService(mContext, 0, Intent(ACTION_BEFORE), 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 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(
|
val mPausePlayAction: Notification.Action = Notification.Action.Builder(
|
||||||
if (mIsPlaying) {
|
when (mPlayState) {
|
||||||
Icon.createWithResource(this, R.drawable.ic_pause)
|
PlayState.STATE_PLAYING -> {
|
||||||
}else{
|
Icon.createWithResource(this, R.drawable.ic_pause)
|
||||||
Icon.createWithResource(this, R.drawable.ic_play)
|
}
|
||||||
|
PlayState.STATE_STOPPED -> {
|
||||||
|
Icon.createWithResource(this, R.drawable.ic_play)
|
||||||
|
}
|
||||||
}, "Start/Stop", togglePlay
|
}, "Start/Stop", togglePlay
|
||||||
|
|
||||||
).build()
|
).build()
|
||||||
|
|
@ -157,6 +171,7 @@ class FMRadioService : Service() {
|
||||||
Icon.createWithResource(this, R.drawable.ic_close),
|
Icon.createWithResource(this, R.drawable.ic_close),
|
||||||
"Close", close
|
"Close", close
|
||||||
).build()
|
).build()
|
||||||
|
builder.addAction(mOutputAction)
|
||||||
builder.addAction(mRewindAction)
|
builder.addAction(mRewindAction)
|
||||||
builder.addAction(mPausePlayAction)
|
builder.addAction(mPausePlayAction)
|
||||||
builder.addAction(mForwardAction)
|
builder.addAction(mForwardAction)
|
||||||
|
|
@ -165,15 +180,23 @@ class FMRadioService : Service() {
|
||||||
}
|
}
|
||||||
companion object {
|
companion object {
|
||||||
private const val PACKAGENAME = "com.eurekateam.fmradio"
|
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_PLAY = "$PACKAGENAME.PLAY_FM"
|
||||||
private const val ACTION_NEXT = "$PACKAGENAME.NEXT"
|
private const val ACTION_NEXT = "$PACKAGENAME.NEXT"
|
||||||
private var mIsPlaying = true
|
|
||||||
private const val ACTION_BEFORE = "$PACKAGENAME.BEFORE"
|
private const val ACTION_BEFORE = "$PACKAGENAME.BEFORE"
|
||||||
private const val ACTION_QUIT = "$PACKAGENAME.QUIT"
|
private const val ACTION_QUIT = "$PACKAGENAME.QUIT"
|
||||||
private const val FM_RADIO_ON = "l_fmradio_mode=on"
|
private const val ACTION_OUTPUT = "$PACKAGENAME.OUTPUT"
|
||||||
private const val FM_RADIO_OFF = "l_fmradio_mode=off"
|
private var mPlayState : PlayState = PlayState.STATE_PLAYING
|
||||||
private const val TAG = "FMRadioService"
|
private var mOutput : OutputState = MainFragment.mHeadset
|
||||||
|
private var fd : Int = -1
|
||||||
|
private var mIndex = -1
|
||||||
lateinit var mMediaSession : MediaSession
|
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.appcompat.widget.AppCompatImageView
|
||||||
import androidx.fragment.app.Fragment
|
import androidx.fragment.app.Fragment
|
||||||
import androidx.fragment.app.FragmentManager
|
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.ChannelListFragment
|
||||||
import com.eurekateam.fmradio.fragments.FavouriteFragment
|
import com.eurekateam.fmradio.fragments.FavouriteFragment
|
||||||
import com.eurekateam.fmradio.fragments.MainFragment
|
import com.eurekateam.fmradio.fragments.MainFragment
|
||||||
|
|
@ -25,6 +27,12 @@ import com.google.android.material.textview.MaterialTextView
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var mIntent : Intent
|
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) {
|
private fun changeFragment(fragment: Fragment?, tagFragmentName: String) {
|
||||||
supportFragmentManager.beginTransaction().apply {
|
supportFragmentManager.beginTransaction().apply {
|
||||||
replace(R.id.container_view, fragment!!, tagFragmentName)
|
replace(R.id.container_view, fragment!!, tagFragmentName)
|
||||||
|
|
@ -40,7 +48,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var mAlertImage : AppCompatImageView
|
private lateinit var mAlertImage : AppCompatImageView
|
||||||
private lateinit var mAudioManager : AudioManager
|
private lateinit var mAudioManager : AudioManager
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
System.loadLibrary("fmioctl_jni")
|
System.loadLibrary("fmnative_jni")
|
||||||
MainFragment.fd = mFMInterface.openFMDevice()
|
MainFragment.fd = mFMInterface.openFMDevice()
|
||||||
mAlertView = (getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater)
|
mAlertView = (getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater)
|
||||||
.inflate(R.layout.alertdialog, null)
|
.inflate(R.layout.alertdialog, null)
|
||||||
|
|
@ -65,15 +73,19 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
DynamicColors.applyToActivitiesIfAvailable(application)
|
DynamicColors.applyToActivitiesIfAvailable(application)
|
||||||
mAudioManager = getSystemService(AUDIO_SERVICE) as AudioManager
|
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)
|
val mAudioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
||||||
for (i in mAudioDeviceInfo.indices){
|
for (i in mAudioDeviceInfo.indices){
|
||||||
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
||||||
mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
|
mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES) {
|
||||||
MainFragment.mHeadSetPlugged = true
|
MainFragment.mHeadSetPlugged = HeadsetState.HEADSET_STATE_CONNECTED
|
||||||
Log.i("Wired Headphones detected")
|
Log.i("Wired Headphones detected")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!MainFragment.mHeadSetPlugged){
|
if (MainFragment.mHeadSetPlugged != HeadsetState.HEADSET_STATE_CONNECTED){
|
||||||
mAlertTitle.text = getString(R.string.no_headphones_error)
|
mAlertTitle.text = getString(R.string.no_headphones_error)
|
||||||
mAlertDesc.text = getString(R.string.no_headphones_error_desc)
|
mAlertDesc.text = getString(R.string.no_headphones_error_desc)
|
||||||
mAlertImage.setImageIcon(Icon.createWithResource(mAlertView.context,
|
mAlertImage.setImageIcon(Icon.createWithResource(mAlertView.context,
|
||||||
|
|
@ -83,7 +95,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
.setCancelable(false)
|
.setCancelable(false)
|
||||||
.setView(mAlertView)
|
.setView(mAlertView)
|
||||||
.setNegativeButton(R.string.ok) { v: DialogInterface, _: Int ->
|
.setNegativeButton(R.string.ok) { v: DialogInterface, _: Int ->
|
||||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||||
v.dismiss()
|
v.dismiss()
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
finish()
|
finish()
|
||||||
|
|
@ -98,8 +110,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
val mFavouriteFragment = FavouriteFragment()
|
val mFavouriteFragment = FavouriteFragment()
|
||||||
changeFragment(mRadioMainFragment, MainFragment::class.java.name)
|
changeFragment(mRadioMainFragment, MainFragment::class.java.name)
|
||||||
mIntent = Intent(this, FMRadioService::class.java)
|
mIntent = Intent(this, FMRadioService::class.java)
|
||||||
mIntent.action = BEGIN_BG_SERVICE
|
|
||||||
startService(mIntent)
|
|
||||||
findViewById<BottomNavigationView>(R.id.bottom_nav_bar).setOnItemSelectedListener {
|
findViewById<BottomNavigationView>(R.id.bottom_nav_bar).setOnItemSelectedListener {
|
||||||
when (it.itemId){
|
when (it.itemId){
|
||||||
R.id.radio_main -> changeFragment(mRadioMainFragment, MainFragment::class.java.name)
|
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() {
|
private val mWiredHeadsetReceiver: BroadcastReceiver = object : BroadcastReceiver() {
|
||||||
override fun onReceive(context: Context, intent: Intent) {
|
override fun onReceive(context: Context, intent: Intent) {
|
||||||
if (intent.action == AudioManager.ACTION_HEADSET_PLUG) {
|
if (intent.action == AudioManager.ACTION_HEADSET_PLUG) {
|
||||||
MainFragment.mHeadSetPlugged = false
|
MainFragment.mHeadSetPlugged = HeadsetState.HEADSET_STATE_DISCONNECTED
|
||||||
val mAudioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
val mAudioDeviceInfo = mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)
|
||||||
for (i in mAudioDeviceInfo.indices){
|
for (i in mAudioDeviceInfo.indices){
|
||||||
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
if (mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADSET ||
|
||||||
mAudioDeviceInfo[i].type == AudioDeviceInfo.TYPE_WIRED_HEADPHONES){
|
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")
|
Log.w("onReceive: Headset Unplugged")
|
||||||
mAlertTitle.text = getString(R.string.no_headphones_error)
|
mAlertTitle.text = getString(R.string.no_headphones_error)
|
||||||
mAlertDesc.text = getString(R.string.no_headphones_error_desc)
|
mAlertDesc.text = getString(R.string.no_headphones_error_desc)
|
||||||
|
|
@ -134,7 +147,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
.setView(mAlertView)
|
.setView(mAlertView)
|
||||||
.setNegativeButton(R.string.ok) { v: DialogInterface, _: Int ->
|
.setNegativeButton(R.string.ok) { v: DialogInterface, _: Int ->
|
||||||
v.dismiss()
|
v.dismiss()
|
||||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||||
Handler(Looper.getMainLooper()).postDelayed({
|
Handler(Looper.getMainLooper()).postDelayed({
|
||||||
finish()
|
finish()
|
||||||
},500)
|
},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 {
|
fun getMySupportFragmentManager(): FragmentManager {
|
||||||
return supportFragmentManager
|
return supportFragmentManager
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onPause() {
|
override fun onPause() {
|
||||||
super.onPause()
|
super.onPause()
|
||||||
mIntent.action = BEGIN_BG_SERVICE
|
Log.i("Application onPause")
|
||||||
|
mIntent.action = ACTION_START
|
||||||
startService(mIntent)
|
startService(mIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,10 +177,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
super.onRestart()
|
super.onRestart()
|
||||||
stopService(mIntent)
|
stopService(mIntent)
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val PACKAGENAME = "com.eurekateam.fmradio"
|
private const val ACTION_START = "com.eurekateam.fmradio.START"
|
||||||
private const val BEGIN_BG_SERVICE = "$PACKAGENAME.STARTBG"
|
|
||||||
private const val FM_RADIO_OFF = "l_fmradio_mode=off"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,4 +21,5 @@ class NativeFMInterface {
|
||||||
external fun setFMRSSI(fd: Int, rssi: Long): Int
|
external fun setFMRSSI(fd: Int, rssi: Long): Int
|
||||||
external fun closeFMDevice(fd: Int)
|
external fun closeFMDevice(fd: Int)
|
||||||
external fun getSysfsSupport(): Boolean
|
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.PebbleTextView
|
||||||
import com.eurekateam.fmradio.R
|
import com.eurekateam.fmradio.R
|
||||||
import com.eurekateam.fmradio.fragments.MainFragment
|
import com.eurekateam.fmradio.fragments.MainFragment
|
||||||
|
import com.eurekateam.fmradio.utils.FileUtilities
|
||||||
|
|
||||||
|
|
||||||
class PebbleLayoutAdapter (private val mContext: Context) : BaseAdapter() {
|
class PebbleLayoutAdapter (private val mContext: Context) : BaseAdapter() {
|
||||||
|
|
@ -21,6 +22,11 @@ class PebbleLayoutAdapter (private val mContext: Context) : BaseAdapter() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
mFavoriteList.sort()
|
mFavoriteList.sort()
|
||||||
|
var mData = ""
|
||||||
|
for (i in mFavoriteList){
|
||||||
|
mData += "$i\n"
|
||||||
|
}
|
||||||
|
FileUtilities.writeToFile(FileUtilities.mFavouriteChannelFileName, mData, mContext)
|
||||||
}
|
}
|
||||||
override fun getCount(): Int {
|
override fun getCount(): Int {
|
||||||
return mFavoriteList.size
|
return mFavoriteList.size
|
||||||
|
|
@ -47,6 +53,10 @@ class PebbleLayoutAdapter (private val mContext: Context) : BaseAdapter() {
|
||||||
setOnClickListener {
|
setOnClickListener {
|
||||||
mFMInterface.setFMFreq(MainFragment.fd, mFavoriteList[id])
|
mFMInterface.setFMFreq(MainFragment.fd, mFavoriteList[id])
|
||||||
MainFragment.mFreqCurrent = mFavoriteList[id]
|
MainFragment.mFreqCurrent = mFavoriteList[id]
|
||||||
|
FileUtilities.writeToFile(
|
||||||
|
FileUtilities.mFMFreqFileName,
|
||||||
|
MainFragment.mFreqCurrent.toString(), mContext
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mAnotherConvertView
|
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.R
|
||||||
import com.eurekateam.fmradio.adapters.ListViewAdapter
|
import com.eurekateam.fmradio.adapters.ListViewAdapter
|
||||||
import com.google.android.material.floatingactionbutton.FloatingActionButton
|
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),
|
class ChannelListFragment : Fragment(R.layout.activity_channel_list),
|
||||||
View.OnClickListener {
|
View.OnClickListener {
|
||||||
|
|
@ -45,11 +49,17 @@ class ChannelListFragment : Fragment(R.layout.activity_channel_list),
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onClick(v: View?) {
|
override fun onClick(v: View?) {
|
||||||
MainFragment.mRefreshTracks()
|
GlobalScope.launch {
|
||||||
(requireActivity() as MainActivity).getMySupportFragmentManager().apply {
|
withContext(Dispatchers.IO){
|
||||||
beginTransaction().remove(this@ChannelListFragment).commit()
|
MainFragment.mRefreshTracks()
|
||||||
executePendingTransactions()
|
}
|
||||||
beginTransaction().add(R.id.container_view, this@ChannelListFragment).commit()
|
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
|
package com.eurekateam.fmradio.fragments
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import android.graphics.drawable.Drawable
|
import android.graphics.drawable.Drawable
|
||||||
import android.graphics.drawable.Icon
|
import android.graphics.drawable.Icon
|
||||||
import android.media.AudioManager
|
import android.media.AudioManager
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.Display
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.FrameLayout
|
import android.widget.FrameLayout
|
||||||
import android.widget.SeekBar
|
import android.widget.SeekBar
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.appcompat.widget.AppCompatSeekBar
|
import androidx.appcompat.widget.AppCompatSeekBar
|
||||||
import androidx.core.content.res.ResourcesCompat
|
import androidx.core.content.res.ResourcesCompat
|
||||||
import androidx.fragment.app.Fragment
|
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.NativeFMInterface
|
||||||
import com.eurekateam.fmradio.R
|
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.floatingactionbutton.FloatingActionButton
|
||||||
import com.google.android.material.textview.MaterialTextView
|
import com.google.android.material.textview.MaterialTextView
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -56,7 +58,7 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
R.drawable.ic_star, requireContext().theme)!!
|
R.drawable.ic_star, requireContext().theme)!!
|
||||||
mStarFilled = ResourcesCompat.getDrawable(requireContext().resources,
|
mStarFilled = ResourcesCompat.getDrawable(requireContext().resources,
|
||||||
R.drawable.ic_star_filled, requireContext().theme)!!
|
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)
|
mVolumeUp = mRootView.findViewById(R.id.volume_up)
|
||||||
mVolumeDown = mRootView.findViewById(R.id.volume_down)
|
mVolumeDown = mRootView.findViewById(R.id.volume_down)
|
||||||
mSeekBar = mRootView.findViewById(R.id.volume_seekbar)
|
mSeekBar = mRootView.findViewById(R.id.volume_seekbar)
|
||||||
|
|
@ -97,15 +99,24 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
else
|
else
|
||||||
setBackgroundColor(resources.getColor(android.R.color.system_accent1_700, requireContext().theme))
|
setBackgroundColor(resources.getColor(android.R.color.system_accent1_700, requireContext().theme))
|
||||||
}
|
}
|
||||||
var mFMInit = false
|
|
||||||
GlobalScope.launch {
|
GlobalScope.launch {
|
||||||
withContext(Dispatchers.IO){
|
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) {
|
if (mFreqCurrent == -1) {
|
||||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
mUpdateEnableDisable(false, mRootView)
|
mUpdateEnableDisable(false, mRootView)
|
||||||
}
|
}
|
||||||
mFMInit = true
|
mMute = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (FileUtilities.checkIfExistFile(FileUtilities.mFMFreqFileName, requireContext())){
|
if (FileUtilities.checkIfExistFile(FileUtilities.mFMFreqFileName, requireContext())){
|
||||||
|
|
@ -141,27 +152,8 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.IO){
|
if (!mMute)
|
||||||
if (FileUtilities.checkIfExistFile(FileUtilities.mHeadsetFileName, requireContext())){
|
mFMInterface.setFMMute(fd, true)
|
||||||
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())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
mFMInterface.setFMFreq(fd, mFMInterface.getFMLower(fd))
|
mFMInterface.setFMFreq(fd, mFMInterface.getFMLower(fd))
|
||||||
mRefreshTracks()
|
mRefreshTracks()
|
||||||
if (mFreqCurrent != -1){
|
if (mFreqCurrent != -1){
|
||||||
|
|
@ -170,14 +162,13 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
mFreqCurrent = mFMInterface.getFMLower(fd)
|
mFreqCurrent = mFMInterface.getFMLower(fd)
|
||||||
}
|
}
|
||||||
mFMInterface.setFMBoot(fd)
|
mFMInterface.setFMBoot(fd)
|
||||||
if (!mFMInit && isAdded) {
|
mFreqCurrent = mFMInterface.getFMFreq(fd).toInt()
|
||||||
mFreqCurrent = mFMInterface.getFMFreq(fd).toInt()
|
mFMInterface.setFMFreq(fd, mFreqCurrent)
|
||||||
mFMInterface.setFMFreq(fd, mFreqCurrent)
|
withContext(Dispatchers.Main) {
|
||||||
requireActivity().mainExecutor.execute {
|
mFMFreq.text = mCleanFormat.format( mFreqCurrent.toFloat() / 1000)
|
||||||
mFMFreq.text = mCleanFormat.format( mFreqCurrent.toFloat() / 1000)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (mTracks.isEmpty())
|
if (!mMute)
|
||||||
|
mFMInterface.setFMMute(fd, false)
|
||||||
if (mFreqCurrent == -1)
|
if (mFreqCurrent == -1)
|
||||||
mFMInterface.setFMThread(fd, true)
|
mFMInterface.setFMThread(fd, true)
|
||||||
withContext(Dispatchers.Main){
|
withContext(Dispatchers.Main){
|
||||||
|
|
@ -196,14 +187,34 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
}
|
}
|
||||||
return mRootView
|
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) {
|
private fun View.disable(mTextView : Boolean = false) {
|
||||||
alpha = .7f
|
alpha = .7f
|
||||||
if(!mTextView) isEnabled = false
|
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) {
|
private fun View.enable(mTextView : Boolean = false) {
|
||||||
alpha = 1.0f
|
alpha = 1.0f
|
||||||
if(!mTextView) isEnabled = true
|
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()){
|
private fun mUpdateEnableDisable(mEnabled: Boolean, mView: View = requireView()){
|
||||||
val mTextViewList = listOf(R.id.fm_freq, R.id.freq_misc)
|
val mTextViewList = listOf(R.id.fm_freq, R.id.freq_misc)
|
||||||
val mFloatButtonList = listOf(
|
val mFloatButtonList = listOf(
|
||||||
|
|
@ -227,29 +238,30 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
override fun onClick(v: View) {
|
override fun onClick(v: View) {
|
||||||
when (v.id) {
|
when (v.id) {
|
||||||
mOutputSwitch.id -> {
|
mOutputSwitch.id -> {
|
||||||
mAudioManager.mode = AudioManager.MODE_IN_COMMUNICATION
|
if (mHeadset == OutputState.OUTPUT_HEADSET) {
|
||||||
mAudioManager.isSpeakerphoneOn = mHeadset
|
|
||||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
|
||||||
mAudioManager.setParameters(FM_RADIO_ON)
|
|
||||||
mAudioManager.mode = AudioManager.MODE_NORMAL
|
|
||||||
if (mHeadset) {
|
|
||||||
mOutputSwitch.setImageIcon(
|
mOutputSwitch.setImageIcon(
|
||||||
Icon.createWithResource(
|
Icon.createWithResource(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
R.drawable.ic_volume_up
|
R.drawable.ic_volume_up
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
FileUtilities.createFile(FileUtilities.mHeadsetFileName, requireContext())
|
val ret = mFMInterface.setAudioRoute(true);
|
||||||
}else{
|
Log.i("mFMInterface.setAudioRoute return $ret");
|
||||||
|
}else if (mHeadset == OutputState.OUTPUT_SPEAKER){
|
||||||
mOutputSwitch.setImageIcon(
|
mOutputSwitch.setImageIcon(
|
||||||
Icon.createWithResource(
|
Icon.createWithResource(
|
||||||
requireContext(),
|
requireContext(),
|
||||||
R.drawable.ic_headphones
|
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 -> {
|
mVolumeUp.id -> {
|
||||||
if (mVolume < 15)
|
if (mVolume < 15)
|
||||||
|
|
@ -302,10 +314,10 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
}
|
}
|
||||||
mPowerBtn.id -> {
|
mPowerBtn.id -> {
|
||||||
if (mFMPower){
|
if (mFMPower){
|
||||||
mAudioManager.setParameters(FM_RADIO_OFF)
|
mAudioManager.setParameters(PowerState.FM_POWER_OFF.mAudioParam)
|
||||||
mFMFreq.text = getText(R.string.inital_freq)
|
mFMFreq.text = getText(R.string.inital_freq)
|
||||||
}else{
|
}else{
|
||||||
mAudioManager.setParameters(FM_RADIO_ON)
|
mAudioManager.setParameters(PowerState.FM_POWER_ON.mAudioParam)
|
||||||
mFMFreq.text = mCleanFormat.format(mFreqCurrent.toFloat() / 1000)
|
mFMFreq.text = mCleanFormat.format(mFreqCurrent.toFloat() / 1000)
|
||||||
}
|
}
|
||||||
mUpdateEnableDisable(!mFMPower)
|
mUpdateEnableDisable(!mFMPower)
|
||||||
|
|
@ -372,13 +384,11 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
companion object {
|
companion object {
|
||||||
private var mVolume = -1
|
private var mVolume = -1
|
||||||
var fd = -1
|
var fd = -1
|
||||||
private var mHeadset = false
|
var mHeadset = OutputState.OUTPUT_SPEAKER
|
||||||
var mFreqCurrent = -1
|
var mFreqCurrent = -1
|
||||||
private var mFMPower = false
|
private var mFMPower = false
|
||||||
var mHeadSetPlugged = false
|
var mHeadSetPlugged : HeadsetState = HeadsetState.HEADSET_STATE_DISCONNECTED
|
||||||
var mTracks : LongArray = emptyArray<Long>().toLongArray()
|
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(){
|
fun mRefreshTracks(){
|
||||||
NativeFMInterface().setFMFreq(fd, NativeFMInterface().getFMLower(fd))
|
NativeFMInterface().setFMFreq(fd, NativeFMInterface().getFMLower(fd))
|
||||||
mTracks = NativeFMInterface().getFMTracks(fd)
|
mTracks = NativeFMInterface().getFMTracks(fd)
|
||||||
|
|
@ -391,9 +401,18 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
val mFavStats = HashMap<Int, Boolean>(30)
|
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{
|
private fun removeZeros(array : LongArray): LongArray{
|
||||||
val mArray : MutableList<Long> = emptyList<Long>().toMutableList()
|
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){
|
for (i in array.indices){
|
||||||
if(array[i] > 0L){
|
if(array[i] > 0L){
|
||||||
mArray.add(array[i])
|
mArray.add(array[i])
|
||||||
|
|
@ -402,4 +421,4 @@ class MainFragment : Fragment(R.layout.fragment_main), View.OnClickListener,
|
||||||
return mArray.toLongArray()
|
return mArray.toLongArray()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ import java.io.*
|
||||||
object FileUtilities {
|
object FileUtilities {
|
||||||
const val mFMFreqFileName = "fm_freq_current"
|
const val mFMFreqFileName = "fm_freq_current"
|
||||||
const val mFMVolumeFileName = "fm_volume_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){
|
fun writeToFile(fileName : String, data: String, mContext: Context){
|
||||||
var os: OutputStream? = null
|
var os: OutputStream? = null
|
||||||
|
|
@ -41,12 +41,7 @@ object FileUtilities {
|
||||||
}
|
}
|
||||||
return String(mByteArray)
|
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 {
|
fun checkIfExistFile(fileName: String, mContext: Context): Boolean {
|
||||||
return File(mContext.filesDir.absolutePath + "/" + fileName).exists()
|
return File(mContext.filesDir.absolutePath + "/" + fileName).exists()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ android_app {
|
||||||
"src/**/*.kt",
|
"src/**/*.kt",
|
||||||
],
|
],
|
||||||
platform_apis: true,
|
platform_apis: true,
|
||||||
privileged: true,
|
|
||||||
certificate: "platform",
|
certificate: "platform",
|
||||||
jni_libs: ["libsamsungparts_jni"],
|
jni_libs: ["libsamsungparts_jni"],
|
||||||
static_libs: ["androidx.preference_preference"],
|
static_libs: ["androidx.preference_preference"],
|
||||||
|
|
|
||||||
|
|
@ -2,4 +2,3 @@
|
||||||
|
|
||||||
### DATA
|
### DATA
|
||||||
/data/zram(/.*)? u:object_r:zram_data_file:s0
|
/data/zram(/.*)? u:object_r:zram_data_file:s0
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue