diff --git a/HEADER b/HEADER
index 127f54a23770d9adf38b20e23c01d7059b406261..84b17628118a35aab8450306fe22ac23f73c3227 100644
--- a/HEADER
+++ b/HEADER
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2024 MURENA SAS
+ * Copyright (C) 2025 MURENA SAS
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index b8864cd8bce737c41e397d87eeb7c0926e1db3dc..d316cc46058b8a6993a1961440c712162e15317f 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -7,6 +7,12 @@
+
+
+
+
+
@@ -15,6 +21,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
.
+ *
+ */
+package foundation.e.parentalcontrol
+
+import android.Manifest
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import androidx.annotation.RequiresPermission
+import foundation.e.parentalcontrol.data.ScreenTimes
+import foundation.e.parentalcontrol.utils.Constants
+import foundation.e.parentalcontrol.utils.Constants.BLOCK_ALL_SYSTEM_APP
+import foundation.e.parentalcontrol.utils.Constants.POPUP_HAS_ALREADY_BEEN_ACTIVATED
+import foundation.e.parentalcontrol.utils.PrefsUtils
+import java.time.LocalTime
+
+class AlarmReceiver : BroadcastReceiver() {
+
+ companion object {
+ const val WHEN_ACTIVATE_POPUP_IN_MS = 10 * 60 * 1000
+ const val ONE_DAY_IN_MS = 24 * 60 * 60 * 1000
+ }
+
+ @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
+ override fun onReceive(context: Context, intent: Intent?) {
+ var allowedScreenTime = PrefsUtils.getAllowedScreenTime()
+ if (allowedScreenTime == ScreenTimes.DISABLE.value) {
+ allowedScreenTime = ONE_DAY_IN_MS
+ } else {
+ allowedScreenTime = allowedScreenTime * 3600 * 1000
+ }
+
+ val popupHasAlreadyBeenActive =
+ intent?.getBooleanExtra(POPUP_HAS_ALREADY_BEEN_ACTIVATED, false) ?: false
+ val stats = UsageStatsManager()
+ val res = stats.showTime(context)
+ if (allowedScreenTime <= res) {
+ blockApp(true, context)
+ stats.activeAlarm(
+ context,
+ (ONE_DAY_IN_MS - LocalTime.now().toSecondOfDay() * 1000).toLong(),
+ popupHasAlreadyBeenActive
+ )
+ } else if (res >= allowedScreenTime - WHEN_ACTIVATE_POPUP_IN_MS) {
+ if (!popupHasAlreadyBeenActive) {
+ sendPopup(context)
+ popupHasAlreadyBeenActive
+ }
+ stats.activeAlarm(context, (allowedScreenTime - res), popupHasAlreadyBeenActive)
+ } else if (res < allowedScreenTime) {
+ blockApp(false, context)
+ stats.activeAlarm(
+ context,
+ (allowedScreenTime - (res + WHEN_ACTIVATE_POPUP_IN_MS)),
+ false
+ )
+ }
+ }
+
+ fun blockApp(blockAllSystemApp: Boolean, context: Context) {
+ val broadcastIntent = Intent()
+ broadcastIntent.action = Constants.BLOCKED_SYSTEM_APP
+ broadcastIntent.setClass(context, DeviceAdmin::class.java)
+ broadcastIntent.putExtra(BLOCK_ALL_SYSTEM_APP, blockAllSystemApp)
+ context.sendBroadcast(broadcastIntent)
+ }
+
+ private fun sendPopup(context: Context) {
+ val intent = Intent(context, OverlayService::class.java)
+ context.startService(intent)
+ }
+}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt b/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt
index ff7ce0f74a790f0bb10155035cdd3df9fd2cf468..328a816a4409b8ff507ba407673e90a1e3105b80 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt
@@ -22,15 +22,21 @@ import android.app.admin.DevicePolicyManager
import android.content.ComponentName
import android.content.Context
import android.content.Intent
+import android.os.Handler
+import android.os.Looper
import android.util.Log
import android.widget.Toast
import foundation.e.parentalcontrol.data.DnsManager
import foundation.e.parentalcontrol.ui.view.MainUI
import foundation.e.parentalcontrol.utils.Constants
+import foundation.e.parentalcontrol.utils.Constants.BLOCK_ALL_SYSTEM_APP
+import foundation.e.parentalcontrol.utils.Constants.DELAY_WAIT_OWNER_ACCESS
+import foundation.e.parentalcontrol.utils.PrefsUtils
import java.util.Objects
class DeviceAdmin : DeviceAdminReceiver() {
private val TAG = "DeviceAdmin"
+ private var blockAllSystemApp = false
/** @return Returns an instance of a DevicePolicyManager object */
fun getDevicePolicyManager(context: Context): DevicePolicyManager {
@@ -55,7 +61,17 @@ class DeviceAdmin : DeviceAdminReceiver() {
}
override fun onReceive(context: Context, intent: Intent) {
+
when (Objects.requireNonNull(intent.action)) {
+ Intent.ACTION_BOOT_COMPLETED, -> {
+ if (isAdminActive(context)) {
+ // Reschedule Alarm on reboot and restore Allowed apps.
+ PrefsUtils.init(context)
+ val stats = UsageStatsManager()
+ stats.activeAlarm(context, 0, false)
+ PrefsUtils.restoreAllowedAppList(context)
+ }
+ }
DevicePolicyManager.ACTION_PROFILE_OWNER_CHANGED,
DevicePolicyManager.ACTION_DEVICE_OWNER_CHANGED -> {
Log.d(TAG, intent.action!!)
@@ -68,6 +84,11 @@ class DeviceAdmin : DeviceAdminReceiver() {
}
setSettings(context)
}
+ Constants.BLOCKED_SYSTEM_APP -> {
+ blockAllSystemApp = intent.getBooleanExtra(BLOCK_ALL_SYSTEM_APP, false)
+ onSetScreenTimeIsOver(blockAllSystemApp)
+ waitOwnerAccess(context)
+ }
Constants.RESET_PRIVATE_DNS -> {
if (isAdminActive(context)) {
val mainUI = MainUI(context)
@@ -97,4 +118,23 @@ class DeviceAdmin : DeviceAdminReceiver() {
val isDeviceOwner = isDeviceOwnerApp(context)
return isProfileOwner || isDeviceOwner
}
+
+ private fun waitOwnerAccess(context: Context) {
+ // Important: needs to be delayed.
+ // see Handler(Looper.getMainLooper()).postDelayed({ onStartUp() }, DELAY_CONFIRM_PASSWORD)
+ Handler(Looper.getMainLooper())
+ .postDelayed(
+ {
+ if (isAdminActive(context)) {
+ val mainUI = MainUI(context)
+ mainUI.blockApps(blockAllSystemApp)
+ }
+ },
+ DELAY_WAIT_OWNER_ACCESS
+ )
+ }
+
+ private fun onSetScreenTimeIsOver(screenTimeIsOver: Boolean) {
+ PrefsUtils.setScreenTimeIsOver(screenTimeIsOver)
+ }
}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt b/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt
index 65b36cb570d02f5dc963707f3036c847c228ecc5..8b8bad5ab198b143bde67a013be84e6a24a5e968 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt
@@ -17,13 +17,13 @@
*/
package foundation.e.parentalcontrol
+import android.Manifest
+import android.annotation.SuppressLint
import android.app.admin.DevicePolicyManager
import android.app.admin.DevicePolicyManager.ACTION_PROVISION_MANAGED_PROFILE
import android.content.ComponentName
import android.content.Context
import android.content.Intent
-import android.content.res.Configuration
-import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.os.Handler
@@ -34,16 +34,24 @@ import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
import androidx.activity.compose.setContent
+import androidx.annotation.RequiresPermission
import androidx.browser.customtabs.CustomTabsIntent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.KeyboardActions
@@ -51,6 +59,7 @@ import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.foundation.verticalScroll
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
+import androidx.compose.material.icons.filled.KeyboardArrowDown
import androidx.compose.material.icons.filled.Lock
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
@@ -61,6 +70,7 @@ import androidx.compose.material3.Text
import androidx.compose.material3.TextField
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
@@ -68,16 +78,18 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
import androidx.compose.ui.focus.FocusRequester
import androidx.compose.ui.focus.focusRequester
import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.platform.LocalClipboardManager
-import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalSoftwareKeyboardController
import androidx.compose.ui.res.colorResource
+import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.TextStyle
@@ -89,8 +101,13 @@ import androidx.compose.ui.text.style.TextDecoration
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
+import androidx.core.net.toUri
+import androidx.lifecycle.lifecycleScope
import foundation.e.parentalcontrol.data.AuthenticationType
import foundation.e.parentalcontrol.data.Pages
+import foundation.e.parentalcontrol.data.ParentalControlStatus
+import foundation.e.parentalcontrol.data.ParentalControlStatusList
+import foundation.e.parentalcontrol.data.ScreenTimes
import foundation.e.parentalcontrol.data.isNotLoggedIn
import foundation.e.parentalcontrol.providers.AppLoungeData
import foundation.e.parentalcontrol.ui.buttons.ToggleWithText
@@ -103,12 +120,31 @@ import foundation.e.parentalcontrol.ui.view.AskPassword
import foundation.e.parentalcontrol.ui.view.AuthenticationTypeSelectionView
import foundation.e.parentalcontrol.ui.view.MainUI
import foundation.e.parentalcontrol.ui.view.SelectAge
+import foundation.e.parentalcontrol.ui.view.SelectAllowedApps
+import foundation.e.parentalcontrol.ui.view.SelectAllowedScreenTime
+import foundation.e.parentalcontrol.ui.view.SelectManageTypeApp
import foundation.e.parentalcontrol.ui.view.selectedAge
+import foundation.e.parentalcontrol.ui.view.selectedAllowedScreenTime
+import foundation.e.parentalcontrol.ui.view.selectedTypeAppManagement
import foundation.e.parentalcontrol.utils.Constants
+import foundation.e.parentalcontrol.utils.Constants.DELAY_CONFIRM_PASSWORD
+import foundation.e.parentalcontrol.utils.Constants.DELAY_DISABLE_FMD
+import foundation.e.parentalcontrol.utils.Constants.KEY_FIND_MY_DEVICE
+import foundation.e.parentalcontrol.utils.Constants.KEY_FIND_MY_DEVICE_STATUS
+import foundation.e.parentalcontrol.utils.Constants.KEY_FMD_STATUS_DONE
+import foundation.e.parentalcontrol.utils.Constants.KEY_FMD_STATUS_ENROLLING
+import foundation.e.parentalcontrol.utils.Constants.KEY_FMD_STATUS_NONE
+import foundation.e.parentalcontrol.utils.Constants.KEY_PARENTAL_CONTROL_AUTHENTICATE
import foundation.e.parentalcontrol.utils.CryptUtils
import foundation.e.parentalcontrol.utils.Dimens
import foundation.e.parentalcontrol.utils.PrefsUtils
+import foundation.e.parentalcontrol.utils.PrefsUtils.getFMDStatus
+import foundation.e.parentalcontrol.utils.PrefsUtils.setFMDStatus
import foundation.e.parentalcontrol.utils.SystemUtils
+import java.util.Date
+import kotlin.math.roundToInt
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.DurationUnit
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
@@ -123,11 +159,32 @@ class MainActivity : ComponentActivity() {
super.onCreate(savedInstanceState)
PrefsUtils.init(mActivity)
-
onStartUp()
}
private fun onStartUp() {
+
+ if (BuildConfig.DEBUG) {
+ lifecycleScope.launch {
+ val stats = UsageStatsManager()
+ val totalUsageMillis = stats.showTime(mActivity)
+ val totalUsageMinutes = totalUsageMillis.milliseconds.toDouble(DurationUnit.MINUTES)
+ Toast.makeText(
+ mActivity,
+ "Usage: ${totalUsageMinutes.roundToInt()} min",
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ }
+ }
+
+ if (intent?.action == KEY_PARENTAL_CONTROL_AUTHENTICATE) {
+ if (isAdminActive()) {
+ mainScreen(page = Pages.AskPasswordForAppInstallation)
+ }
+ return // do NOT startUp
+ }
+
if (SystemUtils.getUserId() != 0) {
mainScreen(page = Pages.NotMainUser)
} else if (isAdminSet()) {
@@ -146,6 +203,19 @@ class MainActivity : ComponentActivity() {
!PrefsUtils.getPassword().isNullOrEmpty())
}
+ private fun disableFMDAccess() {
+ // Important: needs to be delayed.
+ // see Handler(Looper.getMainLooper()).postDelayed({ onStartUp() }, DELAY_CONFIRM_PASSWORD)
+ Handler(Looper.getMainLooper())
+ .postDelayed(
+ {
+ val mainUI = MainUI(mActivity)
+ mainUI.manageFmD(true)
+ },
+ DELAY_DISABLE_FMD
+ )
+ }
+
@Composable
fun SetRestrictionsScreen() {
BackHandler(onBack = { onStartUp() })
@@ -166,10 +236,16 @@ class MainActivity : ComponentActivity() {
setResult(RESULT_OK)
}
finishAfterTransition()
+
+ if (isAdminSet()) disableFMDAccess() else setFMDStatus(KEY_FMD_STATUS_NONE)
}
override fun onResume() {
super.onResume()
+
+ if (getFMDStatus() == KEY_FMD_STATUS_ENROLLING) {
+ setFMDStatus(KEY_FMD_STATUS_DONE)
+ }
if (isAdminActive()) {
onStartUp()
}
@@ -309,6 +385,8 @@ class MainActivity : ComponentActivity() {
verticalArrangement = Arrangement.spacedBy(Dimens.SCREEN_PADDING),
horizontalAlignment = Alignment.CenterHorizontally
) {
+ @SuppressLint("ScheduleExactAlarm")
+ @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
fun confirmPassword() {
val originalPass = passText
val confirmedPass = confirmPassText
@@ -332,16 +410,23 @@ class MainActivity : ComponentActivity() {
confirmPassText = ""
onSetAge()
+ onSetAllowedScreenTime()
+ onSetTypeAppManagement()
+ onSetAllowedApps()
+ val stats = UsageStatsManager()
+ stats.activeAlarm(mActivity, 0, false)
if (!isAdminActive()) {
setAdmin(true)
}
if (SystemUtils.isSetupFinished(mActivity)) {
- Handler(Looper.getMainLooper()).postDelayed({ onStartUp() }, 500L)
+ Handler(Looper.getMainLooper())
+ .postDelayed({ onStartUp() }, DELAY_CONFIRM_PASSWORD)
} else {
onExitApp(true)
}
+ performSelectedAppManagement()
} else {
isError = true
if (
@@ -564,7 +649,7 @@ class MainActivity : ComponentActivity() {
)
}
- ActivateAdminSummary()
+ ActivateAdminSummary(false)
var checkedState by remember { mutableStateOf(false) }
var moveToNext by remember { mutableStateOf(false) }
@@ -576,7 +661,7 @@ class MainActivity : ComponentActivity() {
fontWeight = FontWeight.Medium,
onCheckedChange = {
checkedState = it
- PrefsUtils.clearAll()
+ PrefsUtils.clearAllExcept(Constants.PARENTAL_CONTROL_STATUS_LIST)
if (isAdminActive()) {
setAdmin(false)
}
@@ -595,53 +680,116 @@ class MainActivity : ComponentActivity() {
}
@Composable
- fun ActivateAdminSummary() {
- val clipboardManager = LocalClipboardManager.current
+ fun ActivateAdminSummary(collapsed: Boolean = false) {
+ var isExpanded by remember { mutableStateOf(!collapsed) }
+
+ Column(modifier = Modifier.fillMaxWidth().padding(bottom = 8.dp)) {
+ if (collapsed) {
+ val animationDelay = 250
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ modifier =
+ Modifier.fillMaxWidth()
+ .background(
+ colorResource(foundation.e.elib.R.color.e_background_overlay),
+ shape = RoundedCornerShape(Dimens.SCREEN_PADDING / 2)
+ )
+ .clickable { isExpanded = !isExpanded }
+ .padding(
+ horizontal = Dimens.SCREEN_PADDING / 2,
+ vertical = Dimens.SCREEN_PADDING / 4
+ )
+ ) {
+ Text(
+ text = stringResource(R.string.activate_admin_summary_title),
+ color = colorResource(foundation.e.elib.R.color.e_primary_text_color),
+ modifier = Modifier.weight(1f),
+ fontWeight = FontWeight.Medium,
+ )
- Text(
- text = stringResource(R.string.activate_admin_summary),
- color = colorResource(foundation.e.elib.R.color.e_primary_text_color),
- fontSize = 15.sp,
- maxLines = Int.MAX_VALUE,
- overflow = TextOverflow.Clip,
- modifier = Modifier.padding(bottom = Dimens.SCREEN_PADDING / 2)
- )
+ val rotation by
+ animateFloatAsState(
+ targetValue = if (isExpanded) 180f else 0f,
+ animationSpec = tween(durationMillis = animationDelay),
+ label = "ArrowRotation"
+ )
- val docUrl = stringResource(R.string.e_foundation_docs_link)
- Text(
- text = docUrl,
- fontSize = 15.sp,
- color = colorResource(foundation.e.elib.R.color.e_accent),
- maxLines = Int.MAX_VALUE,
- overflow = TextOverflow.Clip,
- style = TextStyle(textDecoration = TextDecoration.Underline),
- modifier =
- Modifier.padding(bottom = 8.dp).pointerInput(Unit) {
- detectTapGestures(
- onTap = {
- try {
- val customTabsIntent =
- CustomTabsIntent.Builder().setShowTitle(true).build()
- customTabsIntent.launchUrl(mActivity, Uri.parse(docUrl))
- } catch (e: Exception) {
- // Fallback to default browser
- val intent = Intent(Intent.ACTION_VIEW, Uri.parse(docUrl))
- startActivity(intent)
- }
- },
- onLongPress = {
- // Copy to clipboard
- clipboardManager.setText(AnnotatedString(docUrl))
- Toast.makeText(
- mActivity,
- getString(R.string.link_copied_to_clipboard),
- Toast.LENGTH_SHORT
- )
- .show()
- }
+ Icon(
+ imageVector = Icons.Default.KeyboardArrowDown,
+ contentDescription = null,
+ modifier = Modifier.graphicsLayer(rotationZ = rotation),
+ tint = colorResource(foundation.e.elib.R.color.e_primary_text_color)
)
}
- )
+
+ AnimatedVisibility(
+ visible = isExpanded,
+ enter =
+ androidx.compose.animation.expandVertically(
+ animationSpec = tween(durationMillis = animationDelay)
+ ),
+ exit =
+ androidx.compose.animation.shrinkVertically(
+ animationSpec = tween(durationMillis = animationDelay)
+ )
+ ) {
+ ContentAdminSummary()
+ }
+ } else {
+ ContentAdminSummary()
+ }
+ }
+ }
+
+ @Composable
+ private fun ContentAdminSummary() {
+ val docUrl = stringResource(R.string.e_foundation_docs_link)
+ val clipboardManager = LocalClipboardManager.current
+ Column {
+ Text(
+ text = stringResource(R.string.activate_admin_summary),
+ color = colorResource(foundation.e.elib.R.color.e_primary_text_color),
+ fontSize = 15.sp,
+ maxLines = Int.MAX_VALUE,
+ overflow = TextOverflow.Clip,
+ modifier = Modifier.padding(bottom = Dimens.SCREEN_PADDING / 2),
+ )
+
+ Text(
+ text = docUrl,
+ fontSize = 15.sp,
+ color = colorResource(foundation.e.elib.R.color.e_accent),
+ maxLines = Int.MAX_VALUE,
+ overflow = TextOverflow.Clip,
+ style = TextStyle(textDecoration = TextDecoration.Underline),
+ modifier =
+ Modifier.padding(bottom = 8.dp).pointerInput(Unit) {
+ detectTapGestures(
+ onTap = {
+ try {
+ val customTabsIntent =
+ CustomTabsIntent.Builder().setShowTitle(true).build()
+ customTabsIntent.launchUrl(mActivity, docUrl.toUri())
+ } catch (e: Exception) {
+ // Fallback to default browser
+ val intent = Intent(Intent.ACTION_VIEW, docUrl.toUri())
+ startActivity(intent)
+ }
+ },
+ onLongPress = {
+ // Copy to clipboard
+ clipboardManager.setText(AnnotatedString(docUrl))
+ Toast.makeText(
+ mActivity,
+ getString(R.string.link_copied_to_clipboard),
+ Toast.LENGTH_SHORT
+ )
+ .show()
+ }
+ )
+ }
+ )
+ }
}
@Composable
@@ -661,28 +809,272 @@ class MainActivity : ComponentActivity() {
Column(
modifier =
Modifier.fillMaxSize()
- .padding(start = Dimens.SCREEN_PADDING, end = Dimens.SCREEN_PADDING),
+ .padding(start = Dimens.SCREEN_PADDING * 2, end = Dimens.SCREEN_PADDING * 2),
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
) {
+ FindMyDevice()
+
if (showLoginDialog && PrefsUtils.getBlockedApps() == null) {
+
val mainUI = MainUI(mActivity)
+
+ if (getFMDStatus() == KEY_FMD_STATUS_DONE) {
+ AlertDialog(
+ onDismissRequest = {
+ showLoginDialog = false
+ mainUI.blockAllUserApps(true)
+ },
+ title = {
+ Row(
+ verticalAlignment = Alignment.CenterVertically,
+ ) {
+ Icon(
+ imageVector = Icons.Default.Info,
+ contentDescription = "Info icon",
+ tint = Color.Gray
+ )
+ Text(
+ text = stringResource(R.string.login_required),
+ fontSize = 20.sp,
+ modifier = Modifier.padding(start = Dimens.SCREEN_PADDING / 2)
+ )
+ }
+ },
+ text = {
+ Text(
+ text = stringResource(R.string.confirm_dialog_summar),
+ color =
+ colorResource(
+ id = foundation.e.elib.R.color.e_secondary_text_color
+ ),
+ maxLines = Int.MAX_VALUE,
+ overflow = TextOverflow.Clip,
+ )
+ },
+ confirmButton = {
+ Text(
+ modifier =
+ Modifier.clickable {
+ showLoginDialog = false
+ val launchIntent: Intent? =
+ packageManager.getLaunchIntentForPackage(
+ Constants.APP_LOUNGE_PKG
+ )
+ if (launchIntent != null) {
+ launchIntent.putExtra(
+ Constants.REQUEST_GPLAY_LOGIN,
+ true
+ )
+ launchIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
+ startActivity(launchIntent)
+ }
+ }
+ .padding(start = Dimens.SCREEN_PADDING / 2),
+ color = colorResource(id = foundation.e.elib.R.color.e_accent),
+ text = stringResource(R.string.launch_applounge).uppercase(),
+ fontSize = 14.sp
+ )
+ },
+ dismissButton = {
+ Text(
+ modifier =
+ Modifier.clickable {
+ showLoginDialog = false
+ mainUI.blockAllUserApps(true)
+ },
+ color = colorResource(id = foundation.e.elib.R.color.e_accent),
+ text = stringResource(R.string.discard).uppercase(),
+ fontSize = 14.sp
+ )
+ },
+ shape = RoundedCornerShape(4.dp)
+ )
+ }
+ }
+
+ ActivateAdminSummary(true)
+
+ ToggleWithText(
+ text = stringResource(R.string.activate_parental_control),
+ isChecked = isAdminActive(),
+ fontWeight = FontWeight.Medium,
+ onCheckedChange = {
+ if (it) {
+ setAdmin(true)
+ disableFMDAccess()
+ } else {
+ mainScreen(page = Pages.AskPasswordForAdminRemoval)
+ }
+ },
+ )
+
+ SelectAge(
+ onRadioClick = { mainScreen(page = Pages.AskPasswordForAgeReset) },
+ onNextClick = {}
+ )
+
+ Box(modifier = Modifier.fillMaxSize()) {
+ Column(
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Row(
+ modifier =
+ Modifier.fillMaxWidth()
+ .padding(vertical = Dimens.SCREEN_PADDING / 2)
+ .clip(RoundedCornerShape(16.dp))
+ .background(
+ colorResource(foundation.e.elib.R.color.e_background_overlay)
+ )
+ .padding(14.dp),
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ val allowedScreenTime = PrefsUtils.getAllowedScreenTime()
+ val allowedScreenTimeButton: String
+ val line1: String = stringResource(R.string.allowed_screen_time_allowed)
+ val line2: String
+
+ if (allowedScreenTime == ScreenTimes.DISABLE.value) {
+ allowedScreenTimeButton =
+ stringResource(R.string.allowed_screen_time_set).uppercase()
+ line2 = stringResource(R.string.allowed_screen_time_not_set)
+ } else {
+ allowedScreenTimeButton =
+ stringResource(R.string.allowed_screen_time_change).uppercase()
+ line2 =
+ when (allowedScreenTime) {
+ ScreenTimes.NINE_HOURS.value ->
+ stringResource(R.string.slider_9)
+ ScreenTimes.ONE_HOUR.value,
+ ScreenTimes.ZERO_HOUR.value ->
+ "$allowedScreenTime ${stringResource(R.string.allowed_screen_time_unit)}"
+ else ->
+ "$allowedScreenTime ${stringResource(R.string.allowed_screen_time_unit_s)}"
+ }
+ }
+
+ Column(modifier = Modifier.weight(1f)) {
+ Text(
+ text = line1,
+ style = MaterialTheme.typography.bodyMedium,
+ color =
+ colorResource(foundation.e.elib.R.color.e_primary_text_color),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ Text(
+ text = line2,
+ style =
+ MaterialTheme.typography.bodyMedium.copy(
+ fontWeight = FontWeight.Bold
+ ),
+ color =
+ colorResource(foundation.e.elib.R.color.e_primary_text_color),
+ maxLines = 1,
+ overflow = TextOverflow.Ellipsis
+ )
+ }
+
+ Spacer(modifier = Modifier.width(Dimens.SCREEN_PADDING / 2))
+
+ Text(
+ text = allowedScreenTimeButton,
+ color = colorResource(foundation.e.elib.R.color.e_accent),
+ fontSize = 14.sp,
+ modifier =
+ Modifier.clickable(
+ onClick = {
+ mainScreen(page = Pages.AskPasswordForScreenTimeReset)
+ }
+ )
+ .wrapContentWidth(Alignment.End),
+ maxLines = 2
+ )
+ }
+
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Text(
+ text = stringResource(R.string.manage_app_use).uppercase(),
+ color = colorResource(foundation.e.elib.R.color.e_accent),
+ modifier =
+ Modifier.padding(top = Dimens.SCREEN_PADDING)
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(16.dp))
+ .background(
+ colorResource(
+ foundation.e.elib.R.color.e_background_overlay
+ )
+ )
+ .padding(14.dp)
+ .clickable { mainScreen(page = Pages.AskPasswordForAppsReset) }
+ .wrapContentWidth(Alignment.CenterHorizontally),
+ fontSize = 14.sp,
+ )
+
+ Text(
+ text = stringResource(R.string.change_my_security_code).uppercase(),
+ color = colorResource(foundation.e.elib.R.color.e_accent),
+ modifier =
+ Modifier.padding(top = Dimens.SCREEN_PADDING)
+ .fillMaxWidth()
+ .clip(RoundedCornerShape(16.dp))
+ .background(
+ colorResource(
+ foundation.e.elib.R.color.e_background_overlay
+ )
+ )
+ .padding(14.dp)
+ .clickable {
+ mainScreen(page = Pages.AskPasswordForPasswordReset)
+ }
+ .wrapContentWidth(Alignment.CenterHorizontally),
+ fontSize = 14.sp,
+ )
+ }
+ }
+ OnDebugBuild()
+ }
+ }
+ }
+
+ @Composable
+ private fun FindMyDevice() {
+
+ var statusFMD = false
+ val uri = KEY_FIND_MY_DEVICE.toUri()
+ val cursor = this.contentResolver.query(uri, null, null, null, null)
+ cursor?.use {
+ if (it.moveToFirst()) {
+ val statusValue = it.getInt(it.getColumnIndexOrThrow(KEY_FIND_MY_DEVICE_STATUS))
+ statusFMD = statusValue == 1
+ }
+ }
+
+ if (
+ !statusFMD &&
+ (getFMDStatus() == KEY_FMD_STATUS_NONE ||
+ getFMDStatus() == KEY_FMD_STATUS_ENROLLING)
+ ) {
+
+ var dialogDisplayed by remember { mutableStateOf(false) }
+ val onDismissAction = {
+ setFMDStatus(KEY_FMD_STATUS_DONE)
+ dialogDisplayed = false
+ }
+ if (dialogDisplayed) {
AlertDialog(
- onDismissRequest = {
- showLoginDialog = false
- mainUI.blockAllUserApps(true)
- },
+ onDismissRequest = { onDismissAction() },
title = {
Row(
verticalAlignment = Alignment.CenterVertically,
) {
Icon(
- imageVector = Icons.Default.Info,
- contentDescription = "Info icon",
- tint = Color.Gray
+ painter = painterResource(id = R.drawable.find_my_device),
+ contentDescription = "Phone icon",
+ tint = colorResource(id = foundation.e.elib.R.color.e_accent)
)
Text(
- text = stringResource(R.string.login_required),
+ text = stringResource(R.string.fmd_title),
fontSize = 20.sp,
modifier = Modifier.padding(start = Dimens.SCREEN_PADDING / 2)
)
@@ -690,7 +1082,7 @@ class MainActivity : ComponentActivity() {
},
text = {
Text(
- text = stringResource(R.string.confirm_dialog_summar),
+ text = stringResource(R.string.fmd_description),
color =
colorResource(
id = foundation.e.elib.R.color.e_secondary_text_color
@@ -703,75 +1095,36 @@ class MainActivity : ComponentActivity() {
Text(
modifier =
Modifier.clickable {
- showLoginDialog = false
- val launchIntent: Intent? =
- packageManager.getLaunchIntentForPackage(
- Constants.APP_LOUNGE_PKG
- )
- if (launchIntent != null) {
- launchIntent.putExtra(
- Constants.REQUEST_GPLAY_LOGIN,
- true
- )
- launchIntent.flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
- startActivity(launchIntent)
- }
+ onDismissAction()
+ val intent =
+ Intent().apply {
+ component =
+ ComponentName(
+ Constants.APP_FIND_MY_DEVICE_PKG,
+ Constants.APP_FIND_MY_DEVICE_ACT
+ )
+ }
+ this@MainActivity.startActivity(intent)
}
.padding(start = Dimens.SCREEN_PADDING / 2),
color = colorResource(id = foundation.e.elib.R.color.e_accent),
- text = stringResource(R.string.launch_applounge).uppercase(),
+ text = stringResource(R.string.fmd_button_ok).uppercase(),
fontSize = 14.sp
)
},
dismissButton = {
Text(
- modifier =
- Modifier.clickable {
- showLoginDialog = false
- mainUI.blockAllUserApps(true)
- },
+ modifier = Modifier.clickable { onDismissAction() },
color = colorResource(id = foundation.e.elib.R.color.e_accent),
- text = stringResource(R.string.discard).uppercase(),
+ text = stringResource(R.string.fmd_button_cancel).uppercase(),
fontSize = 14.sp
)
},
shape = RoundedCornerShape(4.dp)
)
+ LaunchedEffect(Unit) { setFMDStatus(KEY_FMD_STATUS_ENROLLING) }
}
-
- ActivateAdminSummary()
-
- ToggleWithText(
- text = stringResource(R.string.activate_parental_control),
- isChecked = isAdminActive(),
- fontWeight = FontWeight.Medium,
- onCheckedChange = {
- if (it) {
- setAdmin(true)
- } else {
- mainScreen(page = Pages.AskPasswordForAdminRemoval)
- }
- },
- )
-
- SelectAge(
- onRadioClick = { mainScreen(page = Pages.AskPasswordForAgeReset) },
- onNextClick = {}
- )
-
- Box(modifier = Modifier.fillMaxSize(), contentAlignment = Alignment.Center) {
- Text(
- text = stringResource(R.string.change_my_security_code).uppercase(),
- color = colorResource(foundation.e.elib.R.color.e_accent),
- modifier =
- Modifier.clickable(
- onClick = { mainScreen(page = Pages.AskPasswordForPasswordReset) }
- ),
- fontWeight = FontWeight.Medium,
- )
-
- OnDebugBuild()
- }
+ SideEffect { dialogDisplayed = true }
}
}
@@ -832,11 +1185,125 @@ class MainActivity : ComponentActivity() {
SelectAge(
onRadioClick = {},
- onNextClick = { mainScreen(page = Pages.AuthenticationTypeSelectionView) }
+ onNextClick = { mainScreen(page = Pages.SelectAllowedScreenTime) }
+ )
+ }
+ }
+ /////////////////////////////////////////////////////////////////////////////
+ @Composable
+ fun SelectAllowedAppsPage() {
+ val context = LocalContext.current
+ fun onBackPress() {
+ if (SystemUtils.isSetupFinished(mActivity)) {
+ onStartUp()
+ } else {
+ onExitApp()
+ }
+ }
+
+ BackHandler(onBack = { onBackPress() })
+
+ CustomTopAppBar(
+ title = stringResource(R.string.allowed_apps_caption),
+ onClick = { onBackPress() }
+ )
+
+ Column(
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(start = Dimens.SCREEN_PADDING, end = Dimens.SCREEN_PADDING),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top
+ ) {
+ SelectAllowedApps(
+ onNextClick = {
+ if (DeviceAdmin().isAdminActive(context)) {
+ MainUI(mActivity).blockBlackListedApps()
+ mainScreen(page = Pages.ActivateAdmin)
+ } else {
+ mainScreen(page = Pages.AuthenticationTypeSelectionView)
+ }
+ }
)
}
}
+ private fun performSelectedAppManagement() {}
+
+ private fun onSetAllowedApps() {}
+
+ @Composable
+ fun SelectTypeAppManagementPage() {
+ fun onBackPress() {
+ selectedTypeAppManagement = null
+ if (SystemUtils.isSetupFinished(mActivity)) {
+ onStartUp()
+ } else {
+ onExitApp()
+ }
+ }
+
+ BackHandler(onBack = { onBackPress() })
+
+ CustomTopAppBar(
+ title = stringResource(R.string.manage_app_use),
+ onClick = { onBackPress() }
+ )
+
+ Column(
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(start = Dimens.SCREEN_PADDING, end = Dimens.SCREEN_PADDING),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top
+ ) {
+ SelectManageTypeApp(
+ onRadioClick = { PrefsUtils.setTypeAppManagement(selectedTypeAppManagement) },
+ onNextClick = { mainScreen(page = Pages.SelectAllowedApps) }
+ )
+ }
+ }
+
+ private fun onSetTypeAppManagement() {
+ if (selectedTypeAppManagement == null) return
+ with(PrefsUtils.getEdit()) {
+ putInt(Constants.PREF_TYPE_APP_MANAGEMENT, selectedTypeAppManagement!!.ordinal)
+ apply()
+ }
+ }
+
+ @Composable
+ fun SelectAllowedScreenTimePage() {
+ fun onBackPress() {
+ if (isAdminActive()) mainScreen(page = Pages.ActivateAdmin)
+ else mainScreen(page = Pages.SelectAge)
+ }
+
+ BackHandler(onBack = { onBackPress() })
+
+ CustomTopAppBar(
+ title = stringResource(R.string.allowed_screen_time),
+ onClick = { onBackPress() }
+ )
+
+ Column(
+ modifier =
+ Modifier.fillMaxSize()
+ .padding(start = Dimens.SCREEN_PADDING, end = Dimens.SCREEN_PADDING),
+ horizontalAlignment = Alignment.Start,
+ verticalArrangement = Arrangement.Top
+ ) {
+ SelectAllowedScreenTime({ mainScreen(page = Pages.SelectTypeAppManagement) })
+ }
+ }
+
+ private fun onSetAllowedScreenTime() {
+ with(PrefsUtils.getEdit()) {
+ putInt(Constants.PREF_ALLOWED_SCREEN_TIME, selectedAllowedScreenTime)
+ apply()
+ }
+ }
+
@Composable
fun NotMainUser() {
BackHandler(onBack = { onExitApp() })
@@ -860,22 +1327,37 @@ class MainActivity : ComponentActivity() {
@Suppress("DEPRECATION")
private fun setAdmin(activate: Boolean) {
+ val component = dA.getAdminName(mActivity)
if (activate) {
- SystemUtils.safeSetProp("persist.sys.mdm_active", "1")
+ SystemUtils.safeSetProp(Constants.PROP_MDM_SET_PACKAGE, component.flattenToString())
+ SystemUtils.safeSetProp(Constants.PROP_MDM_ACTIVATE, "1")
} else {
- SystemUtils.safeSetProp("persist.sys.mdm_active", "0")
+ SystemUtils.safeSetProp(Constants.PROP_MDM_SET_PACKAGE, "")
+ SystemUtils.safeSetProp(Constants.PROP_MDM_ACTIVATE, "0")
val mainUI = MainUI(mActivity)
mainUI.blockAllUserApps(false)
+ mainUI.blockApps(false)
mainUI.removePrivateDns()
- val devicePolicyManager: DevicePolicyManager = dA.getDevicePolicyManager(mActivity)
+ mainUI.manageFmD(false)
+ mainUI.removeDateTimeRestriction()
+ val devicePolicyManager = dA.getDevicePolicyManager(mActivity)
if (dA.isDeviceOwnerApp(mActivity)) {
devicePolicyManager.clearDeviceOwnerApp(mActivity.packageName)
} else if (dA.isProfileOwner(mActivity)) {
- devicePolicyManager.clearProfileOwner(
- ComponentName(mActivity, DeviceAdmin::class.java)
- )
+ devicePolicyManager.clearProfileOwner(component)
}
}
+ ParentalControlStatusList.setParentalControlStatus(
+ ParentalControlStatus(Date(System.currentTimeMillis()), activate)
+ )
+ PrefsUtils.saveParentalControlStatusList(
+ ParentalControlStatusList.getParentalControlStatus()
+ )
+ }
+
+ // Allows to navigate to another screen from external component
+ fun navigateScreen(page: Pages) {
+ mainScreen(page)
}
private fun mainScreen(page: Pages) {
@@ -884,14 +1366,16 @@ class MainActivity : ComponentActivity() {
window.statusBarColor = MaterialTheme.colorScheme.background.toArgb()
window.navigationBarColor = MaterialTheme.colorScheme.background.toArgb()
Surface(color = MaterialTheme.colorScheme.background) {
- val configuration = LocalConfiguration.current
- val isLandscape =
- configuration.orientation == Configuration.ORIENTATION_LANDSCAPE
-
Column(
modifier =
Modifier.fillMaxSize().let {
- if (isLandscape) it.verticalScroll(rememberScrollState()) else it
+ if (page == Pages.SelectAllowedApps) {
+ // this page manage its own scrolling
+ it
+ } else {
+ // All other pages whatever their orientation can scroll
+ it.verticalScroll(rememberScrollState())
+ }
},
horizontalAlignment = Alignment.Start,
verticalArrangement = Arrangement.Top
@@ -903,7 +1387,7 @@ class MainActivity : ComponentActivity() {
if (isAdminActive()) {
mainScreen(page = Pages.ActivateAdmin)
} else {
- mainScreen(page = Pages.SelectAge)
+ mainScreen(page = Pages.SelectAllowedScreenTime)
}
},
onSelection = { mainScreen(page = Pages.SetupPinPassword) }
@@ -927,8 +1411,12 @@ class MainActivity : ComponentActivity() {
Pages.AskPasswordForAdminRemoval -> {
AskPassword(
onPasswordMatch = {
- PrefsUtils.clearAll()
+ PrefsUtils.clearAllExcept(
+ Constants.PARENTAL_CONTROL_STATUS_LIST
+ )
selectedAge = null
+ selectedAllowedScreenTime = 0
+ selectedTypeAppManagement = null
setAdmin(false)
onStartUp()
},
@@ -943,6 +1431,18 @@ class MainActivity : ComponentActivity() {
onBackPressed = { onStartUp() }
)
}
+ Pages.AskPasswordForScreenTimeReset -> {
+ AskPassword(
+ onPasswordMatch = {
+ mainScreen(page = Pages.SelectAllowedScreenTime)
+ },
+ onPasswordMisMatch = {
+ selectedAllowedScreenTime =
+ PrefsUtils.getAllowedScreenTime()
+ },
+ onBackPressed = { onStartUp() }
+ )
+ }
Pages.AskPasswordForAgeReset -> {
AskPassword(
onPasswordMatch = {
@@ -950,7 +1450,15 @@ class MainActivity : ComponentActivity() {
MainUI(mActivity).blockBlackListedApps()
onStartUp()
},
- onPasswordMisMatch = { selectedAge = PrefsUtils.getAge() },
+ onBackPressed = { onStartUp() }
+ )
+ }
+ Pages.AskPasswordForAppsReset -> {
+ AskPassword(
+ onPasswordMatch = {
+ mainScreen(page = Pages.SelectTypeAppManagement)
+ },
+ onPasswordMisMatch = {},
onBackPressed = { onStartUp() }
)
}
@@ -962,12 +1470,49 @@ class MainActivity : ComponentActivity() {
onBackPressed = { onStartUp() }
)
}
+ Pages.AskPasswordForAppInstallation -> {
+ AskPassword(
+ onPasswordMatch = {
+ val resultIntent =
+ Intent().apply {
+ putExtra("authentication_result", true)
+ }
+ setResult(RESULT_OK, resultIntent)
+ onExitApp(false)
+ },
+ onPasswordMisMatch = {
+ val resultIntent =
+ Intent().apply {
+ putExtra("authentication_result", false)
+ }
+ setResult(RESULT_CANCELED, resultIntent)
+ onExitApp(false)
+ },
+ onBackPressed = {
+ val resultIntent =
+ Intent().apply {
+ putExtra("authentication_result", false)
+ }
+ setResult(RESULT_CANCELED, resultIntent)
+ onExitApp(false)
+ }
+ )
+ }
Pages.SelectAge -> {
SelectAgePage()
}
Pages.NotMainUser -> {
NotMainUser()
}
+ Pages.SelectAllowedApps -> {
+ SelectAllowedAppsPage()
+ }
+ Pages.SelectTypeAppManagement -> {
+ SelectTypeAppManagementPage()
+ }
+ Pages.SelectAllowedScreenTime -> {
+ SelectAllowedScreenTimePage()
+ }
}
}
}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/OverlayService.kt b/app/src/main/java/foundation/e/parentalcontrol/OverlayService.kt
new file mode 100644
index 0000000000000000000000000000000000000000..c52ba977140dbeee4e807a1567d2ad21fbc9e128
--- /dev/null
+++ b/app/src/main/java/foundation/e/parentalcontrol/OverlayService.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2025 MURENA SAS
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ *
+ */
+package foundation.e.parentalcontrol
+
+import android.app.Service
+import android.content.Intent
+import android.graphics.PixelFormat
+import android.os.IBinder
+import android.util.Log
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.widget.Button
+
+class OverlayService : Service() {
+
+ private lateinit var windowManager: WindowManager
+ private lateinit var overlayView: View
+
+ override fun onCreate() {
+ super.onCreate()
+ Log.d("popup", "in create")
+ windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
+
+ val inflater = getSystemService(LAYOUT_INFLATER_SERVICE) as LayoutInflater
+ overlayView = inflater.inflate(R.layout.overlay_popup, null)
+
+ val params =
+ WindowManager.LayoutParams(
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.WRAP_CONTENT,
+ WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+ WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+ WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN,
+ PixelFormat.TRANSLUCENT
+ )
+
+ params.gravity = Gravity.CENTER
+
+ windowManager.addView(overlayView, params)
+
+ val closeButton = overlayView.findViewById