From 8507845fde65324fca2a3f599946f6a6ae252817 Mon Sep 17 00:00:00 2001 From: Frazew Date: Mon, 25 Jul 2016 14:32:53 +0200 Subject: [PATCH] Support for SDK16 up to SDK23+. Previous versions actually only worked for SDK23+. This lead to a quite big code refactor. It is cleaner now and changing values / adding sensors doesn't mean changing a lot of code. This refactoring allows to avoid code duplication due to the different SDK versions. Hopefully it should work ! --- app/build.gradle | 6 +- .../fr/frazew/virtualgyroscope/XposedMod.java | 419 +++--------------- .../hooks/SensorChangeHook.java | 356 +++++++++++++++ .../hooks/SystemSensorManagerHook.java | 152 +++++++ 4 files changed, 582 insertions(+), 351 deletions(-) create mode 100644 app/src/main/java/fr/frazew/virtualgyroscope/hooks/SensorChangeHook.java create mode 100644 app/src/main/java/fr/frazew/virtualgyroscope/hooks/SystemSensorManagerHook.java diff --git a/app/build.gradle b/app/build.gradle index 58f448c..a719a6b 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,10 +6,10 @@ android { defaultConfig { applicationId "fr.frazew.virtualgyroscope" - minSdkVersion 19 + minSdkVersion 16 targetSdkVersion 23 - versionCode 100 - versionName "1.0" + versionCode 110 + versionName "1.1" } buildTypes { release { diff --git a/app/src/main/java/fr/frazew/virtualgyroscope/XposedMod.java b/app/src/main/java/fr/frazew/virtualgyroscope/XposedMod.java index 92b5eac..6e91311 100644 --- a/app/src/main/java/fr/frazew/virtualgyroscope/XposedMod.java +++ b/app/src/main/java/fr/frazew/virtualgyroscope/XposedMod.java @@ -6,14 +6,13 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.util.ArrayMap; +import android.os.Build; import android.util.SparseArray; import de.robv.android.xposed.XposedBridge; import java.util.ArrayList; import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Map; @@ -21,11 +20,12 @@ import de.robv.android.xposed.XC_MethodHook; import de.robv.android.xposed.XposedHelpers; import de.robv.android.xposed.callbacks.XC_LoadPackage.LoadPackageParam; +import fr.frazew.virtualgyroscope.hooks.SensorChangeHook; +import fr.frazew.virtualgyroscope.hooks.SystemSensorManagerHook; public class XposedMod implements IXposedHookLoadPackage { - private static final float NS2S = 1.0f / 1000000000.0f; - private static final SparseArray sensorsToEmulate = new SparseArray() {{ + public static final SparseArray sensorsToEmulate = new SparseArray() {{ put(Sensor.TYPE_ROTATION_VECTOR, new SensorModel(Sensor.TYPE_ROTATION_VECTOR, "VirtualSensor RotationVector", -1, 0.01F, -1, -1)); put(Sensor.TYPE_GYROSCOPE, new SensorModel(Sensor.TYPE_GYROSCOPE, "VirtualSensor Gyroscope", -1, 0.01F, -1, (float)Math.PI)); put(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, new SensorModel(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR, "VirtualSensor GeomagneticRotationVector", -1, 0.01F, -1, -1)); @@ -36,7 +36,8 @@ public class XposedMod implements IXposedHookLoadPackage { @Override public void handleLoadPackage(final LoadPackageParam lpparam) throws Throwable { if(lpparam.packageName.equals("android")) { - hookPackageFeatures(lpparam); + // Disabled temporarily as it is not yet compatible with all supported SDK versions (at least I haven't verified wether it is or not) + //hookPackageFeatures(lpparam); } hookSensorValues(lpparam); addSensors(lpparam); @@ -63,7 +64,7 @@ private void hookPackageFeatures(final LoadPackageParam lpparam) { XposedBridge.hookAllMethods(pkgMgrSrv, "getAvailableFeatures", new XC_MethodHook() { @Override protected void afterHookedMethod(MethodHookParam param) throws Throwable { - ArrayMap mAvailableFeatures = (ArrayMap) param.getResult(); + Map mAvailableFeatures = (Map) 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(); @@ -76,317 +77,84 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { } }); } - - @SuppressWarnings("unchecked") private void hookSensorValues(final LoadPackageParam lpparam) { - XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$SensorEventQueue", lpparam.classLoader, "dispatchSensorEvent", int.class, float[].class, int.class, long.class, new XC_MethodHook() { - // Noise reduction - float lastFilterValues[][] = new float[3][10]; - float prevValues[] = new float[3]; - - //Sensor values - float[] magneticValues = new float[3]; - float[] accelerometerValues = new float[3]; - - //Keeping track of the previous rotation matrix and timestamp - float[] prevRotationMatrix = new float[9]; - long prevTimestamp = 0; - - @Override - protected void beforeHookedMethod(MethodHookParam param) throws Throwable { - super.beforeHookedMethod(param); - - Object listener = XposedHelpers.getObjectField(param.thisObject, "mListener"); - if (listener instanceof VirtualSensorListener) { // This is our custom listener type, we can start working on the values - int handle = (int) param.args[0]; - Object mgr = XposedHelpers.getObjectField(param.thisObject, "mManager"); - SparseArray sensors = (SparseArray) XposedHelpers.getObjectField(mgr, "mHandleToSensor"); - Sensor s = sensors.get(handle); - - //All calculations need data from these two sensors, we can safely get their value every time - if (s.getType() == Sensor.TYPE_ACCELEROMETER) { - this.accelerometerValues = ((float[]) (param.args[1])).clone(); - } - if (s.getType() == Sensor.TYPE_MAGNETIC_FIELD) { - this.magneticValues = ((float[]) (param.args[1])).clone(); - } - - VirtualSensorListener virtualListener = (VirtualSensorListener) listener; - - // Per default, we set the sensor to null so that it doesn't accidentally send the accelerometer's values - virtualListener.sensorRef = null; - - // We only work when it's an accelerometer's reading. If we were to work on both events, the timeDifference for the gyro would often be 0 resulting in NaN or Infinity - if (s.getType() == Sensor.TYPE_ACCELEROMETER) { - - if (virtualListener.getSensor().getType() == Sensor.TYPE_GYROSCOPE) { - float timeDifference = Math.abs((float) ((long) param.args[3] - this.prevTimestamp) * NS2S); - List valuesList = getGyroscopeValues(this.accelerometerValues, this.magneticValues, this.prevRotationMatrix, timeDifference); - if (timeDifference != 0.0F) { - this.prevTimestamp = (long) param.args[3]; - this.prevRotationMatrix = (float[]) valuesList.get(1); - float[] values = (float[]) valuesList.get(0); - - if (Float.isNaN(values[0]) || Float.isInfinite(values[0])) - XposedBridge.log("VirtualSensor: Value #" + 0 + " is NaN or Infinity, this should not happen"); - - if (Float.isNaN(values[1]) || Float.isInfinite(values[1])) - XposedBridge.log("VirtualSensor: Value #" + 1 + " is NaN or Infinity, this should not happen"); - - if (Float.isNaN(values[2]) || Float.isInfinite(values[2])) - XposedBridge.log("VirtualSensor: Value #" + 2 + " is NaN or Infinity, this should not happen"); - - - List filter = filterValues(values, lastFilterValues, prevValues); - values = (float[]) filter.get(0); - this.prevValues = (float[]) filter.get(1); - this.lastFilterValues = (float[][]) filter.get(2); - - System.arraycopy(values, 0, (float[]) param.args[1], 0, values.length); - virtualListener.sensorRef = sensors.get(Sensor.TYPE_GYROSCOPE); - } - } else if (virtualListener.getSensor().getType() == Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR || virtualListener.getSensor().getType() == Sensor.TYPE_ROTATION_VECTOR) { - float[] values = new float[5]; - float[] rotationMatrix = new float[9]; - SensorManager.getRotationMatrix(rotationMatrix, null, this.accelerometerValues, this.magneticValues); - float[] quaternion = rotationMatrixToQuaternion(rotationMatrix); - - float angle = 2 * (float) Math.asin(quaternion[0]); - values[0] = quaternion[1]; - values[1] = quaternion[2]; - values[2] = quaternion[3]; - values[3] = quaternion[0]; - values[4] = -1; - - System.arraycopy(values, 0, (float[]) param.args[1], 0, values.length); - if (virtualListener.getSensor().getType() == Sensor.TYPE_ROTATION_VECTOR) - virtualListener.sensorRef = sensors.get(Sensor.TYPE_ROTATION_VECTOR); - else - virtualListener.sensorRef = sensors.get(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR); - } else if (virtualListener.getSensor().getType() == Sensor.TYPE_GRAVITY) { - float[] values = new float[3]; - float[] rotationMatrix = new float[9]; - float[] gravity = new float[] {0F, 0F, 9.81F}; - - SensorManager.getRotationMatrix(rotationMatrix, null, this.accelerometerValues, this.magneticValues); - float x = rotationMatrix[0] * this.accelerometerValues[0] + rotationMatrix[1] * this.accelerometerValues[1] + rotationMatrix[2] * this.accelerometerValues[2]; - float y = rotationMatrix[3] * this.accelerometerValues[0] + rotationMatrix[4] * this.accelerometerValues[1] + rotationMatrix[5] * this.accelerometerValues[2]; - float z = rotationMatrix[6] * this.accelerometerValues[0] + rotationMatrix[7] * this.accelerometerValues[1] + rotationMatrix[8] * this.accelerometerValues[2]; - - float[] gravityRot = new float[3]; - gravityRot[0] = gravity[0] * rotationMatrix[0] + gravity[1] * rotationMatrix[3] + gravity[2] * rotationMatrix[6]; - gravityRot[1] = gravity[0] * rotationMatrix[1] + gravity[1] * rotationMatrix[4] + gravity[2] * rotationMatrix[7]; - gravityRot[2] = gravity[0] * rotationMatrix[2] + gravity[1] * rotationMatrix[5] + gravity[2] * rotationMatrix[8]; - - values[0] = gravityRot[0]; - values[1] = gravityRot[1]; - values[2] = gravityRot[2]; - - System.arraycopy(values, 0, (float[]) param.args[1], 0, values.length); - virtualListener.sensorRef = sensors.get(Sensor.TYPE_GRAVITY); - } else if (virtualListener.getSensor().getType() == Sensor.TYPE_LINEAR_ACCELERATION) { - float[] values = new float[3]; - float[] rotationMatrix = new float[9]; - float[] gravity = new float[] {0F, 0F, 9.81F}; - - SensorManager.getRotationMatrix(rotationMatrix, null, this.accelerometerValues, this.magneticValues); - float x = rotationMatrix[0] * this.accelerometerValues[0] + rotationMatrix[1] * this.accelerometerValues[1] + rotationMatrix[2] * this.accelerometerValues[2]; - float y = rotationMatrix[3] * this.accelerometerValues[0] + rotationMatrix[4] * this.accelerometerValues[1] + rotationMatrix[5] * this.accelerometerValues[2]; - float z = rotationMatrix[6] * this.accelerometerValues[0] + rotationMatrix[7] * this.accelerometerValues[1] + rotationMatrix[8] * this.accelerometerValues[2]; - - float[] gravityRot = new float[3]; - gravityRot[0] = gravity[0] * rotationMatrix[0] + gravity[1] * rotationMatrix[3] + gravity[2] * rotationMatrix[6]; - gravityRot[1] = gravity[0] * rotationMatrix[1] + gravity[1] * rotationMatrix[4] + gravity[2] * rotationMatrix[7]; - gravityRot[2] = gravity[0] * rotationMatrix[2] + gravity[1] * rotationMatrix[5] + gravity[2] * rotationMatrix[8]; - - values[0] = this.accelerometerValues[0] - gravityRot[0]; - values[1] = this.accelerometerValues[1] - gravityRot[1]; - values[2] = this.accelerometerValues[2] - gravityRot[2]; - - System.arraycopy(values, 0, (float[]) param.args[1], 0, values.length); - virtualListener.sensorRef = sensors.get(Sensor.TYPE_LINEAR_ACCELERATION); - } - } - } - } - }); - } - - /* - This uses the Hamilton product to multiply the vector converted to a quaternion with the rotation quaternion. - Returns a new quaternion which is the rotated vector. - Source: https://en.wikipedia.org/wiki/Quaternion#Hamilton_product - */ - public float[] rotateVectorByQuaternion(float[] vector, float[] quaternion) { - float a = vector[0]; - float b = vector[1]; - float c = vector[2]; - float d = vector[3]; - - float A = quaternion[0]; - float B = quaternion[1]; - float C = quaternion[2]; - float D = quaternion[3]; - - float newQuaternionReal = a*A - b*B - c*C - d*D; - float newQuaternioni = a*B + b*A + c*D - d*C; - float newQuaternionj = a*C - b*D + c*A + d*B; - float newQuaternionk = a*D + b*C - c*B + d*A; - - return new float[] {newQuaternionReal, newQuaternioni, newQuaternionj, newQuaternionk}; - } - - - /* - Credit for this code goes to http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ - Additional credit goes to https://en.wikipedia.org/wiki/Quaternion for helping me understand how quaternions work - */ - private float[] rotationMatrixToQuaternion(float[] rotationMatrix) { - float m00 = rotationMatrix[0]; - float m01 = rotationMatrix[1]; - float m02 = rotationMatrix[2]; - float m10 = rotationMatrix[3]; - float m11 = rotationMatrix[4]; - float m12 = rotationMatrix[5]; - float m20 = rotationMatrix[6]; - float m21 = rotationMatrix[7]; - float m22 = rotationMatrix[8]; - - float tr = m00 + m11 + m22; - - float qw; - float qx; - float qy; - float qz; - if (tr > 0) { - float S = (float)Math.sqrt(tr+1.0) * 2; - qw = 0.25F * S; - qx = (m21 - m12) / S; - qy = (m02 - m20) / S; - qz = (m10 - m01) / S; - } else if ((m00 > m11)&(m00 > m22)) { - float S = (float)Math.sqrt(1.0 + m00 - m11 - m22) * 2; - qw = (m21 - m12) / S; - qx = 0.25F * S; - qy = (m01 + m10) / S; - qz = (m02 + m20) / S; - } else if (m11 > m22) { - float S = (float)Math.sqrt(1.0 + m11 - m00 - m22) * 2; - qw = (m02 - m20) / S; - qx = (m01 + m10) / S; - qy = 0.25F * S; - qz = (m12 + m21) / S; + if (Build.VERSION.SDK_INT >= 18) { + XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$SensorEventQueue", lpparam.classLoader, "dispatchSensorEvent", int.class, float[].class, int.class, long.class, new SensorChangeHook.API18Plus(lpparam)); } else { - float S = (float)Math.sqrt(1.0 + m22 - m00 - m11) * 2; - qw = (m10 - m01) / S; - qx = (m02 + m20) / S; - qy = (m12 + m21) / S; - qz = 0.25F * S; + XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager$ListenerDelegate", lpparam.classLoader, "onSensorChangedLocked", Sensor.class, float[].class, long[].class, int.class, new SensorChangeHook.API1617(lpparam)); } - return new float[] {qw, qx, qy, qz}; - } - - private List getGyroscopeValues(float[] currentAccelerometer, float[] currentMagnetic, float[] prevRotationMatrix, float timeDifference) { - float[] angularRates = new float[] {0.0F, 0.0F, 0.0F}; - - float[] currentRotationMatrix = new float[9]; - SensorManager.getRotationMatrix(currentRotationMatrix, null, currentAccelerometer, currentMagnetic); - - SensorManager.getAngleChange(angularRates, currentRotationMatrix, prevRotationMatrix); - angularRates[0] = -(angularRates[1]*2) / timeDifference; - angularRates[1] = (angularRates[2]) / timeDifference; - angularRates[2] = ((angularRates[0]/2) / timeDifference)*0.0F; //Right now this returns weird values, need to look into it @TODO - - List returnList = new ArrayList<>(); - returnList.add(angularRates); - returnList.add(currentRotationMatrix); - return returnList; } @SuppressWarnings("unchecked") private void addSensors(final LoadPackageParam lpparam) { - XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.content.Context.class, android.os.Looper.class, new XC_MethodHook() { - @Override - protected void afterHookedMethod(MethodHookParam param) throws Throwable { - super.afterHookedMethod(param); - ArrayList mFullSensorsList = (ArrayList) XposedHelpers.getObjectField(param.thisObject, "mFullSensorsList"); - Iterator iterator = mFullSensorsList.iterator(); - SparseArray mHandleToSensor = (SparseArray) XposedHelpers.getObjectField(param.thisObject, "mHandleToSensor"); - int minDelayAccelerometer = mHandleToSensor.get(Sensor.TYPE_ACCELEROMETER).getMinDelay(); + // SystemSensorManager constructor hook, starting from SDK16 + if (Build.VERSION.SDK_INT == 16 || Build.VERSION.SDK_INT == 17) XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.os.Looper.class, new SystemSensorManagerHook.API1617(lpparam)); + else if (Build.VERSION.SDK_INT > 17 && Build.VERSION.SDK_INT < 23) XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.content.Context.class, android.os.Looper.class, new SystemSensorManagerHook.API18Plus(lpparam)); + else XposedHelpers.findAndHookConstructor("android.hardware.SystemSensorManager", lpparam.classLoader, android.content.Context.class, android.os.Looper.class, new SystemSensorManagerHook.API23Plus(lpparam)); - while (iterator.hasNext()) { - Sensor sensor = iterator.next(); - if (sensorsToEmulate.indexOfKey(sensor.getType()) >= 0) { - sensorsToEmulate.get(sensor.getType()).alreadyThere = true; + // registerListenerImpl hook + if (Build.VERSION.SDK_INT <= 18) { + XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager", lpparam.classLoader, "registerListenerImpl", 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)) { + 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]), + (android.os.Handler) param.args[3] + ); + 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]), + (android.os.Handler) param.args[3] + ); + + param.args[0] = specialListener; } } - - XposedHelpers.findConstructorBestMatch(android.hardware.Sensor.class).setAccessible(true); - XposedHelpers.findMethodBestMatch(android.hardware.Sensor.class, "setType", Integer.class).setAccessible(true); - XposedHelpers.findField(android.hardware.Sensor.class, "mName").setAccessible(true); - XposedHelpers.findField(android.hardware.Sensor.class, "mVendor").setAccessible(true); - XposedHelpers.findField(android.hardware.Sensor.class, "mVersion").setAccessible(true); - XposedHelpers.findField(android.hardware.Sensor.class, "mHandle").setAccessible(true); - XposedHelpers.findField(android.hardware.Sensor.class, "mResolution").setAccessible(true); - XposedHelpers.findField(android.hardware.Sensor.class, "mMinDelay").setAccessible(true); - XposedHelpers.findField(android.hardware.Sensor.class, "mMaxRange").setAccessible(true); - XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "mFullSensorsList").setAccessible(true); - XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "mHandleToSensor").setAccessible(true); - - for (int i = 0; i < sensorsToEmulate.size(); i++) { - SensorModel model = sensorsToEmulate.valueAt(i); - if (!model.alreadyThere) { - Sensor s = (Sensor) XposedHelpers.findConstructorBestMatch(android.hardware.Sensor.class).newInstance(); - XposedHelpers.callMethod(s, "setType", sensorsToEmulate.keyAt(i)); - XposedHelpers.setObjectField(s, "mName", model.name); - XposedHelpers.setObjectField(s, "mVendor", "Frazew"); - XposedHelpers.setObjectField(s, "mVersion", BuildConfig.VERSION_CODE); - XposedHelpers.setObjectField(s, "mHandle", model.handle); - XposedHelpers.setObjectField(s, "mMinDelay", model.minDelay == -1 ? minDelayAccelerometer : model.minDelay); - XposedHelpers.setObjectField(s, "mResolution", model.resolution == -1 ? 0.01F : model.resolution); // This 0.01F is a placeholder, it doesn't seem to change anything but I keep it - if (model.maxRange != -1) - XposedHelpers.setObjectField(s, "mMaxRange", model.maxRange); - mFullSensorsList.add(s); - mHandleToSensor.append(model.handle, s); + }); + } else { + XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager", lpparam.classLoader, "registerListenerImpl", android.hardware.SensorEventListener.class, android.hardware.Sensor.class, int.class, android.os.Handler.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)) { + 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]), + (android.os.Handler) 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]), + (android.os.Handler) param.args[3], + 0, + 0 + ); + + param.args[0] = specialListener; } } + }); + } - XposedHelpers.setObjectField(param.thisObject, "mHandleToSensor", mHandleToSensor); - XposedHelpers.setObjectField(param.thisObject, "mFullSensorsList", mFullSensorsList); - } - }); - - XposedHelpers.findAndHookMethod("android.hardware.SystemSensorManager", lpparam.classLoader, "registerListenerImpl", android.hardware.SensorEventListener.class, android.hardware.Sensor.class, int.class, android.os.Handler.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)) { - 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]), - (android.os.Handler) 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]), - (android.os.Handler) param.args[3], - 0, - 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 { @@ -403,49 +171,4 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable { } }); } - - private static List filterValues(float[] values, float[][] lastFilterValues, float[] prevValues) { - if (Float.isInfinite(values[0]) || Float.isNaN(values[0])) values[0] = prevValues[0]; - if (Float.isInfinite(values[1]) || Float.isNaN(values[1])) values[1] = prevValues[1]; - if (Float.isInfinite(values[2]) || Float.isNaN(values[2])) values[2] = prevValues[2]; - - float[][] newLastFilterValues = new float[3][10]; - for (int i = 0; i < 3; i++) { - // Apply lowpass on the value - float alpha = 0.1F; - float newValue = lowPass(alpha, values[i], prevValues[i]); - //float newValue = values[i]; - - for (int j = 0; j < 10; j++) { - if (j == 0) continue; - newLastFilterValues[i][j-1] = lastFilterValues[i][j]; - } - newLastFilterValues[i][9] = newValue; - - float sum = 0F; - for (int j = 0; j < 10; j++) { - sum += lastFilterValues[i][j]; - } - newValue = sum/10; - - //The gyroscope is moving even after lowpass - if (newValue != 0.0F) { - //We are under the declared resolution of the gyroscope, so the value should be 0 - if (Math.abs(newValue) < 0.01F) newValue = 0.0F; - } - - prevValues[i] = values[i]; - values[i] = newValue; - } - - List returnValue = new ArrayList<>(); - returnValue.add(values); - returnValue.add(prevValues); - returnValue.add(newLastFilterValues); - return returnValue; - } - - private static float lowPass(float alpha, float value, float prev) { - return prev + alpha * (value - prev); - } } \ No newline at end of file diff --git a/app/src/main/java/fr/frazew/virtualgyroscope/hooks/SensorChangeHook.java b/app/src/main/java/fr/frazew/virtualgyroscope/hooks/SensorChangeHook.java new file mode 100644 index 0000000..6efd8b7 --- /dev/null +++ b/app/src/main/java/fr/frazew/virtualgyroscope/hooks/SensorChangeHook.java @@ -0,0 +1,356 @@ +package fr.frazew.virtualgyroscope.hooks; + +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.util.SparseArray; + +import java.util.ArrayList; +import java.util.List; + +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; +import fr.frazew.virtualgyroscope.VirtualSensorListener; + +public class SensorChangeHook { + private static final float NS2S = 1.0f / 1000000000.0f; + + public static List changeSensorValues(Sensor s, float[] accelerometerValues, float[] magneticValues, Object listener, float[] prevRotationMatrix, long timestamp, long prevTimestamp, + float[] prevValues, float[][] lastFilterValues, SparseArray sensors) { + float[] returnValues = new float[3]; + VirtualSensorListener virtualListener = (VirtualSensorListener) listener; + + // Per default, we set the sensor to null so that it doesn't accidentally send the accelerometer's values + virtualListener.sensorRef = null; + + // We only work when it's an accelerometer's reading. If we were to work on both events, the timeDifference for the gyro would often be 0 resulting in NaN or Infinity + if (s.getType() == Sensor.TYPE_ACCELEROMETER) { + + if (virtualListener.getSensor().getType() == Sensor.TYPE_GYROSCOPE) { + float timeDifference = Math.abs((float) (timestamp - prevTimestamp) * NS2S); + List valuesList = getGyroscopeValues(accelerometerValues, magneticValues, prevRotationMatrix, timeDifference); + if (timeDifference != 0.0F) { + prevTimestamp = timestamp; + prevRotationMatrix = (float[]) valuesList.get(1); + float[] values = (float[]) valuesList.get(0); + + if (Float.isNaN(values[0]) || Float.isInfinite(values[0])) + XposedBridge.log("VirtualSensor: Value #" + 0 + " is NaN or Infinity, this should not happen"); + + if (Float.isNaN(values[1]) || Float.isInfinite(values[1])) + XposedBridge.log("VirtualSensor: Value #" + 1 + " is NaN or Infinity, this should not happen"); + + if (Float.isNaN(values[2]) || Float.isInfinite(values[2])) + XposedBridge.log("VirtualSensor: Value #" + 2 + " is NaN or Infinity, this should not happen"); + + + List filter = filterValues(values, lastFilterValues, prevValues); + values = (float[]) filter.get(0); + prevValues = (float[]) filter.get(1); + lastFilterValues = (float[][]) filter.get(2); + + System.arraycopy(values, 0, returnValues, 0, values.length); + virtualListener.sensorRef = sensors.get(Sensor.TYPE_GYROSCOPE); + } + } else if (virtualListener.getSensor().getType() == Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR || virtualListener.getSensor().getType() == Sensor.TYPE_ROTATION_VECTOR) { + float[] values = new float[5]; + float[] rotationMatrix = new float[9]; + SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerValues, magneticValues); + float[] quaternion = rotationMatrixToQuaternion(rotationMatrix); + + values[0] = quaternion[1]; + values[1] = quaternion[2]; + values[2] = quaternion[3]; + values[3] = quaternion[0]; + values[4] = -1; + + System.arraycopy(values, 0, returnValues, 0, returnValues.length); + if (virtualListener.getSensor().getType() == Sensor.TYPE_ROTATION_VECTOR) + virtualListener.sensorRef = sensors.get(Sensor.TYPE_ROTATION_VECTOR); + else + virtualListener.sensorRef = sensors.get(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR); + } else if (virtualListener.getSensor().getType() == Sensor.TYPE_GRAVITY) { + float[] values = new float[3]; + float[] rotationMatrix = new float[9]; + float[] gravity = new float[]{0F, 0F, 9.81F}; + + SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerValues, magneticValues); + float[] gravityRot = new float[3]; + gravityRot[0] = gravity[0] * rotationMatrix[0] + gravity[1] * rotationMatrix[3] + gravity[2] * rotationMatrix[6]; + gravityRot[1] = gravity[0] * rotationMatrix[1] + gravity[1] * rotationMatrix[4] + gravity[2] * rotationMatrix[7]; + gravityRot[2] = gravity[0] * rotationMatrix[2] + gravity[1] * rotationMatrix[5] + gravity[2] * rotationMatrix[8]; + + values[0] = gravityRot[0]; + values[1] = gravityRot[1]; + values[2] = gravityRot[2]; + + System.arraycopy(values, 0, returnValues, 0, values.length); + virtualListener.sensorRef = sensors.get(Sensor.TYPE_GRAVITY); + } else if (virtualListener.getSensor().getType() == Sensor.TYPE_LINEAR_ACCELERATION) { + float[] values = new float[3]; + float[] rotationMatrix = new float[9]; + float[] gravity = new float[]{0F, 0F, 9.81F}; + + SensorManager.getRotationMatrix(rotationMatrix, null, accelerometerValues, magneticValues); + + float[] gravityRot = new float[3]; + gravityRot[0] = gravity[0] * rotationMatrix[0] + gravity[1] * rotationMatrix[3] + gravity[2] * rotationMatrix[6]; + gravityRot[1] = gravity[0] * rotationMatrix[1] + gravity[1] * rotationMatrix[4] + gravity[2] * rotationMatrix[7]; + gravityRot[2] = gravity[0] * rotationMatrix[2] + gravity[1] * rotationMatrix[5] + gravity[2] * rotationMatrix[8]; + + values[0] = accelerometerValues[0] - gravityRot[0]; + values[1] = accelerometerValues[1] - gravityRot[1]; + values[2] = accelerometerValues[2] - gravityRot[2]; + + System.arraycopy(values, 0, returnValues, 0, values.length); + virtualListener.sensorRef = sensors.get(Sensor.TYPE_LINEAR_ACCELERATION); + } + } + + List list = new ArrayList<>(); + list.add(returnValues); + list.add(prevTimestamp); + list.add(prevRotationMatrix); + list.add(prevValues); + list.add(lastFilterValues); + return list; + } + + @SuppressWarnings("unchecked") + public static class API1617 extends XC_MethodHook { + // Noise reduction + float lastFilterValues[][] = new float[3][10]; + float prevValues[] = new float[3]; + + //Sensor values + float[] magneticValues = new float[3]; + float[] accelerometerValues = new float[3]; + + //Keeping track of the previous rotation matrix and timestamp + float[] prevRotationMatrix = new float[9]; + long prevTimestamp = 0; + + private XC_LoadPackage.LoadPackageParam lpparam; + + public API1617(XC_LoadPackage.LoadPackageParam lpparam) { + this.lpparam = lpparam; + } + + @Override + protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + SparseArray sensors = (SparseArray) XposedHelpers.getStaticObjectField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "mHandleToSensor"); + + Object listener = XposedHelpers.getObjectField(param.thisObject, "mSensorEventListener"); + if (listener instanceof VirtualSensorListener) { // This is our custom listener type, we can start working on the values + Sensor s = (Sensor)param.args[0]; + + //All calculations need data from these two sensors, we can safely get their value every time + if (s.getType() == Sensor.TYPE_ACCELEROMETER) { + this.accelerometerValues = ((float[]) (param.args[1])).clone(); + } + if (s.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + this.magneticValues = ((float[]) (param.args[1])).clone(); + } + + List list = changeSensorValues(s, this.accelerometerValues, this.magneticValues, listener, this.prevRotationMatrix, ((long[]) param.args[2])[0], this.prevTimestamp, this.prevValues, this.lastFilterValues, sensors); + System.arraycopy((float[])list.get(0), 0, (float[])param.args[1], 0, ((float[])list.get(0)).length); + this.prevTimestamp = (long)list.get(1); + this.prevRotationMatrix = (float[])list.get(2); + this.prevValues = (float[])list.get(3); + this.lastFilterValues = (float[][])list.get(4); + } + } + } + + @SuppressWarnings("unchecked") + public static class API18Plus extends XC_MethodHook { + // Noise reduction + float lastFilterValues[][] = new float[3][10]; + float prevValues[] = new float[3]; + + //Sensor values + float[] magneticValues = new float[3]; + float[] accelerometerValues = new float[3]; + + //Keeping track of the previous rotation matrix and timestamp + float[] prevRotationMatrix = new float[9]; + long prevTimestamp = 0; + + private XC_LoadPackage.LoadPackageParam lpparam; + + public API18Plus(XC_LoadPackage.LoadPackageParam lpparam) { + this.lpparam = lpparam; + } + + @Override + protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { + super.beforeHookedMethod(param); + + Object listener = XposedHelpers.getObjectField(param.thisObject, "mListener"); + if (listener instanceof VirtualSensorListener) { // This is our custom listener type, we can start working on the values + int handle = (int) param.args[0]; + Object mgr = XposedHelpers.getObjectField(param.thisObject, "mManager"); + SparseArray sensors = (SparseArray) XposedHelpers.getObjectField(mgr, "mHandleToSensor"); + Sensor s = sensors.get(handle); + + //All calculations need data from these two sensors, we can safely get their value every time + if (s.getType() == Sensor.TYPE_ACCELEROMETER) { + this.accelerometerValues = ((float[]) (param.args[1])).clone(); + } + if (s.getType() == Sensor.TYPE_MAGNETIC_FIELD) { + this.magneticValues = ((float[]) (param.args[1])).clone(); + } + + List list = changeSensorValues(s, this.accelerometerValues, this.magneticValues, listener, this.prevRotationMatrix, (long) param.args[3], this.prevTimestamp, this.prevValues, this.lastFilterValues, sensors); + System.arraycopy((float[])list.get(0), 0, (float[])param.args[1], 0, ((float[])list.get(0)).length); + this.prevTimestamp = (long)list.get(1); + this.prevRotationMatrix = (float[])list.get(2); + this.prevValues = (float[])list.get(3); + this.lastFilterValues = (float[][])list.get(4); + } + } + } + + + /* + Helper functions + */ + + private static List getGyroscopeValues(float[] currentAccelerometer, float[] currentMagnetic, float[] prevRotationMatrix, float timeDifference) { + float[] angularRates = new float[] {0.0F, 0.0F, 0.0F}; + + float[] currentRotationMatrix = new float[9]; + SensorManager.getRotationMatrix(currentRotationMatrix, null, currentAccelerometer, currentMagnetic); + + SensorManager.getAngleChange(angularRates, currentRotationMatrix, prevRotationMatrix); + angularRates[0] = -(angularRates[1]*2) / timeDifference; + angularRates[1] = (angularRates[2]) / timeDifference; + angularRates[2] = ((angularRates[0]/2) / timeDifference)*0.0F; //Right now this returns weird values, need to look into it @TODO + + List returnList = new ArrayList<>(); + returnList.add(angularRates); + returnList.add(currentRotationMatrix); + return returnList; + } + + private static List filterValues(float[] values, float[][] lastFilterValues, float[] prevValues) { + if (Float.isInfinite(values[0]) || Float.isNaN(values[0])) values[0] = prevValues[0]; + if (Float.isInfinite(values[1]) || Float.isNaN(values[1])) values[1] = prevValues[1]; + if (Float.isInfinite(values[2]) || Float.isNaN(values[2])) values[2] = prevValues[2]; + + float[][] newLastFilterValues = new float[3][10]; + for (int i = 0; i < 3; i++) { + // Apply lowpass on the value + float alpha = 0.1F; + float newValue = lowPass(alpha, values[i], prevValues[i]); + //float newValue = values[i]; + + for (int j = 0; j < 10; j++) { + if (j == 0) continue; + newLastFilterValues[i][j-1] = lastFilterValues[i][j]; + } + newLastFilterValues[i][9] = newValue; + + float sum = 0F; + for (int j = 0; j < 10; j++) { + sum += lastFilterValues[i][j]; + } + newValue = sum/10; + + //The gyroscope is moving even after lowpass + if (newValue != 0.0F) { + //We are under the declared resolution of the gyroscope, so the value should be 0 + if (Math.abs(newValue) < 0.01F) newValue = 0.0F; + } + + prevValues[i] = values[i]; + values[i] = newValue; + } + + List returnValue = new ArrayList<>(); + returnValue.add(values); + returnValue.add(prevValues); + returnValue.add(newLastFilterValues); + return returnValue; + } + + private static float lowPass(float alpha, float value, float prev) { + return prev + alpha * (value - prev); + } + + /* + This uses the Hamilton product to multiply the vector converted to a quaternion with the rotation quaternion. + Returns a new quaternion which is the rotated vector. + Source: https://en.wikipedia.org/wiki/Quaternion#Hamilton_product + -- Not used, but keeping it just in case + */ + public static float[] rotateVectorByQuaternion(float[] vector, float[] quaternion) { + float a = vector[0]; + float b = vector[1]; + float c = vector[2]; + float d = vector[3]; + + float A = quaternion[0]; + float B = quaternion[1]; + float C = quaternion[2]; + float D = quaternion[3]; + + float newQuaternionReal = a*A - b*B - c*C - d*D; + float newQuaternioni = a*B + b*A + c*D - d*C; + float newQuaternionj = a*C - b*D + c*A + d*B; + float newQuaternionk = a*D + b*C - c*B + d*A; + + return new float[] {newQuaternionReal, newQuaternioni, newQuaternionj, newQuaternionk}; + } + + /* + Credit for this code goes to http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/ + Additional credit goes to https://en.wikipedia.org/wiki/Quaternion for helping me understand how quaternions work + */ + private static float[] rotationMatrixToQuaternion(float[] rotationMatrix) { + float m00 = rotationMatrix[0]; + float m01 = rotationMatrix[1]; + float m02 = rotationMatrix[2]; + float m10 = rotationMatrix[3]; + float m11 = rotationMatrix[4]; + float m12 = rotationMatrix[5]; + float m20 = rotationMatrix[6]; + float m21 = rotationMatrix[7]; + float m22 = rotationMatrix[8]; + + float tr = m00 + m11 + m22; + + float qw; + float qx; + float qy; + float qz; + if (tr > 0) { + float S = (float)Math.sqrt(tr+1.0) * 2; + qw = 0.25F * S; + qx = (m21 - m12) / S; + qy = (m02 - m20) / S; + qz = (m10 - m01) / S; + } else if ((m00 > m11)&(m00 > m22)) { + float S = (float)Math.sqrt(1.0 + m00 - m11 - m22) * 2; + qw = (m21 - m12) / S; + qx = 0.25F * S; + qy = (m01 + m10) / S; + qz = (m02 + m20) / S; + } else if (m11 > m22) { + float S = (float)Math.sqrt(1.0 + m11 - m00 - m22) * 2; + qw = (m02 - m20) / S; + qx = (m01 + m10) / S; + qy = 0.25F * S; + qz = (m12 + m21) / S; + } else { + float S = (float)Math.sqrt(1.0 + m22 - m00 - m11) * 2; + qw = (m10 - m01) / S; + qx = (m02 + m20) / S; + qy = (m12 + m21) / S; + qz = 0.25F * S; + } + return new float[] {qw, qx, qy, qz}; + } +} diff --git a/app/src/main/java/fr/frazew/virtualgyroscope/hooks/SystemSensorManagerHook.java b/app/src/main/java/fr/frazew/virtualgyroscope/hooks/SystemSensorManagerHook.java new file mode 100644 index 0000000..b118f9b --- /dev/null +++ b/app/src/main/java/fr/frazew/virtualgyroscope/hooks/SystemSensorManagerHook.java @@ -0,0 +1,152 @@ +package fr.frazew.virtualgyroscope.hooks; + +import android.hardware.Sensor; +import android.util.SparseArray; + +import java.lang.reflect.InvocationTargetException; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import de.robv.android.xposed.XC_MethodHook; +import de.robv.android.xposed.XposedHelpers; +import de.robv.android.xposed.callbacks.XC_LoadPackage; +import fr.frazew.virtualgyroscope.BuildConfig; +import fr.frazew.virtualgyroscope.SensorModel; +import fr.frazew.virtualgyroscope.XposedMod; + +public class SystemSensorManagerHook { + + public static List fillSensorLists(ArrayList fullSensorList, SparseArray handleToSensor, XC_LoadPackage.LoadPackageParam lpparam) throws IllegalAccessException, InstantiationException, InvocationTargetException { + Iterator iterator = fullSensorList.iterator(); + + int minDelayAccelerometer = handleToSensor.get(Sensor.TYPE_ACCELEROMETER).getMinDelay(); + + while (iterator.hasNext()) { + Sensor sensor = iterator.next(); + if (XposedMod.sensorsToEmulate.indexOfKey(sensor.getType()) >= 0) { + XposedMod.sensorsToEmulate.get(sensor.getType()).alreadyThere = true; + } + } + + XposedHelpers.findConstructorBestMatch(Sensor.class).setAccessible(true); + XposedHelpers.findField(Sensor.class, "mName").setAccessible(true); + XposedHelpers.findField(Sensor.class, "mType").setAccessible(true); + XposedHelpers.findField(Sensor.class, "mVendor").setAccessible(true); + XposedHelpers.findField(Sensor.class, "mVersion").setAccessible(true); + XposedHelpers.findField(Sensor.class, "mHandle").setAccessible(true); + XposedHelpers.findField(Sensor.class, "mResolution").setAccessible(true); + XposedHelpers.findField(Sensor.class, "mMinDelay").setAccessible(true); + XposedHelpers.findField(Sensor.class, "mMaxRange").setAccessible(true); + + for (int i = 0; i < XposedMod.sensorsToEmulate.size(); i++) { + SensorModel model = XposedMod.sensorsToEmulate.valueAt(i); + if (!model.alreadyThere) { + Sensor s = (Sensor) XposedHelpers.findConstructorBestMatch(Sensor.class).newInstance(); + XposedHelpers.setObjectField(s, "mType", XposedMod.sensorsToEmulate.keyAt(i)); + XposedHelpers.setObjectField(s, "mName", model.name); + XposedHelpers.setObjectField(s, "mVendor", "Frazew"); + XposedHelpers.setObjectField(s, "mVersion", BuildConfig.VERSION_CODE); + XposedHelpers.setObjectField(s, "mHandle", model.handle); + XposedHelpers.setObjectField(s, "mMinDelay", model.minDelay == -1 ? minDelayAccelerometer : model.minDelay); + XposedHelpers.setObjectField(s, "mResolution", model.resolution == -1 ? 0.01F : model.resolution); // This 0.01F is a placeholder, it doesn't seem to change anything but I keep it + if (model.maxRange != -1) + XposedHelpers.setObjectField(s, "mMaxRange", model.maxRange); + fullSensorList.add(s); + handleToSensor.append(model.handle, s); + } + } + + List list = new ArrayList<>(); + list.add(fullSensorList); + list.add(handleToSensor); + return list; + } + + @SuppressWarnings("unchecked") + public static class API1617 extends XC_MethodHook { + private XC_LoadPackage.LoadPackageParam lpparam; + + public API1617(XC_LoadPackage.LoadPackageParam lpparam) { + this.lpparam = lpparam; + } + + @Override + protected void afterHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable { + super.afterHookedMethod(param); + ArrayList sListeners = (ArrayList) XposedHelpers.getStaticObjectField(param.thisObject.getClass(), "sListeners"); + + synchronized (sListeners) { + if (!XposedHelpers.getStaticBooleanField(param.thisObject.getClass(), "sSensorModuleInitialized")) { + ArrayList sFullSensorsList = (ArrayList) XposedHelpers.getStaticObjectField(param.thisObject.getClass(), "sFullSensorsList"); + SparseArray sHandleToSensor = (SparseArray) XposedHelpers.getStaticObjectField(param.thisObject.getClass(), "sHandleToSensor"); + + List sensors = fillSensorLists(sFullSensorsList, sHandleToSensor, lpparam); + + XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "sFullSensorsList").setAccessible(true); + XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "sHandleToSensor").setAccessible(true); + + XposedHelpers.setStaticObjectField(param.thisObject.getClass(), "sHandleToSensor", sensors.get(0)); + XposedHelpers.setStaticObjectField(param.thisObject.getClass(), "sFullSensorsList", sensors.get(1)); + + Class sensorEventPoolClass = XposedHelpers.findClass("android.hardware.SensorManager$SensorEventPool", lpparam.classLoader); + Object sPool = XposedHelpers.newInstance(sensorEventPoolClass, int.class, ((ArrayList)sensors.get(1)).size() * 2); + XposedHelpers.setStaticObjectField(param.thisObject.getClass(), "sPool", sPool); + } + } + } + } + + @SuppressWarnings("unchecked") + public static class API18Plus extends XC_MethodHook { + private XC_LoadPackage.LoadPackageParam lpparam; + + public API18Plus(XC_LoadPackage.LoadPackageParam lpparam) { + this.lpparam = lpparam; + } + + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + super.afterHookedMethod(param); + Object sSensorModuleLock = XposedHelpers.getStaticObjectField(param.thisObject.getClass(), "sSensorModuleLock"); + + synchronized (sSensorModuleLock) { + if (!XposedHelpers.getStaticBooleanField(param.thisObject.getClass(), "sSensorModuleInitialized")) { + ArrayList sFullSensorsList = (ArrayList) XposedHelpers.getStaticObjectField(param.thisObject.getClass(), "sFullSensorsList"); + SparseArray sHandleToSensor = (SparseArray) XposedHelpers.getStaticObjectField(param.thisObject.getClass(), "sHandleToSensor"); + + List sensors = fillSensorLists(sFullSensorsList, sHandleToSensor, lpparam); + + XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "sFullSensorsList").setAccessible(true); + XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "sHandleToSensor").setAccessible(true); + XposedHelpers.setStaticObjectField(param.thisObject.getClass(), "sHandleToSensor", sensors.get(1)); + XposedHelpers.setStaticObjectField(param.thisObject.getClass(), "sFullSensorsList", sensors.get(0)); + } + } + } + } + + @SuppressWarnings("unchecked") + public static class API23Plus extends XC_MethodHook { + private XC_LoadPackage.LoadPackageParam lpparam; + + public API23Plus(XC_LoadPackage.LoadPackageParam lpparam) { + this.lpparam = lpparam; + } + + @Override + protected void afterHookedMethod(MethodHookParam param) throws Throwable { + super.afterHookedMethod(param); + ArrayList mFullSensorsList = (ArrayList) XposedHelpers.getObjectField(param.thisObject, "mFullSensorsList"); + SparseArray mHandleToSensor = (SparseArray) XposedHelpers.getObjectField(param.thisObject, "mHandleToSensor"); + + List sensors = fillSensorLists(mFullSensorsList, mHandleToSensor, lpparam); + + XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "mFullSensorsList").setAccessible(true); + XposedHelpers.findField(XposedHelpers.findClass("android.hardware.SystemSensorManager", lpparam.classLoader), "mHandleToSensor").setAccessible(true); + + XposedHelpers.setObjectField(param.thisObject, "mHandleToSensor", sensors.get(1)); + XposedHelpers.setObjectField(param.thisObject, "mFullSensorsList", sensors.get(0)); + } + } +}