e-mission / e-mission-docs

Repository for docs and issues. If you need help, please file an issue here. Public conversations are better for open source projects than private email.
https://e-mission.readthedocs.io/en/latest
BSD 3-Clause "New" or "Revised" License
15 stars 34 forks source link

[native code] Experiment with entering/exiting airplane mode #1043

Open shankari opened 9 months ago

shankari commented 9 months ago

Anecdotally, I seem to remember that when I got out of airplane mode, I would frequently miss trips. I do not remember whether the foreground service was missing at the time.

The goals for this issue are to go int and out of airplane mode on both a stock android phone and a custom (Samsung/OnePlus phone) and see what happens under the hood. We can also look at my logs (in the internal issue) and see the FSM state when exiting from airplane mode.

A potential change might be to register for the "leave airplane mode" callback and reset the FSM if needed. As a preliminary step, we can register for the "leave airplane mode" callback and log the state of the FSM and the foreground service at the time.

louisg1337 commented 9 months ago

Airplane Mode

I dug into the airplane mode a bit and wanted to get my findings out before we get swept away in the GSA project. I did not have enough time (nor a Samsung/OnePlus) to do tests with physical phones, so I did all of my work in a pixel emulator. I also didn't see an internal issue for this, I'm not sure where it is exactly, so I didn't get the chance to look at any logs.

Tests

I did 3 types of tests to see how airplane mode would affect the FSM. All of these tests involved me broadcasting a custom intent that would allow me to view the state of the FSM at any given time.

  1. Control For this test, I did a fresh install of the app, and got myself to the home screen. From there I retrieved the state, in which it was local.state.waiting_for_trip_start. I then went to the Android home screen to put the app in the background, and turned on airplane mode. I then pinged for the state and I got local.state.waiting_for_trip_start. I then turn off airplane mode, and went back into the app, and checked the state once again, being local.state.waiting_for_trip_start.

  2. Simulate trip trying to start This test I wanted to focus on seeing what would happen if we tried to exit our geofence while in airplane mode. I've read conflicting things online about how some manufacturers may or may not allow geofence services to still work in airplane mode, hence why I tried it. The experiment followed the same suit as above until I got into airplane mode, which is when I broadcasted local.transition.exited_geofence. After the app spins its wheels, we end up being stuck in the local.state.start state, as a result of the error below. Turning off airplane mode and entering the app again does not seem to fix our state by itself.

    02-01 13:25:04.532 13981 13981 I SensorControlBackgroundChecker: location settings are invalid, generating tracking 
    error and visible notification
  3. Simulate performPeriodicActivity() I also thought I'd try to see what happens if performPeriodicActivity was called and the foreground service tries to restart itself. I did the exact same thing as I did in step 2, except I broadcasted for performPeriodicActivity to be called instead of local.transition.exited_geofence. We end up getting the same results, being that we get stuck in local.state.start.

Potential Solutions

I know it was mentioned above that we could register for that airplane mode callback and restart the FSM, and I think thats a great idea. I have not been able to test it, however, as I am having trouble editing the AndroidManifest, so in turn I can't register any BroadcastReceiver to that event. Each time I try to run the app using npx cordova emulate android the AndroidManifest gets rebuilt. I definitely would appreciate some help on figuring out how I can write to AndroidManifest without it getting overwritten so I can try this out.

Another alternative method that we can do instead is to check in TripDiaryStateMachineService.handleAction if airplane mode is turned on. If it is turned on, then we can ignore any action request or automatically change the action request to something like local.state.waiting_for_trip_start to prevent the state from being stuck in limbo.

Please let me know what you think @shankari, I would appreciate any feedback you may have.

shankari commented 9 months ago

@louisg1337 You can edit the AndroidManifest.xml by changing the plugin.xml.

  1. You can change the plugin.xml once, install the plugin (so AndroidManifest.xml is updated) and then change the code
  2. Edit AndroidManifest.xml while working on the code with a visual studio plugin https://marketplace.visualstudio.com/items?itemName=adelphes.android-dev-ext (note that you will need to open the build.gradle file in platforms/android
  3. If you can build the apk without cordova, you can drag and drop it to the emulator. I think you should also be able to use gradelew build manually from platforms/android to build the apk.
  4. Make changes on your personal laptop.

Let us know which works best!

louisg1337 commented 9 months ago

Status Update

Thank you for helping me figure out the AndroidManfiest issue, I appreciate it! I managed to get together a draft PR with both the airplane mode and foreground service disappearance fixes. I decided to combine the two into one PR to 1) get these changes out faster and 2) because I had to edit the same file for both issues.

Code

For the airplane mode change, I implemented the registering for the airplane mode receiver idea. I did run into an issue however, which is that apparently, with the new background execution limits, an app cannot register for implicit intents in the AndroidManifest, besides for these exceptions. The docs on this specific issue can be found here. In that case, I had to instead register the the intent via Context.registerReceiver() as specified by the docs. I did this in DataCollectionPlugin.pluginInitialize() as I figured that would be a good spot to put it, although let me know if theres a better place.

The rest of the change is straight forward with the app listening for the airplane intent in TripDiaryStateMachineReceiver, and then restarting the FSM.

Testing

To test this I did 3 separate tests which were as follows.

  1. Toggle airplane mode on and off as a fresh user (no consent, no permissions) to ensure that the FSM does not start.
  2. Toggle airplane mode on and off as a normal user to see if the new code messes up current state. (i.e. changes the state from local.state.waiting_for_trip_start to something different)
  3. Turn airplane mode on, broadcast exit geofence, and then turn airplane mode back off to see if it corrected the state from local.start.state to local.state.waiting_for_trip_start.

All tests were successful and nothing seemed too weird.

Concern

One concern I did have though was that in TripDiaryStateMachineReceiver when restarting the state all I did was broadcast a transition_initialize to the FSM. I was wondering if maybe it would be better to use this function as it only restarts the FSM if the state is local.start.state. I wanted some input to determine if it was better to just always restart the FSM, or only when we are in that weird state.

Please let me know what you think @shankari, thank you!

shankari commented 9 months ago

@louisg1337 thank you for doing this; hopefully it will make the app a lot more stable, at least for people who travel by plane.

Two comments:

Once you make that change, I am happy to merge the PR

louisg1337 commented 9 months ago

Sounds great, thank you for the feedback! I just made both changes and they are now pushed to the PR. To also address the first bullet point, yes that is what I observed. Right when you switch to airplane mode the state is still in local.state.waiting_for_trip_start, but the second the app does anything it switches over to local.start.state.