diff --git a/src/org/microg/nlp/api/CellBackendHelper.java b/src/org/microg/nlp/api/CellBackendHelper.java
index 91dd240..538ce2f 100644
--- a/src/org/microg/nlp/api/CellBackendHelper.java
+++ b/src/org/microg/nlp/api/CellBackendHelper.java
@@ -37,9 +37,8 @@
import android.telephony.PhoneStateListener;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
-import android.telephony.gsm.GsmCellLocation;
-import android.util.Log;
+import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -47,16 +46,15 @@
/**
* Utility class to support backends that use Cells for geolocation.
*
- * Due to consistent changes in APIs for cell retrieval, this class will only work on Android 4.2+
- *
- * This class is incomplete. Do not use in production grade code.
+ * Due to changes in APIs for cell retrieval, this class will only work on Android 4.2+
+ * Support for earlier Android versions might be added later...
*/
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR1)
public class CellBackendHelper extends AbstractBackendHelper {
- private static final String TAG = "NlpCellBackendHelper";
private final Listener listener;
private final TelephonyManager telephonyManager;
private PhoneStateListener phoneStateListener;
+ private boolean supportsCellInfoChanged = true;
private Set cells = new HashSet<>();
/**
@@ -82,15 +80,14 @@ public CellBackendHelper(Context context, Listener listener) {
@Override
public void run() {
phoneStateListener = new PhoneStateListener() {
- private boolean supportsCellInfoChanged = true;
@Override
public void onCellInfoChanged(List cellInfo) {
if (cellInfo != null) {
onCellsChanged(cellInfo);
- } else {
- Log.d(TAG, "Device seems not to support onCellInfoChanged()");
+ } else if (supportsCellInfoChanged) {
supportsCellInfoChanged = false;
+ onCellsChanged(telephonyManager.getAllCellInfo());
}
}
@@ -114,11 +111,44 @@ private int getMcc() {
}
}
+ private int getMnc() {
+ try {
+ return Integer.parseInt(telephonyManager.getNetworkOperator().substring(3));
+ } catch (Exception e) {
+ return -1;
+ }
+ }
+
+ private static Cell.CellType getCellType(int networkType) {
+ switch (networkType) {
+ case TelephonyManager.NETWORK_TYPE_GPRS:
+ case TelephonyManager.NETWORK_TYPE_EDGE:
+ return Cell.CellType.GSM;
+ case TelephonyManager.NETWORK_TYPE_UMTS:
+ case TelephonyManager.NETWORK_TYPE_HSDPA:
+ case TelephonyManager.NETWORK_TYPE_HSUPA:
+ case TelephonyManager.NETWORK_TYPE_HSPA:
+ case TelephonyManager.NETWORK_TYPE_HSPAP:
+ return Cell.CellType.UMTS;
+ case TelephonyManager.NETWORK_TYPE_LTE:
+ return Cell.CellType.LTE;
+ case TelephonyManager.NETWORK_TYPE_EVDO_0:
+ case TelephonyManager.NETWORK_TYPE_EVDO_A:
+ case TelephonyManager.NETWORK_TYPE_EVDO_B:
+ case TelephonyManager.NETWORK_TYPE_1xRTT:
+ case TelephonyManager.NETWORK_TYPE_EHRPD:
+ case TelephonyManager.NETWORK_TYPE_IDEN:
+ return Cell.CellType.CDMA;
+ }
+ return null;
+ }
+
@SuppressWarnings("ChainOfInstanceofChecks")
private Cell parseCellInfo(CellInfo info) {
try {
if (info instanceof CellInfoGsm) {
CellIdentityGsm identity = ((CellInfoGsm) info).getCellIdentity();
+ if (identity.getMcc() == Integer.MAX_VALUE) return null;
CellSignalStrengthGsm strength = ((CellInfoGsm) info).getCellSignalStrength();
return new Cell(Cell.CellType.GSM, identity.getMcc(), identity.getMnc(),
identity.getLac(), identity.getCid(), -1, strength.getDbm());
@@ -141,11 +171,13 @@ private Cell parceCellInfo18(CellInfo info) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) return null;
if (info instanceof CellInfoWcdma) {
CellIdentityWcdma identity = ((CellInfoWcdma) info).getCellIdentity();
+ if (identity.getMcc() == Integer.MAX_VALUE) return null;
CellSignalStrengthWcdma strength = ((CellInfoWcdma) info).getCellSignalStrength();
return new Cell(Cell.CellType.UMTS, identity.getMcc(), identity.getMnc(),
identity.getLac(), identity.getCid(), identity.getPsc(), strength.getDbm());
} else if (info instanceof CellInfoLte) {
CellIdentityLte identity = ((CellInfoLte) info).getCellIdentity();
+ if (identity.getMcc() == Integer.MAX_VALUE) return null;
CellSignalStrengthLte strength = ((CellInfoLte) info).getCellSignalStrength();
return new Cell(Cell.CellType.LTE, identity.getMcc(), identity.getMnc(),
identity.getTac(), identity.getCi(), identity.getPci(), strength.getDbm());
@@ -153,24 +185,93 @@ private Cell parceCellInfo18(CellInfo info) {
return null;
}
+ private Cell parseCellInfo(NeighboringCellInfo info) {
+ try {
+ if (getCellType(info.getNetworkType()) != Cell.CellType.GSM) return null;
+ return new Cell(Cell.CellType.GSM, getMcc(), getMnc(), info.getLac(), info.getCid(),
+ info.getPsc(), info.getRssi());
+ } catch (Exception ignored) {
+ }
+ return null;
+ }
+
private void onCellsChanged(List cellInfo) {
if (loadCells(cellInfo)) {
listener.onCellsChanged(getCells());
}
}
+ /**
+ * This will fix values returned by {@link TelephonyManager#getAllCellInfo()} as described
+ * here: https://github.com/mozilla/ichnaea/issues/340
+ */
+ @SuppressWarnings({"ChainOfInstanceofChecks", "MagicNumber"})
+ @TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
+ private void fixAllCellInfo(List cellInfo) {
+ if (cellInfo == null) return;
+ String networkOperator = telephonyManager.getNetworkOperator();
+ if (networkOperator.length() != 5) return;
+ int realMnc = Integer.parseInt(networkOperator.substring(3));
+ boolean theBug = false;
+ for (CellInfo info : cellInfo) {
+ if (info instanceof CellInfoCdma) return;
+ if (info.isRegistered()) {
+ Cell cell = parseCellInfo(info);
+ if (cell == null) continue;
+ int infoMnc = cell.getMnc();
+ if (infoMnc == (realMnc * 10 + 15)) {
+ theBug = true;
+ }
+ }
+ }
+ if (theBug) {
+ for (CellInfo info : cellInfo) {
+ Object identity = null;
+ if (info instanceof CellInfoGsm)
+ identity = ((CellInfoGsm) info).getCellIdentity();
+ else if (info instanceof CellInfoLte)
+ identity = ((CellInfoLte) info).getCellIdentity();
+ else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2 &&
+ info instanceof CellInfoWcdma)
+ identity = ((CellInfoWcdma) info).getCellIdentity();
+ if (identity == null) continue;
+ try {
+ Field mncField = identity.getClass().getDeclaredField("mMnc");
+ mncField.setAccessible(true);
+ int mnc = (int) mncField.get(identity);
+ if (mnc >= 25 && mnc <= 1005) {
+ mnc = (mnc - 15) / 10;
+ mncField.setInt(identity, mnc);
+ }
+ } catch (NoSuchFieldException | IllegalAccessException ignored) {
+ }
+ }
+ }
+ }
+
+ private boolean hasCid(long cid) {
+ for (Cell cell : cells) {
+ if (cell.getCid() == cid) return true;
+ }
+ return false;
+ }
+
private synchronized boolean loadCells(List cellInfo) {
cells.clear();
currentDataUsed = false;
+ fixAllCellInfo(cellInfo);
for (CellInfo info : cellInfo) {
Cell cell = parseCellInfo(info);
if (cell == null) continue;
- Log.d(TAG, "onCellsChanged.allCellInfo: " + cell);
+ cells.add(cell);
}
for (NeighboringCellInfo info : telephonyManager.getNeighboringCellInfo()) {
- Log.d(TAG, "onCellsChange.neighboringCellInfo: " + info);
+ if (!hasCid(info.getCid())) {
+ Cell cell = parseCellInfo(info);
+ if (cell == null) continue;
+ cells.add(cell);
+ }
}
- Log.d(TAG, "onCellsChange.operator: " + telephonyManager.getNetworkOperator());
if (state == State.DISABLING)
state = State.DISABLED;
switch (state) {
@@ -203,6 +304,15 @@ private synchronized void registerPhoneStateListener() {
| PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
}
+ @Override
+ public synchronized void onUpdate() {
+ if (!currentDataUsed) {
+ listener.onCellsChanged(getCells());
+ } else {
+ state = State.SCANNING;
+ }
+ }
+
/**
* Call this in {@link org.microg.nlp.api.LocationBackendService#onClose()}.
*/
@@ -232,16 +342,16 @@ public Cell(CellType type, int mcc, int mnc, int lac, long cid, int psc, int sig
this.type = type;
boolean cdma = type == CellType.CDMA;
if (mcc < 0 || mcc > 999)
- throw new IllegalArgumentException("Invalid MCC");
+ throw new IllegalArgumentException("Invalid MCC: " + mcc);
this.mcc = mcc;
if (cdma ? (mnc < 1 || mnc > 32767) : (mnc < 0 || mnc > 999))
- throw new IllegalArgumentException("Invalid MNC");
+ throw new IllegalArgumentException("Invalid MNC: " + mnc);
this.mnc = mnc;
if (lac < 1 || lac > (cdma ? 65534 : 65533))
- throw new IllegalArgumentException("Invalid LAC");
+ throw new IllegalArgumentException("Invalid LAC: " + lac);
this.lac = lac;
if (cid < 0)
- throw new IllegalArgumentException("Invalid CID");
+ throw new IllegalArgumentException("Invalid CID: " + cid);
this.cid = cid;
this.psc = psc;
this.signal = signal;
diff --git a/src/org/microg/nlp/api/WiFiBackendHelper.java b/src/org/microg/nlp/api/WiFiBackendHelper.java
index ed635fb..bdae78d 100644
--- a/src/org/microg/nlp/api/WiFiBackendHelper.java
+++ b/src/org/microg/nlp/api/WiFiBackendHelper.java
@@ -95,8 +95,7 @@ public synchronized void onClose() {
*/
public synchronized void onUpdate() {
if (!currentDataUsed) {
- currentDataUsed = true;
- listener.onWiFisChanged(wiFis);
+ listener.onWiFisChanged(getWiFis());
} else {
scanWiFis();
}
@@ -110,7 +109,7 @@ private void onWiFisChanged() {
private synchronized boolean scanWiFis() {
if (state == State.DISABLED)
- throw new IllegalStateException("can't scan on disabled WiFiBackendHelper");
+ return false;
if (wifiManager.isWifiEnabled() || isScanAlawaysAvailable()) {
state = State.SCANNING;
wifiManager.startScan();
|