-
Notifications
You must be signed in to change notification settings - Fork 88
fix(android): defer wakelock toggle when no activity is attached W… #134
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -8,4 +8,5 @@ | |
| .buildlog/ | ||
| .history | ||
| .svn/ | ||
| .metadata | ||
| .metadata | ||
| CLAUDE.md | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,34 +6,35 @@ import android.app.Activity | |
| import android.view.WindowManager | ||
|
|
||
| internal class Wakelock { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If there's a way to create native tests for this change, that would be ideal. Given that this requires destroying an activity in order to simulate going to the background, you'll probably need something like robolectric for this. |
||
| var activity: Activity? = null | ||
|
|
||
| private val enabled | ||
| get() = activity!!.window.attributes.flags and | ||
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON != 0 | ||
| // The desired wakelock state. Tracked independently of [activity] so that a | ||
| // toggle requested while no activity is attached (e.g. the app is in the | ||
| // background or mid lifecycle transition) is remembered and re-applied once an | ||
| // activity (re)attaches, instead of throwing a NoActivityException. | ||
| private var enableWakelock = false | ||
|
|
||
| fun toggle(message: ToggleMessage) { | ||
| if (activity == null) { | ||
| throw NoActivityException() | ||
| var activity: Activity? = null | ||
| set(value) { | ||
| field = value | ||
| // Re-assert the wakelock on the newly attached activity's window, but only | ||
| // when it was actually requested. If the user never enabled it (or last | ||
| // disabled it), leave the flag alone — the activity may keep the screen on | ||
| // for its own reasons. | ||
| if (enableWakelock) applyWakelock() | ||
| } | ||
|
|
||
| val activity = this.activity!! | ||
| val enabled = this.enabled | ||
|
|
||
| if (message.enable!!) { | ||
| if (!enabled) activity.window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||
| } else if (enabled) { | ||
| activity.window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||
| private fun applyWakelock() { | ||
| val window = activity?.window ?: return | ||
| if (enableWakelock) { | ||
| window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||
| } else { | ||
| window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) | ||
| } | ||
| } | ||
|
|
||
| fun isEnabled(): IsEnabledMessage { | ||
| if (activity == null) { | ||
| throw NoActivityException() | ||
| } | ||
|
|
||
| return IsEnabledMessage(enabled = enabled) | ||
| fun toggle(message: ToggleMessage) { | ||
| enableWakelock = message.enable!! | ||
| applyWakelock() | ||
| } | ||
| } | ||
|
|
||
| class NoActivityException : Exception("wakelock requires a foreground activity") | ||
| fun isEnabled(): IsEnabledMessage = IsEnabledMessage(enabled = enableWakelock) | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,97 @@ | ||
| package dev.fluttercommunity.plus.wakelock | ||
|
|
||
| import ToggleMessage | ||
| import android.app.Activity | ||
| import android.os.Build | ||
| import android.view.WindowManager | ||
| import org.junit.Assert.assertFalse | ||
| import org.junit.Assert.assertTrue | ||
| import org.junit.Test | ||
| import org.junit.runner.RunWith | ||
| import org.robolectric.Robolectric | ||
| import org.robolectric.RobolectricTestRunner | ||
| import org.robolectric.annotation.Config | ||
|
|
||
| /** | ||
| * Unit tests for [Wakelock] covering the "no foreground activity attached" | ||
| * behaviour introduced to stop [toggle]/[isEnabled] from throwing when the app | ||
| * is backgrounded or mid lifecycle transition. The desired state is tracked in | ||
| * Kotlin and (re)applied to whichever activity window is attached. | ||
| */ | ||
| @RunWith(RobolectricTestRunner::class) | ||
| @Config(sdk = [Build.VERSION_CODES.UPSIDE_DOWN_CAKE]) | ||
| class WakelockTest { | ||
|
|
||
| private fun buildActivity(): Activity = | ||
| Robolectric.buildActivity(Activity::class.java).setup().get() | ||
|
|
||
| private val Activity.keepScreenOn: Boolean | ||
| get() = window.attributes.flags and | ||
| WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON != 0 | ||
|
|
||
| @Test | ||
| fun `toggle enable with no activity attached does not throw`() { | ||
| val wakelock = Wakelock() | ||
|
|
||
| wakelock.toggle(ToggleMessage(enable = true)) | ||
|
|
||
| assertTrue(wakelock.isEnabled().enabled == true) | ||
| } | ||
|
|
||
| @Test | ||
| fun `isEnabled with no activity attached does not throw and defaults to false`() { | ||
| assertFalse(Wakelock().isEnabled().enabled == true) | ||
| } | ||
|
|
||
| @Test | ||
| fun `enabling before an activity attaches applies the flag once it does`() { | ||
| val wakelock = Wakelock() | ||
|
|
||
| wakelock.toggle(ToggleMessage(enable = true)) | ||
| val activity = buildActivity() | ||
| wakelock.activity = activity | ||
|
|
||
| assertTrue(activity.keepScreenOn) | ||
| } | ||
|
|
||
| @Test | ||
| fun `attaching an activity while disabled leaves the flag untouched`() { | ||
| val wakelock = Wakelock() | ||
| val activity = buildActivity() | ||
|
|
||
| wakelock.activity = activity | ||
|
|
||
| assertFalse(activity.keepScreenOn) | ||
| } | ||
|
|
||
| @Test | ||
| fun `disabling clears the flag on the attached activity`() { | ||
| val wakelock = Wakelock() | ||
| val activity = buildActivity() | ||
| wakelock.activity = activity | ||
|
|
||
| wakelock.toggle(ToggleMessage(enable = true)) | ||
| assertTrue(activity.keepScreenOn) | ||
|
|
||
| wakelock.toggle(ToggleMessage(enable = false)) | ||
| assertFalse(activity.keepScreenOn) | ||
| } | ||
|
|
||
| @Test | ||
| fun `enabled state is re-applied to a new activity after detach`() { | ||
| val wakelock = Wakelock() | ||
| val first = buildActivity() | ||
| wakelock.activity = first | ||
| wakelock.toggle(ToggleMessage(enable = true)) | ||
| assertTrue(first.keepScreenOn) | ||
|
|
||
| // The activity goes away (e.g. the app is backgrounded). This used to throw. | ||
| wakelock.activity = null | ||
| assertTrue(wakelock.isEnabled().enabled == true) | ||
|
|
||
| // A fresh activity attaches; the requested state must be re-asserted on it. | ||
| val second = buildActivity() | ||
| wakelock.activity = second | ||
| assertTrue(second.keepScreenOn) | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,7 @@ name: wakelock_plus | |
| description: >-2 | ||
| Plugin that allows you to keep the device screen awake, i.e. prevent the screen from sleeping on | ||
| Android, iOS, macOS, Windows, Linux, and web. | ||
| version: 1.6.1 | ||
| version: 1.6.2 | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not eagerly increment the pubspec. I'll handle deploying the app when the PR is merged. |
||
| repository: https://github.com/fluttercommunity/wakelock_plus/tree/main/wakelock_plus | ||
|
|
||
| environment: | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do not eagerly increment the changelog.
I'll handle deploying the app when the PR is merged.