Android
Android File Manager
by balleman on Feb.25, 2018, under Android
It annoys me that Android has no native file management application. I know, I know, “it’s a phone, not a computer and phones don’t have files”, or some such nonsense. I’ve mostly used OI File Manager, since it is used as the file choose for some (old?) apps, and it’s been around a long time. Any more though, I tend to get lost in the navigation, having to close the app for it to see files again. This morning I went through a lot of the file management apps on F-Droid, and concluded that Amaze is the best for my needs currently. It seems to be actively maintained (latest release less than 6 months ago). It supports root access. The navigation seems smooth and intuitive. Have you found a better free or open source file manager? I’d be curious to know.
Automated hotspot control on the Nexus 5x, continued: Android 8
by balleman on Sep.14, 2017, under Android, Happenings
I like Oreos, but every time Google hijacks a new dessert, I need to revist the HSC app to accommodate its new flavor. Sigh.
Firstly, startTethering()
now checks for and fails if it is passed a null callback (see here), which I had been doing. You wouldn’t think this would be a big thing to handle. The callback class is ConnectivityManager.OnStartTetheringCallback, an abstract class that is not present in the Android SDK. I’ve still been using reflection to access parts of the System API, as it has seemed to be the easiest approach for this type of thing. However, you can’t create a subclass using reflection. With Dynamic Proxy you could do this if it were an interface, but it’s not.
So I needed to find a better way of compiling against the non-public pieces of the Android API. The way that ended up working for me was to build the SDK from the AOSP sources. Following the documentation, I had a number of build errors having to do with things being defined in multiple places, which I would comment out as I encountered them. Eventually the build was almost successful (seemed to fail on the last step or so), but it got far enough to produce the .../framework_intermediates/classes.jar
, which is to have what is needed. After stumbling through various things (I’ve found no great documentation for this), I ended up going with these gradle modifications to let me get at the class in question. I copied the above classes.jar
as system_libraries/framework_all-26.jar
.
top-level build.gradle, added to “allprojects” block:
gradle.projectsEvaluated {
tasks.withType(JavaCompile) {
options.compilerArgs.add('-Xbootclasspath/p:hsc/system_libraries/framework_all-26.jar:android/net/ConnectivityManager.class')
}
}
app-level build.gradle, added to the “dependencies” block:
provided files('system_libraries/framework_all-26.jar')
With these changes, AndroidStudio still can’t see changes (the IDE is unable to resolve the symbols), but the build process is nevertheless successful. There’s probably a better way, maybe even something trivial, or perhaps using a different IDE that is more flexible, for one.
Secondly, this: Privileged Permission Whitelist Requirement. Enforcement of this started in Android 8.0, and it means that even putting HSC into the /system/priv-app
directory is not enough to give it the necessary privileges. Fortunately, adding the necessary permissions to /system/etc/permissions/privapp-permissions-platform.xml
is apparently sufficient:
<privapp-permissions package="me.alleman.brady.hsc">
<permission name="android.permission.CHANGE_NETWORK_STATE" />
<permission name="android.permission.CHANGE_WIFI_STATE" />
<permission name="android.permission.ACCESS_WIFI_STATE" />
<permission name="android.permission.CONNECTIVITY_INTERNAL" />
<permission name="android.permission.TETHER_PRIVILEGED" />
<permission name="android.permission.ACCESS_NETWORK_STATE" />
<permission name="android.permission.MANAGE_USERS" />
<permission name="android.permission.WRITE_SETTINGS" />
</privapp-permissions>
The updated package, in case you find it useful: hsc8.apk
Automated hotspot control on the Nexus 5x, continued: Android 7.1.1
by balleman 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?
Automated hotspot control on the Nexus 5x
by balleman on Feb.07, 2016, under Android
There are few things more frustrating than when an upgrade causes the loss of features you depend upon. In the case of my upgrade from the Nexus 4 to the Nexus 5x (and therefore from Android 5 to 6, ahem, Lollipop to Marshmallow) I lost my automation that would enable the WiFi hotspot while in my vehicle. Tethering works fine by enabling it through Settings, but it generates an error when enabling it with Macrodroid.
Enabling the hotspot is not something that is accessible from a public API, so the ordinary method seems to be to use reflection to make the change using the private API.
From setWifiApEnabled()
, eventually enforceTetherChangePermission()
is called in ConnectivityManager.java. This method checks if config_mobile_hotspot_provision_app
is set, and if so, requires the CONNECTIVITY_INTERNAL
permission which is only granted to system applications.
The cause of my problem, therefore, seems to be that “Entitlement APP provisioning for Tethering” (config_mobile_hotspot_provision_app
) is enabled in the Nexus 5x-specific configuration for at least my mcc/mnc, and was not present on the Nexus 4.
After not finding any other applications or configuration to solve this problem, the path I went down was to create my own system application with the CONNECTIVITY_INTERNAL
permission, to see if it could enable the hotspot. I was successful, so I implemented the application as a Locale plugin (i.e., quickly hacked it together with the example plugin) so that it could be called directly by Macrodroid. This seems to be working fine. You can (at your own risk) download it here and then you will need to move it into /system/priv-app for it to get the privileges of a system application, probably using your recovery image (TWRP, for instance).
It may also be possible (and perhaps better) to resolve this by removing the config_mobile_hotspot_provision_app
configuration, after which it should be possible to use the native Macrodroid hotspot actions, as described here, but I’ve not tried it yet for myself.
Update 2016-09-10: For Android 7 / Nougat, the permission required is now TETHER_PRIVILEGED
, but otherwise the above method still works. An updated APK is here.