I am trying to open the Overview/Recents screen in Android. From Android: Programmatically open "Recent Apps" dialog, I can use the following code:
try {
Class serviceManagerClass = Class.forName("android.os.ServiceManager");
Method getService = serviceManagerClass.getMethod("getService", String.class);
IBinder retbinder = (IBinder) getService.invoke(null, "statusbar");
Class statusBarClass = Class.forName(retbinder.getInterfaceDescriptor());
Object statusBarObject = statusBarClass.getClasses()[0]
.getMethod("asInterface", IBinder.class).invoke(null, retbinder);
Method toggleRecentApps = statusBarClass.getMethod("toggleRecentApps");
toggleRecentApps.setAccessible(true);
toggleRecentApps.invoke(statusBarObject);
} catch (InvocationTargetException| NoSuchMethodException | RemoteException | IllegalAccessException | ClassNotFoundException e){
handleError(e);
}
However, toggleRecentApps()
is no longer in IStatusBarService since Nougat, and it causes a NoSuchMethodException.
Is there any other way (excluding AccessibilityService) that can be used to open the Overview/Recents screen?
EDIT: The class that implements IStatusBarService seems to be StatusBarManagerService. However, calling statusBarObject.getClass().getCanonicalName()
returns com.android.internal.statusbar.IStatusBarService.Stub.Proxy
, so getting an IStatusBar
(which does implement toggleRecentApps()
) through the private field mBar
doesn't seem to be a working way to get it.
EDIT 2: Seeing that StatusBarManagerInternal.java is an interface that contains void toggleRecentApps()
, I tried to find its implementation, which I found in StatusBarManagerService
:
private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() { ... }
So it's an anonymous class in StatusManagerService. >:(.
However, I also found:
/**
* Construct the service, add the status bar view to the window manager
*/
public StatusBarManagerService(Context context, WindowManagerService windowManager) {
mContext = context;
mWindowManager = windowManager;
LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
}
So it apparently registers it in LocalServices
, which in turn manages it in an ArrayMap
:
private static final ArrayMap<Class<?>, Object> sLocalServiceObjects =
new ArrayMap<Class<?>, Object>();
Therefore, I tried to access it:
try {
Field services = Class.forName("com.android.server.LocalServices")
.getDeclaredField("sLocalServiceObjects");
services.setAccessible(true);
ArrayMap<Class<?>, Object> serviceMap = (ArrayMap<Class<?>, Object>) services.get(null);
Set<Map.Entry<Class<?>, Object>> serviceSet = serviceMap.entrySet();
for (Map.Entry<Class<?>, Object> serviceEntry : serviceSet) {
if (serviceEntry.getKey().isInterface()) {
if ("com.android.server.statusbar.StatusBarManagerInternal"
.equals(serviceEntry.getKey().getName())) {
Object statusBarInternalObject = serviceEntry.getValue();
Class statusBarInternalClass = serviceEntry.getKey();
Method openRecents = statusBarInternalClass.getMethod("toggleRecentApps");
openRecents.setAccessible(true);
openRecents.invoke(statusBarInternalObject);
return;
}
}
}
} catch (Exception e) {
handleError(e);
}
However:
/**
* This class is used in a similar way as ServiceManager, except the services registered here
* are not Binder objects and are only available in the same process.
*
* Once all services are converted to the SystemService interface, this class can be absorbed
* into SystemServiceManager.
*
* {@hide}
*/
Alas, I have to be in the SystemUI process (or so it seems) to be able to access it. (Effectively, the ArrayMap
is empty, rendering my attempt useless.)
Any guidance on how to proceed from here would be appreciated.