diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d316cc46058b8a6993a1961440c712162e15317f..538a16baf28176fb06995cb730827915e356f967 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -55,6 +55,16 @@
+
+
+
+
= allowedScreenTime - WHEN_ACTIVATE_POPUP_IN_MS) {
if (!popupHasAlreadyBeenActive) {
sendPopup(context)
- popupHasAlreadyBeenActive
}
- stats.activeAlarm(context, (allowedScreenTime - res), popupHasAlreadyBeenActive)
+ stats.activeAlarm(context, (allowedScreenTime - res), true)
} else if (res < allowedScreenTime) {
blockApp(false, context)
stats.activeAlarm(
diff --git a/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt b/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt
index 328a816a4409b8ff507ba407673e90a1e3105b80..6ff44a8a0cda231ee03c508a111899f80186b5b9 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/DeviceAdmin.kt
@@ -28,6 +28,7 @@ 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.AlarmUtils
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
@@ -69,6 +70,8 @@ class DeviceAdmin : DeviceAdminReceiver() {
PrefsUtils.init(context)
val stats = UsageStatsManager()
stats.activeAlarm(context, 0, false)
+
+ AlarmUtils.scheduleMidnightTrigger(context)
PrefsUtils.restoreAllowedAppList(context)
}
}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt b/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt
index 8b8bad5ab198b143bde67a013be84e6a24a5e968..9d9579f6c5b73d1a073f7591aed6e0d36bad5462 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/MainActivity.kt
@@ -30,6 +30,7 @@ import android.os.Handler
import android.os.Looper
import android.os.UserManager
import android.provider.Settings
+import android.util.Log
import android.widget.Toast
import androidx.activity.ComponentActivity
import androidx.activity.compose.BackHandler
@@ -149,6 +150,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
class MainActivity : ComponentActivity() {
+ private val TAG = "MainActivity"
private var mActivity: ComponentActivity = this
private val dA: DeviceAdmin = DeviceAdmin()
@@ -164,11 +166,12 @@ class MainActivity : ComponentActivity() {
private fun onStartUp() {
+ val stats = UsageStatsManager()
+ val totalUsageMillis = stats.showTime(mActivity)
+ val totalUsageMinutes = totalUsageMillis.milliseconds.toDouble(DurationUnit.MINUTES)
+
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",
@@ -176,6 +179,8 @@ class MainActivity : ComponentActivity() {
)
.show()
}
+ } else {
+ Log.d(TAG, "Usage: ${totalUsageMinutes.roundToInt()} min")
}
if (intent?.action == KEY_PARENTAL_CONTROL_AUTHENTICATE) {
diff --git a/app/src/main/java/foundation/e/parentalcontrol/MidnightTriggerReceiver.kt b/app/src/main/java/foundation/e/parentalcontrol/MidnightTriggerReceiver.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a362b1389684325fce93f084f2a70f91a2b33b99
--- /dev/null
+++ b/app/src/main/java/foundation/e/parentalcontrol/MidnightTriggerReceiver.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import foundation.e.parentalcontrol.utils.AlarmUtils
+
+class MidnightTriggerReceiver : BroadcastReceiver() {
+ override fun onReceive(context: Context, intent: Intent) {
+ val stealthIntent =
+ Intent(context, StealthActivity::class.java).apply {
+ addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ }
+ context.startActivity(stealthIntent)
+
+ AlarmUtils.scheduleMidnightTrigger(context)
+ }
+}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/StealthActivity.kt b/app/src/main/java/foundation/e/parentalcontrol/StealthActivity.kt
new file mode 100644
index 0000000000000000000000000000000000000000..fccb985925fab4df19abbdb657f0083da419b7bd
--- /dev/null
+++ b/app/src/main/java/foundation/e/parentalcontrol/StealthActivity.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.Activity
+import android.os.Bundle
+
+/*
+ Stealth Activity
+ Briefly displays something invisible at midnight to handle the special case of an activity (like video)
+ without user interaction.
+
+ In order to have an event at this date in UsageStatsManager so as to be able to count down the times correctly.
+*/
+class StealthActivity : Activity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ finish()
+ }
+}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/UsageStatsManager.kt b/app/src/main/java/foundation/e/parentalcontrol/UsageStatsManager.kt
index fb8e6287a6ec0d1d0cafd21df9a3863587af5067..da9d39f0186cb109cc713ee87a557374da6c54b7 100644
--- a/app/src/main/java/foundation/e/parentalcontrol/UsageStatsManager.kt
+++ b/app/src/main/java/foundation/e/parentalcontrol/UsageStatsManager.kt
@@ -172,7 +172,7 @@ class UsageStatsManager {
if (statusList.isEmpty()) {
return false
}
- val lastStatus = statusList.get(statusList.lastIndex)
+ val lastStatus = statusList.last()
if (!lastStatus.activated) {
return false
}
diff --git a/app/src/main/java/foundation/e/parentalcontrol/utils/AlarmUtils.kt b/app/src/main/java/foundation/e/parentalcontrol/utils/AlarmUtils.kt
new file mode 100644
index 0000000000000000000000000000000000000000..a5767bad628e1408b832cfe5807f349af512fb31
--- /dev/null
+++ b/app/src/main/java/foundation/e/parentalcontrol/utils/AlarmUtils.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.utils
+
+import android.app.AlarmManager
+import android.app.PendingIntent
+import android.content.Context
+import android.content.Intent
+import foundation.e.parentalcontrol.MidnightTriggerReceiver
+import java.util.Calendar
+
+object AlarmUtils {
+ fun scheduleMidnightTrigger(context: Context) {
+ val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
+ val intent = Intent(context, MidnightTriggerReceiver::class.java)
+ val pendingIntent =
+ PendingIntent.getBroadcast(
+ context,
+ 0,
+ intent,
+ PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
+ )
+
+ val calendar =
+ Calendar.getInstance().apply {
+ set(Calendar.HOUR_OF_DAY, 0)
+ set(Calendar.MINUTE, 1)
+ set(Calendar.SECOND, 0)
+ set(Calendar.MILLISECOND, 0)
+ if (before(Calendar.getInstance())) add(Calendar.DAY_OF_MONTH, 1)
+ }
+
+ alarmManager.setExactAndAllowWhileIdle(
+ AlarmManager.RTC_WAKEUP,
+ calendar.timeInMillis,
+ pendingIntent
+ )
+ }
+}