Skip to content

Conversation

@pacjo
Copy link

@pacjo pacjo commented Jun 28, 2025

Hi,
this PR adds support for Device Controls introduced with Android 11.

Code is mostly just modified example from https://developer.android.com/develop/ui/views/device-control

It's pretty much ready to merge, a few things though:

  • there are some hardcoded strings in the DeviceControlsProviderService, I'll remove them later today
  • I didn't add any strings to strings-fr.xml since I don't know the language well enough to do that
  • I'm not sure weather to show 0-255 or 0%-100% in the Device controls (there's a TODO for this in the code) - asking for your opinion here
  • long-pressing the device in the device controls view doesn't open device specific screen (I'm not sure how to achieve that in a simple way and didn't want to mess with UI code much, maybe can be implemented later) (there are two TODOs for this in the code)
  • two methods are copied straight from another ViewModel (annotated in the code) - maybe should be exported into some common utils?
  • after clearing app data (or an app reinstall) first tap and hold opens MainActivity, but it only shows up for a split second before turning transparent. this happens before and after changes in this PS and I don't know whats causing it

probably closes #21 (at least for people on Android 11 and higher) and helps with #17

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Summary of Changes

Hello @pacjo, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant new feature by integrating Android 11's Device Controls API. This allows users to conveniently control their WLED devices, specifically power and brightness, directly from the system's quick access controls, enhancing usability and accessibility for Android 11 and higher users.

Highlights

  • Android Device Controls Integration: I've added support for Android 11+ Device Controls, allowing users to manage WLED devices directly from their quick settings or power menu.
  • Power and Brightness Control: The implementation provides controls for toggling device power and adjusting brightness for connected WLED devices.
  • Dynamic Device State: The controls dynamically reflect the online/offline status and current power/brightness state of WLED devices, updating in real-time.
  • New Service Implementation: A new DeviceControlsProviderService has been introduced to handle the communication between the Android system's Device Controls UI and the application's device management logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point in your pull request via creating an issue comment (i.e. comment on the pull request page) using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in issue comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist is currently in preview and may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments to provide feedback.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request adds support for Android 11's Device Controls. I've found a critical issue in the control action handling that could lead to incorrect responses being sent to the system. I've also identified several areas for improvement regarding code duplication, use of hardcoded strings, and the generation of unique IDs for PendingIntents. My suggestions aim to improve the code's robustness and maintainability.

*/
private fun createAppIntentForDevice(device: Device): PendingIntent {
// TODO: we can just remove everything but numbers and be left with a string of a unique integer
val integerId = device.macAddress.hashCode()

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Using String.hashCode() for the PendingIntent request code is not ideal. Hash codes are not guaranteed to be unique, which could lead to collisions where different devices get the same PendingIntent, causing unexpected behavior.

A more robust approach is to generate a unique integer from the device's MAC address.

        val integerId = try {
            // Use the last 6 characters of the MAC address, which should be unique enough for this purpose.
            device.macAddress.replace(":", "").takeLast(6).toInt(16)
        } catch (e: Exception) {
            // Fallback to hashCode if parsing fails for some reason (e.g. invalid MAC format)
            device.macAddress.hashCode()
        }

Comment on lines 192 to 199
private fun toggleDevicePower(device: Device, isOn: Boolean) {
val deviceSetPost = JsonPost(isOn = isOn)
scope.launch(Dispatchers.IO) {
stateFactory.getState(device).requestsManager.addRequest(
StateChangeRequest(device, deviceSetPost)
)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This method is copied from DeviceListViewModel, which introduces code duplication. This should be refactored by extracting the device action logic into a shared component (e.g., a DeviceActionManager or a UseCase) that can be injected into both the ViewModel and this Service.

Additionally, this function is called from a coroutine that already runs on Dispatchers.IO. The inner scope.launch(Dispatchers.IO) is redundant and creates an unnecessary nested coroutine. You can remove the inner launch.

    private fun toggleDevicePower(device: Device, isOn: Boolean) {
        val deviceSetPost = JsonPost(isOn = isOn)
        stateFactory.getState(device).requestsManager.addRequest(
            StateChangeRequest(device, deviceSetPost)
        )
    }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated scope.launch removed, I'm leaving those functions in for now

Comment on lines 202 to 209
private fun setDeviceBrightness(device: Device, brightness: Int) {
val deviceSetPost = JsonPost(brightness = brightness)
scope.launch(Dispatchers.IO) {
stateFactory.getState(device).requestsManager.addRequest(
StateChangeRequest(device, deviceSetPost)
)
}
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This method is copied from DeviceListViewModel, which introduces code duplication. This should be refactored by extracting the device action logic into a shared component (e.g., a DeviceActionManager or a UseCase) that can be injected into both the ViewModel and this Service.

Additionally, this function is called from a coroutine that already runs on Dispatchers.IO. The inner scope.launch(Dispatchers.IO) is redundant and creates an unnecessary nested coroutine. You can remove the inner launch.

    private fun setDeviceBrightness(device: Device, brightness: Int) {
        val deviceSetPost = JsonPost(brightness = brightness)
        stateFactory.getState(device).requestsManager.addRequest(
            StateChangeRequest(device, deviceSetPost)
        )
    }

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duplicated scope.launch removed, I'm leaving those functions in for now

pacjo added 3 commits June 28, 2025 21:55
(remove from the toggleDevicePower and setDeviceBrightness functions, now only present in performControlAction)
@pacjo
Copy link
Author

pacjo commented Jun 28, 2025

I addressed some of the gemini's review. Duplicated functions are left (but modified) and I didn't touch createAppIntentForDevice since there's no corresponding part for it in the MainActivity.

interesting wording used by AI

When skimming through gemeni's messages I saw this beauty:

Android Device Controls Integration: I've added support for Android 11+ Device Controls, allowing users to manage WLED devices directly from their quick settings or power menu.

you haven't added anything. This wording should have never shown up.

Waiting for a non-AI review.

@Manut38
Copy link

Manut38 commented Jun 30, 2025

I haven't checked out the code yet but I just compiled your branch and it works great!
One thing I noticed though, is that it showed e.g 128,0 for me underneath the name with the first one being the brightness value. Not sure what the other number means in that context.

Speaking about the number, I would prefer it using percent (0%-100%) instead of the brightness value that you see in WLED because I think it's a cleaner approach for this part of the Android UI.

I am further of the opinion that getting straight into the details page by long pressing the tile is an absolute must for this feature.
If I find some time, maybe I can see if I'll manage to fix support it.

Thanks for now, great idea! 😁

@pacjo
Copy link
Author

pacjo commented Jun 30, 2025

I haven't checked out the code yet but I just compiled your branch and it works great!

Glad to hear.

One thing I noticed though, is that it showed e.g 128,0 for me underneath the name with the first one being the brightness value. Not sure what the other number means in that context.

it's just 128, but I might have set it to display as float. This will change when moving to 0-100% scale.

I am further of the opinion that getting straight into the details page by long pressing the tile is an absolute must for this feature. If I find some time, maybe I can see if I'll manage to fix support it.

I'll make an attempt too. It's not an absolute must for me, but it sure would be nice to have.

@pacjo pacjo force-pushed the device-controls branch from 124e045 to 91ab26e Compare July 1, 2025 10:22
@pacjo
Copy link
Author

pacjo commented Jul 1, 2025

Alright, I've fixed the format in device controls view and added navigation to specific device.
I not a huge fan of this navigation code, so if there's a better way to write it I'd be happy to change it.

Other then that there is still some duplicated code in DeviceControlsProviderService, not sure if this should be merged with the one in DeviceListViewModel. Strings aren't translated and I'd like to go over the whole PR to check for anything left. Since some of this is more a matter of opinion I'd like some input from @Moustachauve if you have some time.

@Moustachauve
Copy link
Owner

Wow, this is great! I will have to test it myself (might take a while, sorry) but I didn't think this was possible for local controls!

It's pretty cool. What happens when you are away from home, I assume it says unavailable or something like that?

Related to the AI, feel free to ignore it, but I find it nice to have a summary and initial look, even though it's not super accurate for reviews.

@pacjo
Copy link
Author

pacjo commented Jul 2, 2025

Wow, this is great! I will have to test it myself (might take a while, sorry) but I didn't think this was possible for local controls!

Awesome. Take as much time as you need.

What happens when you are away from home, I assume it says unavailable or something like that?

Yea, Offline to be specific.

Screenshot_20250702-114515_System UI

Related to the AI, feel free to ignore it, but I find it nice to have a summary and initial look, even though it's not super accurate for reviews.

Well, it got things right. It's just that most of those already had a note attached.

@Manut38
Copy link

Manut38 commented Jul 4, 2025

I personally approve of the 0-100% range. It's funny because I did almost the exact same changes on my local branch, but yours are a bit cleaner.
Just from a usage perspective, it seems that the implementation is not completely reliably yet. Sometimes I'm unable to toggle or change, and then it works again a few seconds later.
This could very well be an issue of the DeviceControl implementation itself, but I don't know.
Thanks for the work! 😄

@pacjo
Copy link
Author

pacjo commented Jul 6, 2025

I personally approve of the 0-100% range.

❤️

Just from a usage perspective, it seems that the implementation is not completely reliably yet. Sometimes I'm unable to toggle or change, and then it works again a few seconds later.

I'm having the same issues on my main strip, but that one is running on ESP01 so not exactly a recommended configuration. I did setup another one with ESP32 to test multiple devices and I don't recall having problems there. Not sure how to troubleshoot this either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Shortcut/Quick actions menu

3 participants