christianrowlands / android-network-survey

Cellular Survey Android App
https://www.networksurvey.app
Apache License 2.0
172 stars 29 forks source link

[Feature Request] Intents to start/stop MQTT streaming #23

Closed joelkoen closed 3 months ago

joelkoen commented 1 year ago

I would like to be able to automatically turn on/off whether my phone is surveying. I assume the best way to do this would be through Android intents?

christianrowlands commented 1 year ago

Let me make sure I understand this correctly. Are you looking for programmatic API access to Network Survey? In other words, you want to be able to have an Android app you write trigger Network Survey to start a survey?

joelkoen commented 1 year ago

Yes, there are various automation apps for Android such as Tasker and Home Assistant that allow users to broadcast intents to control apps. For example, GPSLogger for Android integrates with these automation apps by exposing some controls for the app.

If NetworkSurvey was to expose an on/off control using intents, users could then build various automations with these apps:

christianrowlands commented 1 year ago

Yeah, I like the idea of providing the automation options. I will take a look and see what I can do.

sudoritz commented 9 months ago

it would be cool if we could geofence where to enable this in a GPS range like a square polygon if your in that zone to measure.

christianrowlands commented 9 months ago

Yeah, that is a nice feature as well (the geofence). Being able to automate anything is typically nice feature.

I still need to look into exposing control using the intents. I will try to see if I can design a solution this week or next week. I am concerned about some edge case scenarios that I need to think through so I don't break any of the existing features, but hopefully I can deconflict those.

TomBeckett commented 4 months ago

@christianrowlands did you have more thoughts on this?

For our use case we'd like to set the MQTT config programatically. Ideally we do this via MDM however each device needs a unique and human clientId (think nickname).

We'd like to set clientId via intent which the user has entered within another app, and let the MDM set other values.

A secondary option is for an intent to set the entire MQTT config. That would also work well for our use case.

christianrowlands commented 4 months ago

Thanks for bumping this issue. I meant to get back to it sooner.

I will start on the original intent of this issue (no pun intended), and as I work on that I will see how much effort it would be to set just the clientId via an intent as well.

christianrowlands commented 4 months ago

I have this mostly coded up, but before I get any farther I wanted to share the current "API" intent interaction so that you all can validate if it matches your expectations and to see if you have any suggestions. Following is an example of firing the intent to start Network Survey file logging for wifi and cellular, as well as the MQTT connection for wifi and cellular.

final Intent startNetworkSurveyIntent = new Intent("com.craxiom.networksurvey.START_SURVEY");

        // These flags are for file logging
        startNetworkSurveyIntent.putExtra("cellular_file_logging", true);
        startNetworkSurveyIntent.putExtra("wifi_file_logging", true);

        // MQTT is configured via a JSON string
        final String mqttConfigJsonString = "{\"mqtt_username\": \"auser\", " +
                "\"mqtt_password\": \"apassword\", " +
                "\"mqtt_host\": \"cloud.mymqttserver.com\", " +
                "\"mqtt_port\": 8883, " +
                "\"mqtt_client\": \"aclient\", " +
                "\"mqtt_tls\": true, " +
                "\"cellular_stream_enabled\": true, " +
                "\"wifi_stream_enabled\": true";
        startNetworkSurveyIntent.putExtra("mqtt_config_json", mqttConfigJsonString);
        context.sendBroadcast(startNetworkSurveyIntent);

What are your thoughts?

TomBeckett commented 4 months ago

Looks perfect to me @christianrowlands! Would work for our use case perfectly.

For context, we'd likely move from MDM to this approach. It gives us better flexibility and I think from a UX perfective, is simpler than needing to 'override' the MDM settings via UI. This means we can set it programatically and more importantly reset it programatically.

The zero touch future is almost here!

We'd also likely want to set start survey on boot and similar settings. If its easy to do great, if its out of scope thats okay also. MQTT settings is the big fish for us.

christianrowlands commented 4 months ago

Great!

I am going to hold off on settings configuration for now because I would need to add all the settings, but that is certainly something I could add in the future.

christianrowlands commented 3 months ago

@TomBeckett and @joelkoen . I have had several false starts on making the intent work for a couple different reasons.

  1. I first went with a broadcast receiver class that is dedicated to handling these intents. However, the way Android security restrictions are set up this approach won't work because to start a service that runs in the foreground (like Network Survey does) you have to trigger it from a UI action. Even if you trigger the original intent via a UI action, when the broadcast receiver receives it and starts the service, the broadcast receiver is considered "in the background", so it can't start the service.
  2. My next approach was to have any apps send the intent directly to the NetworkSurveyService. This worked great, but then I realized I could not prevent apps from stopping Network Survey. I felt this was a security vulnerability because apps could arbitrarily stop a survey that a user started or another app started. I was able to control this in approach # 1 with the broadcast receiver, but not in the service directly.
  3. My new approach is to have apps start the Network Survey Activity via an intent and then the NS Activity will start the service. I think this works because the NS Activity has a UI, and I can also prevent apps from stopping NS. My current approach is to have a user setting that turns on or off the ability to control NS via intents. However, this is a all or nothing setting, so if "Intent control" is enabled, then any app can send an intent to start or stop NS.

What do you guys think about approach # 3? Does that seem reasonable (assuming it pans out the way I want it to)?

Edit/Update: The reason I am asking about # 3 is because it will cause the NS UI to be shown. I should be able to finish() the activity so that it is hidden, but it will show for a couple seconds while things are kicked off. Is that a problem?

joelkoen commented 3 months ago

.3 sounds fine to me, but does the phone then have to be actively in use for the UI to show?

Regarding the toggle, I think that is a good idea. The Wireguard app for Android supports intents, but it must be enabled by the user first, similar to what you've described.

TomBeckett commented 3 months ago

.3 Is also fine for me.

We're using fully managed devices which provides the assumption we're controlling which other apps are on that device.

Will we be able to set that user setting via MDM?

christianrowlands commented 3 months ago

does the phone then have to be actively in use for the UI to show?

Ahh, good point. Yes, it would require the phone to be in use so that the UI can show. That might cause problems. I really want option # 1 to work, which GPSLogger uses that approach. Maybe I missed something that allows me to get past the ForegroundServiceStartNotAllowedException, I will dig through the GPSLogger code a bit more.

Will we be able to set that user setting via MDM?

Yes, I will setup MDM control of that setting.

christianrowlands commented 3 months ago

I spent some time this last weekend trying to figure out why GPSLogger works with the broadcast intent approach, and when I update the app to have a targetSdkVersion of 34 (it is currently 30) I started getting the same ForegroundServiceStartNotAllowedException in GPSLogger. Therefore, I don't think it is possible to use that approach.

I will keep pushing forward with the direct service approach (# 2).

TomBeckett commented 3 months ago

@christianrowlands Sounds good! I've done some work my side based on your Intent example above.

I'd be very happy to give a preview version a go if it helps with testing?

christianrowlands commented 3 months ago

@TomBeckett , I appreciate that. I will clean up my work and let you know when I have the next round of changes done.

christianrowlands commented 3 months ago

I thought I posted 4 hours ago, but I committed the cleanup. Go ahead and give it a go if you have some time.

val startNetworkSurveyIntent =
                                Intent("com.craxiom.networksurvey.START_SURVEY")

                            // These flags are for file logging
                            startNetworkSurveyIntent.putExtra("cellular_file_logging", true)
                            startNetworkSurveyIntent.putExtra("wifi_file_logging", true)

                            startNetworkSurveyIntent.component = ComponentName(
                                "com.craxiom.networksurvey",
                                "com.craxiom.networksurvey.services.NetworkSurveyService"
                            )
                            startForegroundService(startNetworkSurveyIntent)
christianrowlands commented 3 months ago

I threw together a quick guide on the website on using the Intents. https://www.networksurvey.app/intent-api

I plan on linking to that from the README.

christianrowlands commented 3 months ago

I am going to go ahead and close out this issue, but feel free to create a new issue if any problems arise when testing out the Intent or if any changes need to be made. A discussion would also work if that is easier.

TomBeckett commented 3 months ago

@christianrowlands Just an FYI we've successfully integrated it into our app and pushed it out to beta testers. We're currently using a sideloaded debug APK for Network Survey (just to test with).

Any issues we'll let you know in the discussion.

Thanks again for getting it out so quickly, I can tell it was a bit of a battle with various approaches! 👍

Do you know when the next scheduled release on the Play stores would be?

christianrowlands commented 3 months ago

I can push out a release today!

Thanks for testing it.