Skip to content

A powerful Flutter plugin for comprehensive screen security features including screenshot and screen recording protection across Android and iOS platforms.

License

Notifications You must be signed in to change notification settings

shariaralphabyte/screen_secure

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

7 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Screen Secure πŸ”

pub package GitHub Platform Build Status

A powerful Flutter plugin for comprehensive screen security features including screenshot and screen recording protection across Android and iOS platforms.

✨ Features

πŸ” Cross-platform screen security

  • Android: Uses FLAG_SECURE to block screenshots and screen recordings
  • iOS: Detects screen recording and overlays a secure warning screen

⚑ Simple Integration

  • One-line initialization with customizable options
  • Dynamic enable/disable functionality at runtime
  • Real-time screen recording detection callbacks

🎯 Professional Grade

  • Comprehensive error handling with custom exceptions
  • Platform-specific optimizations
  • Extensive documentation and examples

πŸ“± Platform Support

Platform Screenshot Block Screen Record Block Real-time Detection Min Version
Android βœ… βœ… ❌ API 16+
iOS βœ… βœ… βœ… iOS 11.0+

πŸš€ Quick Start

Installation

Add this to your pubspec.yaml:

dependencies:
  screen_secure: ^1.0.3

Run:

flutter pub get

Basic Usage

import 'package:screen_secure/screen_secure.dart';

// Initialize with both features enabled
await ScreenSecure.init(
  screenshotBlock: true,
  screenRecordBlock: true,
);

That's it! Your app is now protected from screenshots and screen recordings.

πŸ“‹ Usage Examples

Complete Implementation

import 'package:flutter/material.dart';
import 'package:screen_secure/screen_secure.dart';

class MySecureApp extends StatefulWidget {
  @override
  _MySecureAppState createState() => _MySecureAppState();
}

class _MySecureAppState extends State<MySecureApp> {
  bool _isRecording = false;
  Map<String, dynamic> _securityStatus = {};

  @override
  void initState() {
    super.initState();
    _initializeScreenSecure();
    _setupScreenRecordingCallback();
  }

  Future<void> _initializeScreenSecure() async {
    try {
      // Initialize with custom settings
      await ScreenSecure.init(
        screenshotBlock: true,
        screenRecordBlock: true,
      );
      
      // Get current status
      final status = await ScreenSecure.getSecurityStatus();
      setState(() {
        _securityStatus = status;
      });
      
      print('Screen security initialized successfully');
    } on ScreenSecureException catch (e) {
      print('Failed to initialize screen security: ${e.message}');
    }
  }

  void _setupScreenRecordingCallback() {
    // Set up callback for real-time screen recording detection (iOS)
    ScreenSecure.setScreenRecordingCallback((isRecording) {
      setState(() {
        _isRecording = isRecording;
      });
      
      if (isRecording) {
        // Handle screen recording started
        _showRecordingAlert();
      } else {
        // Handle screen recording stopped
        print('Screen recording stopped');
      }
    });
  }

  void _showRecordingAlert() {
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (context) => AlertDialog(
        title: Row(
          children: [
            Icon(Icons.warning, color: Colors.red),
            SizedBox(width: 8),
            Text('Security Alert'),
          ],
        ),
        content: Text(
          'Screen recording has been detected. For security reasons, '
          'sensitive content may be hidden.',
        ),
        actions: [
          TextButton(
            onPressed: () => Navigator.of(context).pop(),
            child: Text('OK'),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Secure App'),
        backgroundColor: _isRecording ? Colors.red : Colors.blue,
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Security Status Card
            Card(
              elevation: 4,
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Security Status',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    SizedBox(height: 12),
                    _buildStatusRow(
                      'Screenshot Protection',
                      _securityStatus['screenshotBlocked'] ?? false,
                    ),
                    _buildStatusRow(
                      'Screen Record Protection',
                      _securityStatus['screenRecordBlocked'] ?? false,
                    ),
                    _buildStatusRow(
                      'Currently Recording',
                      _isRecording,
                      isWarning: _isRecording,
                    ),
                  ],
                ),
              ),
            ),
            
            SizedBox(height: 24),
            
            // Control Buttons
            Text(
              'Security Controls',
              style: TextStyle(
                fontSize: 18,
                fontWeight: FontWeight.bold,
              ),
            ),
            SizedBox(height: 16),
            
            ElevatedButton.icon(
              onPressed: _toggleScreenshotProtection,
              icon: Icon(Icons.screenshot_monitor),
              label: Text('Toggle Screenshot Protection'),
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(vertical: 12),
              ),
            ),
            
            SizedBox(height: 8),
            
            ElevatedButton.icon(
              onPressed: _toggleScreenRecordProtection,
              icon: Icon(Icons.videocam_off),
              label: Text('Toggle Screen Record Protection'),
              style: ElevatedButton.styleFrom(
                padding: EdgeInsets.symmetric(vertical: 12),
              ),
            ),
            
            SizedBox(height: 8),
            
            OutlinedButton.icon(
              onPressed: _checkSecurityStatus,
              icon: Icon(Icons.refresh),
              label: Text('Refresh Status'),
              style: OutlinedButton.styleFrom(
                padding: EdgeInsets.symmetric(vertical: 12),
              ),
            ),
            
            SizedBox(height: 24),
            
            // Sensitive Content Area
            Card(
              color: _isRecording ? Colors.red.shade50 : Colors.green.shade50,
              child: Padding(
                padding: EdgeInsets.all(16.0),
                child: Column(
                  children: [
                    Icon(
                      _isRecording ? Icons.visibility_off : Icons.lock,
                      size: 48,
                      color: _isRecording ? Colors.red : Colors.green,
                    ),
                    SizedBox(height: 8),
                    Text(
                      _isRecording 
                        ? 'Sensitive content hidden due to screen recording'
                        : 'Sensitive content visible - Screen is secure',
                      textAlign: TextAlign.center,
                      style: TextStyle(
                        fontSize: 16,
                        color: _isRecording ? Colors.red : Colors.green,
                        fontWeight: FontWeight.w500,
                      ),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  Widget _buildStatusRow(String label, bool isEnabled, {bool isWarning = false}) {
    return Padding(
      padding: EdgeInsets.symmetric(vertical: 4),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text(label),
          Row(
            children: [
              Icon(
                isEnabled ? Icons.check_circle : Icons.cancel,
                color: isWarning 
                  ? Colors.orange 
                  : (isEnabled ? Colors.green : Colors.grey),
                size: 20,
              ),
              SizedBox(width: 4),
              Text(
                isEnabled ? 'Active' : 'Inactive',
                style: TextStyle(
                  color: isWarning 
                    ? Colors.orange 
                    : (isEnabled ? Colors.green : Colors.grey),
                  fontWeight: FontWeight.w500,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Future<void> _toggleScreenshotProtection() async {
    try {
      final isEnabled = _securityStatus['screenshotBlocked'] ?? false;
      
      if (isEnabled) {
        await ScreenSecure.disableScreenshotBlock();
      } else {
        await ScreenSecure.enableScreenshotBlock();
      }
      
      _checkSecurityStatus();
    } on ScreenSecureException catch (e) {
      _showErrorSnackBar('Failed to toggle screenshot protection: ${e.message}');
    }
  }

  Future<void> _toggleScreenRecordProtection() async {
    try {
      final isEnabled = _securityStatus['screenRecordBlocked'] ?? false;
      
      if (isEnabled) {
        await ScreenSecure.disableScreenRecordBlock();
      } else {
        await ScreenSecure.enableScreenRecordBlock();
      }
      
      _checkSecurityStatus();
    } on ScreenSecureException catch (e) {
      _showErrorSnackBar('Failed to toggle screen record protection: ${e.message}');
    }
  }

  Future<void> _checkSecurityStatus() async {
    try {
      final status = await ScreenSecure.getSecurityStatus();
      setState(() {
        _securityStatus = status;
      });
    } on ScreenSecureException catch (e) {
      _showErrorSnackBar('Failed to get security status: ${e.message}');
    }
  }

  void _showErrorSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
      ),
    );
  }
}

Simple Usage Examples

Basic initialization:

// Enable both features
await ScreenSecure.init();

// Enable only screenshot protection
await ScreenSecure.init(
  screenshotBlock: true,
  screenRecordBlock: false,
);

Individual control:

// Enable screenshot protection
await ScreenSecure.enableScreenshotBlock();

// Disable screenshot protection
await ScreenSecure.disableScreenshotBlock();

// Enable screen recording protection
await ScreenSecure.enableScreenRecordBlock();

// Disable screen recording protection
await ScreenSecure.disableScreenRecordBlock();

Status monitoring:

// Get current security status
final status = await ScreenSecure.getSecurityStatus();
print('Screenshot blocked: ${status['screenshotBlocked']}');
print('Screen record blocked: ${status['screenRecordBlocked']}');
print('Platform: ${status['platform']}');

// Check if currently recording (iOS only)
final isRecording = await ScreenSecure.isScreenRecording();
print('Currently recording: $isRecording');

Real-time callbacks:

// Set up screen recording detection callback
ScreenSecure.setScreenRecordingCallback((isRecording) {
  if (isRecording) {
    print('Screen recording started!');
    // Hide sensitive content
    // Show warning to user
  } else {
    print('Screen recording stopped!');
    // Show sensitive content again
  }
});

πŸ“– API Reference

Core Methods

init()

Initialize screen security with options.

static Future<bool> init({
  bool screenshotBlock = true,
  bool screenRecordBlock = true,
}) async

Parameters:

  • screenshotBlock: Enable screenshot protection (default: true)
  • screenRecordBlock: Enable screen recording protection (default: true)

Returns: Future<bool> - Success status

Throws: ScreenSecureException on failure


enableScreenshotBlock()

Enable screenshot protection.

static Future<bool> enableScreenshotBlock() async

Returns: Future<bool> - Success status

Throws: ScreenSecureException on failure


disableScreenshotBlock()

Disable screenshot protection.

static Future<bool> disableScreenshotBlock() async

Returns: Future<bool> - Success status

Throws: ScreenSecureException on failure


enableScreenRecordBlock()

Enable screen recording protection.

static Future<bool> enableScreenRecordBlock() async

Returns: Future<bool> - Success status

Throws: ScreenSecureException on failure


disableScreenRecordBlock()

Disable screen recording protection.

static Future<bool> disableScreenRecordBlock() async

Returns: Future<bool> - Success status

Throws: ScreenSecureException on failure


isScreenRecording()

Check if screen recording is currently active (iOS only).

static Future<bool> isScreenRecording() async

Returns: Future<bool> - Recording status

Throws: ScreenSecureException on failure

Note: Always returns false on Android


getSecurityStatus()

Get current security status.

static Future<Map<String, dynamic>> getSecurityStatus() async

Returns: Future<Map<String, dynamic>> with keys:

  • screenshotBlocked: Boolean indicating screenshot protection status
  • screenRecordBlocked: Boolean indicating screen recording protection status
  • platform: String indicating platform ("android" or "ios")
  • isCurrentlyRecording: Boolean indicating current recording status (iOS only)

Throws: ScreenSecureException on failure


setScreenRecordingCallback()

Set up callback for real-time screen recording detection.

static void setScreenRecordingCallback(Function(bool isRecording) callback)

Parameters:

  • callback: Function called when screen recording status changes

Note: Only functional on iOS

Exception Handling

The plugin provides custom exception handling through ScreenSecureException:

try {
  await ScreenSecure.init();
} on ScreenSecureException catch (e) {
  print('Screen security error: ${e.message}');
} catch (e) {
  print('Unexpected error: $e');
}

πŸ”§ Platform-Specific Implementation

Android Implementation

The Android implementation uses WindowManager.LayoutParams.FLAG_SECURE to prevent:

  • Screenshots via system screenshot function
  • Screen recordings via system screen recording
  • Screenshots via accessibility services
  • Content visibility in recent apps overview
// Enable protection
activity?.window?.setFlags(
    WindowManager.LayoutParams.FLAG_SECURE,
    WindowManager.LayoutParams.FLAG_SECURE
)

// Disable protection
activity?.window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE)

Limitations:

  • No real-time detection of recording attempts
  • Protection applies to entire activity window

iOS Implementation

The iOS implementation provides:

  • Screen recording detection via UIScreen.capturedDidChangeNotification
  • Secure view overlay during recording
  • Real-time callback notifications
// Enable screen recording detection
NotificationCenter.default.addObserver(
    self,
    selector: #selector(screenCaptureChanged),
    name: UIScreen.capturedDidChangeNotification,
    object: nil
)

// Check recording status
let isRecording = UIScreen.main.isCaptured

Features:

  • Real-time screen recording detection
  • Customizable warning overlays
  • Callback-based notifications

🎯 Best Practices

1. Initialize Early

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return FutureBuilder(
      future: ScreenSecure.init(),
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          return MaterialApp(/* your app */);
        }
        return CircularProgressIndicator();
      },
    );
  }
}

2. Handle Errors Gracefully

Future<void> initSecurityWithFallback() async {
  try {
    await ScreenSecure.init();
  } on ScreenSecureException catch (e) {
    // Log error but continue app execution
    print('Screen security not available: ${e.message}');
    // Optionally show user notification
  }
}

3. Responsive UI Based on Recording Status

Widget buildSensitiveContent() {
  return StreamBuilder<bool>(
    stream: _recordingStatusStream,
    builder: (context, snapshot) {
      final isRecording = snapshot.data ?? false;
      
      if (isRecording) {
        return _buildSecureOverlay();
      }
      
      return _buildSensitiveContent();
    },
  );
}

4. Conditional Feature Enabling

Future<void> setupPlatformSpecificSecurity() async {
  final status = await ScreenSecure.getSecurityStatus();
  final platform = status['platform'];
  
  if (platform == 'ios') {
    // Enable iOS-specific features
    ScreenSecure.setScreenRecordingCallback(_handleRecording);
  } else if (platform == 'android') {
    // Enable Android-specific features
    await ScreenSecure.enableScreenshotBlock();
  }
}

πŸ“‹ Permissions

No additional permissions are required for this plugin. It uses only system-level APIs available to all apps.

πŸš€ Performance Considerations

  • Minimal overhead: The plugin uses native platform APIs with negligible performance impact
  • Memory efficient: No continuous monitoring or polling
  • Battery friendly: Event-driven callbacks only when needed

πŸ” Testing

Unit Testing

import 'package:flutter_test/flutter_test.dart';
import 'package:screen_secure/screen_secure.dart';

void main() {
  group('ScreenSecure Tests', () {
    test('init returns true on successful initialization', () async {
      final result = await ScreenSecure.init();
      expect(result, isTrue);
    });
    
    test('getSecurityStatus returns valid status', () async {
      final status = await ScreenSecure.getSecurityStatus();
      expect(status, isA<Map<String, dynamic>>());
      expect(status.containsKey('platform'), isTrue);
    });
  });
}

Integration Testing

import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:screen_secure_example/main.dart' as app;

void main() {
  IntegrationTestWidgetsFlutterBinding.ensureInitialized();
  
  group('Screen Security Integration Tests', () {
    testWidgets('App initializes with security enabled', (tester) async {
      app.main();
      await tester.pumpAndSettle();
      
      // Verify security status indicators
      expect(find.text('Screenshot Protection'), findsOneWidget);
      expect(find.text('Active'), findsWidgets);
    });
  });
}

🀝 Contributing

We welcome contributions! Please see our Contributing Guide for details.

Development Setup

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests for your changes
  5. Ensure all tests pass (flutter test)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to the branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Code Style

  • Follow Dart Style Guide
  • Use meaningful variable and function names
  • Add documentation for all public APIs
  • Include examples in documentation

πŸ“ License

This project is licensed under the MIT License - see the LICENSE file for details.

πŸ“ž Support

🌟 Acknowledgments

  • Flutter team for the excellent plugin architecture
  • Android and iOS teams for providing robust security APIs
  • Open source community for inspiration and feedback

πŸ“ˆ Changelog

See CHANGELOG.md for a detailed history of changes.


Made with ❀️ by Sharia Hossain

If you find this plugin helpful, please consider giving it a ⭐ on GitHub!

About

A powerful Flutter plugin for comprehensive screen security features including screenshot and screen recording protection across Android and iOS platforms.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published