I am developing a lock app using Flutter with Dart and Kotlin. My goal is to display an overlay on top of other apps, similar to how screen lock or app lock features work.
#this is my terminal output
√ Built build\app\outputs\flutter-apk\app-debug.apk
Installing build\app\outputs\flutter-apk\app-debug.apk...
I/flutter: HomeScreen - Error fetching app info: type 'bool' is not a subtype of type 'BuiltWith?' in type cast
I/flutter: ┌───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
I/flutter: │ #0 _HomeScreenState._checkUsageStatsPermission (package:free/screens/homepage.dart:82:14)
I/flutter: │ #1
I/flutter: │ ⛔ MissingPluginException: 'No implementation found for method checkUsageStatsPermission on channel usage_stats'. Ensure the platform-specific implementation is added.
I/flutter: └───────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
E/flutter: [ERROR:flutter/runtime/dart_vm_initializer(41)] Unhandled Exception: MissingPluginException(No implementation found for method checkUsageAccessPermission on channel usage_stats_channel)
E/flutter: [ERROR:flutter/runtime/dart_vm_initializer(41)] Unhandled Exception: type 'StatefulElement' is not a subtype of type 'List' in type cast
#Problem summary
"Flutter app is crashing with MissingPluginException
related to the usage_stats
plugin. Additionally, there's a type casting error: type 'bool' is not a subtype of type 'BuiltWith?'
. It seems like there might be an issue with the plugin setup or data types in the HomeScreen
widget."
My ForeGroundService.kt file code
package com.example.free
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import android.app.usage.UsageStatsManager
import android.app.usage.UsageEvents
import kotlinx.coroutines.*
import android.util.Log
class LockForegroundService : Service() {
private lateinit var usageStatsManager: UsageStatsManager
private lateinit var overlayHelper: OverlayHelper
private var job: Job? = null
override fun onCreate() {
super.onCreate()
usageStatsManager = getSystemService(Context.USAGE_STATS_SERVICE) as UsageStatsManager
overlayHelper = OverlayHelper(this)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
createNotificationChannel()
val notification = NotificationCompat.Builder(this, "foreground_notification_channel")
.setContentTitle("Foreground Service")
.setContentText("App lock service is running")
.setSmallIcon(R.mipmap.ic_launcher)
.build()
startForeground(1, notification)
job = GlobalScope.launch(Dispatchers.Default) {
while (isActive) {
checkAppLaunch()
delay(1000) // Check every 1 second
}
}
return START_STICKY
}
private fun createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
val serviceChannel = NotificationChannel(
"foreground_notification_channel",
"Foreground Service Channel",
NotificationManager.IMPORTANCE_DEFAULT
)
val manager = getSystemService(NotificationManager::class.java)
manager.createNotificationChannel(serviceChannel)
}
}
private fun getForegroundApp(): String {
val endTime = System.currentTimeMillis()
val beginTime = endTime - 10 * 1000 // Check for the last 10 seconds
val usageEvents = usageStatsManager.queryEvents(beginTime, endTime)
val event = UsageEvents.Event()
var foregroundAppPackageName = "unknown"
var recentTime = 0L
while (usageEvents.hasNextEvent()) {
usageEvents.getNextEvent(event)
if (event.eventType == UsageEvents.Event.ACTIVITY_RESUMED) {
if (event.timeStamp > recentTime) {
recentTime = event.timeStamp
foregroundAppPackageName = event.packageName
}
}
}
Log.d("ForegroundService", "Foreground App Package: $foregroundAppPackageName")
return foregroundAppPackageName
}
private fun getSelectedApp(context: Context): List<String> {
val prefs = context.getSharedPreferences("FlutterSharedPreferences", Context.MODE_PRIVATE)
val lockedApps = prefs.getStringSet("flutter.locked_apps", emptySet()) // Corrected Key
return lockedApps?.toList() ?: emptyList()
}
private fun checkAppLaunch() {
Log.v("AppLockLog", "checkAppLaunch called")
val foregroundAppPackageName = getForegroundApp()
Log.v("AppLockLog", "Foreground App Package Name: $foregroundAppPackageName") // Modified log
val selectedApps = getSelectedApp(this)
Log.v("AppLockLog", "Selected Apps: $selectedApps")
if (selectedApps.contains(foregroundAppPackageName)) {
Log.v("AppLockLog", "App is locked, showing overlay for package: $foregroundAppPackageName") // Modified log
overlayHelper.showOverlay()
} else {
Log.v("AppLockLog", "App is not locked, removing overlay for package: $foregroundAppPackageName") // Modified log
overlayHelper.removeOverlay()
}
}
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onDestroy() {
job?.cancel()
overlayHelper.removeOverlay()
super.onDestroy()
}
}
#my overlayHelper.kt file
import android.content.Context
import android.content.Intent
import android.Uri
import android.os.Build
import android.provider.Settings
import android.view.WindowManager
import android.view.WindowManager.LayoutParams
import android.graphics.PixelFormat
import android.view.Gravity
import android.widget.TextView
import android.view.LayoutInflater
import android.widget.LinearLayout
import androidx.core.content.ContextCompat
import android.util.DisplayMetrics
import android.widget.Button
import android.view.View
class OverlayHelper(private val context: Context) {
private var windowManager: WindowManager? = null
private var overlayView: LinearLayout? = null
fun showOverlay() {
android.util.Log.d("OverlayDebug", "showOverlay function called") // Added log
if (overlayView != null) {
android.util.Log.d("OverlayDebug", "overlayView is not null, returning")
return
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && !Settings.canDrawOverlays(context)) {
android.util.Log.d("OverlayDebug", "Overlay permission not granted") // Added log
return
}
windowManager = context.getSystemService(Context.WINDOW_SERVICE) as WindowManager
val displayMetrics = DisplayMetrics()
windowManager?.defaultDisplay?.getMetrics(displayMetrics)
val screenWidth = displayMetrics.widthPixels
val screenHeight = displayMetrics.heightPixels
android.util.Log.d("OverlayDebug", "Overlay width: $screenWidth, height: $screenHeight")
overlayView = LinearLayout(context)
overlayView?.setBackgroundColor(ContextCompat.getColor(context, android.R.color.holo_blue_light))
val textView = TextView(context)
textView.text = "Overlay Window"
textView.setTextColor(ContextCompat.getColor(context, android.R.color.white))
overlayView?.addView(textView)
// Button to stop overlay
val stopButton = Button(context)
stopButton.text = "Stop Overlay"
stopButton.setTextColor(ContextCompat.getColor(context, android.R.color.black))
stopButton.setBackgroundColor(ContextCompat.getColor(context, android.R.color.white))
val layoutParamsButton = LinearLayout.LayoutParams(
LinearLayout.LayoutParams.WRAP_CONTENT,
LinearLayout.LayoutParams.WRAP_CONTENT
)
layoutParamsButton.gravity = Gravity.BOTTOM or Gravity.CENTER_HORIZONTAL
stopButton.layoutParams = layoutParamsButton
stopButton.setOnClickListener(View.OnClickListener {
removeOverlay()
})
overlayView?.addView(stopButton)
val params = LayoutParams(
screenWidth,
screenHeight,
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
} else {
WindowManager.LayoutParams.TYPE_SYSTEM_ALERT
},
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL,
PixelFormat.TRANSLUCENT
)
params.gravity = Gravity.CENTER
windowManager?.addView(overlayView, params)
}
fun removeOverlay() {
android.util.Log.d("OverlayDebug", "removeOverlay function called") // Added log
if (overlayView != null) {
windowManager?.removeView(overlayView)
overlayView = null
}
}
fun isOverlayShowing(): Boolean {
return overlayView != null
}
}
my homepage.dart file code
import 'package:free/main.dart'; // Import main.dart
import 'package:flutter/material.dart';
import 'package:free/permission/SystemAlertWindow.dart';
import 'package:free/screens/foregroundServiceHelper.dart';
import 'package:free/screens/time_picker.dart';
import 'package:free/widget/notificationRequest.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:free/screens/app_list.dart';
import 'package:installed_apps/installed_apps.dart';
import 'package:installed_apps/app_info.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:android_intent_plus/android_intent.dart';
import 'package:android_intent_plus/flag.dart';
import 'package:logger/logger.dart'; // Add this import
class HomeScreen extends StatefulWidget {
@override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
bool isFabVisible = true;
List<AppInfo> selectedApps = [];
bool isLoading = true;
// Duplicate declaration removed
final Logger logger = Logger(); // Initialize the logger
static const platform = const MethodChannel('usage_stats');
void _setFebVisible(bool isVisible) {
setState(() {
isFabVisible = isVisible;
});
}
Future<bool> _showPermissionDialog() async {
return await showDialog(
context: context,
builder: (context) => NotificationPermissionWidget(),
) ?? false; // Return false if dialog is dismissed
}
Future<void> requestUsageStatsPermission() async {
try {
bool hasPermission = await platform.invokeMethod('checkUsageStatsPermission');
if (!hasPermission) {
await platform.invokeMethod('requestUsageStatsPermission');
} else {
print("Usage Stats permission already granted.");
}
} on PlatformException catch (e) {
print("Failed to request Usage Stats permission: '${e.message}'.");
}
}
Future<void> _checkUsageStatsPermission() async {
try {
bool hasPermission = await platform.invokeMethod('checkUsageStatsPermission');
if (!hasPermission) {
requestUsageStatsPermission(); // Request permission if not granted
} else {
logger.i("Usage Stats permission already granted.");
}
} on PlatformException catch (e) {
logger.e("Failed to check Usage Stats permission: '${e.message}'.");
} on MissingPluginException catch (e) {
logger.e("MissingPluginException: '${e.message}'. Ensure the platform-specific implementation is added.");
}
}
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_showPermissionDialog();});
WidgetsBinding.instance.addPostFrameCallback((_) {
_checkUsageStatsPermission();
});
_loadSelectedApps();
selectedTimeVN.addListener(_timeListener); // Add listener
}
String _getDisplayText() {
return selectedTimeVN.value ?? "No time selection";
}
@override
void dispose() {
selectedTimeVN.removeListener(_timeListener); // Remove listener
super.dispose();
}
/// Update the displayed time
void _timeListener() {
setState(() {});
}
/// Loads selected apps from SharedPreferences
Future<void> _loadSelectedApps() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
List<String>? savedPackageNames = prefs.getStringList('locked_apps');
print("HomeScreen - Loaded package names from prefs: ${savedPackageNames}");
if (savedPackageNames != null) {
List<AppInfo> loadedApps = [];
for (String package in savedPackageNames) {
try {
final appInfo = await InstalledApps.getAppInfo(package, true as BuiltWith?); // Added the missing argument
if (appInfo != null) {
loadedApps.add(appInfo);
} else {
savedPackageNames.remove(package);
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setStringList('locked_apps', savedPackageNames);
}
} catch (e) {
print("HomeScreen - Error fetching app info: $e");
}
}
print(
"HomeScreen - Loaded apps: ${loadedApps.map((e) => e.packageName).toList()}");
setState(() {
selectedApps = loadedApps;
isLoading = false;
});
} else {
setState(() {
isLoading = false;
});
}
}
/// Saves selected apps to SharedPreferences
Future<void> _saveSelectedApps() async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
List<String> packageNames = selectedApps.map((app) => app.packageName).toList();
print("HomeScreen - Saving selected apps: ${packageNames}");
await prefs.setStringList('locked_apps', packageNames);
}
@override
Widget build(BuildContext context) {
bool isDarkMode = Theme.of(context).brightness == Brightness.dark;
return Scaffold(
appBar: AppBar(
title: Text("Freedom"),
),
body: Column(
children: [
NotificationPermissionWidget(),
SizedBox(height: 20),
Center(
child: Container(
padding: EdgeInsets.all(10),
decoration: BoxDecoration(
color: Theme.of(context).scaffoldBackgroundColor,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isDarkMode ? Colors.white : Colors.black,
width: 2,
),
boxShadow: [
BoxShadow(color: Colors.black26, blurRadius: 5),
],
),
child: isLoading
? CircularProgressIndicator()
: ValueListenableBuilder<String?>(
valueListenable: selectedTimeVN,
builder: (context, timeValue, child) {
return Text(
_getDisplayText(),
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
);
},
),
),
),
SizedBox(height: 20),
Expanded(
child: isLoading
? Center(child: CircularProgressIndicator())
: selectedApps.isEmpty
? Center(
child: Text(
'No apps selected',
style: TextStyle(fontSize: 16, color: Colors.red),
),
)
: GridView.builder(
padding: EdgeInsets.all(10),
gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent(
maxCrossAxisExtent: 100.0,
mainAxisSpacing: 10.0,
crossAxisSpacing: 10.0,
childAspectRatio: 0.9,
),
itemCount: selectedApps.length,
itemBuilder: (context, index) {
return Column(
children: [
SizedBox(
width: 55,
height: 55,
child: FittedBox(
child: CircleAvatar(
backgroundColor: Colors.transparent,
radius: 30,
child: selectedApps[index].icon != null
? SizedBox(
width: 55,
height: 55,
child: Image.memory(
selectedApps[index].icon!,
),
)
: Icon(Icons.apps, size: 30),
),
),
),
SizedBox(height: 5),
Text(
selectedApps[index].name,
style: TextStyle(fontSize: 12, color: Colors.white),
overflow: TextOverflow.ellipsis,
textAlign: TextAlign.center,
),
],
);
},
),
),
],
),
floatingActionButton: isFabVisible
? SpeedDial(
icon: Icons.lock_outline_rounded,
backgroundColor: Colors.blue,
elevation: 8.0,
children: [
SpeedDialChild(
child: const Icon(Icons.lock_outline_rounded),
label: 'Lock app',
labelStyle: TextStyle(fontSize: 18.0),
backgroundColor: Colors.blueAccent,
onTap: () async {
final bool? permission = await SystemAlertWindow.requestPermissions();
if (permission == true) {
SystemAlertWindow.showOverlay();
startForegroundService(context as List<AppInfo>, "Foreground Service Started");
}
},
),
],
)
: null,
);
}
}