Skip to content

Commit

Permalink
add hooks for FusedLocationApi (not complete/fully tested), some prob…
Browse files Browse the repository at this point in the history
…lems with location updates only getting called sporadically
  • Loading branch information
spezifisch committed Sep 1, 2016
1 parent c7e71de commit fd18965
Show file tree
Hide file tree
Showing 7 changed files with 203 additions and 16 deletions.
3 changes: 1 addition & 2 deletions .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
This is an Android App which simulates your location.
Its goal is to simulate the whole bunch of GPS/GNSS and sensor data realistically.

## Releases
## Download

For now, grab the most recent test release from [Github's Release page](https://github.com/spezifisch/ThreeStepsAhead/releases).
Grab the most recent release from [Github's Release page](https://github.com/spezifisch/ThreeStepsAhead/releases).

Follow install instructions below!

## Features

Expand All @@ -14,7 +16,7 @@ For now, grab the most recent test release from [Github's Release page](https://
- use altitude (this is not ideal), accuracy, TTF, location update timings from real GPS receiver
- add noise to appear more realistic
- choose coarse location in map, move around using joystick
- display floating joystick overlay above other apps
- display joystick overlay above other apps
- only hook selected apps (currently a static list: SatStat for testing and PoGo)
- WIP for next release: modify (accelerometer, gyroscope, magnetometer, etc.) sensor data to match simulated movement

Expand Down Expand Up @@ -49,10 +51,24 @@ Speed and bearing are simulated according to your joystick.
- Install ThreeStepsAhead
- Activate it in Xposed settings
- (Soft) Reboot
- start ThreeStepsAhead and pick a location before starting PoGo

Some hints:

- we rely on a real GPS fix to get realistic accuracy, altitude, and timings.
- GPS only location mode recommended
- disable fused location provider in case of problems
- make sure to disable Mock Locations

## Compatibility

- Android 4.4+
- Android 4.4+, tested with 4.4 - 6.0

## Permissions

- Network/State: needed for map service
- Storage: needed for map tile caching
- Draw Overlays/AlertWindow: needed to draw joystick overlay over other apps

## Credits

Expand All @@ -64,4 +80,3 @@ Speed and bearing are simulated according to your joystick.
- App icon made by [Roundicons Freebies](http://www.flaticon.com/authors/roundicons-freebies) ([CC-BY](https://creativecommons.org/licenses/by/3.0/), background added)
- Marker icon made by [freepik](http://www.flaticon.com/authors/freepik) from www.flaticon.com
- Zoom icons from [SatStat](https://github.com/mvglasow/satstat) by mvglasow

Original file line number Diff line number Diff line change
Expand Up @@ -367,7 +367,8 @@ protected boolean send(Messenger m, Message message) {
try {
m.send(message);
} catch (RemoteException e) {
log("send " + e);
log("IPC send exception");
e.printStackTrace();
return false;
}

Expand Down
176 changes: 170 additions & 6 deletions app/src/main/java/com/github/spezifisch/threestepsahead/hooks/GPS.java
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
package com.github.spezifisch.threestepsahead.hooks;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.Location;
import android.os.Message;

import com.github.spezifisch.threestepsahead.utils.Helper;
import com.github.spezifisch.threestepsahead.utils.SpaceMan;

import de.robv.android.xposed.IXposedHookZygoteInit;
Expand Down Expand Up @@ -40,6 +44,7 @@ public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) thr
initHookListenerTransport(lpparam);
initHookGpsStatus(lpparam);
initHookGetLastKnownLocation(lpparam);
initHookFusedLocation(lpparam);
}

void initHookListenerTransport(final XC_LoadPackage.LoadPackageParam lpparam) {
Expand Down Expand Up @@ -80,10 +85,10 @@ protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
param.args[0] = message;

Location mojl = (Location) message.obj;
if (DEBUG) {
//if (DEBUG) {
XposedBridge.log(Main.Shared.packageName + " | " + locationTime +
" ListenerTransport Location faked(" + mojl.getTime() + ") " + mojl);
}
//}
}
} else {
XposedBridge.log("ListenerTransport unhandled message(" + message.what + ") " + message.obj);
Expand Down Expand Up @@ -294,17 +299,176 @@ protected void afterHookedMethod(MethodHookParam param) throws Throwable {
"getLastKnownLocation", String.class, new LastKnownLocationHook());
}

void initHookFusedLocation(final XC_LoadPackage.LoadPackageParam lpparam) {
// hook for com.google.android.gms.location.LocationListener
class onLocationChangedFusedHook extends XC_MethodHook {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.hasThrowable()) {
return;
}
if (!Main.connectAndRun()) {
return;
}

if (param.args[0] != null) {
Location location = fakeLocation((Location)param.args[0]);
param.args[0] = location;

XposedBridge.log("onLocationChangedFusedHook Location faked: " + location);
}
}
}

// hook for com.google.android.gms.location.LocationCallback
class onLocationResultFusedHook extends XC_MethodHook {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.hasThrowable()) {
return;
}
if (!Main.connectAndRun()) {
return;
}

XposedBridge.log("onLocationResultFusedHook called");

if (param.args[0] != null) {
Object locationResult = param.args[0];

// get real location list from members of LocationResult
List<Location> llObject = new ArrayList<>();
Field locationList = XposedHelpers.findFirstFieldByExactType(locationResult.getClass(), llObject.getClass());
locationList.setAccessible(true);
locationList.get(llObject);

XposedBridge.log("onLocationResultFusedHook real list: " + llObject);

if (llObject.size() > 0) {
// get data from newest location
Location location = fakeLocation(llObject.get(llObject.size() - 1));

// build own list
List<Location> llMy = new ArrayList<>();
llMy.add(location);

// overwrite original list
locationList.set(llObject, llMy);

XposedBridge.log("onLocationResultFusedHook real list replaced");
} else {
XposedBridge.log("onLocationResultFusedHook real list was empty");
}
}
}
}

// hook for com.google.android.gms.location.LocationServices
class requestLocationUpdatesFusedHook extends XC_MethodHook {
@Override
protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
if (param.hasThrowable()) {
return;
}
if (!Main.connectAndRun()) {
return;
}

// parse args ... maybe fake them later
for (Object arg: param.args) {
if (arg == null) {
continue;
}

String cname = arg.getClass().getName();
XposedBridge.log("FusedLocationApi.requestLocationUpdates arg: " + cname + " -> " + arg);

if (!cname.startsWith("com.google.android.gms.internal") &&
!cname.equals("com.google.android.gms.location.LocationRequest") &&
!cname.equals("android.os.Looper")) {
hookLocationListener(arg.getClass());
hookLocationCallback(arg.getClass());
}
}
}

void hookLocationListener(Class<?> ll) {
Set<Unhook> unhooks = XposedBridge.hookAllMethods(ll, "onLocationChanged", new onLocationChangedFusedHook());
if (unhooks.size() > 0) {
XposedBridge.log("hooked onLocationChanged");
}
}

void hookLocationCallback(Class<?> lc) {
Set<Unhook> unhooks = XposedBridge.hookAllMethods(lc, "onLocationResult", new onLocationResultFusedHook());
if (unhooks.size() > 0) {
XposedBridge.log("hooked onLocationResult");
}
}
}

// https://developers.google.com/android/reference/com/google/android/gms/location/FusedLocationProviderApi.html
// getLastLocation(com.google.android.gms.common.api.GoogleApiClient)
class getLastLocationFusedHook extends XC_MethodHook {
@Override
protected void afterHookedMethod(MethodHookParam param) throws Throwable {
if (param.hasThrowable()) {
return;
}
if (!Main.connectAndRun()) {
return;
}

if (param.getResult() != null) {
Location location = fakeLocation((Location)param.getResult());
param.setResult(location);

XposedBridge.log("getLastLocationFusedHook Location faked: " + location);
}
}
}

Class<?> clazz = XposedHelpers.findClassIfExists(
"com.google.android.gms.location.LocationServices", lpparam.classLoader);
if (clazz != null) {
XposedBridge.log("LocationServices found, hooking FusedLocationApi");

// get FusedLocationProviderApi implementation
Object fla = XposedHelpers.getStaticObjectField(clazz, "FusedLocationApi");

// hook all versions of requestLocationUpdates
XposedBridge.hookAllMethods(fla.getClass(), "requestLocationUpdates", new requestLocationUpdatesFusedHook());

// hook getLastLocation
XposedBridge.hookAllMethods(fla.getClass(), "getLastLocation", new getLastLocationFusedHook());
} else {
XposedBridge.log("LocationServices not found, don't need to hook FusedLocationApi");
}
}

private Location fakeLocation(Location loc) {
final Location l = Main.State.location;

// overwrite faked parts of Location
loc.setLatitude(l.getLatitude());
loc.setLongitude(l.getLongitude());

// optional fields...
//location.setTime(System.currentTimeMillis());
//loc.setAltitude(l.getAltitude());
loc.setSpeed(l.getSpeed());
//loc.setAccuracy(l.getAccuracy());
loc.setBearing(l.getBearing());
if (loc.hasAltitude()) {
// not faked yet
//loc.setAltitude(l.getAltitude());
}
if (loc.hasSpeed()) {
loc.setSpeed(l.getSpeed());
}
if (loc.hasAccuracy()) {
// not faked yet
//loc.setAccuracy(l.getAccuracy());
}
if (loc.hasBearing()) {
loc.setBearing(l.getBearing());
}

return loc;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ public static class Settings {
static final List<String> hookedApps = Arrays.asList(
"com.google.android.gms",
"com.vonglasow.michael.satstat",
"com.nianticlabs.pokemongo"
"com.nianticlabs.pokemongo",
"com.google.android.gms.location.sample.locationupdates"
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.github.spezifisch.threestepsahead.utils;

public class Helper {
public static boolean isPoGo(String name) {
return name.equals("com.nianticlabs.pokemongo");
}
}

0 comments on commit fd18965

Please sign in to comment.