8

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.

Community
  • 1
  • 1
Jonathan Precise
  • 327
  • 3
  • 12
  • 1
    That is the problem with methods which are invoked via reflection the APIs can change. – rekire Jan 05 '17 at 19:16
  • That method is also not necessarily available on any given device, as device manufacturers and custom ROM developers are welcome to modify Android internals as they see fit. – CommonsWare Jan 05 '17 at 19:17
  • @CommonsWare true, but I at least need it to work on stock Android. – Jonathan Precise Jan 05 '17 at 19:21
  • Ok, just made another attempt at this and failed. Is there I can access the same map that is holding the `StatusBarInternal`? Or someway to access that `StatusBarInternal` instance? Or at least an `IStatusBar`? – Jonathan Precise Jan 07 '17 at 00:59

1 Answers1

4

Create service extended from AccessibilityService:

class ExampleAccessService:AccessibilityService() {
    override fun onInterrupt() {
    }

    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
    }

    fun doAction(){
        performGlobalAction(GLOBAL_ACTION_RECENTS)
//        performGlobalAction(GLOBAL_ACTION_BACK)
//        performGlobalAction(GLOBAL_ACTION_HOME)
//        performGlobalAction(GLOBAL_ACTION_NOTIFICATIONS)
//        performGlobalAction(GLOBAL_ACTION_POWER_DIALOG)
//        performGlobalAction(GLOBAL_ACTION_QUICK_SETTINGS)
//        performGlobalAction(GLOBAL_ACTION_TOGGLE_SPLIT_SCREEN)
    }
}

Call doAction() where you want action

Add to Manifest:

<application
...
    <service
        android:name=".ExampleAccessService"
        android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
        android:label="Name of servise" // it will be viewed in Settings->Accessibility->Services
        android:enabled="true"
        android:exported="false" >
        <intent-filter>
            <action android:name="android.accessibilityservice.AccessibilityService"/>
        </intent-filter>
        <meta-data
            android:name="android.accessibilityservice"
            android:resource="@xml/accessibility_service_config"/>
    </service>
...
</application>

accessibility_service_config.xml:

<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackAllMask"
    android:accessibilityFlags="flagDefault"
    android:canRetrieveWindowContent="false"
    android:description="your description"
    android:notificationTimeout="100"
    android:packageNames="your app package, ex: ex: com.example.android"
    android:settingsActivity="your settings activity ex: com.example.android.MainActivity" />

for more info look at https://developer.android.com/guide/topics/ui/accessibility/services.html

Evgenii Vorobei
  • 1,457
  • 18
  • 20
  • In short: I've tried the solution and can not get the action, probably missing some initialization. – Arkady Apr 25 '18 at 11:19
  • 1
    Did you turn on Accessibility service on device? Settings -> Accessibility -> Services-> your service name (at top of screen) -> turn it on – Evgenii Vorobei Apr 25 '18 at 12:02
  • public class ExampleAccessService extends AccessibilityService {private static ExampleAccessService instance;@Override protected void onServiceConnected(){super.onServiceConnected();instance=this;}@Override public boolean onUnbind(Intent intent){instance=null;return super.onUnbind(intent);}@Override public void onInterrupt(){}@Override public void onAccessibilityEvent(AccessibilityEvent event){} public static ExampleAccessService getInstance(){return instance;}@RequiresApi(api=Build.VERSION_CODES.JELLY_BEAN) public void openRecents(){performGlobalAction(GLOBAL_ACTION_RECENTS);}} – Michael Kern Mar 26 '21 at 00:47