I am working on an android application using kotlin, I want this app to automatically start when the phone is done booting, or whenever the user reboots the phone. I had made some research and realized it can be implemented using broadcast, however I have tried some codes but it's not working. Please 🙏🙏 someone should help, I will be submitting the app in about 4days and this feature is keep me back.
BootUpReceiver.kt
package com.example.jofi
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
class BootUpReceiver:BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (Intent.ACTION_BOOT_COMPLETED == intent!!.action) {
val i = Intent(context, MainActivity::class.java)
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
context!!.startActivity(i)
}
}
}
MainActivity.kt
class MainActivity : AppCompatActivity(), TextToSpeech.OnInitListener {
lateinit var reciever:StartUpOnBootUpReciever
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// val intent1 = Intent(this#MainActivity, RunServiceOnBoot::class.java)
// startService(intent1)
reciever = StartUpOnBootUpReciever()
reciever = StartUpOnBootUpReciever()
IntentFilter(Intent.ACTION_BOOT_COMPLETED).also {
registerReceiver(reciever, it)
}
}
}
ManiFest.xml
I had included the following in my manifest
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<receiver android:name=".StartUpOnBootUpReciever"
android:exported="true"
android:enabled="true"
android:permission="android.permission.RECEIVE_BOOT_COMPLETED"
tools:node="merge">
<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>
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>
What solution I am looking for
How to make onCallAdded(Call call) method of InCallService called every time when I make call in the main thread ( using #repeat for cycling).
Background
I'm writing a small Android auto test APP to automatically make calls then do web browsing...etc for 10 cycles.
My code is based on this article : Answer incoming call using android.telecom and InCallService
How it is working
Here is the MainActivity : Start to make call to $autoCallNumber repeatedly When I click button(autoCall).
import android.Manifest
import android.content.Intent
import android.os.Bundle
import android.os.CountDownTimer
import android.telecom.TelecomManager
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.core.content.PermissionChecker
import androidx.core.net.toUri
import kotlinx.android.synthetic.main.activity_call.*
import kotlinx.android.synthetic.main.activity_dialer.*
import kotlinx.android.synthetic.main.activity_main.*
import java.util.concurrent.TimeUnit
class MainActivity : AppCompatActivity() {
private val TAG = "${javaClass.simpleName} Wynne"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onStart() {
super.onStart()
offerReplacingDefaultDialer()
autoCall.setOnClickListener {
repeat(2) { i ->
makeCall()
Log.d(TAG,"We are on the ${i + 1}. loop")
TimeUnit.SECONDS.sleep(5);
}
}
}
private fun makeCall() {
if (PermissionChecker.checkSelfPermission(this, Manifest.permission.CALL_PHONE) == PermissionChecker.PERMISSION_GRANTED) {
var User: GeneralSettings = applicationContext as GeneralSettings
User.ongoingCalltype = "MO"
val uri = "tel:${User.autoCallNumber}".toUri()
startActivity(Intent(Intent.ACTION_CALL, uri))
} else {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.CALL_PHONE),
DialerActivity.REQUEST_PERMISSION
)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
if (requestCode == DialerActivity.REQUEST_PERMISSION && PermissionChecker.PERMISSION_GRANTED in grantResults) {
makeCall()
}
}
private fun offerReplacingDefaultDialer() {
if (getSystemService(TelecomManager::class.java).defaultDialerPackage != packageName) {
Intent(TelecomManager.ACTION_CHANGE_DEFAULT_DIALER)
.putExtra(TelecomManager.EXTRA_CHANGE_DEFAULT_DIALER_PACKAGE_NAME, packageName)
.let(::startActivity)
}
}
}
Here is the Object : Ongoingcall (for me to handle answer or hangup)
package com.github.arekolek.phone
import android.telecom.Call
import android.telecom.VideoProfile
import android.util.Log
import io.reactivex.subjects.BehaviorSubject
import timber.log.Timber
object OngoingCall {
val state: BehaviorSubject<Int> = BehaviorSubject.create()
private val TAG = "${javaClass.simpleName} Wynne"
private val callback = object : Call.Callback() {
override fun onStateChanged(call: Call, newState: Int) {
Log.d(TAG,"${call.toString()}")
state.onNext(newState)
Log.d(TAG,"New state : $newState")
}
}
var call: Call? = null
set(value) {
field?.unregisterCallback(callback)
value?.let {
it.registerCallback(callback)
state.onNext(it.state)
}
field = value
}
fun answer() {
call!!.answer(VideoProfile.STATE_AUDIO_ONLY)
}
fun hangup() {
call!!.disconnect()
}
}
Here is the CallService (Rewrite onCallAdded/ onCallRemoved of InCallService to handling activities when call arrives/ remove)
package com.github.arekolek.phone
import android.telecom.Call
import android.telecom.InCallService
import android.util.Log
class CallService : InCallService() {
private val TAG = "${javaClass.simpleName} Wynne"
override fun onCallAdded(call: Call) {
OngoingCall.call = call
try {
CallActivity.start(this, call)
Log.d(TAG,"Start Call Activity")
}
catch ( e : Exception ) {
Log.d(TAG,"$e")
}
}
override fun onCallRemoved(call: Call) {
OngoingCall.call = null
}
}
Here is Call Activity (update call info on UI and hangup unknown call...etc)
package com.github.arekolek.phone
import android.R.id.button1
import android.annotation.SuppressLint
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.os.Handler
import android.telecom.Call
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import androidx.core.view.isVisible
import io.reactivex.disposables.CompositeDisposable
import io.reactivex.rxkotlin.addTo
import kotlinx.android.synthetic.main.activity_call.*
import java.util.concurrent.TimeUnit
class CallActivity : AppCompatActivity() {
private val TAG = javaClass.simpleName
private val disposables = CompositeDisposable()
private lateinit var ongoingCallNumber: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_call)
ongoingCallNumber = intent.data.schemeSpecificPart
}
override fun onStart() {
super.onStart()
var User: GeneralSettings = applicationContext as GeneralSettings
OngoingCall.state
.subscribe(::updateUi)
.addTo(disposables)
OngoingCall.state
.filter { it == Call.STATE_DISCONNECTED }
.delay(1, TimeUnit.SECONDS)
.firstElement()
.subscribe { finish() }
.addTo(disposables)
if (User.ongoingCalltype == "MO") {
Handler().postDelayed(Runnable { hangup.performClick() }, 5000)
Log.d(TAG, "Wynne : End MO call after 5 second")
User.ongoingCalltype = ""
} else if (ongoingCallNumber == User.autoAnswerNumber) {
Handler().post(Runnable { answer.performClick() })
Log.d(TAG, "Wynne : Answer incoming call ${User.autoAnswerNumber} ")
}
else{
Handler().post(Runnable { hangup.performClick() })
Log.d(TAG, "Wynne : End Unknown incoming call $ongoingCallNumber ")
}
}
#SuppressLint("SetTextI18n")
private fun updateUi(state: Int) {
callInfo.text = "${state.asString().toLowerCase().capitalize()}\n$ongoingCallNumber"
answer.isVisible = state == Call.STATE_RINGING
hangup.isVisible = state in listOf(
Call.STATE_DIALING,
Call.STATE_RINGING,
Call.STATE_ACTIVE
)
}
override fun onStop() {
super.onStop()
disposables.clear()
}
companion object {
fun start(context: Context, call: Call) {
Intent(context, CallActivity::class.java)
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
.setData(call.details.handle)
.let(context::startActivity)
}
}
}
Here is the AndroidManifest (Bind .CallService (re-written InCallService))
<?xml version="1.0" encoding="utf-8"?>
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="com.github.arekolek.phone"
>
<uses-permission android:name="android.permission.CALL_PHONE" />
<application
android:name=".GeneralSettings"
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"
android:windowSoftInputMode="stateAlwaysVisible|adjustResize">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<!-- Handle links from other applications -->
<action android:name="android.intent.action.VIEW" />
<action android:name="android.intent.action.DIAL" />
<!-- Populate the system chooser -->
<category android:name="android.intent.category.DEFAULT" />
<!-- Handle links in browsers -->
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="tel" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.DIAL" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
<service
android:name=".CallService"
android:permission="android.permission.BIND_INCALL_SERVICE">
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true"
/>
<intent-filter>
<action android:name="android.telecom.InCallService" />
</intent-filter>
</service>
<activity
android:name=".CallActivity">
</activity>
</application>
</manifest>
Here is GeneralSettings (Global Variables)
package com.github.arekolek.phone
import android.app.Application
class GeneralSettings : Application() {
val autoCallNumber: String = "0988102544"
var ongoingCalltype: String = ""
val autoAnswerNumber: String = "0905112980"
}
What is the problem then
onCallAdded() of CallService supposed to be called after makeCall() each cycle in #repeat.
However, based on the debug message, I assume that
the onCallAdded() of CallService is not called until autoCall.setOnClickListener completed.
Here is the Debug message
16:52:24.859 D/MainActivity Wynne: We are on the 1. loop
16:52:29.895 D/MainActivity Wynne: We are on the 2. loop
16:52:35.041 D/CallService Wynne: Start Call Activity
16:52:35.048 D/CallService Wynne: Start Call Activity
16:52:35.116 D/CallActivity: Wynne : End MO call after 5 second
16:52:40.161 D/OngoingCall Wynne: New state : 10
16:52:40.419 D/OngoingCall Wynne: New state : 7
16:52:41.551 D/CallActivity: Wynne : End Unknown incoming call 0988102544
Can somebody tell me:
How to make onCallAdded() of CallService called every time when I run makeCall() in the main thread? so that I can disconnect the call before the next cycle.
Thank you for taking the time to read my question.
I think if you are declaring IN_CALL_SERVICE_UI=true in metadata you need to set your application as the default dialer as well.
<meta-data
android:name="android.telecom.IN_CALL_SERVICE_UI"
android:value="true"
/>
You can do it with the following ADB command:
adb shell telecom set-default-dialer $your_package_name
The quote from docs is below:
METADATA_IN_CALL_SERVICE_UI
A boolean meta-data value indicating
whether an InCallService implements an in-call user interface. Dialer
implementations (see getDefaultDialerPackage()) which would also like
to replace the in-call interface should set this meta-data to true in
the manifest registration of their InCallService.
Android Developers website
This is my broadcast receiver class and the implementation of it in main.
Problem is that onReceive method never gets called.
class MyBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(p0: Context?, p1: Intent?) {
Toast.makeText(p0, "It works", Toast.LENGTH_LONG).show()
}
}
class MainActivity : AppCompatActivity() {
......
private var broadcastReceiver: MyBroadcastReceiver = MyBroadcastReceiver()
override fun onCreate(savedInstanceState: Bundle?) {
......
registerReceiver(broadcastReceiver, IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
})
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(broadcastReceiver)
}
}
Please help. Thanks in advance.
I could make it work with the following code
class KotlinBroadcastReceiver(action: (context: Context, intent: Intent) -> Unit) : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) = action(context, intent)
}
class MainActivity : AppCompatActivity() {
private val broadcastReceiver = KotlinBroadcastReceiver { context, _ ->
Toast.makeText(context, "It works", Toast.LENGTH_LONG).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
registerReceiver(broadcastReceiver, IntentFilter().apply {
addAction(Intent.ACTION_PACKAGE_ADDED)
addAction(Intent.ACTION_PACKAGE_REMOVED)
addDataScheme("package") // I could not find a constant for that :(
})
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(broadcastReceiver)
}
}
Usually the BroadcastReceiver needs a number of steps to be set up.
First of all did yout pass to the manifest the receiver?
<receiver android:name=".MyBroadcastReceiver" android:exported="true">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.INPUT_METHOD_CHANGED" />
</intent-filter>
</receiver>
Edit: Please see this post, where is suggested ACTION_PACKAGE_FULLY_REMOVED or JobScheduler
try also to reinstall the app on the emulator
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.