最新消息:雨落星辰是一个专注网站SEO优化、网站SEO诊断、搜索引擎研究、网络营销推广、网站策划运营及站长类的自媒体原创博客

kotlin - How to show an overlay on top of other apps in Flutter (Lock App)? - Stack Overflow

programmeradmin3浏览0评论

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,
    );
  }


}
发布评论

评论列表(0)

  1. 暂无评论