package me.ele.amigo.hook; import android.app.Service; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ServiceInfo; import android.os.Build; import android.os.IBinder; import android.util.Pair; import java.lang.reflect.Constructor; import java.util.HashMap; import java.util.Map; import me.ele.amigo.compat.ActivityThreadCompat; import me.ele.amigo.compat.CompatibilityInfoCompat; import me.ele.amigo.compat.QueuedWorkCompat; import me.ele.amigo.reflect.FieldUtils; import me.ele.amigo.reflect.MethodUtils; import me.ele.amigo.utils.component.ServiceFinder; import static me.ele.amigo.AmigoInstrumentation.EXTRA_TARGET_INTENT; public class ServiceManager { private Map<Object, Service> mTokenServices = new HashMap<>(); private Map<String, Service> mNameService = new HashMap<>(); private Map<Object, Integer> mServiceTaskIds = new HashMap<>(); private Map<Object, Pair<Intent, Object>> mBindServiceRecord = new HashMap<>(); private ServiceManager() { } private static ServiceManager sServiceManager; public static ServiceManager getDefault() { synchronized (ServiceManager.class) { if (sServiceManager == null) { sServiceManager = new ServiceManager(); } } return sServiceManager; } public boolean hasServiceRunning() { return mTokenServices.size() > 0 && mNameService.size() > 0; } private Object findTokenByService(Service service) { for (Object s : mTokenServices.keySet()) { if (mTokenServices.get(s) == service) { return s; } } return null; } public void addBindServiceRecord(Object connection, Intent intent, Object proxyConnection) { mBindServiceRecord.put(connection, new Pair<>(intent, proxyConnection)); } private ClassLoader getClassLoader(Context context) { return context.getClassLoader(); } private void handleCreateServiceOne(ServiceInfo info) throws Exception { Object activityThread = ActivityThreadCompat.instance(); IBinder fakeToken = new MyFakeIBinder(); Class CreateServiceData = Class.forName(ActivityThreadCompat.clazz().getName() + "$CreateServiceData"); Constructor init = CreateServiceData.getDeclaredConstructor(); if (!init.isAccessible()) { init.setAccessible(true); } Object data = init.newInstance(); FieldUtils.writeField(data, "token", fakeToken); FieldUtils.writeField(data, "info", info); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { FieldUtils.writeField(data, "compatInfo", CompatibilityInfoCompat .DEFAULT_COMPATIBILITY_INFO()); } MethodUtils.invokeMethod(activityThread, "handleCreateService", data); Object mService = FieldUtils.readField(activityThread, "mServices"); Service service = (Service) MethodUtils.invokeMethod(mService, "get", fakeToken); MethodUtils.invokeMethod(mService, "remove", fakeToken); mTokenServices.put(fakeToken, service); mNameService.put(info.name, service); } private int handleOnStartOne(Context context, Intent intent, int flags) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, intent); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(context); intent.setExtrasClassLoader(classLoader); Object token = findTokenByService(service); Integer integer = mServiceTaskIds.get(token); if (integer == null) { integer = -1; } int startId = integer + 1; mServiceTaskIds.put(token, startId); int res = service.onStartCommand(intent, flags, startId); QueuedWorkCompat.waitToFinish(); return res; } } return -1; } private void handleOnTaskRemovedOne(Context context, Intent intent) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, intent); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(context); intent.setExtrasClassLoader(classLoader); service.onTaskRemoved(intent); } QueuedWorkCompat.waitToFinish(); } } } // TODO check if the service was ready to be destroyed ? private void handleOnDestroyOne(ServiceInfo targetInfo) { Service service = mNameService.get(targetInfo.name); if (service != null) { service.onDestroy(); mNameService.remove(targetInfo.name); Object token = findTokenByService(service); mTokenServices.remove(token); mServiceTaskIds.remove(token); } QueuedWorkCompat.waitToFinish(); } private IBinder handleOnBindOne(Context context, Intent intent) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, intent); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(context); intent.setExtrasClassLoader(classLoader); return service.onBind(intent); } } return null; } private void handleOnRebindOne(Context context, Intent intent) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, intent); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(context); intent.setExtrasClassLoader(classLoader); service.onRebind(intent); } } } private boolean handleOnUnbindOne(Context context, Intent intent) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, intent); if (info != null) { Service service = mNameService.get(info.name); if (service != null) { ClassLoader classLoader = getClassLoader(context); intent.setExtrasClassLoader(classLoader); return service.onUnbind(intent); } } return false; } public int onStart(Context context, Intent intent, int flags, int startId) throws Exception { Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo targetInfo = ServiceFinder.resolveNewServiceInfo(context, targetIntent); if (targetInfo != null) { Service service = mNameService.get(targetInfo.name); if (service == null) { handleCreateServiceOne(targetInfo); } return handleOnStartOne(context, targetIntent, flags); } } return -1; } public void onTaskRemoved(Context context, Intent intent) throws Exception { Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, intent); Service service = mNameService.get(info.name); if (service == null) { handleCreateServiceOne(info); } handleOnTaskRemovedOne(context, targetIntent); } } public IBinder onBind(Context context, Intent intent) throws Exception { Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, targetIntent); Service service = mNameService.get(info.name); if (service == null) { handleCreateServiceOne(info); } return handleOnBindOne(context, targetIntent); } return null; } public void onRebind(Context context, Intent intent) throws Exception { Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, targetIntent); Service service = mNameService.get(info.name); if (service == null) { handleCreateServiceOne(info); } handleOnRebindOne(context, targetIntent); } } public boolean onUnbind(Context context, Intent intent) { Intent targetIntent = intent.getParcelableExtra(EXTRA_TARGET_INTENT); if (targetIntent != null) { ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, targetIntent); Service service = mNameService.get(info.name); if (service != null) { return handleOnUnbindOne(context, targetIntent); } } return false; } public boolean unbind(Context context, Object connection) { if (!mBindServiceRecord.containsKey(connection)) { return false; } Intent intent = mBindServiceRecord.remove(connection).first; if (intent != null) return onUnbind(context, intent); return false; } public Object getProxyConnection(Object connection) { Pair<Intent, Object> value = mBindServiceRecord.get(connection); return value != null ? value.second : null; } public int stopService(Context context, Intent intent) { ServiceInfo targetInfo = ServiceFinder.resolveNewServiceInfo(context, intent); if (targetInfo != null) { handleOnUnbindOne(context, intent); handleOnDestroyOne(targetInfo); return 1; } return 0; } public boolean stopServiceToken(Context context, ComponentName cn, IBinder token, int startId) { Service service = mTokenServices.get(token); if (service != null) { Integer lastId = mServiceTaskIds.get(token); if (lastId == null) { return false; } if (startId != lastId) { return false; } Intent intent = new Intent(); intent.setComponent(cn); ServiceInfo info = ServiceFinder.resolveNewServiceInfo(context, intent); if (info != null) { handleOnUnbindOne(context, intent); handleOnDestroyOne(info); return true; } } return false; } public void onDestroy() { for (Service service : mTokenServices.values()) { service.onDestroy(); } mTokenServices.clear(); mServiceTaskIds.clear(); mNameService.clear(); QueuedWorkCompat.waitToFinish(); } }