In today’s fast-paced digital world, we spend a significant amount of time on our smartphones. While technology makes life easier, excessive screen time can negatively impact our productivity, focus, and mental health.
A Digital Wellbeing app helps users monitor and manage their daily screen usage, enabling them to build healthier digital habits and maintain a better work-life balance.
Manifest File
<uses-permission android:name="android.permission.PACKAGE_USAGE_STATS"
tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
tools:ignore="PackageVisibilityPolicy,QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<application
android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules"
android:fullBackupContent="@xml/backup_rules"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.DigitWellBeing">
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name=".MyAccessibilityService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_config"/>
</service>
</application>
Model Class
data class AppUsage(
val appName: String,
val packageName: String,
val usageTime: Long,
val category: String
)
Main Activity
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
private lateinit var adapter: AppUsageAdapter
val categories = listOf(
"All Categories",
"No Specified",
"Education/Business",
"Entertainment",
"Family",
"Game",
"Health/Fitness",
"Social Networking",
"System Apps",
"Utility"
)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
ViewCompat.setOnApplyWindowInsetsListener(findViewById(R.id.main)) { v, insets ->
val systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars())
v.setPadding(systemBars.left, systemBars.top, systemBars.right, systemBars.bottom)
insets
}
}
private fun initView() {
val spinnerAdapter = ArrayAdapter(
this,
android.R.layout.simple_spinner_item,
categories
)
spinnerAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
binding.spinnerCategories.adapter = spinnerAdapter
adapter = AppUsageAdapter(emptyList(), { itemData ->
if (Settings.canDrawOverlays(this) && isAccessibilityServiceEnabled(this)) {
showTimerDialog(itemData)
} else {
if (!Settings.canDrawOverlays(this)) {
val intent = Intent(
Settings.ACTION_MANAGE_OVERLAY_PERMISSION,
Uri.parse("package:$packageName")
)
startActivity(intent)
}
if (!isAccessibilityServiceEnabled(this)) {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
}
})
binding.recyclerApps.layoutManager = LinearLayoutManager(this)
binding.recyclerApps.adapter = adapter
val calendar = Calendar.getInstance()
calendar.set(Calendar.HOUR_OF_DAY, 0)
calendar.set(Calendar.MINUTE, 0)
calendar.set(Calendar.SECOND, 0)
val startTime = calendar.timeInMillis
val endTime = System.currentTimeMillis()
val usageStatsManager = getSystemService(USAGE_STATS_SERVICE) as UsageStatsManager
val stats = usageStatsManager.queryUsageStats(
UsageStatsManager.INTERVAL_DAILY,
startTime,
endTime
)
val pm = packageManager
val appMap = mutableMapOf<String, AppUsage>()
for (usage in stats) {
val totalTime = usage.totalTimeInForeground
if (totalTime > 0) {
try {
val packageName = usage.packageName
val appInfo = pm.getApplicationInfo(packageName, 0)
val appName = pm.getApplicationLabel(appInfo).toString()
val category = getCategoryName(appInfo.category, packageName)
if (appMap.containsKey(packageName)) {
val existing = appMap[packageName]!!
appMap[packageName] = existing.copy(
usageTime = existing.usageTime + totalTime
)
} else {
appMap[packageName] = AppUsage(
appName = appName,
packageName = packageName,
usageTime = totalTime,
category = category
)
}
} catch (e: Exception) {
e.printStackTrace()
}
}
}
val appList = appMap.values
.sortedByDescending { it.usageTime }
binding.spinnerCategories.onItemSelectedListener =
object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(
parent: AdapterView<*>,
view: View?,
position: Int,
id: Long
) {
val selectedCategory = categories[position]
val filteredList = filterApps(selectedCategory, appList)
adapter.updateList(filteredList)
}
override fun onNothingSelected(parent: AdapterView<*>) {}
}
val totalUsageTime = appList.sumOf { it.usageTime }
val formattedTime = formatTime(totalUsageTime)
binding.tvTotalTime.text = "Total Uses Time:- $formattedTime"
}
override fun onResume() {
super.onResume()
if (!isUsageAccessGranted(this)) {
startActivity(Intent(Settings.ACTION_USAGE_ACCESS_SETTINGS))
} else {
initView()
}
}
fun filterApps(category: String, list: List<AppUsage>): List<AppUsage> {
return if (category == "All Categories") {
list
} else {
list.filter { it.category == category }
}
}
fun isUsageAccessGranted(context: Context): Boolean {
val appOps = context.getSystemService(APP_OPS_SERVICE) as AppOpsManager
val mode = appOps.checkOpNoThrow(
AppOpsManager.OPSTR_GET_USAGE_STATS,
android.os.Process.myUid(),
context.packageName
)
return if (mode == AppOpsManager.MODE_DEFAULT) {
context.checkCallingOrSelfPermission(
android.Manifest.permission.PACKAGE_USAGE_STATS
) == PackageManager.PERMISSION_GRANTED
} else {
mode == AppOpsManager.MODE_ALLOWED
}
}
fun isAccessibilityServiceEnabled(context: Context): Boolean {
val expectedComponent = ComponentName(context, MyAccessibilityService::class.java)
val enabledServices = Settings.Secure.getString(
context.contentResolver,
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES
) ?: return false
return enabledServices.contains(expectedComponent.flattenToString())
}
fun formatTime(ms: Long): String {
val seconds = ms / 1000
val minutes = seconds / 60
val hours = minutes / 60
return "${hours}h ${minutes % 60}m"
}
fun getCategoryName(category: Int, packageName: String): String {
return when (category) {
ApplicationInfo.CATEGORY_GAME -> "Game"
ApplicationInfo.CATEGORY_SOCIAL -> "Social Networking"
ApplicationInfo.CATEGORY_PRODUCTIVITY -> "Education/Business"
ApplicationInfo.CATEGORY_AUDIO,
ApplicationInfo.CATEGORY_VIDEO -> "Entertainment"
ApplicationInfo.CATEGORY_NEWS -> "Education/Business"
ApplicationInfo.CATEGORY_MAPS -> "Utility"
ApplicationInfo.CATEGORY_ACCESSIBILITY -> "Utility"
ApplicationInfo.CATEGORY_IMAGE -> "Entertainment"
ApplicationInfo.CATEGORY_UNDEFINED -> {
if (isSystemApp(packageName)) "System Apps"
else "No Specified"
}
else -> "No Specified"
}
}
fun isSystemApp(packageName: String): Boolean {
return try {
val appInfo = packageManager.getApplicationInfo(packageName, 0)
(appInfo.flags and ApplicationInfo.FLAG_SYSTEM) != 0
} catch (e: Exception) {
false
}
}
fun showTimerDialog(app: AppUsage) {
val dialog = Dialog(this)
val binding = DialogTimerDesignBinding.inflate(LayoutInflater.from(this), null, false)
dialog.setContentView(binding.root)
binding.btnSet.setOnClickListener {
val minutes = binding.etMinutes.text.toString().toIntOrNull() ?: 0
if (minutes > 0) {
val endTime = System.currentTimeMillis() + minutes * 60 * 1000
val pref = getSharedPreferences("app_limits", MODE_PRIVATE)
pref.edit { putLong(app.packageName, endTime) }
Toast.makeText(this, "Timer Set", Toast.LENGTH_SHORT).show()
dialog.dismiss()
}
}
dialog.show()
}
}
Serviece Class
package com.example.digitwellbeing
import android.accessibilityservice.AccessibilityService
import android.graphics.PixelFormat
import android.os.Handler
import android.os.Looper
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.WindowManager
import android.view.accessibility.AccessibilityEvent
import android.widget.Button
class MyAccessibilityService : AccessibilityService() {
private var windowManager: WindowManager? = null
private var overlayView: View? = null
private var currentPackage: String? = null
private var isHandlerStarted = false
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
Log.d("ACCESS_SERVICE", "Event: ${event?.packageName}")
val pkg = event?.packageName?.toString() ?: return
currentPackage = pkg
// 🔥 Start handler here (ONLY ONCE)
if (!isHandlerStarted) {
isHandlerStarted = true
handler.post(runnable)
}
if (event?.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED ||
event?.eventType == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
currentPackage = event.packageName?.toString()
}
}
override fun onInterrupt() {
TODO("Not yet implemented")
}
override fun onServiceConnected() {
super.onServiceConnected()
Log.d("ACCESS_SERVICE", "Service Connected ✅")
handler.post(runnable)
}
private val handler = Handler(Looper.getMainLooper())
private val runnable = object : Runnable {
override fun run() {
currentPackage?.let { packageName ->
if (packageName == this@MyAccessibilityService.packageName) return
val pref = getSharedPreferences("app_limits", MODE_PRIVATE)
val endTime = pref.getLong(packageName, 0)
if (endTime > 0 && System.currentTimeMillis() > endTime) {
showOverlay()
}
}
handler.postDelayed(this, 1000) // every 1 sec
}
}
private fun showOverlay() {
Log.d("BLOCK_CHECK", "LIMIT EXCEEDED 🚫")
if (overlayView != null) return
windowManager = getSystemService(WINDOW_SERVICE) as WindowManager
val inflater = LayoutInflater.from(this)
overlayView = inflater.inflate(R.layout.overlay_block, null)
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.MATCH_PARENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
0,
PixelFormat.TRANSLUCENT
)
windowManager?.addView(overlayView, params)
overlayView?.findViewById<Button>(R.id.btnClose)?.setOnClickListener {
removeOverlay()
}
}
private fun removeOverlay() {
if (overlayView != null) {
windowManager?.removeView(overlayView)
overlayView = null
}
}
}
Resources (accessibility_config)
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityEventTypes="typeWindowStateChanged|typeWindowContentChanged"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault"
android:canRetrieveWindowContent="false"
android:description="@string/accessibility_desc"
android:notificationTimeout="100" />
<string name="accessibility_desc">
This app monitors app usage to block apps after time limit.
</string>
This app acts as a personal assistant that guides users toward mindful usage. It encourages breaks, limits unnecessary usage, and promotes a healthier lifestyle.Digital wellbeing is not about avoiding technology but using it wisely. With the help of this app, users can take control of their digital life and create a more balanced and productive routine.
Top comments (0)