0

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.

1 Answers1

0

You are doing this:

context = MyApp().getContext()

This is definitely wrong. MyApp is an Android component. You are not allowed to instantiate Android components yourself. Only the Android framework can do this (as it also sets up the Context and other important values.

If you want the Service context, just use this (A Service is a Context).

If you want the application context, just use getApplication() (Application is also a Context).

David Wasser
  • 93,459
  • 16
  • 209
  • 274