I`m trying to build a service that detects if the screen of a device is locked/unlocked (which I will later user as a native module in React). However, it seems like my service is not starting, and I don't receive the expected logs. Where is my mistake? (It's my first time dealing with native android & Kotlin, so apologies if this is a dumb question, and duplicates were related to java code)..
I have defined a Broadcast Receiver for each event here:
ScreenOnReceiver.kt
class screenOnReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent!!.action == Intent.ACTION_SCREEN_ON) {
val screenOff = false
val i = Intent(context, PowerButtonService::class.java)
i.putExtra("screenState", screenOff)
context!!.startService(i)
}
}
}
ScreenOffReceiver.kt
class screenOffReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent!!.action == Intent.ACTION_SCREEN_ON) {
val screenOff = false
val i = Intent(context, PowerButtonService::class.java)
i.putExtra("screenState", screenOff)
context!!.startService(i)
}
}
}
ScreenChangeService.kt:
class ScreenChangeService: Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onCreate() {
val screenOnReceiver = screenONReceiver()
val screenOnFilter = IntentFilter(Intent.ACTION_SCREEN_ON)
registerReceiver(screenOnReceiver, screenOnFilter)
val screenOffReceiver = screenOffReceiver()
val screenOffFilter = IntentFilter(Intent.ACTION_SCREEN_OFF)
registerReceiver(screenOffreceiver, screenOffFilter)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val screenState = intent!!.getBooleanExtra("screenState", false)
if (screenState == true) {
Log.d("TAG", "Screen On")
} else {
Log.d("TAG", "Screen Off")
}
return START_NOT_STICKY
}
}
Manifest.xml
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.Test">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".PowerButtonService"/>
</application>
Related
I have read multiple threads on this issue but none of them solved my problem, so I gave up and decided to write this.
I have a service (right now it's a background service but I am going to turn it into a foreground service) which monitors the battery level so it can notify the user when it reaches a certain percentage.
abstract class MonitoringService : Service() {
private var maxPercentage: Int = -1
private var thread: Thread? = null
private lateinit var batteryManager: BatteryManager
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
Log.d(null, "created service")
}
override fun onDestroy() {
thread?.interrupt()
updateServiceState(false)
Log.d(null, "destroyed service")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
println("started service")
if (intent == null) {
stopSelf(startId)
updateServiceState(false)
report(applicationContext, "service started with intent null")
return START_NOT_STICKY
}
when (intent.action) {
ACTION_START_SERVICE -> {
// if a thread is already active interrupt it
thread?.interrupt()
if (updateMaxPercentage(intent, startId) == 1) return START_NOT_STICKY
thread = Thread(Runnable {
while (true) {
if (batteryManager.isCharging && batteryManager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
) == maxPercentage
) {
Log.d(null, "time to do it")
}
Log.d(null, "time to not do it")
try {
// sleep for 1 minute
Thread.sleep(60000)
} catch (exception: InterruptedException) {
stopSelf(startId)
updateServiceState(false)
return#Runnable
}
}
})
thread?.start()
updateServiceState(true)
}
ACTION_STOP_SERVICE -> {
thread?.interrupt()
stopSelf(startId)
updateServiceState(false)
return START_NOT_STICKY
}
ACTION_UPDATE_SERVICE -> if (updateMaxPercentage(intent, startId) == 1) return START_NOT_STICKY
else -> {
stopSelf(startId)
updateServiceState(false)
report(applicationContext, "service started with action null")
return START_NOT_STICKY
}
}
return START_REDELIVER_INTENT
}
}
I start the service like so from my activity
private fun start() {
val serviceIntent = Intent(applicationContext, MonitoringService::class.java)
serviceIntent.action = ACTION_START_SERVICE
serviceIntent.putExtra("maxPercentage", maxPercentage)
println(startService(intent))
Log.d(null, "startService")
}
This is my AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.segv.batconv">
<application
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:theme="#style/Theme.Batconv"
tools:targetApi="31">
<service
android:name=".MonitoringService"
android:enabled="true"/>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
I am testing this on an Android 11 device. The logs show no errors but the service doesn't start.
Thanks.
Remove the abstract keyword from your MonitoringService declaration:
class MonitoringService : Service()
By declaring it abstract, you are saying that nothing can create an instance of that class.
I am developing a sample app where my goal is to display a notification but initially, just for testing purposes I'm playing an alarm sound when Screen goes ON twice via 4 pressed in the power button. I also intend to run a Service even if the app is totally closed that's why I put the broadcast receiver inside the service.
Before when I implemented this using Service, the app seems to be working fine except for Oreo and above. I found out regarding the execution limit for background, so I updated my code by using Job Intent Service. I just noticed that my broadcast receiver is not being called though it gets registered in the JobIntentService class. I tried playing the alarm sound just on the service (broadcast receiver excluded), and the alarm goes playing, meaning that my Service is functioning properly. I was just wandering why the broadcast receiver is not getting triggered?
This is my code for this:
MainActivity :
class MainActivity : AppCompatActivity() {
private lateinit var receiver: SampleReceiver
private val actionCheck = "action_check"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
receiver = SampleReceiver()
btnStart.setOnClickListener {
SampleJobIntentService.enqueueWork(
applicationContext,
Intent().setAction(actionCheck)
)
}
}
}
JobIntentService Class:
class SampleJobIntentService : JobIntentService() {
private lateinit var receiver: SampleReceiver
private val actionCheck = "action_check"
companion object {
fun enqueueWork(context: Context, intent: Intent) {
enqueueWork(context, SampleJobIntentService::class.java, 123, intent)
}
}
override fun onHandleWork(intent: Intent) {
Log.v("Service", "Service is running")
receiver = SampleReceiver()
when (intent.action) {
actionCheck -> {
IntentFilter(Intent.ACTION_SCREEN_ON).also { filter ->
this.registerReceiver(receiver, filter)
}
}
}
}
override fun onDestroy() {
unregisterReceiver(receiver)
super.onDestroy()
Log.v("Receiver", "Receiver is now unregistered...")
}
}
Receiver class:
class SampleReceiver : BroadcastReceiver() {
private lateinit var player: MediaPlayer
private var pressedCounts = 0
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
Intent.ACTION_SCREEN_ON -> {
pressedCounts =+ 1
if (pressedCounts == 2) {
player.start()
}
}
}
}
}
AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.samplereceiver">
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<service
android:name=".SampleJobIntentService"
android:exported="false"
android:permission="android.permission.BIND_JOB_SERVICE">
<intent-filter>
<action android:name="android.intent.action.SCREEN_OFF" />
<action android:name="android.intent.action.SCREEN_ON" />
</intent-filter>
</service>
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
Here is my fragment -
#SuppressLint("HardwareIds")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mPresenter = VerifyOtpPresenter(this)
androidId = Settings.Secure.getString(activity?.contentResolver, Settings.Secure.ANDROID_ID)
mPresenter.requestOtp(phoneNumber)
initClickAndTextListeners()
initOtpCountdownTimer()
}
/**
* Requesting OTP password for our phone number
*/
override fun requestOtp(phoneNumber: Long) {
OtpNetworking.requestOtp(phoneNumber, object : OtpNetworking.RequestOtpCallback {
override fun onSuccess() {
Toast.makeText(context, getString(R.string.verify_otp_fragment_sms_arrived), Toast.LENGTH_SHORT).show()
startSmsRetriever()
}
override fun onError(reason: String) {
Toast.makeText(context, reason, Toast.LENGTH_SHORT).show()
}
})
}
private fun startSmsRetriever() {
val client = SmsRetriever.getClient(context!!)
val task = client.startSmsRetriever()
task.addOnSuccessListener {
Toast.makeText(context, "Successfully added retriever", Toast.LENGTH_SHORT).show()
}
task.addOnFailureListener {
Toast.makeText(context, "Failed to get SMS", Toast.LENGTH_SHORT).show()
}
}
here is my OtpBroadcastReceiver -
class OtpBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
Toast.makeText(context, "onReceive", Toast.LENGTH_SHORT).show()
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status: Status? = extras!![SmsRetriever.EXTRA_STATUS] as Status?
when (status?.statusCode) {
CommonStatusCodes.SUCCESS -> {
val message: String? = extras[SmsRetriever.EXTRA_SMS_MESSAGE] as String?
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
CommonStatusCodes.TIMEOUT -> {
Toast.makeText(context, "Timeout", Toast.LENGTH_SHORT).show()
}
}
}
}
}
and my manifest file -
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="#style/AppTheme">
<activity android:name=".startup.StartupActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".otp.service.OtpBroadcastReceiver" android:exported="true"
android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
<meta-data
android:name="preloaded_fonts"
android:resource="#array/preloaded_fonts" />
</application>
I can't seem to get any information from my broadcast receiver, eventhough the toast message of the sms retriever does say Successfully added retriever
I think I am missing the connection between the fragment and the broadcast receiver but I am not sure - does anyone have an idea what I am missing?
You could try to pass some onOtpReceived function into your OtpBroadcastReceiver and see if that helps.
class OtpBroadcastReceiver(onOtpReceived: (String) -> Unit, onOtpTimeout: () -> Unit) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status: Status? = extras!![SmsRetriever.EXTRA_STATUS] as Status?
when (status?.statusCode) {
CommonStatusCodes.SUCCESS -> {
val message: String? = extras[SmsRetriever.EXTRA_SMS_MESSAGE] as String?
onOtpReceived(message)
}
CommonStatusCodes.TIMEOUT -> {
onOtpTimeout()
}
}
}
}
}
I'm trying to receive media button events from Wired/Bluetooth headsets
I'm receiving media button events on onMediaButtonEvent(mediaButtonEvent: Intent?) method in MediaSessionCallback
but nothing happens to the music playback.
Here is my MediaPlayerService
private const val LOG_TAG = "LOG_TAG"
private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"
private const val AUDIO_URL_1 = "https://www.listennotes.com/e/p/94051189e660408b861be9ee28f17f06/"
class MediaPlaybackService : MediaBrowserServiceCompat() {
private val TAG = "MediaPlaybackService"
private lateinit var context: Context
private lateinit var mediaSession: MediaSessionCompat
private lateinit var stateBuilder: PlaybackStateCompat.Builder
private lateinit var exoPlayer: SimpleExoPlayer
private lateinit var dataSourceFactory: DefaultDataSourceFactory
private val audioAttributes = AudioAttributes.Builder()
.setContentType(C.CONTENT_TYPE_MUSIC)
.setUsage(C.USAGE_MEDIA)
.build()
override fun onCreate() {
super.onCreate()
context = this
initExoPlayer()
initDataSourceFactory()
initMediaSession()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand ${intent?.getParcelableExtra<KeyEvent>
(Intent.EXTRA_KEY_EVENT)?.keyCode}")
MediaButtonReceiver.handleIntent(mediaSession, intent)
return super.onStartCommand(intent, flags, startId)
}
private fun initExoPlayer() {
exoPlayer = ExoPlayerFactory.newSimpleInstance(context)
exoPlayer.setAudioAttributes(audioAttributes, true)
}
private fun initDataSourceFactory() {
val httpDataSourceFactory = DefaultHttpDataSourceFactory(
Util.getUserAgent(context, "media-player"),
null,
DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
true
)
dataSourceFactory = DefaultDataSourceFactory(context, null, httpDataSourceFactory)
}
private fun initMediaSession() {
mediaSession = MediaSessionCompat(context, LOG_TAG).apply {
setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
stateBuilder = PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY_PAUSE
or PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PAUSE
or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
)
setPlaybackState(stateBuilder.build())
setCallback(mediaSessionCallback())
setSessionToken(sessionToken)
isActive = true
}
}
private fun mediaSessionCallback() = object : MediaSessionCompat.Callback() {
override fun onPlay() {
super.onPlay()
play()
}
override fun onPause() {
super.onPause()
pause()
}
override fun onSkipToNext() {
super.onSkipToNext()
skipToNext()
}
override fun onSkipToPrevious() {
super.onSkipToPrevious()
skipToPrevious()
}
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
Log.i(TAG, "MediaButtonEvent: ${mediaButtonEvent?.getParcelableExtra<KeyEvent>
(Intent.EXTRA_KEY_EVENT)?.keyCode}")
return super.onMediaButtonEvent(mediaButtonEvent)
}
}
fun play() {
Log.i(TAG, "Playback State: Playing")
if (mediaSession.controller.playbackState.state != PlaybackStateCompat.STATE_PAUSED) {
val mediaSource = getMediaSource(AUDIO_URL_1)
exoPlayer.prepare(mediaSource)
}
setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
exoPlayer.playWhenReady = true
setMediaMetadata(title = "Episode 131: Bourne Wild")
}
fun pause() {
Log.i(TAG, "Playback State: Paused")
setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
exoPlayer.playWhenReady = false
}
fun skipToNext() {
if (exoPlayer.hasNext()) {
Log.i(TAG, "ExoPLayer: Skip to Next")
exoPlayer.next()
}
}
fun skipToPrevious() {
if (exoPlayer.hasPrevious()) {
Log.i(TAG, "ExoPLayer: Skip to Previous")
exoPlayer.previous()
}
}
override fun onLoadChildren(parentId: String, result:
Result<MutableList<MediaBrowserCompat.MediaItem>>) {
result.sendResult(null)
}
override fun onGetRoot(lientPackageName: String, clientUid: Int,
rootHints: Bundle?): BrowserRoot? {
return BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null)
}
override fun onDestroy() {
exoPlayer.release()
mediaSession.run {
isActive = false
release()
}
super.onDestroy()
}
}
Here is my Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.media_player">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:usesCleartextTraffic="true"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
It looks like I'm missing something, after many days of struggling, I couldn't find what's wrong.
Any Help will be very much appreciated
Thanks
I'm building a simple app, that keep monitoring the media level, and adjust it to be 20% of the maximum level all the time, if the user increased,it should back to 20%again.
Th concept I followed is doing the monitoring process via a service, once this service is destroyed it calls a broadcast receiver, which in its turn calls the receiver again, and so on, as endless cycle, but looks something wrong in the code below,soit is not working as desired, and service/broadcast not keep calling each others!
I started the mainActivity as:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val audio = this.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val level = audio.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val percent = 0.2f
val twintyVolume = (maxVolume * percent).toInt()
if ( level > twintyVolume) {
Toast.makeText(this,"audio level is $level", Toast.LENGTH_LONG).show()
audio.setStreamVolume(AudioManager.STREAM_MUSIC,twintyVolume,0)
}
this.startService(Intent(this, VolumeCheck::class.java))
}
}
The above make initial check and reduce the media volume to 20% of the max volume, then start the service, which is doing the same with the below code:
class VolumeCheck : Service() {
private lateinit var context: Context
override fun onCreate() {
super.onCreate()
context = this
Toast.makeText(this, "service created", Toast.LENGTH_SHORT).show();
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val mediaPlayer = MediaPlayer()
// Thread().run {
val maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val percent = 0.2f
val twintyVolume = (maxVolume * percent).toInt()
if (mediaPlayer.isPlaying) {
val level = audio.getStreamVolume(AudioManager.STREAM_MUSIC)
if ( level > twintyVolume) {
Toast.makeText(context,"audio level is $level", Toast.LENGTH_LONG).show()
audio.setStreamVolume(AudioManager.STREAM_MUSIC,twintyVolume,0)
}
}
// Thread.sleep(3000)
// }
stopSelf()
return Service.START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
//TODO for communication return IBinder implementation
return null
}
override fun onDestroy() {
super.onDestroy()
Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show()
val intent = Intent("com.kortex.mediafix.BootUpReceiver")
sendBroadcast(intent)
}
}
Once the service is destroyed, it calls the boot broadcast receiver, which in its turn call the service again:
class BootUpReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
context.startService(Intent(context, VolumeCheck::class.java))
}
}
The Manifest is:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kortex.mediafix">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".BootUpReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<service android:name=".VolumeCheck" />
</application>
</manifest>
val intent = Intent("com.kortex.mediafix.BootUpReceiver")
This line does not call your broadcast receiver but rather makes an intent with intent action as "com.kortex.mediafix.BootUpReceiver"
Change your BootUpReceiver's entry in manifest to receive this action
<receiver android:name=".BootUpReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.kortex.mediafix.BootUpReceiver" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
Another way to do it, without Service is to call ContentObserver from BroadcastReceiver
BroadcastReceiver:
class BootUpReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val myObserver = VolumeOnserver(context, Handler())
// Register the VolumeOnserver for setting changes
context.contentResolver.registerContentObserver(
android.provider.Settings.System.CONTENT_URI ,true,
myObserver)
}
}
ContentObserver:
class VolumeOnserver (context: Context, h: Handler?): ContentObserver(h) {
private val context = context
override fun onChange(selfChange: Boolean) {
onChange(selfChange, uri = null)
}
// Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
override fun onChange(selfChange: Boolean, uri: Uri?) {
// Handle change.
val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val level = audio.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val percent = 0.2f
val twintyVolume = (maxVolume * percent).toInt()
if ( level > twintyVolume) audio.setStreamVolume(AudioManager.STREAM_MUSIC,twintyVolume,0)
}
}
MainActivity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val audio = this.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val level = audio.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val percent = 0.2f
val twintyVolume = (maxVolume * percent).toInt()
if ( level > twintyVolume) audio.setStreamVolume(AudioManager.STREAM_MUSIC,twintyVolume,0)
finish()
}
}
I solved it by using ContentObserver that is called from the service, so my code now is:
MainActivity to launch the app, and do first time adjustment, and to start the service:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val audio = this.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val level = audio.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val percent = 0.2f
val twintyVolume = (maxVolume * percent).toInt()
if ( level > twintyVolume) audio.setStreamVolume(AudioManager.STREAM_MUSIC,twintyVolume,0)
Toast.makeText(this,"Audio level adjusted to 20% instead of $level", Toast.LENGTH_LONG).show()
this.startService(Intent(this, VolumeCheck::class.java))
finish()
}
}
Broadcast to start the app and the service each time the device is rebooted:
class BootUpReceiver: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
context.startService(Intent(context, VolumeCheck::class.java))
}
}
Service to register and call the observer:
class VolumeCheck : Service() {
private lateinit var context: Context
private lateinit var myObserver: VolumeOnserver
override fun onCreate() {
super.onCreate()
context = this
// Define the VolumeOnserver
myObserver = VolumeOnserver(context, Handler())
Toast.makeText(this, "service created", Toast.LENGTH_SHORT).show();
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
super.onStartCommand(intent, flags, startId)
Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show();
// Register the VolumeOnserver for setting changes
contentResolver.registerContentObserver(android.provider.Settings.System.CONTENT_URI ,true, myObserver);
return Service.START_STICKY
}
override fun onBind(intent: Intent): IBinder? {
return null
}
override fun onDestroy() {
super.onDestroy()
// Unregister the VolumeOnserver
contentResolver.unregisterContentObserver(myObserver);
}
}
Observer that observe any changes in the settings and check the media volume and adjust it if required:
class VolumeOnserver (context: Context, h: Handler?): ContentObserver(h) {
private val context = context
override fun onChange(selfChange: Boolean) {
onChange(selfChange, uri = null)
}
// Implement the onChange(boolean, Uri) method to take advantage of the new Uri argument.
override fun onChange(selfChange: Boolean, uri: Uri?) {
// Handle change.
val audio = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
val level = audio.getStreamVolume(AudioManager.STREAM_MUSIC)
val maxVolume = audio.getStreamMaxVolume(AudioManager.STREAM_MUSIC)
val percent = 0.2f
val twintyVolume = (maxVolume * percent).toInt()
if ( level > twintyVolume) audio.setStreamVolume(AudioManager.STREAM_MUSIC,twintyVolume,0)
// Toast.makeText(context,"audio level is $level", Toast.LENGTH_LONG).show()
}
}
The Manifest file is:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.kortex.mediafix">
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".BootUpReceiver"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED" />
<action android:name="android.intent.action.QUICKBOOT_POWERON" />
<action android:name="com.kortex.mediafix.BootUpReceiver" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</receiver>
<service android:name=".VolumeCheck" />
</application>
</manifest>
And my running app is here.
But have an issue it is not stable, I the user insist to change the volume, after many trials the service and/or the observer is/are no more working till the app is restarted.