package fr.frazew.virtualgyroscope; import android.app.AndroidAppHelper; import android.content.Context; import android.content.pm.FeatureInfo; import android.content.pm.PackageManager; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.Build; import android.util.SparseArray; import android.util.SparseIntArray; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import de.robv.android.xposed.IXposedHookLoadPackage; import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedBridge; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; public class XposedMod implements IXposedHookLoadPackage { public static boolean FIRST_LAUNCH_SINCE_BOOT = true; public static float MAGNETIC_RESOLUTION; // @TODO Change this too public static float ACCELEROMETER_RESOLUTION; // @TODO And this public static final SparseIntArray sensorTypetoHandle = new SparseIntArray() {{ append(Sensor.TYPE_ROTATION_VECTOR, 4242); append(Sensor.TYPE_GYROSCOPE, 4243); if (Build.VERSION.SDK_INT >= 19) put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, 4244); append(Sensor.TYPE_GRAVITY, 4245); append(Sensor.TYPE_LINEAR_ACCELERATION, 4246); if (Build.VERSION.SDK_INT >= 18) append(Sensor.TYPE_GAME_ROTATION_VECTOR, 4247); }}; public static final SparseArray<SensorModel> sensorsToEmulate = new SparseArray<SensorModel>() {{ append(Sensor.TYPE_ROTATION_VECTOR, new SensorModel(Sensor.TYPE_ROTATION_VECTOR, "VirtualSensor RotationVector", sensorTypetoHandle.get(Sensor.TYPE_ROTATION_VECTOR), 0.01F, -1, -1, (Build.VERSION.SDK_INT > 19) ? Sensor.STRING_TYPE_ROTATION_VECTOR : "", "none")); append(Sensor.TYPE_GYROSCOPE, new SensorModel(Sensor.TYPE_GYROSCOPE, "VirtualSensor Gyroscope", sensorTypetoHandle.get(Sensor.TYPE_GYROSCOPE), 0.01F, -1, 5460, (Build.VERSION.SDK_INT > 19) ? Sensor.STRING_TYPE_GYROSCOPE : "", "android.hardware.sensor.gyroscope")); if (Build.VERSION.SDK_INT >= 19) append(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, new SensorModel(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, "VirtualSensor GeomagneticRotationVector", sensorTypetoHandle.get(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR), 0.01F, -1, -1, (Build.VERSION.SDK_INT > 19) ? Sensor.STRING_TYPE_GEOMAGNETIC_ROTATION_VECTOR : "", "none")); append(Sensor.TYPE_GRAVITY, new SensorModel(Sensor.TYPE_GRAVITY, "VirtualSensor Gravity", sensorTypetoHandle.get(Sensor.TYPE_GRAVITY), 0.01F, -1, -1, (Build.VERSION.SDK_INT > 19) ? Sensor.STRING_TYPE_GRAVITY : "", "none")); append(Sensor.TYPE_LINEAR_ACCELERATION, new SensorModel(Sensor.TYPE_LINEAR_ACCELERATION, "VirtualSensor LinearAcceleration", sensorTypetoHandle.get(Sensor.TYPE_LINEAR_ACCELERATION), 0.01F, -1, -1, (Build.VERSION.SDK_INT > 19) ? Sensor.STRING_TYPE_LINEAR_ACCELERATION : "", "none")); // Had to use another handle as it broke the magnetic sensor's readings (?!) if (Build.VERSION.SDK_INT >= 18) append(Sensor.TYPE_GAME_ROTATION_VECTOR, new SensorModel(Sensor.TYPE_GAME_ROTATION_VECTOR, "VirtualSensor GameRotationVector", sensorTypetoHandle.get(Sensor.TYPE_GAME_ROTATION_VECTOR), 0.01F, -1, -1, (Build.VERSION.SDK_INT > 19) ? Sensor.STRING_TYPE_GAME_ROTATION_VECTOR : "", "none")); }}; @Override public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { if(lpparam.packageName.equals("android")) { if (FIRST_LAUNCH_SINCE_BOOT) { FIRST_LAUNCH_SINCE_BOOT = false; XposedBridge.log("VirtualSensor: Using version " + BuildConfig.VERSION_NAME); } hookPackageFeatures(lpparam); // @TODO Revisit this hook to make it more flexible } hookSensorValues(lpparam); addSensors(lpparam); enableSensors(lpparam); registerListenerHook(lpparam); hookCardboard(lpparam); } private void hookSensorValues(final LoadPackageParam lpparam) { if (Build.VERSION.SDK_INT >= 24) XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$SensorEventQueue", lpparam.classLoader, "dispatchSensorEvent", int.class, float[].class, int.class, long.class, new fr.frazew.virtualgyroscope.hooks.sensorchange.API24()); else if (Build.VERSION.SDK_INT == 23) XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$SensorEventQueue", lpparam.classLoader, "dispatchSensorEvent", int.class, float[].class, int.class, long.class, new fr.frazew.virtualgyroscope.hooks.sensorchange.API23()); else if (Build.VERSION.SDK_INT >= 18) XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$SensorEventQueue", lpparam.classLoader, "dispatchSensorEvent", int.class, float[].class, int.class, long.class, new fr.frazew.virtualgyroscope.hooks.sensorchange.API18()); else if (Build.VERSION.SDK_INT >= 16) XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$ListenerDelegate", lpparam.classLoader, "onSensorChangedLocked", Sensor.class, float[].class, long[].class, int.class, new fr.frazew.virtualgyroscope.hooks.sensorchange.API16()); else XposedBridge.log("VirtualSensor: Using SDK version " + Build.VERSION.SDK_INT + ", this is not supported"); } @SuppressWarnings("unchecked") private void addSensors(final LoadPackageParam lpparam) { if (Build.VERSION.SDK_INT >= 24) XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.content.Context.class, android.os.Looper.class, new fr.frazew.virtualgyroscope.hooks.constructor.API24(lpparam)); else if (Build.VERSION.SDK_INT == 23) XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.content.Context.class, android.os.Looper.class, new fr.frazew.virtualgyroscope.hooks.constructor.API23(lpparam)); else if (Build.VERSION.SDK_INT >= 18) XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.content.Context.class, android.os.Looper.class, new fr.frazew.virtualgyroscope.hooks.constructor.API18(lpparam)); else if (Build.VERSION.SDK_INT >= 16) XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.os.Looper.class, new fr.frazew.virtualgyroscope.hooks.constructor.API16(lpparam)); else XposedBridge.log("VirtualSensor: Using SDK version " + Build.VERSION.SDK_INT + ", this is not supported"); } private void enableSensors(final LoadPackageParam lpparam) throws XposedHelpers.ClassNotFoundError { if (Build.VERSION.SDK_INT >= 18) { try { XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$BaseEventQueue", lpparam.classLoader, "enableSensor", android.hardware.Sensor.class, int.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (sensorsToEmulate.indexOfKey(((Sensor) param.args[0]).getType()) >= 0 && !sensorsToEmulate.get(((Sensor) param.args[0]).getType()).isAlreadyNative) { param.setResult(0); } super.afterHookedMethod(param); } }); } catch (NoSuchMethodError e) { XposedBridge.log("VirtualSensor: Could not hook the AOSP enableSensor method, trying an alternative hook."); try { XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$BaseEventQueue", lpparam.classLoader, "enableSensor", android.hardware.Sensor.class, int.class, int.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (sensorsToEmulate.indexOfKey(((Sensor) param.args[0]).getType()) >= 0 && !sensorsToEmulate.get(((Sensor) param.args[0]).getType()).isAlreadyNative) { param.setResult(0); } super.afterHookedMethod(param); } }); } catch (NoSuchMethodError e1) { XposedBridge.log("VirtualSensor: The alternative enableSensor hook failed, but the module might still work."); } } } else if (Build.VERSION.SDK_INT >= 16) { XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager", lpparam.classLoader, "enableSensorLocked", android.hardware.Sensor.class, int.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (sensorsToEmulate.indexOfKey(((Sensor) param.args[0]).getType()) >= 0 && !sensorsToEmulate.get(((Sensor) param.args[0]).getType()).isAlreadyNative) { param.setResult(true); } super.afterHookedMethod(param); } }); } else XposedBridge.log("VirtualSensor: Using SDK version " + Build.VERSION.SDK_INT + ", this is not supported"); } private void registerListenerHook(final LoadPackageParam lpparam) { if (Build.VERSION.SDK_INT <= 18) { XposedHelpers.findAndHookMethod("android.hardware.SensorManager", lpparam.classLoader, "registerListener", android.hardware.SensorEventListener.class, android.hardware.Sensor.class, int.class, android.os.Handler.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (param.args[1] == null) return; SensorEventListener listener = (SensorEventListener) param.args[0]; // We check that the listener isn't of type VirtualSensorListener. Although that should not happen, it would probably be nasty. if (sensorsToEmulate.indexOfKey(((Sensor) param.args[1]).getType()) >= 0 && !(listener instanceof VirtualSensorListener) && !sensorsToEmulate.get(((Sensor) param.args[1]).getType()).isAlreadyNative) { SensorEventListener specialListener = new VirtualSensorListener(listener, ((Sensor) param.args[1])); XposedHelpers.callMethod(param.thisObject, "registerListener", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_ACCELEROMETER), param.args[2], param.args[3] ); XposedHelpers.callMethod(param.thisObject, "registerListener", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_MAGNETIC_FIELD), param.args[2], param.args[3] ); param.args[0] = specialListener; } } }); } else { XposedHelpers.findAndHookMethod("android.hardware.SensorManager", lpparam.classLoader, "registerListener", android.hardware.SensorEventListener.class, android.hardware.Sensor.class, int.class, int.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (param.args[1] == null) return; SensorEventListener listener = (SensorEventListener) param.args[0]; // We check that the listener isn't of type VirtualSensorListener. Although that should not happen, it would probably be nasty. if (sensorsToEmulate.indexOfKey(((Sensor) param.args[1]).getType()) >= 0 && !(listener instanceof VirtualSensorListener) && !sensorsToEmulate.get(((Sensor) param.args[1]).getType()).isAlreadyNative) { SensorEventListener specialListener = new VirtualSensorListener(listener, ((Sensor) param.args[1])); XposedHelpers.callMethod(param.thisObject, "registerListenerImpl", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_ACCELEROMETER), XposedHelpers.callStaticMethod(android.hardware.SensorManager.class, "getDelay", param.args[2]), null, param.args[3], 0 ); XposedHelpers.callMethod(param.thisObject, "registerListenerImpl", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_MAGNETIC_FIELD), XposedHelpers.callStaticMethod(android.hardware.SensorManager.class, "getDelay", param.args[2]), null, param.args[3], 0 ); param.args[0] = specialListener; } } }); XposedHelpers.findAndHookMethod("android.hardware.SensorManager", lpparam.classLoader, "registerListener", android.hardware.SensorEventListener.class, android.hardware.Sensor.class, int.class, android.os.Handler.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (param.args[1] == null) return; SensorEventListener listener = (SensorEventListener) param.args[0]; // We check that the listener isn't of type VirtualSensorListener. Although that should not happen, it would probably be nasty. if (sensorsToEmulate.indexOfKey(((Sensor) param.args[1]).getType()) >= 0 && !(listener instanceof VirtualSensorListener) && !sensorsToEmulate.get(((Sensor) param.args[1]).getType()).isAlreadyNative) { SensorEventListener specialListener = new VirtualSensorListener(listener, ((Sensor) param.args[1])); XposedHelpers.callMethod(param.thisObject, "registerListenerImpl", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_ACCELEROMETER), XposedHelpers.callStaticMethod(android.hardware.SensorManager.class, "getDelay", param.args[2]), param.args[3], 0, 0 ); XposedHelpers.callMethod(param.thisObject, "registerListenerImpl", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_MAGNETIC_FIELD), XposedHelpers.callStaticMethod(android.hardware.SensorManager.class, "getDelay", param.args[2]), param.args[3], 0, 0 ); param.args[0] = specialListener; } } }); XposedHelpers.findAndHookMethod("android.hardware.SensorManager", lpparam.classLoader, "registerListener", android.hardware.SensorEventListener.class, android.hardware.Sensor.class, int.class, int.class, android.os.Handler.class, new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (param.args[1] == null) return; SensorEventListener listener = (SensorEventListener) param.args[0]; // We check that the listener isn't of type VirtualSensorListener. Although that should not happen, it would probably be nasty. if (sensorsToEmulate.indexOfKey(((Sensor) param.args[1]).getType()) >= 0 && !(listener instanceof VirtualSensorListener) && !sensorsToEmulate.get(((Sensor) param.args[1]).getType()).isAlreadyNative) { SensorEventListener specialListener = new VirtualSensorListener(listener, ((Sensor) param.args[1])); XposedHelpers.callMethod(param.thisObject, "registerListenerImpl", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_ACCELEROMETER), XposedHelpers.callStaticMethod(android.hardware.SensorManager.class, "getDelay", param.args[2]), param.args[4], param.args[3], 0 ); XposedHelpers.callMethod(param.thisObject, "registerListenerImpl", specialListener, XposedHelpers.callMethod(param.thisObject, "getDefaultSensor", Sensor.TYPE_MAGNETIC_FIELD), XposedHelpers.callStaticMethod(android.hardware.SensorManager.class, "getDelay", param.args[2]), param.args[4], param.args[3], 0 ); param.args[0] = specialListener; } } }); } // This hook does not need to change depending on the SDK version XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager", lpparam.classLoader, "unregisterListenerImpl", android.hardware.SensorEventListener.class, android.hardware.Sensor.class, new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { List<SensorEventListener> listenersToRemove = new ArrayList<>(); for (Map.Entry<Object, Object> entry : ((HashMap<Object, Object>) XposedHelpers.getObjectField(param.thisObject, "mSensorListeners")).entrySet()) { SensorEventListener listener = (SensorEventListener) entry.getKey(); if (listener instanceof VirtualSensorListener) { VirtualSensorListener specialListener = (VirtualSensorListener) listener; if (specialListener.getRealListener() == param.args[0]) { listenersToRemove.add(listener); } } } for (SensorEventListener listener : listenersToRemove) { XposedHelpers.callMethod(param.thisObject, "unregisterListenerImpl", listener, null); } } }); } @SuppressWarnings("unchecked") private void hookPackageFeatures(final LoadPackageParam lpparam) { if (Build.VERSION.SDK_INT >= 21) { Class<?> pkgMgrSrv = XposedHelpers.findClass("com.android.server.SystemConfig", lpparam.classLoader); XposedBridge.hookAllMethods(pkgMgrSrv, "getAvailableFeatures", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { Map<String, FeatureInfo> mAvailableFeatures = (Map<String, FeatureInfo>) param.getResult(); int openGLEsVersion = (int) XposedHelpers.callStaticMethod(XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader), "getInt", "ro.opengles.version", FeatureInfo.GL_ES_VERSION_UNDEFINED); if (!mAvailableFeatures.containsKey(PackageManager.FEATURE_SENSOR_GYROSCOPE)) { FeatureInfo gyro = new FeatureInfo(); gyro.name = PackageManager.FEATURE_SENSOR_GYROSCOPE; gyro.reqGlEsVersion = openGLEsVersion; mAvailableFeatures.put(PackageManager.FEATURE_SENSOR_GYROSCOPE, gyro); } XposedHelpers.setObjectField(param.thisObject, "mAvailableFeatures", mAvailableFeatures); param.setResult(mAvailableFeatures); } }); } else { Class<?> pkgMgrSrv = XposedHelpers.findClass("com.android.server.pm.PackageManagerService", lpparam.classLoader); XposedBridge.hookAllMethods(pkgMgrSrv, "getSystemAvailableFeatures", new XC_MethodHook() { @Override protected void beforeHookedMethod(MethodHookParam param) throws Throwable { if (param.getResult() != null) { Object mPackages = XposedHelpers.getObjectField(param.thisObject, "mPackages"); synchronized (mPackages) { Map<String, FeatureInfo> mAvailableFeatures = (Map<String, FeatureInfo>) XposedHelpers.getObjectField(param.thisObject, "mAvailableFeatures"); int openGLEsVersion = (int) XposedHelpers.callStaticMethod(XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader), "getInt", "ro.opengles.version", FeatureInfo.GL_ES_VERSION_UNDEFINED); if (!mAvailableFeatures.containsKey(PackageManager.FEATURE_SENSOR_GYROSCOPE)) { FeatureInfo gyro = new FeatureInfo(); gyro.name = PackageManager.FEATURE_SENSOR_GYROSCOPE; gyro.reqGlEsVersion = openGLEsVersion; mAvailableFeatures.put(PackageManager.FEATURE_SENSOR_GYROSCOPE, gyro); } XposedHelpers.setObjectField(param.thisObject, "mAvailableFeatures", mAvailableFeatures); } } } }); XposedBridge.hookAllMethods(pkgMgrSrv, "hasSystemFeature", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { if (!(boolean) param.getResult() && param.args[0] == PackageManager.FEATURE_SENSOR_GYROSCOPE) { Object mPackages = XposedHelpers.getObjectField(param.thisObject, "mPackages"); synchronized (mPackages) { Map<String, FeatureInfo> mAvailableFeatures = (Map<String, FeatureInfo>) param.getResult(); int openGLEsVersion = (int) XposedHelpers.callStaticMethod(XposedHelpers.findClass("android.os.SystemProperties", lpparam.classLoader), "getInt", "ro.opengles.version", FeatureInfo.GL_ES_VERSION_UNDEFINED); FeatureInfo gyro = new FeatureInfo(); gyro.name = PackageManager.FEATURE_SENSOR_GYROSCOPE; gyro.reqGlEsVersion = openGLEsVersion; mAvailableFeatures.put(PackageManager.FEATURE_SENSOR_GYROSCOPE, gyro); XposedHelpers.setObjectField(param.thisObject, "mAvailableFeatures", mAvailableFeatures); param.setResult(true); } } } }); } } @SuppressWarnings("unchecked") private void hookCardboard(final LoadPackageParam lpparam) { try { Class headTransformTMP = null; try { headTransformTMP = XposedHelpers.findClass("com.google.vrtoolkit.cardboard.HeadTransform", lpparam.classLoader); } catch (XposedHelpers.ClassNotFoundError e) {} if (headTransformTMP == null) { try { headTransformTMP = XposedHelpers.findClass("com.google.vr.sdk.base.HeadTransform", lpparam.classLoader); } catch (XposedHelpers.ClassNotFoundError e) {} if (headTransformTMP != null) XposedBridge.log("VirtualSensor: Did not find com.google.vrtoolkit.cardboard.HeadTransform but found com.google.vr.sdk.base.HeadTransform"); } final Class headTransform = headTransformTMP; final int sensorToUse = Build.VERSION.SDK_INT >= 18 ? Sensor.TYPE_GAME_ROTATION_VECTOR : Sensor.TYPE_ROTATION_VECTOR; if (headTransform != null) { XposedBridge.log("VirtualSensor: Found the Google Cardboard library in " + lpparam.packageName + ", hooking HeadTransform"); XposedHelpers.findAndHookConstructor(headTransform, new XC_MethodHook() { @Override protected void afterHookedMethod(final MethodHookParam param) throws Throwable { SensorManager mgr = (SensorManager) AndroidAppHelper.currentApplication().getSystemService(Context.SENSOR_SERVICE); SensorEventListener listener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { if (event.sensor.getType() == sensorToUse) { if (event.values != null) { try { Field htMatrix = XposedHelpers.findFirstFieldByExactType(headTransform, float[].class); float[] rotationMatrix = (float[]) htMatrix.get(param.thisObject); SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values); XposedHelpers.setObjectField(param.thisObject, htMatrix.getName(), rotationMatrix); } catch (Exception e) { e.printStackTrace(); } } } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }; mgr.registerListener(listener, mgr.getDefaultSensor(sensorToUse), mgr.getDefaultSensor(sensorToUse).getMinDelay()); super.afterHookedMethod(param); } }); } } catch (Exception e) {e.printStackTrace();} } }