Use the hidden API
You can do this using a hidden API called IWindowManager.watchRotation(IRotationWatcher)
. From my testing, it seems to take a callback that is called every time the screen rotation changes. The callback also seems to be given the current screen rotation.
Being a hidden API, you can't just call it directly. How to use hidden APIs is a topic of its own. And, of course, this might not be as reliable from a maintainability perspective as normal APIs.
Also, onRotationChanged
isn't called in the same thread you used to call watchRotation
. You'll probably want to delegate some work to a different thread such as the UI thread once you're in onRotationChanged
.
Compatibility
Tested and working in API 14 - 28.
Example
Here's one way to get it to work:
- Copy
android.view.IRotationWatcher
into your app. Make sure to keep it in its original package. This seems to cause the development tools to think your code has access to it while also causing the operating system to still use the real one rather than your own copy.
Use reflection to call watchRotation
:
try {
Class<?> serviceManager = Class.forName("android.os.ServiceManager");
IBinder serviceBinder = (IBinder)serviceManager.getMethod("getService", String.class).invoke(serviceManager, "window");
Class<?> stub = Class.forName("android.view.IWindowManager$Stub");
Object windowManagerService = stub.getMethod("asInterface", IBinder.class).invoke(stub, serviceBinder);
Method watchRotation;
if (Build.VERSION.SDK_INT >= 26)
watchRotation = windowManagerService.getClass().getMethod("watchRotation", IRotationWatcher.class, int.class);
else
watchRotation = windowManagerService.getClass().getMethod("watchRotation", IRotationWatcher.class);
//This method seems to have been introduced in Android 4.3, so don't expect to always find it
Method removeRotationWatcher = null;
try {
removeRotationWatcher = windowManagerService.getClass().getMethod("removeRotationWatcher", IRotationWatcher.class);
}
catch (NoSuchMethodException ignored) {}
IRotationWatcher.Stub screenRotationChanged = new IRotationWatcher.Stub() {
@Override
public void onRotationChanged(int rotation) throws RemoteException {
//Do what you want here
//WARNING: This isn't called in the same thread you were in when you called watchRotation
}
};
//Start monitoring for changes
if (Build.VERSION.SDK_INT >= 26)
watchRotation.invoke(windowManagerService, screenRotationChanged, Display.DEFAULT_DISPLAY);
else
watchRotation.invoke(windowManagerService, screenRotationChanged);
//Stop monitoring for changes when you're done
if (removeRotationWatcher != null) {
removeRotationWatcher.invoke(windowManagerService, screenRotationChanged);
} catch (ClassNotFoundException | ClassCastException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
throw new RuntimeException(e);
}
onRotationChanged
is never called for me – Terramycin