expo-app-blocker 0.1.61 → 0.1.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -22,12 +22,23 @@ class AppBlockerService : Service() {
22
22
  private var lastForegroundPackage: String? = null
23
23
  private lateinit var overlayManager: OverlayManager
24
24
  private val unlockController by lazy { TemporaryUnlockController(this, handler) }
25
+ private var wasUnlocked = false
25
26
 
26
27
  private val pollRunnable = object : Runnable {
27
28
  override fun run() {
28
- if (!unlockController.isUnlocked) {
29
+ if (unlockController.isUnlocked) {
30
+ wasUnlocked = true
31
+ } else {
29
32
  val foregroundPackage = getCurrentForegroundPackage()
30
- if (foregroundPackage != null && foregroundPackage != lastForegroundPackage) {
33
+ val unlockJustExpired = wasUnlocked
34
+ wasUnlocked = false
35
+
36
+ if (unlockJustExpired && foregroundPackage != null && isBlocked(foregroundPackage)) {
37
+ // Earned time ran out while the user was still inside a blocked app.
38
+ Log.d(TAG, "Unlock expired in foreground app: $foregroundPackage")
39
+ lastForegroundPackage = foregroundPackage
40
+ block(foregroundPackage, BlockReason.EXPIRED)
41
+ } else if (foregroundPackage != null && foregroundPackage != lastForegroundPackage) {
31
42
  Log.d(TAG, "Foreground changed: $foregroundPackage")
32
43
  lastForegroundPackage = foregroundPackage
33
44
  handleForegroundChange(foregroundPackage)
@@ -48,18 +59,24 @@ class AppBlockerService : Service() {
48
59
  handler.post(pollRunnable)
49
60
  }
50
61
 
62
+ private fun isBlocked(packageName: String): Boolean =
63
+ packageName in AppBlockerPrefs.getBlockedPackages(this)
64
+
51
65
  private fun handleForegroundChange(foregroundPackage: String) {
52
- val blocked = AppBlockerPrefs.getBlockedPackages(this)
53
- if (foregroundPackage in blocked) {
66
+ if (isBlocked(foregroundPackage)) {
54
67
  Log.d(TAG, "Blocked app in foreground: $foregroundPackage")
55
- overlayManager.show(foregroundPackage)
56
- showBlockedNotification(foregroundPackage)
68
+ block(foregroundPackage, BlockReason.OPENED)
57
69
  } else {
58
70
  overlayManager.hide()
59
71
  }
60
72
  }
61
73
 
62
- private fun showBlockedNotification(packageName: String) {
74
+ private fun block(packageName: String, reason: BlockReason) {
75
+ overlayManager.show(packageName, reason)
76
+ showBlockedNotification(packageName, reason)
77
+ }
78
+
79
+ private fun showBlockedNotification(packageName: String, reason: BlockReason) {
63
80
  val appName = try {
64
81
  val pm = this.packageManager
65
82
  val appInfo = pm.getApplicationInfo(packageName, 0)
@@ -74,7 +91,10 @@ class AppBlockerService : Service() {
74
91
  val scheme = getAppScheme()
75
92
  val deepLinkIntent = Intent(
76
93
  Intent.ACTION_VIEW,
77
- Uri.parse("${scheme}://blocked?app=${Uri.encode(appName)}&package=${Uri.encode(packageName)}")
94
+ Uri.parse(
95
+ "${scheme}://blocked?app=${Uri.encode(appName)}" +
96
+ "&package=${Uri.encode(packageName)}&reason=${reason.slug}"
97
+ )
78
98
  ).apply {
79
99
  addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
80
100
  }
@@ -0,0 +1,14 @@
1
+ package expo.modules.appblocker
2
+
3
+ /**
4
+ * Why a blocked app is being intercepted right now. Carried into the deep link
5
+ * so the JS side can branch (e.g. a softer "time's up" interstitial when an
6
+ * earned-time window expires while the user is still inside the app).
7
+ */
8
+ enum class BlockReason(val slug: String) {
9
+ /** A blocked app was freshly brought to the foreground. */
10
+ OPENED("opened"),
11
+
12
+ /** A temporary unlock expired while a blocked app was already in the foreground. */
13
+ EXPIRED("expired"),
14
+ }
@@ -24,11 +24,11 @@ class OverlayManager(private val context: Context) {
24
24
 
25
25
  private var overlayView: View? = null
26
26
 
27
- fun show(blockedPackageName: String? = null) {
27
+ fun show(blockedPackageName: String? = null, reason: BlockReason = BlockReason.OPENED) {
28
28
  if (overlayView != null) {
29
29
  Log.d(TAG, "Overlay already visible")
30
30
  if (blockedPackageName != null) {
31
- navigateToApp(blockedPackageName)
31
+ navigateToApp(blockedPackageName, reason)
32
32
  } else {
33
33
  bringAppToFront()
34
34
  }
@@ -47,7 +47,7 @@ class OverlayManager(private val context: Context) {
47
47
  }
48
48
 
49
49
  if (blockedPackageName != null) {
50
- navigateToApp(blockedPackageName)
50
+ navigateToApp(blockedPackageName, reason)
51
51
  } else {
52
52
  bringAppToFront()
53
53
  }
@@ -72,14 +72,17 @@ class OverlayManager(private val context: Context) {
72
72
  packageName
73
73
  }
74
74
 
75
- private fun navigateToApp(blockedPackageName: String) {
75
+ private fun navigateToApp(blockedPackageName: String, reason: BlockReason) {
76
76
  val appName = resolveAppName(blockedPackageName)
77
77
 
78
78
  // Use the app's own scheme for deep linking
79
79
  val scheme = getAppScheme()
80
80
  val deepLinkIntent = Intent(
81
81
  Intent.ACTION_VIEW,
82
- Uri.parse("${scheme}://blocked?app=${Uri.encode(appName)}&package=${Uri.encode(blockedPackageName)}")
82
+ Uri.parse(
83
+ "${scheme}://blocked?app=${Uri.encode(appName)}" +
84
+ "&package=${Uri.encode(blockedPackageName)}&reason=${reason.slug}"
85
+ )
83
86
  ).apply {
84
87
  addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP)
85
88
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "expo-app-blocker",
3
- "version": "0.1.61",
3
+ "version": "0.1.62",
4
4
  "description": "Expo module for cross-platform app blocking. Android: UsageStatsManager + Overlay. iOS: Screen Time API (FamilyControls + ManagedSettings + DeviceActivity).",
5
5
  "main": "src/index.ts",
6
6
  "types": "src/index.ts",