I'm trying to get the context of my Service in order. The service opens up an overlay that draws on other apps. The overlay comes up but if I interact with any of the views, the app crashes and gives this error.
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
Here is the full error.
android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRootImpl.setView(ViewRootImpl.java:1068)
at android.view.WindowManagerGlobal.addView(WindowManagerGlobal.java:409)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:109)
at android.app.Dialog.show(Dialog.java:340)
at android.widget.Spinner$DialogPopup.show(Spinner.java:1146)
at android.widget.Spinner.performClick(Spinner.java:792)
at android.view.View.performClickInternal(View.java:7425)
at android.view.View.access$3600(View.java:810)
at android.view.View$PerformClick.run(View.java:28305)
at android.os.Handler.handleCallback(Handler.java:938)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:223)
at android.app.ActivityThread.main(ActivityThread.java:7656)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:592)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:947)
From what I've been able to determine through Google Search and SO is that the issue is with the context. Below is the code for my Service.
class Dooa: Service() {
private lateinit var floatView: ViewGroup
private lateinit var floatWindowLayoutParams: WindowManager.LayoutParams
private var LAYOUT_TYPE: Int? = null
private lateinit var windowManager: WindowManager
private lateinit var spinnerAccount: Spinner
private lateinit var tvDateAT: TextView
private lateinit var spinnerType: Spinner
private lateinit var etTitle: EditText
private lateinit var etMemo: EditText
private lateinit var spinnerCategory: Spinner
private lateinit var spinnerDebitOrCredit: Spinner
private lateinit var etAmount: EditText
private lateinit var ibSave: ImageButton
private lateinit var ibCancel: ImageButton
private var account: String = "Joint"
private var debitOrCredit: String = "Debit"
private var category: String = ""
private var type: String = "CC"
private var mils: Long = 0
private var balance: String = ""
private var context: Context? = null
private lateinit var db : FirebaseFirestore
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
super.onCreate()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
context = MyApp().getContext()
Log.d("blocks", "context: $context")
if (intent != null) {
if (intent.action == START) {
db = FirebaseFirestore.getInstance()
val metrics = applicationContext.resources.displayMetrics
val width = metrics.widthPixels
val height = metrics.heightPixels
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val inflator = this.getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
floatView = inflator.inflate(R.layout.dooa_transaction_card, null) as ViewGroup
spinnerAccount = floatView.findViewById(R.id.spinnerAccount)
tvDateAT = floatView.findViewById(R.id.tvDateAT)
spinnerType = floatView.findViewById(R.id.spinnerType)
etTitle = floatView.findViewById(R.id.etTitle)
etMemo = floatView.findViewById(R.id.etMemo)
spinnerCategory = floatView.findViewById(R.id.spinnerCategory)
spinnerDebitOrCredit = floatView.findViewById(R.id.spinnerDebitOrCredit)
etAmount = floatView.findViewById(R.id.etAmount)
ibSave = floatView.findViewById(R.id.ibSave)
ibCancel = floatView.findViewById(R.id.ibCancel)
//ACCOUNT SPINNER
ArrayAdapter.createFromResource(
this,
R.array.accounts,
R.layout.spinner_item
).also { adapter ->
Log.d("blocks", "AS ArrayAdapter ran")
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerAccount.adapter = adapter
spinnerAccount.setSelection(0)
}
spinnerAccount.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
account = selection.toString()
getDB()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
}
}
tvDateAT.setOnClickListener {
val c = Calendar.getInstance()
val year = c.get(Calendar.YEAR)
val month = c.get(Calendar.MONTH)
val day = c.get(Calendar.DAY_OF_MONTH)
val dpd = DatePickerDialog(
this@Dooa,
{ view, year, monthOfYear, dayOfMonth ->
// Save milliseconds for date picked.
mils = c.timeInMillis
val m = monthOfYear + 1
// Display Selected date in textbox
tvDateAT.text = getString(
R.string.date_picked,
m.toString(),
dayOfMonth.toString(),
year.toString()
)
},
year,
month,
day
)
dpd.show()
}
//TYPE
ArrayAdapter.createFromResource(
this,
R.array.type,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerType.adapter = adapter
spinnerType.setSelection(0)
}
spinnerType.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
type = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
etTitle.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
etTitle.isCursorVisible = true
val updatedFloatParamsFlag = floatWindowLayoutParams
updatedFloatParamsFlag.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
updatedFloatParamsFlag.flags = WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE
windowManager.updateViewLayout(floatView, updatedFloatParamsFlag)
return false
}
})
//CATEGORY
ArrayAdapter.createFromResource(
this,
R.array.category,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerCategory.adapter = adapter
spinnerCategory.setSelection(0)
}
spinnerCategory.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
category = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
//DEBIT OR CREDIT
ArrayAdapter.createFromResource(
this,
R.array.debit_or_credit,
R.layout.spinner_item
).also { adapter ->
adapter.setDropDownViewResource(R.layout.spinner_dropdown_item)
spinnerDebitOrCredit.adapter = adapter
spinnerDebitOrCredit.setSelection(0)
}
spinnerDebitOrCredit.onItemSelectedListener = object : AdapterView.OnItemSelectedListener{
override fun onItemSelected(
parent: AdapterView<*>?,
view: View?,
position: Int,
id: Long
) {
val selection = parent?.getItemAtPosition(position)
debitOrCredit = selection.toString()
}
override fun onNothingSelected(parent: AdapterView<*>?) {
TODO("Not yet implemented")
}
}
ibSave.setOnClickListener {
// save all the info.
val date = tvDateAT.text
val memo = etMemo.text
val title = etTitle.text
val amount = etAmount.text
val uid = FirebaseAuth.getInstance().currentUser!!.uid
val serverTS = FieldValue.serverTimestamp()
val collection = account
var nb = 0.0
val newBalance = if (debitOrCredit.contains("Debit")){
nb = (balance.toDouble() - etAmount.text.toString().toDouble())
} else {
nb = (balance.toDouble() + etAmount.text.toString().toDouble())
}
val info = hashMapOf(
"date" to date.toString(),
"type" to type,
"title" to title.toString(),
"memo" to memo.toString(),
"category" to category,
"debitOrCredit" to debitOrCredit,
"amount" to amount.toString(),
"clearReconcile" to "NA",
"mils" to mils,
"timeStamp" to serverTS
)
if (date != "Date"){
if (title?.isNotEmpty() == true && amount?.isNotEmpty() == true){
val dbAccountTransaction = db.collection("Users").document(uid).collection(collection)
dbAccountTransaction.add(info)
.addOnSuccessListener {
db.collection("Users").document(uid).collection(collection).document("balance")
.update("balance", nb.toString())
.addOnSuccessListener {
Toast.makeText(this, "Transaction was saved.", Toast.LENGTH_SHORT).show()
val i = Intent(this, MainActivity::class.java)
startActivity(i)
}
}
.addOnFailureListener{
Toast.makeText(this, "There was an error. Transaction wasn't saved.", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Please fill out Title and Amount.", Toast.LENGTH_SHORT).show()
}
} else {
Toast.makeText(this, "Please select a date.", Toast.LENGTH_SHORT).show()
}
}
ibCancel.setOnClickListener {
stopSelf()
windowManager.removeView(floatView)
}
LAYOUT_TYPE = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_TOAST
}
floatWindowLayoutParams = WindowManager.LayoutParams(
(width * 0.55f).toInt(),
(height * 0.55f).toInt(),
LAYOUT_TYPE!!,
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
)
floatWindowLayoutParams.gravity = Gravity.CENTER
floatWindowLayoutParams.x = 0
floatWindowLayoutParams.y = 0
windowManager.addView(floatView, floatWindowLayoutParams)
floatView.setOnTouchListener(object : View.OnTouchListener{
val updatedFloatWindowLayoutParam = floatWindowLayoutParams
private var initialX = 0.0
private var initialY = 0.0
private var initialTouchX = 0.0
private var initialTouchY = 0.0
override fun onTouch(v: View?, event: MotionEvent): Boolean {
when (event.action) {
MotionEvent.ACTION_DOWN -> {
initialX = updatedFloatWindowLayoutParam.x.toDouble()
initialY = updatedFloatWindowLayoutParam.y.toDouble()
initialTouchX = event.rawX.toDouble()
initialTouchY = event.rawY.toDouble()
return true
}
MotionEvent.ACTION_MOVE -> {
updatedFloatWindowLayoutParam.x = (initialX + event.rawX - initialTouchX).toInt()
updatedFloatWindowLayoutParam.y = (initialY + event.rawY - initialTouchY).toInt()
windowManager.updateViewLayout(floatView, updatedFloatWindowLayoutParam)
return true
}
}
return false
}
})
spinnerAccount.setOnTouchListener(object : View.OnTouchListener {
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
val updatedFloatParamsFlag = floatWindowLayoutParams
updatedFloatParamsFlag.flags =
WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
windowManager.updateViewLayout(floatView, updatedFloatParamsFlag)
return false
}
})
}
}
return START_NOT_STICKY
}
private fun getDB() {
try {
db.collection("Users").document(FirebaseAuth.getInstance().currentUser!!.uid)
.collection(account).document("balance")
.get()
.addOnSuccessListener { document ->
if (document != null) {
val balanceResult = StringBuffer()
balanceResult.append(document.data?.getValue("balance"))
balance = balanceResult.toString()
}
}
} catch (e: Exception){
e.printStackTrace()
}
}
override fun onDestroy() {
super.onDestroy()
stopSelf()
windowManager.removeView(floatView)
}
companion object {
var FOREGROUND_SERVICE_ID = 101
var START = "start"
var STOP_ACTION = "stop"
private const val CHANNEL = "default"
}
}
I have tried several ways of getting the context, but it always seems to come back null. I have tried this, this@Dooa, this.applicationContext, I also created a class MyApp to get context that way. it didn't work either. I used this link. Code below.
class MyApp: Application() {
private var context: Context? = null
override fun onCreate() {
super.onCreate()
context = applicationContext
}
fun getContext(): Context? {
return context?.applicationContext
}
}
I've also checked out this answer about Service is a Context, but I still haven't been able to get this to work.
I have tried the code in the onCreate first then I tried the onStartCommand to no avail. What am I missing?
The window pops up, It pops up with a button click, or from a notification, either way if I click on a view, it gives me the error at the top of this question.