diff --git a/universal7885-common/apps/FMRadio/Android.bp b/universal7885-common/apps/FMRadio/Android.bp
index d990883..25046db 100644
--- a/universal7885-common/apps/FMRadio/Android.bp
+++ b/universal7885-common/apps/FMRadio/Android.bp
@@ -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",
diff --git a/universal7885-common/apps/FMRadio/AndroidManifest.xml b/universal7885-common/apps/FMRadio/AndroidManifest.xml
index 3c9c98b..fda923d 100644
--- a/universal7885-common/apps/FMRadio/AndroidManifest.xml
+++ b/universal7885-common/apps/FMRadio/AndroidManifest.xml
@@ -21,11 +21,11 @@
android:name=".FMRadioService"
android:exported="false">
-
+
diff --git a/universal7885-common/apps/FMRadio/jni/Android.bp b/universal7885-common/apps/FMRadio/jni/Android.bp
index 56e7e41..16652d5 100644
--- a/universal7885-common/apps/FMRadio/jni/Android.bp
+++ b/universal7885-common/apps/FMRadio/jni/Android.bp
@@ -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"],
}
diff --git a/universal7885-common/apps/FMRadio/jni/fm_route.cpp b/universal7885-common/apps/FMRadio/jni/fm_route.cpp
new file mode 100644
index 0000000..c3b1b3c
--- /dev/null
+++ b/universal7885-common/apps/FMRadio/jni/fm_route.cpp
@@ -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
+#include
+#include
+
+#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& 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;
+}
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/FMRadioService.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/FMRadioService.kt
index 9eaf698..b3632bf 100644
--- a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/FMRadioService.kt
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/FMRadioService.kt
@@ -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);
+ }
+ }
}
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/MainActivity.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/MainActivity.kt
index 3d0d9b2..66aa543 100644
--- a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/MainActivity.kt
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/MainActivity.kt
@@ -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(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"
}
}
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/NativeFMInterface.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/NativeFMInterface.kt
index 2d405f5..11797a9 100644
--- a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/NativeFMInterface.kt
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/NativeFMInterface.kt
@@ -21,4 +21,5 @@ class NativeFMInterface {
external fun setFMRSSI(fd: Int, rssi: Long): Int
external fun closeFMDevice(fd: Int)
external fun getSysfsSupport(): Boolean
-}
\ No newline at end of file
+ external fun setAudioRoute(speaker: Boolean): Int
+}
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/adapters/PebbleLayoutAdapter.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/adapters/PebbleLayoutAdapter.kt
index d033531..e32fb14 100644
--- a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/adapters/PebbleLayoutAdapter.kt
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/adapters/PebbleLayoutAdapter.kt
@@ -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
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/HeadsetState.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/HeadsetState.kt
new file mode 100644
index 0000000..8323743
--- /dev/null
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/HeadsetState.kt
@@ -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,
+}
\ No newline at end of file
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/OutputState.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/OutputState.kt
new file mode 100644
index 0000000..d632149
--- /dev/null
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/OutputState.kt
@@ -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
+}
\ No newline at end of file
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/PlayState.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/PlayState.kt
new file mode 100644
index 0000000..291fee7
--- /dev/null
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/PlayState.kt
@@ -0,0 +1,11 @@
+package com.eurekateam.fmradio.enums
+
+/***
+ * [STATE_PLAYING] : Currently playing
+ *
+ * [STATE_STOPPED] : Stopped playing
+ */
+enum class PlayState {
+ STATE_PLAYING,
+ STATE_STOPPED
+}
\ No newline at end of file
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/PowerState.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/PowerState.kt
new file mode 100644
index 0000000..2b1fff9
--- /dev/null
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/enums/PowerState.kt
@@ -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")
+}
\ No newline at end of file
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/ChannelListFragment.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/ChannelListFragment.kt
index a095eca..5fbfc08 100644
--- a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/ChannelListFragment.kt
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/ChannelListFragment.kt
@@ -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()
+ }
+ }
}
}
}
\ No newline at end of file
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/MainFragment.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/MainFragment.kt
index a952c80..f10a256 100644
--- a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/MainFragment.kt
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/fragments/MainFragment.kt
@@ -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().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(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 = emptyList().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()
}
-}
\ No newline at end of file
+}
diff --git a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/utils/FileUtilities.kt b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/utils/FileUtilities.kt
index 03ad842..e5b8bff 100644
--- a/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/utils/FileUtilities.kt
+++ b/universal7885-common/apps/FMRadio/src/com/eurekateam/fmradio/utils/FileUtilities.kt
@@ -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()
}
diff --git a/universal7885-common/apps/SamsungParts/Android.bp b/universal7885-common/apps/SamsungParts/Android.bp
index 3cf02ed..22b97a4 100644
--- a/universal7885-common/apps/SamsungParts/Android.bp
+++ b/universal7885-common/apps/SamsungParts/Android.bp
@@ -4,7 +4,6 @@ android_app {
"src/**/*.kt",
],
platform_apis: true,
- privileged: true,
certificate: "platform",
jni_libs: ["libsamsungparts_jni"],
static_libs: ["androidx.preference_preference"],
diff --git a/universal7885-common/sepolicy/private/file_contexts b/universal7885-common/sepolicy/private/file_contexts
index 1ec7550..d0618ad 100644
--- a/universal7885-common/sepolicy/private/file_contexts
+++ b/universal7885-common/sepolicy/private/file_contexts
@@ -2,4 +2,3 @@
### DATA
/data/zram(/.*)? u:object_r:zram_data_file:s0
-