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 + ) + } +}