Displaying over other apps in android
Android allows an app to display its content on other apps or the device home screen. An example of its usage is in the screen recorder apps that have floating menus. In this article we are creating a full screen notification that will only be shown once.
Android allows an app to display its content on other apps or the device home screen. An example of its usage is in the screen recorder apps that have floating menus. In this article we are creating a full screen notification that will only be shown once.
To start off, create a new project and create a layout for the notification
full_screen_notification_layout.xml
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:background="#10110f"> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:padding="16dp" android:orientation="vertical" android:gravity="center"> <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:text="This is an overlay" android:textSize="32sp" android:textColor="#ffffff" android:textAlignment="center" android:fontFamily="sans-serif-medium" /> <View android:layout_width="0dp" android:layout_height="0dp" android:layout_weight="1"/> <ImageView android:id="@+id/btn_close" android:layout_width="50dp" android:layout_height="50dp" android:src="@drawable/ic_close" android:background="@drawable/btn_bg"/> </LinearLayout> </LinearLayout>
Then we create a Notification view class that will be used to display our layout. Below is a function that will create the LayoutParams and inflate full_screen_notification_layout.xml to overlay view and initializes it.
class FullScreenNotificationView( private val context: Context ) { private var windowManager: WindowManager? = null private var overlayView: View? = null private var params: WindowManager.LayoutParams? = null init { showOverlay() } private fun showOverlay() { params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_FULLSCREEN, PixelFormat.TRANSLUCENT ) val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater overlayView = layoutInflater.inflate(R.layout.full_screen_notification_layout, null) params?.gravity = Gravity.START or Gravity.TOP windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager } }
Add the function below will be used by service to display the view inside our window. We check if there is no other view in the window before adding a new one.
fun open() { try { if (overlayView?.windowToken == null) { windowManager?.addView(overlayView, params) } } catch (e: Exception) { Log.d("window open", e.toString()) } }
Also add a function that will remove view from the window and set the layout to null.
fun removeOverLay() { try { overlayView?.let { windowManager?.removeView(it) overlayView = null params = null } } catch (e: Exception) { Log.d("window close", e.toString()) } }
The function below will be used when a user wants to close the view. Add it to the image view.
overlayView?.findViewById<ImageView>(R.id.btn_close)?.setOnClickListener { removeOverLay() } FullScreenNotificationView.kt class FullScreenNotificationView( private val context: Context ) { private var windowManager: WindowManager? = null private var overlayView: View? = null private var params: WindowManager.LayoutParams? = null init { showOverlay() } private fun showOverlay() { params = WindowManager.LayoutParams( WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.MATCH_PARENT, if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY else WindowManager.LayoutParams.TYPE_SYSTEM_OVERLAY, WindowManager.LayoutParams.FLAG_FULLSCREEN, PixelFormat.TRANSLUCENT ) val layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater overlayView = layoutInflater.inflate(R.layout.full_screen_notification_layout, null) overlayView?.findViewById<ImageView>(R.id.btn_close)?.setOnClickListener { removeOverLay() } params?.gravity = Gravity.START or Gravity.TOP windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager } fun open() { try { if (overlayView?.windowToken == null) { windowManager?.addView(overlayView, params) } } catch (e: Exception) { Log.d("window open", e.toString()) } } fun removeOverLay() { try { overlayView?.let { windowManager?.removeView(it) overlayView = null params = null } } catch (e: Exception) { Log.d("window close", e.toString()) } } }
We will create a foreground service that runs in foreground by displaying the FullScreenNotificationView that is initialized when the service is created and also displays the view.
class ForegroundService : Service() { private lateinit var fullScreenNotificationView: FullScreenNotificationView override fun onBind(p0: Intent?): IBinder? { throw UnsupportedOperationException("Not yet Implemented") } override fun onCreate() { super.onCreate() if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { startMyForeground() } else { startForeground(1, Notification()) } fullScreenNotificationView = FullScreenNotificationView(applicationContext) fullScreenNotificationView.open() } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { return super.onStartCommand(intent, flags, startId) } override fun onDestroy() { super.onDestroy() fullScreenNotificationView.removeOverLay() } @RequiresApi(Build.VERSION_CODES.O) private fun startMyForeground() { val channel = NotificationChannel( CHANNEL_NAME, "Full Screen Demo", NotificationManager.IMPORTANCE_MIN ) val notificationManager = getSystemService(NotificationManager::class.java) notificationManager.createNotificationChannel(channel) val notification = NotificationCompat.Builder(applicationContext, CHANNEL_NAME) .setContentTitle("Overlay Demo") .setSmallIcon(R.mipmap.ic_launcher) .setPriority(NotificationManager.IMPORTANCE_HIGH) .setCategory(Notification.CATEGORY_SERVICE) .build() startForeground(2, notification) } companion object { private const val CHANNEL_NAME = "Full_Screen_Demo_channel" } }
we will also add notification when the service is started.
To stop the foreground service when user closes the view, update the function as below
fun removeOverLay() { try { overlayView?.let { windowManager?.removeView(it) overlayView = null params = null context.stopService(Intent(context, ForegroundService::class.java)) } } catch (e: Exception) { Log.d("window close", e.toString()) } }
Don’t forget to register the service in our manifest file and allow permission.
<manifest ...> <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/> <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> <uses-permission android:name="android.permission.FOREGROUND_SERVICE" /> <application ...> ... <service android:name=".ForegroundService" android:exported="false" /> </application> </manifest>
To display it inside the MainActivity.kt, we will ask for permission to allow app to display the view which is delayed by 10 seconds so one can open another app. Refer here.
val coroutineScope = rememberCoroutineScope() var isDialogVisible by remember { mutableStateOf(false) } LaunchedEffect(key1 = Unit) { isDialogVisible = !Settings.canDrawOverlays(this@MainActivity) } Surface( modifier = Modifier.fillMaxSize(), color = MaterialTheme.colorScheme.background ) { Box( modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center ) { if (isDialogVisible) { PermissionDialog( onRequest = { isDialogVisible = false Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).also { startActivity(it) } }, content = "Request permission to overlay over other apps" ) } Button( onClick = { if (Settings.canDrawOverlays(this@MainActivity)) { coroutineScope.launch { delay(10 * 1000L) startService( Intent( this@MainActivity, ForegroundService::class.java ) ) } } else { Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION).also { startActivity(it) } } } ) { Text(text = "Open Layout") } } } @Composable fun PermissionDialog( onRequest: () -> Unit, content: String ) { Dialog( onDismissRequest = {}, properties = DialogProperties( dismissOnBackPress = true, dismissOnClickOutside = true ) ) { Column( modifier = Modifier .size(200.dp) .background(MaterialTheme.colorScheme.background) .padding(12.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = Alignment.CenterHorizontally ) { Text(text = content) Spacer(modifier = Modifier.height(12.dp)) Button(onClick = onRequest) { Text(text = "Request") } } } }
Check full code.