package biz.bokhorst.xprivacy;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;
import android.app.PendingIntent;
import android.location.Location;
import android.os.Binder;
import android.os.Bundle;
import android.os.IInterface;
import android.util.Log;
import android.location.GpsSatellite;
import android.location.GpsStatus;
import android.location.LocationListener;
public class XLocationManager extends XHook {
private Methods mMethod;
private String mClassName;
private static final String cClassName = "android.location.LocationManager";
private static final Map<Object, Object> mMapProxy = new WeakHashMap<Object, Object>();
private XLocationManager(Methods method, String restrictionName, String className) {
super(restrictionName, method.name().replace("Srv_", ""), method.name());
mMethod = method;
mClassName = className;
}
public String getClassName() {
return mClassName;
}
// @formatter:off
// public void addGeofence(LocationRequest request, Geofence fence, PendingIntent intent)
// public boolean addGpsStatusListener(GpsStatus.Listener listener)
// public boolean addNmeaListener(GpsStatus.NmeaListener listener)
// public void addProximityAlert(double latitude, double longitude, float radius, long expiration, PendingIntent intent)
// public List<String> getAllProviders()
// public String getBestProvider(Criteria criteria, boolean enabledOnly)
// public GpsStatus getGpsStatus(GpsStatus status)
// public Location getLastKnownLocation(String provider)
// public List<String> getProviders(boolean enabledOnly)
// public List<String> getProviders(Criteria criteria, boolean enabledOnly)
// public boolean isProviderEnabled(String provider)
// public void removeUpdates(LocationListener listener)
// public void removeUpdates(PendingIntent intent)
// public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener)
// public void requestLocationUpdates(String provider, long minTime, float minDistance, LocationListener listener, Looper looper)
// public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, LocationListener listener, Looper looper)
// public void requestLocationUpdates(String provider, long minTime, float minDistance, PendingIntent intent)
// public void requestLocationUpdates(long minTime, float minDistance, Criteria criteria, PendingIntent intent)
// public void requestSingleUpdate(String provider, LocationListener listener, Looper looper)
// public void requestSingleUpdate(Criteria criteria, LocationListener listener, Looper looper)
// public void requestSingleUpdate(String provider, PendingIntent intent)
// public void requestSingleUpdate(Criteria criteria, PendingIntent intent)
// public boolean sendExtraCommand(String provider, String command, Bundle extras)
// frameworks/base/location/java/android/location/LocationManager.java
// http://developer.android.com/reference/android/location/LocationManager.html
// public void requestLocationUpdates(LocationRequest request, ILocationListener listener, android.app.PendingIntent intent, java.lang.String packageName)
// public void removeUpdates(ILocationListener listener, android.app.PendingIntent intent, java.lang.String packageName)
// public void requestGeofence(LocationRequest request, Geofence geofence, android.app.PendingIntent intent, java.lang.String packageName)
// public void removeGeofence(Geofence fence, android.app.PendingIntent intent, java.lang.String packageName)
// public Location getLastLocation(LocationRequest request, java.lang.String packageName)
// public boolean addGpsStatusListener(IGpsStatusListener listener, java.lang.String packageName)
// public void removeGpsStatusListener(IGpsStatusListener listener)
// public java.util.List<java.lang.String> getAllProviders()
// public java.util.List<java.lang.String> getProviders(Criteria criteria, boolean enabledOnly)
// public java.lang.String getBestProvider(Criteria criteria, boolean enabledOnly)
// public boolean isProviderEnabled(java.lang.String provider)
// public boolean sendExtraCommand(java.lang.String provider, java.lang.String command, android.os.Bundle extras)
// http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.4.4_r1/com/android/server/LocationManagerService.java
// public boolean addGpsMeasurementsListener(IGpsMeasurementsListener listener, String packageName)
// public boolean addGpsNavigationMessageListener(IGpsNavigationMessageListener listener, String packageName)
// public boolean removeGpsMeasurementsListener(IGpsMeasurementsListener listener)
// public boolean removeGpsNavigationMessageListener(IGpsNavigationMessageListener listener)
// http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.0.0_r1/com/android/server/LocationManagerService.java
// @formatter:on
// @formatter:off
private enum Methods {
addGeofence, addGpsStatusListener, addNmeaListener, addProximityAlert,
getAllProviders, getBestProvider, getProviders, isProviderEnabled,
getGpsStatus,
getLastKnownLocation,
removeUpdates,
requestLocationUpdates, requestSingleUpdate,
sendExtraCommand,
Srv_requestLocationUpdates, Srv_removeUpdates,
Srv_requestGeofence, Srv_removeGeofence,
Srv_getLastLocation,
Srv_addGpsStatusListener, Srv_removeGpsStatusListener,
Srv_getAllProviders, Srv_getProviders, Srv_getBestProvider, Srv_isProviderEnabled,
Srv_sendExtraCommand,
Srv_addGpsMeasurementsListener, Srv_addGpsNavigationMessageListener, Srv_removeGpsMeasurementsListener, Srv_removeGpsNavigationMessageListener
};
// @formatter:on
public static List<XHook> getInstances(String className, boolean server) {
List<XHook> listHook = new ArrayList<XHook>();
if (!cClassName.equals(className)) {
if (className == null)
className = cClassName;
for (Methods loc : Methods.values())
if (loc == Methods.removeUpdates)
listHook.add(new XLocationManager(loc, null, className));
else if (loc.name().startsWith("Srv_remove")) {
if (server)
listHook.add(new XLocationManager(loc, null, "com.android.server.LocationManagerService"));
} else if (loc.name().startsWith("Srv_")) {
if (server)
listHook.add(new XLocationManager(loc, PrivacyManager.cLocation,
"com.android.server.LocationManagerService"));
} else
listHook.add(new XLocationManager(loc, PrivacyManager.cLocation, className));
}
return listHook;
}
@Override
protected void before(XParam param) throws Throwable {
switch (mMethod) {
case addGeofence:
case addProximityAlert:
case Srv_requestGeofence:
if (isRestricted(param))
param.setResult(null);
break;
case Srv_removeGeofence:
if (isRestricted(param, PrivacyManager.cLocation, "Srv_requestGeofence"))
param.setResult(null);
break;
case addGpsStatusListener:
case addNmeaListener:
case Srv_addGpsStatusListener:
case Srv_addGpsMeasurementsListener:
case Srv_addGpsNavigationMessageListener:
if (isRestricted(param))
param.setResult(false);
break;
case Srv_removeGpsStatusListener:
if (isRestricted(param, PrivacyManager.cLocation, "Srv_addGpsStatusListener"))
param.setResult(null);
break;
case Srv_removeGpsMeasurementsListener:
if (isRestricted(param, PrivacyManager.cLocation, "Srv_addGpsMeasurementsListener"))
param.setResult(null);
break;
case Srv_removeGpsNavigationMessageListener:
if (isRestricted(param, PrivacyManager.cLocation, "Srv_addGpsNavigationMessageListener"))
param.setResult(null);
break;
case getAllProviders:
case getBestProvider:
case getGpsStatus:
case getLastKnownLocation:
case getProviders:
case isProviderEnabled:
case Srv_getAllProviders:
case Srv_getProviders:
case Srv_getBestProvider:
case Srv_isProviderEnabled:
case Srv_getLastLocation:
// Do nothing
break;
case removeUpdates:
if (isRestricted(param, PrivacyManager.cLocation, "requestLocationUpdates"))
unproxyLocationListener(param, 0, true);
break;
case requestLocationUpdates:
if (param.args.length > 0 && param.args[0] instanceof String) {
if (isRestrictedExtra(param, (String) param.args[0]))
proxyLocationListener(param, 3, LocationListener.class, true);
} else {
if (isRestricted(param))
proxyLocationListener(param, 3, LocationListener.class, true);
}
break;
case Srv_removeUpdates:
if (isRestricted(param, PrivacyManager.cLocation, "Srv_requestLocationUpdates"))
if (param.args.length > 1)
if (param.args[0] != null) // ILocationListener
unproxyLocationListener(param, 0, false);
else if (param.args[1] != null) // PendingIntent
param.setResult(null);
break;
case Srv_requestLocationUpdates:
if (isRestricted(param))
if (param.args.length > 2)
if (param.args[1] != null) // ILocationListener
proxyLocationListener(param, 1, Class.forName("android.location.ILocationListener"), false);
else if (param.args[2] != null) // PendingIntent
param.setResult(null);
break;
case requestSingleUpdate:
if (param.args.length > 0 && param.args[0] instanceof String) {
if (isRestrictedExtra(param, (String) param.args[0]))
proxyLocationListener(param, 1, LocationListener.class, true);
} else {
if (isRestricted(param))
proxyLocationListener(param, 1, LocationListener.class, true);
}
break;
case sendExtraCommand:
case Srv_sendExtraCommand:
// Do nothing
break;
}
}
@Override
protected void after(XParam param) throws Throwable {
switch (mMethod) {
case addGeofence:
case addNmeaListener:
case addGpsStatusListener:
case addProximityAlert:
case Srv_requestGeofence:
case Srv_addGpsStatusListener:
case Srv_addGpsMeasurementsListener:
case Srv_addGpsNavigationMessageListener:
case Srv_removeGeofence:
case Srv_removeGpsStatusListener:
case Srv_removeGpsMeasurementsListener:
case Srv_removeGpsNavigationMessageListener:
// Do nothing
break;
case isProviderEnabled:
case Srv_isProviderEnabled:
if (param.args.length > 0) {
String provider = (String) param.args[0];
if (isRestrictedExtra(param, provider))
param.setResult(false);
}
break;
case getGpsStatus:
if (param.getResult() instanceof GpsStatus)
if (isRestricted(param)) {
GpsStatus status = (GpsStatus) param.getResult();
// private GpsSatellite mSatellites[]
try {
Field mSatellites = status.getClass().getDeclaredField("mSatellites");
mSatellites.setAccessible(true);
mSatellites.set(status, new GpsSatellite[0]);
} catch (Throwable ex) {
Util.bug(null, ex);
}
}
break;
case getProviders:
case getAllProviders:
case Srv_getAllProviders:
case Srv_getProviders:
if (isRestricted(param))
param.setResult(new ArrayList<String>());
break;
case getBestProvider:
case Srv_getBestProvider:
if (param.getResult() != null)
if (isRestricted(param))
param.setResult(null);
break;
case getLastKnownLocation:
if (param.args.length > 0 && param.getResult() instanceof Location) {
String provider = (String) param.args[0];
Location location = (Location) param.getResult();
if (isRestrictedExtra(param, provider))
param.setResult(PrivacyManager.getDefacedLocation(Binder.getCallingUid(), location));
}
break;
case Srv_getLastLocation:
if (param.getResult() instanceof Location) {
Location location = (Location) param.getResult();
if (isRestricted(param))
param.setResult(PrivacyManager.getDefacedLocation(Binder.getCallingUid(), location));
}
break;
case removeUpdates:
case requestLocationUpdates:
case requestSingleUpdate:
case Srv_removeUpdates:
case Srv_requestLocationUpdates:
// Do nothing
break;
case sendExtraCommand:
case Srv_sendExtraCommand:
if (param.args.length > 0) {
String provider = (String) param.args[0];
if (isRestrictedExtra(param, provider))
param.setResult(false);
}
break;
}
}
private void proxyLocationListener(XParam param, int arg, Class<?> interfaze, boolean client) throws Throwable {
if (param.args.length > arg)
if (param.args[arg] instanceof PendingIntent)
param.setResult(null);
else if (param.args[arg] != null && param.thisObject != null) {
if (client) {
Object key = param.args[arg];
synchronized (mMapProxy) {
// Reuse existing proxy
if (mMapProxy.containsKey(key)) {
Util.log(this, Log.INFO, "Reuse existing proxy uid=" + Binder.getCallingUid());
param.args[arg] = mMapProxy.get(key);
return;
}
// Already proxied
if (mMapProxy.containsValue(key)) {
Util.log(this, Log.INFO, "Already proxied uid=" + Binder.getCallingUid());
return;
}
}
// Create proxy
Util.log(this, Log.INFO, "Creating proxy uid=" + Binder.getCallingUid());
Object proxy = new ProxyLocationListener(Binder.getCallingUid(), (LocationListener) param.args[arg]);
// Use proxy
synchronized (mMapProxy) {
mMapProxy.put(key, proxy);
}
param.args[arg] = proxy;
} else {
// Create proxy
ClassLoader cl = param.thisObject.getClass().getClassLoader();
InvocationHandler ih = new OnLocationChangedHandler(Binder.getCallingUid(), param.args[arg]);
Object proxy = Proxy.newProxyInstance(cl, new Class<?>[] { interfaze }, ih);
Object key = param.args[arg];
if (key instanceof IInterface)
key = ((IInterface) key).asBinder();
// Use proxy
synchronized (mMapProxy) {
mMapProxy.put(key, proxy);
}
param.args[arg] = proxy;
}
}
}
private void unproxyLocationListener(XParam param, int arg, boolean client) {
if (param.args.length > arg)
if (param.args[arg] instanceof PendingIntent)
param.setResult(null);
else if (param.args[arg] != null) {
if (client) {
Object key = param.args[arg];
synchronized (mMapProxy) {
if (mMapProxy.containsKey(key)) {
Util.log(this, Log.INFO, "Removing proxy uid=" + Binder.getCallingUid());
param.args[arg] = mMapProxy.get(key);
}
}
} else {
Object key = param.args[arg];
if (key instanceof IInterface)
key = ((IInterface) key).asBinder();
synchronized (mMapProxy) {
if (mMapProxy.containsKey(key))
param.args[arg] = mMapProxy.get(key);
}
}
}
}
private static class ProxyLocationListener implements LocationListener {
private int mUid;
private LocationListener mListener;
public ProxyLocationListener(int uid, LocationListener listener) {
mUid = uid;
mListener = listener;
}
@Override
public void onLocationChanged(Location location) {
Util.log(null, Log.INFO, "Location changed uid=" + Binder.getCallingUid());
Location fakeLocation = PrivacyManager.getDefacedLocation(mUid, location);
mListener.onLocationChanged(fakeLocation);
}
@Override
public void onProviderDisabled(String provider) {
mListener.onProviderDisabled(provider);
}
@Override
public void onProviderEnabled(String provider) {
mListener.onProviderEnabled(provider);
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
mListener.onStatusChanged(provider, status, extras);
}
}
private class OnLocationChangedHandler implements InvocationHandler {
private int mUid;
private Object mTarget;
public OnLocationChangedHandler(int uid, Object target) {
mUid = uid;
mTarget = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("onLocationChanged".equals(method.getName()))
args[0] = PrivacyManager.getDefacedLocation(mUid, (Location) args[0]);
return method.invoke(mTarget, args);
}
}
}