I'm developing an android app that is mostly run in the background and uses Firebase Realtime Database for data syncing.
I want the app to play a 10 second sound when new data is received from Firebase and I've successfully managed to implement this, however, my solution doesn't work on all devices.
Here's my code:
class MyViewModel constructor(
) : ViewModel() {
private var mediaPlayer: MediaPlayer? = null
fun initRtdListener(context: Context) {
val ref = Firebase.database.getReference("my-path")
mediaPlayer = MediaPlayer.create(context, R.raw.my_short_audio)
ref.addValueEventListener(object : ValueEventListener {
override fun onDataChange(dataSnapshot: DataSnapshot) {
mediaPlayer?.start()
}
override fun onCancelled(error: DatabaseError) {}
})
}
}
This solution works perfectly on most devices even when the app is running in the background, however, on some devices (like Realme X) the sound is played only when the app is in foreground. What could be the problem? Is it because I play the sound not in the context of a service? If yes, do I really need a service considering that my sounds are short?
P.S. I have two background services which are working without any issues on all devices
Related
We use chromecast android sdk to cast some video files. we have our own implementation of play/pause/seek using RemoteMediaClient (play(), pause(), seek(Long)).
Also, we do allow chromecast to show notifications on the same device from which casting is happening using CastMediaOptions.Builder#setNotificationOptions(NotificationOptions).
To listen to notification controls we use CastMediaOptions.Builder()#setMediaIntentReceiverClassName(MediaIntentReceiver) which works completely fine when we control video from cast notification of the same device from which casting is done.
As we know Chromecast allows us to control casting media from all android devices in the same wifi network. when we try to play/pause the video from another device in same network MediaIntentReceiver won't receive anything and we are unable to change the player state inside our app.
Below is code for referance
OptionProvider
class CastOptionsProvider : OptionsProvider {
override fun getCastOptions(context: Context): CastOptions {
val notificationOptions = NotificationOptions.Builder()
.build()
val mediaOptions = CastMediaOptions.Builder()
.setNotificationOptions(notificationOptions)
.setMediaIntentReceiverClassName(FitbuddMediaIntentReceiver::class.java.name)
.build()
return CastOptions.Builder()
.setReceiverApplicationId(CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID)
.setCastMediaOptions(mediaOptions)
.setStopReceiverApplicationWhenEndingSession(true)
.build()
}
override fun getAdditionalSessionProviders(context: Context): List<SessionProvider>? {
return null
}
}
MediaIntentReceiver (registered inside manifest too)
class FitbuddMediaIntentReceiver : MediaIntentReceiver() {
//this is getting call when playback toggle from same device's notification
//which is casting media but
//wont get call when playback is toggled from notification from another device
//inside same network.
override fun onReceiveActionTogglePlayback(currentSession: Session) {
"CAST -> onReceiveActionTogglePlayback ->".dumpError()
super.onReceiveActionTogglePlayback(currentSession)
//send event to active activity
}
}
We found that setMediaIntentReceiverClassName only give you event when interacting with notification which created by using CastMediaOptions.Builder()#setNotificationOptions().
So to check player state, when changed from other network device, we use RemoteMediaClient.addProgressListener(RemoteMediaClient.ProgressListener) which does not provide player state but does provide progress of cast receiver after given period. On onProgressUpdated we check for RemoteMediaClient.playerState so our ui is up to date with cast receiver state.
I want to make an Android application that can record both incoming and outgoing calls in the background as a service in kotlin and at a particular time in the day, it sends all that recordings to a server by API. I had researched about it all I found is to use Device Policy Manager and Telephoney Manager but it is not much about it on the internet. So can you help me with any article, documentation, or tutorial?
There is no solution from Google as of now. Google has deprecated the feature of recording the calls in it's latest versions of Android OS. Earlier it was possible, I had tried various methods but I was only getting the silent audio when I had tried to record calls. When using Google's Phone application it only allows that application to use the microphone and other things it won't allow any other application to overpower and get that hardware access.
But there are actually two hacks to do that.
Build your own phone application like Truecaller and manage every call and other things from that application by doing this you can get access to managing calls on your device and you will also get the access to record the calls.
If your work is specific to any one mobile example like Samsung, OnePlus, etc. Then you can use any Truecaller or Google's Phone application which will store the recordings of the calls in file storage and then you can make a service to upload that call recording from that particular file location every night at 12 AM or something.
first create MyCallRecordReceiver class
class MyCallRecordReceiver(callRecord: CallRecord) : CallRecordReceiver(callRecord) {
override fun onIncomingCallReceived(context: Context, number: String?, start: Date) {
super.onIncomingCallReceived(context, number, start)
}
}
then in MainActivity
class MainActivity : AppCompatActivity() {
companion object {
private val TAG = MainActivity::class.java.simpleName
}
private lateinit var callRecord: CallRecord
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
callRecord = CallRecord.Builder(this)
.setLogEnable(true)
.setRecordFileName("CallRecorderTestFile")
.setRecordDirName("CallRecorderTest")
.setAudioSource(MediaRecorder.AudioSource.VOICE_COMMUNICATION)
.setShowSeed(true)
.build()
}
fun StartCallRecordClick(view: View) {
LogUtils.i(TAG, "StartCallRecordClick")
callRecord.startCallReceiver()
}
fun StopCallRecordClick(view: View) {
LogUtils.i(TAG, "StopCallRecordClick")
callRecord.stopCallReceiver()
}
}
In addition Add it as a dependency in your app's build.gradle file
allprojects {
repositories {
maven { url 'https://jitpack.io' }
}
}
and this
compile 'com.github.aykuttasil:CallRecorder:1.5.3'
I build an app that is able to deploy a periodic work using workmanager and I want to perform some actions like saving key-value in sharePreference or uploading data to firebase when the work is told to be stopped either by users themselves or somehow the system, through overriding the onStopped method. But it doesn't work. I also tested it by setting a button to manually cancel the work using cancelAllWorkByTag to check how it works and nothing is working. Can someone help me out?
override fun onStopped() {
super.onStopped()
val sharedPreferences = applicationContext.getSharedPreferences("AppSharedPreference", Context.MODE_PRIVATE)
with(sharedPreferences.edit()) {
putString("working_state", "STOPPED")
commit()
}
val db = Firebase.firestore
val now = DateTime.now()
val input = hashMapOf("timestamp" to now.toString(), "stopped" to "the service is somehow stopped")
db.collection("test").document(now.toString()).set(input, SetOptions.merge())
}
I am building an offline first chat app and so I'm using Workmanager to handle POST requests to send chat messages.
I'm learning android development and so I wanted some help on architecturing the upload of chat messages
Current implementation
When ever a new chat message needs to be posted the client does the following
Saves the chat to SQLite using Room with a new UUID
Starts a workmanager unique work to POST this message
This way I can be sure the message is posted eventually when the client has internet
Is this ideal? There are a few issues I see.
I'm starting too many workers. Each message has a work request.
Chronology of messages posted to the server is lost.
A better implementation
A single unique worker to POST messages. Which will fetch all offline messages and post them in the right order
Still not ideal
The issues with these implementation are:
You have very little control on a work (not so easy) once it's started.
If a work fails we've set a backoff time. So when a new message is to be sent we need to replace the old worker with the new work request. This just seems nonoptimal.
We are mutating the worker instead of appending a new task to the queue.
We can't use the one worker per message implementation because we loose chronology and there are too many workers
This is sort of a distributed systems question.
We are starting workers who should work independ of the lifetime of the app
Workers should come back alive in a case they die (already managed by android-workmanager)
Workers should read from a queue of task to be executed (which is what I'm looking for)
There should be a persistance store that acts as a queue for the workers
There should be a service or a factory that invokes workers when ever needed (We don't have this in the current impl)
Questions
Is there a better way to post offline messages to the server when the client has internet? Like a service?
Is there a community build library that does this?
Can the current implementation be scaled to files?
I would need long running workers
Or could use this lib - android-upload-service
Yes you can use Service to effectively sync the messages, First create an Object which will extend from Live Data Class to get live updates for network updates like this,
object NetworkState : LiveData<Boolean>() {
private lateinit var application: Application
private lateinit var networkRequest: NetworkRequest
private lateinit var connectivityManager: ConnectivityManager
fun init(application: Application) {
this.application = application
connectivityManager = application.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build()
}
override fun onActive() {
super.onActive()
getDetails()
}
private fun getDetails() {
connectivityManager.registerNetworkCallback(networkRequest, object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
postValue(true)
}
override fun onLost(network: Network) {
super.onLost(network)
postValue(false)
}
override fun onUnavailable() {
super.onUnavailable()
postValue(false)
}
})
}
Then initialize this in your Application class like this,
#HiltAndroidApp
class BaseApplication : Application(), Configuration.Provider {
#Inject
lateinit var workerFactory: HiltWorkerFactory
override fun getWorkManagerConfiguration() =
Configuration.Builder()
.setWorkerFactory(workerFactory)
.build()
override fun onCreate() {
super.onCreate()
NetworkState.init(this)
if (BuildConfig.DEBUG){
Timber.plant(Timber.DebugTree())
}
}
}
Now you can create a Service class which will extend from Lifecycle Service and observe the Network updated there. And when the Live Data will emit value true you can check if there are messages which needs to be synced online. But remember it will only work when the app is in foreground.
I've implemented Android client using Twilio SDK to do video calls. It works as expected but I found an edge case which I can not figure out how to fix. Here is essence of video call code:
val connectionOptions = ConnectOptions.Builder(accessToken)
.audioTracks(listOf(audioManager.getLocalAudioTrack()))
.roomName(roomId)
.build()
val roomListener = RoomListener()
Video.connect(androidContext, connectOptions, roomListener)
class RoomEventListener : Room.Listener {
override fun onParticipantDisconnected(room: Room, remoteParticipant: RemoteParticipant) {
// remove participant from the screen, unsubscribe from events
}
override fun onConnectFailure(room: Room, twilioException: TwilioException) {
exitScreenWithErrorMessage(R.string.video_consult_room_connection_error)
}
override fun onReconnected(room: Room) {
_shouldShowReconnectionActivity.value = false
}
override fun onParticipantConnected(room: Room, remoteParticipant: RemoteParticipant) {
onRemoteParticipantConnected(remoteParticipant)
}
override fun onConnected(room: Room) {
_shouldShowConnectionActivity.value = false
this#VideoCallViewModel.room = room
room.remoteParticipants.forEach { onRemoteParticipantConnected(it) }
determineMainParticipant()
onLocalParticipantConnected(room)
}
override fun onDisconnected(room: Room, twilioException: TwilioException?) {
exitVideoConsultScreen()
}
override fun onReconnecting(room: Room, twilioException: TwilioException) {
_shouldShowReconnectionActivity.value = true
}
}
Test case:
Bob joins video call using Android phone
Jane joins same video call from any device (iOS, web, Android)
When Jane loses connection (i.e. turn off internet)
Then Bob sees for 1-2 minutes reconnecting (programmatically "onReconnecting" callback triggered)
[Actual] And Bob disconnected from room (in logs I see Media connection failed or Media activity ceased with error code 53405)
[Expected] Bob stays at the room.
I'm not sure why under such conditions Android client has been disconnected (We tested it on different devices with Android 8/9).
Couple more details:
If Jane exits room using "End call" button (so room.disconnect() code from Twilio SDK has been called) then Bob stays in the room.
When Bob using iOS device (implementation of iOS and Android quite the same) then described use case passes.
We tried 5.0.1 and 5.1.0 version of com.twilio:video-android library.
I noticed Known Issue for Android Twilio Video library in Release notes and I'm not sure can it affects described use case or not:
Unpublishing and republishing a LocalAudioTrack or LocalVideoTrack might not be seen by Participants. As a result, tracks published after a Room.State.RECONNECTED event might not be subscribed to by a RemoteParticipant.
I opened issue on twilio github repo https://github.com/twilio/video-quickstart-android/issues/454 - and this is expected behaviour for twilio video sdk 5.x+. Both for Android and iOS sdks.