From 29e1a0b1b9ef366c564aa02eff8143e83182654a Mon Sep 17 00:00:00 2001 From: mar-v-in Date: Tue, 17 Feb 2015 13:14:45 +0100 Subject: [PATCH] Finalize first version of CellBackendHelper --- src/org/microg/nlp/api/CellBackendHelper.java | 142 ++++++++++++++++-- src/org/microg/nlp/api/WiFiBackendHelper.java | 5 +- 2 files changed, 128 insertions(+), 19 deletions(-) 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();