Package Exports
- @breakr/react-native-device-activity-android
- @breakr/react-native-device-activity-android/lib/index.js
This package does not declare an exports field, so the exports above have been automatically detected and optimized by JSPM instead. If any package subpath is missing, it is recommended to post an issue to the original package (@breakr/react-native-device-activity-android) to support the "exports" field. If that is not possible, create a JSPM override to customize the exports field for this package.
Readme
@breakr/react-native-device-activity-android
React Native library for Android that provides screen blocking functionality similar to Apple's DeviceActivity APIs. This library uses Android's Accessibility Service, Overlay permissions, and Usage Access to monitor and block apps during focus sessions.
Features
- 🚫 Block apps during focus sessions
- ⏰ Time-based sessions with start/end times
- 🎨 Customizable block screens with title, message, and CTA
- 📊 Event streaming for app attempts and blocks
- ✅ Permission management with helper methods
- 🔄 Multiple sessions support
- 📱 Expo compatible via config plugin
Installation
# Using yarn
yarn add @breakr/react-native-device-activity-android
# Using npm
npm install @breakr/react-native-device-activity-androidRequirements
- Expo SDK 54+ (React Native 0.81+)
- Android 7.0 (API 24) or higher
- Requires dev build - This library uses native Android code and cannot run in Expo Go
Setup
1. Add to app.json
Add the plugin to your app.json:
{
"expo": {
"plugins": ["@breakr/react-native-device-activity-android"]
}
}2. Prebuild
Run prebuild to generate native Android code:
expo prebuild -p android3. Build and run
expo run:androidPermissions
This library requires four Android permissions:
- Accessibility Service - Monitors foreground app changes
- Draw Over Apps (Overlay) - Shows block screen over blocked apps
- Usage Access - Provides app usage statistics for reliable app detection
- Schedule Exact Alarms - Required for temporary unblock feature (Android 12+)
Requesting Permissions
import DeviceActivityAndroid from '@breakr/react-native-device-activity-android'
// Check current status
const status = await DeviceActivityAndroid.getPermissionsStatus()
console.log(status)
// {
// accessibilityEnabled: false,
// overlayEnabled: false,
// usageAccessEnabled: false,
// scheduleExactAlarmEnabled: false
// }
// Open settings to grant permissions
await DeviceActivityAndroid.requestAccessibilityPermission()
await DeviceActivityAndroid.requestOverlayPermission()
await DeviceActivityAndroid.requestUsageAccessPermission()Important: These permissions require the user to manually enable them in Android Settings. Your app should guide users through this process.
Usage
Basic Example
import DeviceActivityAndroid from '@breakr/react-native-device-activity-android'
// Start a focus session blocking Instagram and Twitter for 5 minutes
await DeviceActivityAndroid.startSession(
{
id: 'focus-session',
blockedPackages: ['com.instagram.android', 'com.twitter.android'],
endsAt: Date.now() + 5 * 60 * 1000, // 5 minutes from now
},
{
title: 'Stay Focused',
message: 'This app is blocked during your focus session.',
ctaText: 'Return to Focus',
}
)
// Stop the session
await DeviceActivityAndroid.stopSession('focus-session')Session Configuration
type SessionConfig = {
id: string // Unique session identifier
blockedPackages: string[] // Package IDs to block
allowPackages?: string[] // Whitelist mode (if provided, only these apps allowed)
startsAt?: number // Start time in ms since epoch (default: now)
endsAt?: number // End time in ms since epoch (default: indefinite)
reason?: string // Optional reason for the session
}Quick Actions
The library provides convenient methods for common blocking scenarios:
Block All Apps
// Block all installed apps for 1 hour
await DeviceActivityAndroid.blockAllApps(
'deep-focus-session',
Date.now() + 60 * 60 * 1000,
{
title: 'Deep Focus Mode',
message: 'All apps are blocked for 1 hour.',
ctaText: 'Go Back',
}
)Check Blocking Status
const status = await DeviceActivityAndroid.getBlockStatus()
console.log(`Blocking: ${status.isBlocking}`)
console.log(`Active sessions: ${status.activeSessionCount}`)
console.log(`Session IDs: ${status.activeSessions.join(', ')}`)
console.log(`Current app: ${status.currentForegroundApp}`)Temporary Unblock
// Pause blocking for 60 seconds, then automatically resume
await DeviceActivityAndroid.temporaryUnblock(60)
// Listen for when blocking resumes
const subscription = DeviceActivityAndroid.addListener(event => {
if (event.type === 'temporary_unblock_ended') {
console.log('Blocking has resumed!')
subscription.remove()
}
})Temporary Block
// Block all apps for 5 minutes (300 seconds)
await DeviceActivityAndroid.temporaryBlock(300, {
title: 'Quick Focus',
subtitle: 'Taking a 5 minute break from apps',
primaryButtonLabel: 'Dismiss'
})
// Session automatically expires after duration
// Listen for when session expires
const subscription = DeviceActivityAndroid.addListener(event => {
if (event.type === 'session_expired') {
console.log('Temporary block ended:', event.sessionId)
subscription.remove()
}
})Unblock All Apps
// Stop all active blocking sessions
await DeviceActivityAndroid.unblockAllApps()How It Works
The library uses a combination of Android system APIs to detect and block apps:
UsageStatsManager Polling: Continuously monitors the foreground app using UsageStatsManager API, which provides reliable app detection without extensive accessibility permissions abuse.
Accessibility Service: Required to display system-level overlays and provide the accessibility context needed for blocking functionality.
Window Overlays: When a blocked app is detected, the library displays a full-screen overlay with customizable messaging to prevent access.
The blocking mechanism includes:
- A foreground check that runs periodically to detect app switches
- Session-based blocking with configurable start/end times
- Cooldown periods to prevent overlay reappearing after dismissal
- Event emission for tracking user behavior and block attempts
Listening to Events
const subscription = DeviceActivityAndroid.addListener(event => {
switch (event.type) {
case 'block_shown':
console.log('Block shown for session:', event.sessionId)
break
case 'block_dismissed':
console.log('User dismissed block')
break
case 'app_attempt':
console.log('User tried to open:', event.packageName)
break
case 'service_state':
console.log('Service running:', event.running)
break
case 'temporary_unblock_ended':
console.log('Temporary unblock period has ended, blocking resumed')
break
}
})
// Clean up when done
subscription.remove()Getting Installed Apps
// Get list of all installed user-facing apps
const apps = await DeviceActivityAndroid.getInstalledApps(true) // true = include icons
// Filter and display apps
apps.forEach(app => {
console.log(`${app.name} (${app.packageName})`)
if (app.icon) {
// icon is base64-encoded PNG, ready for <Image source={{ uri: `data:image/png;base64,${app.icon}` }} />
}
})Note: This method filters out system apps and returns only apps with launcher activities. It includes updated system apps (like pre-installed apps that have been updated via Play Store).
Custom Shield Icons
You can display custom icons on the blocking overlay instead of the default emoji. This is useful for branding your focus/wellbeing app.
Setting Up Custom Icons
Add your icon asset to your project (e.g.,
assets/robot-head.png)Create a constants file to manage versioning:
// constants.ts
export const ICON_VERSION_NUMBER = 1
export const ICON_ASSET_PATH = './assets/robot-head.png'
export const DEFAULT_ICON_SIZE = 64 // in dp- Cache the icon from React Native assets to internal storage:
// utils/iconHelper.ts
import { NativeModules } from 'react-native'
import { ICON_ASSET_PATH, ICON_VERSION_NUMBER } from '../constants'
const { RNDeviceActivityAndroid } = NativeModules
export async function ensureCustomIconCached(): Promise<string | null> {
try {
const cachedPath = await RNDeviceActivityAndroid.ensureIconCached(
ICON_ASSET_PATH,
ICON_VERSION_NUMBER
)
if (cachedPath) {
console.log('Icon cached successfully:', cachedPath)
return cachedPath
}
return null
} catch (error) {
console.error('Error caching custom icon:', error)
return null
}
}- Use the custom icon in your shield configuration:
import DeviceActivityAndroid from '@breakr/react-native-device-activity-android'
import { ensureCustomIconCached } from './utils/iconHelper'
import { ICON_ASSET_PATH, DEFAULT_ICON_SIZE } from './constants'
// Cache the icon when app starts
useEffect(() => {
ensureCustomIconCached()
}, [])
// Use in shield style
await DeviceActivityAndroid.startSession(
{
id: 'focus-session',
blockedPackages: ['com.instagram.android'],
endsAt: Date.now() + 30 * 60 * 1000,
},
{
title: 'Stay Focused',
subtitle: 'This app is blocked during your focus session',
primaryImagePath: ICON_ASSET_PATH,
iconSize: DEFAULT_ICON_SIZE, // Size in dp (density-independent pixels)
backgroundColor: { red: 255, green: 253, blue: 249 },
}
)Icon Versioning and Caching
The library uses a versioning system for icon caching:
- Path format: Use relative paths with
./prefix (e.g.,./assets/robot-head.png) - Cached filename:
breakr-icon-v{version}.png(e.g.,breakr-icon-v1.png) - Storage location: Android internal storage (
/data/data/your.package/files/shield-icons/) - Cache invalidation: Increment the version number when you update the icon
- Old versions: Automatically cleaned up when new version is cached
The ensureIconCached() method:
- Checks if the current version is already cached
- If not, copies the asset from React Native bundle to internal storage
- Returns the absolute file path for use in shield configurations
- Returns
nullif the operation fails
Custom Icon Configuration
The ShieldStyle type supports the following icon-related fields:
type ShieldStyle = {
// Icon configuration
primaryImagePath?: string // Path to custom icon image file
iconSize?: number // Icon size in dp (default: 64dp)
iconTint?: RGBColor // Optional tint color for the icon
// Deprecated fields (still supported for backwards compatibility)
iconSystemName?: string // System icon name (Android drawable resource)
// ... other style fields
}Notes:
- Icon size is specified in density-independent pixels (dp)
- If
primaryImagePathis not provided or fails to load, the overlay shows a default emoji - The cached icon persists across app restarts
- Asset copying happens once per version, subsequent calls use the cached file
API Reference
Methods
Permission Management
getPermissionsStatus(): Promise<PermissionsStatus>- Get current permission status for all required permissionsrequestAccessibilityPermission(): Promise<void>- Open system settings to enable Accessibility ServicerequestOverlayPermission(): Promise<void>- Open system settings to grant overlay permissionrequestUsageAccessPermission(): Promise<void>- Open system settings to grant usage access permission
Session Management
startSession(config: SessionConfig, style?: ShieldStyle, shieldId?: string): Promise<void>- Start a new blocking session with inline style or pre-configured shield IDupdateSession(config: Partial<SessionConfig> & { id: string }): Promise<void>- Update an existing session configurationstopSession(sessionId: string): Promise<void>- Stop a specific blocking session by IDstopAllSessions(): Promise<void>- Stop all active blocking sessionsblockAllApps(sessionId?: string, endsAt?: number, style?: ShieldStyle): Promise<void>- Block all installed user apps in one sessionunblockAllApps(): Promise<void>- Unblock all apps by stopping all sessions (alias for stopAllSessions)getBlockStatus(): Promise<BlockStatus>- Get current blocking status including active sessionstemporaryUnblock(durationSeconds: number): Promise<void>- Temporarily pause all blocking for N seconds, then auto-resumetemporaryBlock(durationSeconds: number, style?: ShieldStyle): Promise<void>- Block all apps for N seconds with automatic expiration
App Information
getCurrentForegroundApp(): Promise<ForegroundApp>- Get the current foreground app package name (best effort)getInstalledApps(includeIcons?: boolean): Promise<Array<{ packageName: string; name: string; category: number; icon?: string }>>- Get list of installed user-facing applications with optional icons and categoriesisServiceRunning(): Promise<boolean>- Check if the accessibility service is currently runninggetAppMetadataDebug(): Promise<Array<AppMetadata>>- DEBUG: Get comprehensive metadata for all installed apps (for debugging and development)
Shield Configuration
configureShielding(configId: string, style: ShieldStyle): Promise<void>- Register a reusable shield configuration with a unique IDupdateShielding(configId: string, style: ShieldStyle): Promise<void>- Update an existing shield configurationremoveShielding(configId: string): Promise<boolean>- Remove a shield configuration by IDgetShieldingConfigurations(): Promise<{ [configId: string]: ShieldStyle }>- Get all registered shield configurationsensureIconCached(imagePath: string, version: number): Promise<string | null>- Copy custom icon from React Native assets to internal storage with versioning support
Event Handling
addListener(callback: (event: BlockEvent) => void): { remove(): void }- Add a listener for block events
Event Types:
block_shown- Block overlay was shown for a sessionblock_dismissed- User dismissed the block overlay (primary button); redirects user back to your appsecondary_action- User tapped secondary button on block overlayapp_attempt- User attempted to open a blocked appservice_state- Accessibility service started or stoppedtemporary_unblock_ended- Temporary unblock period ended, blocking resumedsession_expired- A blocking session reached its end time and expired
Note: When the user taps the primary button (e.g., "Return to Focus"), they are automatically redirected back to your app. This helps guide users back to their focus/wellbeing app after attempting to access a blocked app.
See index.d.ts for complete type definitions.
Google Play Policy Compliance
IMPORTANT: This library uses Android Accessibility Services, which are subject to strict Google Play policies.
Required Disclosures
When submitting to Google Play, you MUST:
- Provide a clear privacy policy explaining Accessibility Service usage
- Create a YouTube video demonstrating the feature
- Explain that the service is used for:
- Digital Wellbeing / Focus management
- Blocking distracting apps
- NOT for automation or data collection
Acceptable Use
✅ Allowed:
- Focus and digital wellbeing apps
- Parental control features
- Self-imposed app blocking
❌ NOT Allowed:
- Automating user actions
- Reading sensitive data from other apps
- Background surveillance
- Anything not directly visible to the user
See Google Play Accessibility Policy for details.
Known Limitations
- Android 14+: Overlay restrictions are tighter. Users may need to grant additional permissions.
- Samsung/MIUI: Some device manufacturers have additional restrictions on Accessibility Services.
- Battery optimization: The service may be killed by aggressive battery savers. Guide users to whitelist your app.
- System apps: Cannot block system apps or the Settings app itself.
Troubleshooting
Service not starting
const running = await DeviceActivityAndroid.isServiceRunning()
if (!running) {
// Guide user to enable Accessibility Service
await DeviceActivityAndroid.requestAccessibilityPermission()
}Block overlay not showing
Check overlay permission:
const { overlayEnabled } = await DeviceActivityAndroid.getPermissionsStatus()
if (!overlayEnabled) {
await DeviceActivityAndroid.requestOverlayPermission()
}Documentation
For a comprehensive guide with detailed API examples and common use cases, see:
- Getting Started Guide - Step-by-step guide with 20+ API examples, real-world use cases, best practices, and troubleshooting
Example App
See the example app for a complete working implementation that demonstrates:
- Permission onboarding flow
- App selection UI with icons and search
- Starting/stopping focus sessions
- Visual feedback during active sessions
To run the example app:
cd apps/example
npx expo prebuild -p android
npx expo run:androidDevelopment
This package is part of a monorepo. To contribute:
- Clone the repository
- Install dependencies:
yarn install - Navigate to the example app:
cd apps/example - Prebuild and run:
npx expo prebuild -p android && npx expo run:android
Making Changes
- Native code: Edit files in
packages/device-activity-android/android/src/main/java/com/breakr/deviceactivity/ - JavaScript bridge: Edit
packages/device-activity-android/src/index.ts - Type definitions: Update
packages/device-activity-android/index.d.ts - Config plugin: Modify
packages/device-activity-android/plugin/app.plugin.js
After making native changes, rebuild the example app:
cd apps/example
npx expo prebuild -p android --clean
npx expo run:androidArchitecture Notes
Why UsageStatsManager?
Initially, this library used AccessibilityEvent callbacks to detect app changes. However, this approach had reliability issues and raised concerns about excessive accessibility API usage for Play Store compliance.
The current implementation uses UsageStatsManager polling, which:
- Provides reliable foreground app detection
- Reduces dependency on accessibility-specific APIs
- Better aligns with Play Store policies for digital wellbeing apps
- Offers better compatibility across device manufacturers
Permissions Required
| Permission | Purpose | Request Method |
|---|---|---|
| Accessibility Service | Display system overlays, detect blocking context | requestAccessibilityPermission() |
| Draw Over Apps | Show blocking overlay on top of blocked apps | requestOverlayPermission() |
| Usage Access | Monitor foreground app via UsageStatsManager | requestUsageAccessPermission() |
| Schedule Exact Alarms | Enable temporary unblock feature with auto-resume | Granted automatically on install (Android 12+) |
All permissions must be granted by the user in system settings (except Schedule Exact Alarms, which is typically granted on install).
Comparison with iOS DeviceActivity
This library aims for API parity with react-native-device-activity for iOS. Key differences:
| Feature | iOS (DeviceActivity) | Android (This Package) |
|---|---|---|
| API Style | Shield configuration, Family Controls | Session-based blocking with style config |
| Permission Model | Screen Time API | Accessibility + Overlay + Usage Access |
| Enforcement | System-level, unbreakable | Overlay-based, can be dismissed (configurable) |
| App Detection | Native API | UsageStatsManager polling |
License
MIT
Contributing
Contributions are welcome! This package will be open-sourced soon. Please ensure:
- All public APIs are documented in this README
- TypeScript types are updated for any native module changes
- The example app demonstrates new features
- Code follows the project's style guide (see CLAUDE.md)