Automated hotspot control on the Nexus 5x, continued: Android 7.1.1
on Jan.23, 2017, under Android
I’m in the habit of manually upgrading the OS on my Nexus 5x for each monthly update of the AOSP from Google. I root my phone, and from some past experiences, don’t bother trying to apply the OTA updates. I also reinstall my HSC (hot spot control) application in /system/priv-app each time, and then run it through its paces – I use an NFC widget to do an almost-end-to-end test of my automation with Macrodroid. When I see the WiFi icon disappear, the LTE connection come up, and the hotspot icon appear, I know I’m good to go.
Until recently. On Saturday morning, I was trying to get Google navigation to work in my car. It showed that it was connected to the AP, but it acted like it did not have data. Manually cycling the hotspot on the phone solved the problem. I dismissed it as a fluke. Then on Sunday, the exact same thing happened. The fact that the WiFi was showing connected had me thinking there was something wrong with the car radio instead of my hotspot. I tried it at home with my NFC widget and laptop… and the laptop would connect, but it would never get an IP address. Manually enabled it would. So… again there is something that has changed with the (private) hotspot APIs in Android. Since my testing method didn’t catch it, I’m not certain which version it broke in, but 7.1.1 seems to be a reasonable guess.
The method I had been using, setWifiApEnabled()
would bring up the access point mode, but it would not set up the other network services needed to tether traffic. I found this git log entry that seemed to be relevant:
commit 26bd4efcaaad4a866310d6421909645e81167d1f
Author: Christopher Wiley
Date: Wed Jul 13 19:36:03 2016 -0700TetherUtil: Use ConnectivityManager for tethering
In the past, enabling the SoftAP would cause tethering to happen because
of some unfortunate side effects. This is no longer the case, and
using WifiManager for this purpose is not a good idea.
With some more poking around, I found that the ConnectivityManager
class now (well, it looks like the code is from Jan 2016, 36c7aa03255d91cfa0808323ac475ad02d161d7d) has startTethering()
and stopTethering()
methods, which handle the necessary service startups. After a few more annoying attempts (my workflow to test this is to reboot the phone in recovery, apply the update, reboot in system, and then try it… do I really need to do that?), I dialed in some reflection that seems to work (AndroidStudio annoyingly can’t see @SystemApi stuff, and it seems non-trivial to fix it), and found that I additionally had to give myself ACCESS_NETWORK_STATE
and MANAGE_USERS
permissions. The relevant code (which is not as clean/safe as it could be) now looks like this:
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(context.CONNECTIVITY_SERVICE);
final int TETHERING_WIFI = 0;
if (enableHotspot) {
Method startTethering = null;
for (Method method : cm.getClass().getDeclaredMethods()) {
if (method.getName().equals("startTethering") && (method.getParameterTypes().length == 3)) {
startTethering = method;
}
}
startTethering.invoke(cm, TETHERING_WIFI, false, null);
}
else
{
Method stopTethering = cm.getClass().getMethod("stopTethering", int.class);
stopTethering.invoke(cm, TETHERING_WIFI);
}
The updated APK is here.
And Google… how about giving us a public API for controlling the mobile hotspot? Please?