package org.fitchfamily.android.gsmlocation;
import android.content.Context;
import android.location.Location;
import android.os.Bundle;
import android.telephony.CellIdentityGsm;
import android.telephony.CellInfoGsm;
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;
import org.fitchfamily.android.gsmlocation.database.CellLocationDatabase;
import org.microg.nlp.api.LocationHelper;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import static org.fitchfamily.android.gsmlocation.LogUtils.makeLogTag;
public class TelephonyHelper {
private static final String TAG = makeLogTag(TelephonyHelper.class);
private static final boolean DEBUG = Config.DEBUG;
/* Reflection-based shims to use CellInfoWcdma and stay compatible with API level 17 */
private static class CellIdentityWcdma {
private static Class<?> mCls;
private static Method mGetCid;
private static Method mGetLac;
private static Method mGetMcc;
private static Method mGetMnc;
private static Method mGetPsc;
static {
try {
mCls = Class.forName("android.telephony.CellIdentityWcdma");
mGetCid = mCls.getMethod("getCid");
mGetLac = mCls.getMethod("getLac");
mGetMcc = mCls.getMethod("getMcc");
mGetMnc = mCls.getMethod("getMnc");
mGetPsc = mCls.getMethod("getPsc");
} catch (final ClassNotFoundException e) {
} catch (final NoSuchMethodException e) {
}
}
private final Object mObj;
public CellIdentityWcdma(final Object obj) {
mObj = obj;
}
public int getCid() throws IllegalAccessException, InvocationTargetException {
return ((Integer)mGetCid.invoke(mObj)).intValue();
}
public int getLac() throws IllegalAccessException, InvocationTargetException {
return ((Integer)mGetLac.invoke(mObj)).intValue();
}
public int getMcc() throws IllegalAccessException, InvocationTargetException {
return ((Integer)mGetMcc.invoke(mObj)).intValue();
}
public int getMnc() throws IllegalAccessException, InvocationTargetException {
return ((Integer)mGetMnc.invoke(mObj)).intValue();
}
public int getPsc() throws IllegalAccessException, InvocationTargetException {
return ((Integer)mGetPsc.invoke(mObj)).intValue();
}
}
private static class CellSignalStrengthWcdma {
private static Class<?> mCls;
private static Method mGetAsuLevel;
static {
try {
mCls = Class.forName("android.telephony.CellSignalStrengthWcdma");
mGetAsuLevel = mCls.getMethod("getAsuLevel");
} catch (final ClassNotFoundException e) {
} catch (final NoSuchMethodException e) {
}
}
private final Object mObj;
public CellSignalStrengthWcdma(final Object obj) {
mObj = obj;
}
public int getAsuLevel() throws IllegalAccessException, InvocationTargetException {
return ((Integer)mGetAsuLevel.invoke(mObj)).intValue();
}
}
private static class CellInfoWcdma {
private static Class<?> mCls;
private static Method mGetCellIdentity;
private static Method mGetCellSignalStrength;
static {
try {
mCls = Class.forName("android.telephony.CellInfoWcdma");
mGetCellIdentity = mCls.getMethod("getCellIdentity");
mGetCellSignalStrength = mCls.getMethod("getCellSignalStrength");
} catch (final ClassNotFoundException e) {
} catch (final NoSuchMethodException e) {
}
}
private final Object mObj;
public CellInfoWcdma(final Object obj) {
mObj = obj;
}
public static boolean isInstance(final Object obj) {
return null != mCls && mCls.isInstance(obj);
}
public CellIdentityWcdma getCellIdentity()
throws IllegalAccessException, InvocationTargetException {
return new CellIdentityWcdma(mGetCellIdentity.invoke(mObj));
}
public CellSignalStrengthWcdma getCellSignalStrength()
throws IllegalAccessException, InvocationTargetException {
return new CellSignalStrengthWcdma(mGetCellSignalStrength.invoke(mObj));
}
}
private TelephonyManager tm;
private CellLocationDatabase db;
public TelephonyHelper(Context context) {
tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
db = new CellLocationDatabase(context);
}
// call getAllCellInfo() in a way that is safe for many
// versions of Android. If does not exist, returns null or
// returns an empty list then return null otherwise return
// list of cells.
public synchronized List<Location> getAllCellInfoWrapper() {
if (tm == null)
return null;
List<android.telephony.CellInfo> allCells;
try {
allCells = tm.getAllCellInfo();
} catch (NoSuchMethodError e) {
allCells = null;
if (DEBUG) Log.i(TAG, "no such method: getAllCellInfo().");
}
if ((allCells == null) || allCells.isEmpty()) {
if (DEBUG) Log.i(TAG, "getAllCellInfo() returned null or empty set");
return null;
}
List<Location> rslt = new ArrayList<Location>();
for (android.telephony.CellInfo inputCellInfo : allCells) {
Location cellLocation = null;
if (inputCellInfo instanceof CellInfoGsm) {
CellInfoGsm gsm = (CellInfoGsm) inputCellInfo;
CellIdentityGsm id = gsm.getCellIdentity();
cellLocation = db.query(id.getMcc(), id.getMnc(), id.getCid(), id.getLac());
} else if (CellInfoWcdma.isInstance(inputCellInfo)) {
try {
CellInfoWcdma wcdma = new CellInfoWcdma(inputCellInfo);
CellIdentityWcdma id = wcdma.getCellIdentity();
cellLocation = db.query(id.getMcc(), id.getMnc(), id.getCid(), id.getLac());
} catch(IllegalAccessException e) {
if (DEBUG)
Log.i(TAG, "getAllCellInfoWrapper(), Wcdma: " + e.toString());
} catch(InvocationTargetException e) {
if (DEBUG)
Log.i(TAG, "getAllCellInfoWrapper(), Wcdma: " + e.toString());
}
}
if ((cellLocation != null)) {
rslt.add(cellLocation);
}
}
if (rslt.isEmpty())
return null;
return rslt;
}
public synchronized List<Location> legacyGetCellTowers() {
if (tm == null)
return null;
List<Location> rslt = new ArrayList<Location>();
String mncString = tm.getNetworkOperator();
if ((mncString == null) || (mncString.length() < 5) || (mncString.length() > 6)) {
if (DEBUG)
Log.i(TAG, "legacyGetCellTowers(): mncString is NULL or not recognized.");
return null;
}
int mcc = Integer.parseInt(mncString.substring(0, 3));
int mnc = Integer.parseInt(mncString.substring(3));
final CellLocation cellLocation = tm.getCellLocation();
if ((cellLocation != null) && (cellLocation instanceof GsmCellLocation)) {
GsmCellLocation cell = (GsmCellLocation) cellLocation;
Location cellLocInfo = db.query(mcc, mnc, cell.getCid(), cell.getLac());
if (cellLocInfo != null)
rslt.add(cellLocInfo);
else if (DEBUG)
Log.i(TAG, "Unknown cell tower detected: mcc="+mcc+
", mnc="+mcc+", cid="+cell.getCid()+", lac="+cell.getLac());
} else {
if (DEBUG)
Log.i(TAG, "getCellLocation() returned null or no GsmCellLocation.");
}
try {
final List<NeighboringCellInfo> neighbours = tm.getNeighboringCellInfo();
if ((neighbours != null) && !neighbours.isEmpty()) {
for (NeighboringCellInfo neighbour : neighbours) {
Location cellLocInfo = db.query(mcc, mnc, neighbour.getCid(), neighbour.getLac());
if (cellLocInfo != null) {
rslt.add(cellLocInfo);
}
}
} else {
if (DEBUG) Log.i(TAG, "getNeighboringCellInfo() returned null or empty set.");
}
} catch (NoSuchMethodError e) {
if (DEBUG) Log.i(TAG, "no such method: getNeighboringCellInfo().");
}
if (rslt.isEmpty() && DEBUG) {
Log.i(TAG, "No known cell towers found.");
}
if (rslt.isEmpty())
return null;
return rslt;
}
public synchronized List<Location> getTowerLocations() {
if (tm == null)
return null;
db.checkForNewDatabase();
List<Location> rslt = getAllCellInfoWrapper();
if (rslt == null) {
if (DEBUG)
Log.i(TAG, "getAllCellInfoWrapper() returned nothing, trying legacyGetCellTowers().");
rslt = legacyGetCellTowers();
}
if ((rslt == null) || rslt.isEmpty()) {
if (DEBUG) Log.i(TAG, "getTowerLocations(): No tower information.");
return null;
}
return rslt;
}
public Location weightedAverage(String source, Collection<Location> locations) {
Location rslt;
if (locations == null || locations.size() == 0) {
return null;
}
int num = locations.size();
int totalWeight = 0;
double latitude = 0;
double longitude = 0;
float accuracy = 0;
int altitudes = 0;
double altitude = 0;
for (Location value : locations) {
if (value != null) {
// Create weight value based on accuracy. Higher accuracy
// (lower tower radius/range) towers get higher weight.
float thisAcc = value.getAccuracy();
if (thisAcc < 1f)
thisAcc = 1f;
int wgt = (int) (100000f / thisAcc);
if (wgt < 1)
wgt = 1;
latitude += (value.getLatitude() * wgt);
longitude += (value.getLongitude() * wgt);
accuracy += (value.getAccuracy() * wgt);
totalWeight += wgt;
// if (DEBUG) Log.i(TAG, "(lat="+ latitude + ", lng=" + longitude + ", acc=" + accuracy + ") / wgt=" + totalWeight );
if (value.hasAltitude()) {
altitude += value.getAltitude();
altitudes++;
}
}
}
latitude = latitude / totalWeight;
longitude = longitude / totalWeight;
accuracy = accuracy / totalWeight;
altitude = altitude / altitudes;
Bundle extras = new Bundle();
extras.putInt("AVERAGED_OF", num);
// if (DEBUG) Log.i(TAG, "Location est (lat="+ latitude + ", lng=" + longitude + ", acc=" + accuracy);
if (altitudes > 0) {
rslt = LocationHelper.create(source,
latitude,
longitude,
altitude,
accuracy,
extras);
} else {
rslt = LocationHelper.create(source,
latitude,
longitude,
accuracy,
extras);
}
rslt.setTime(System.currentTimeMillis());
return rslt;
}
public synchronized Location getLocationEstimate() {
if (tm == null)
return null;
return weightedAverage("gsm", getTowerLocations());
}
}