DEV Community

Cover image for #3 Floating Windows on Android: Permissions
vaclavhodek for Localazy

Posted on • Edited on

#3 Floating Windows on Android: Permissions

Have you ever wondered how to make those floating windows used by Facebook Heads and other apps? Have you ever wanted to use the same technology in your app? It’s easy, and I will guide you through the whole process.

I'm the author of Floating Apps; the first app of its kind on Google Play and the most popular one with over 8 million downloads. After 6 years of the development of the app, I know a bit about it. It’s sometimes tricky, and I spent months reading documentation and Android source code and experimenting. I received feedback from tens of thousands of users and see various issues on different phones with different Android versions.

Here's what I learned along the way.

Before reading this article, it's recommended to go through Floating Windows on Android 2: Foreground Service.

In this article, I will teach you how to ask users for special permission that is required for the floating technology to work.

SYSTEM_ALERT_WINDOW

For the floating technology to work, we need to define SYSTEM_ALERT_WINDOW in AndroidManifest.xml. Adding this single line is enough:

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

This permission was initially meant to provide a mechanism for showing system windows – small notifications, error messages, call-in-progress overlays, etc. However, with a bit of effort, we can use it for our floating windows.

Draw Over Other Apps

The previous step with altering AndroidManifest.xml is not enough. To show floating windows, we need special permission called Draw over other apps. On some phones, it may have a different name. For example, on some Xiaomi phones, it’s Popup permission.

As it’s special permission, we need to point the user to a particular screen in the phone’s settings and manually enable it. Fortunately, there is a way to check for the permission status to implement decent logic for requesting it from the user.

Keep in mind that anytime in the future, the permission may be revoked for the app and so it’s necessary to do periodic checks. For this reason, we create an activity to ask for permission. We can start this activity from anywhere - from other activities as well as from the foreground service.

First, let’s add two small helper methods for checking whether the permission is granted or not

fun Context.drawOverOtherAppsEnabled(): Boolean {
  return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
    true
  } else {
    Settings.canDrawOverlays(this)
  }
}


fun Context.startPermissionActivity() {
  startActivity(
    Intent(this, PermissionActivity::class.java).apply {
      flags = Intent.FLAG_ACTIVITY_NEW_TASK
    }
  )
}

Using these two methods, we can add a check for permission into onStartCommand method of our FloatingService introduced in the previous article.

override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
  // ...unrelated code omitted for brevity...

  // Show the floating window for adding a new note.
  if (command == INTENT_COMMAND_NOTE) {
    if (!drawOverOtherAppsEnabled()) {
      startPermissionActivity()
    } else {
      Toast.makeText(
        this,
        "Floating window to be added in the next lessons.",
        Toast.LENGTH_SHORT
      ).show()
    }
  }

  return START_STICKY  
}

Of course, we need to add our new activity to the AndroidManifest.xml. No extra care is necessary.

<activity android:name=".PermissionActivity" />

And finally, here goes the full source code of the permission activity:

const val PERMISSION_REQUEST_CODE = 1

class PermissionActivity : AppCompatActivity() {

  private fun showDialog(titleText: String, messageText: String) {
    with(AlertDialog.Builder(this)) {
      title = titleText
      setMessage(messageText)
      setPositiveButton(R.string.common_ok) { dialog, _ ->
        dialog.dismiss()
      }
      show()
    }
  }

  @RequiresApi(Build.VERSION_CODES.M)
  private fun requestPermission() {
    val intent = Intent(
      Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
      Uri.parse("package:$packageName")
    )
    try {
      startActivityForResult(intent, PERMISSION_REQUEST_CODE)
    } catch (e: Exception) {
      showDialog(
        getString(R.string.permission_error_title),
        getString(R.string.permission_error_text)
      )
    }
  }

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
      Column {

        Text(
          text = getString(R.string.permission_required_title),
          fontSize = 16.sp,
          fontWeight = FontWeight.Bold,
          modifier = Modifier.padding(16.dp, 16.dp, 16.dp, 8.dp)
        )

        Text(
          text = getString(R.string.permission_required_text),
          modifier = Modifier.padding(16.dp, 4.dp)
        )

        Button(
          onClick = {
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
              finish()
            } else {
              requestPermission()
            }
          },
          modifier = Modifier.padding(16.dp, 8.dp)
        ) {
          Text(text = getString(R.string.permission_required_open))
        }

      }
    }
  }

  override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
    // Don't check for resultCode == Activity.RESULT_OK because the overlay activity
    // is closed with the back button and so the RESULT_CANCELLED is always received.
    if (requestCode == PERMISSION_REQUEST_CODE) {
      if (drawOverOtherAppsEnabled()) {
        // The permission has been granted.
        // Resend the last command - we have only one, so no additional logic needed.
        startFloatingService(INTENT_COMMAND_NOTE)
        finish()
      }
    } else {
      super.onActivityResult(requestCode, resultCode, data)
    }
  }

}

Android 5 & Older

Everything described above is valid on Android 6 and newer. On Android 5 and older devices, there is no mechanism for obtaining the state of the permission nor the standard mechanism for asking the user to grant it.

Based on my experience, on the majority of older devices, permission is enabled by default. Also, Android 5 is now obsolete enough, so it’s not a concern.

The very first version of Floating Apps was developed for then recent Android 2.3.3, and before Android 6 took over, it was a bit of pain for us :-).

Unsupported Devices

On a few devices, this permission is not accessible and can only be enabled through ADB. The same situation applies to some custom ROMs.

Also, on some specific devices such as Android TV boxes, this permission may not be available at all, and the floating technology won’t work.

However, this is my experience gathered from running Floating Apps on about 11.000 different devices, and there is mostly no problem with Draw over other apps. So don’t worry much about it.

Results

The animation below demonstrates how permission is acquired.

Source Code

The whole source code for this article is available on Github.

Stay Tuned

Eager to learn more about Android development? Follow me (@vaclavhodek) and Localazy (@localazy) on Twitter, or like Localazy on Facebook.

The Series

This article is part of the Floating Windows on Android series.

Top comments (0)