Skip to content

Commit

Permalink
Finalize first version of CellBackendHelper
Browse files Browse the repository at this point in the history
  • Loading branch information
mar-v-in committed Feb 17, 2015
1 parent 80c5583 commit 29e1a0b
Show file tree
Hide file tree
Showing 2 changed files with 128 additions and 19 deletions.
142 changes: 126 additions & 16 deletions src/org/microg/nlp/api/CellBackendHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,26 +37,24 @@
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;

/**
* Utility class to support backends that use Cells for geolocation.
* <p/>
* Due to consistent changes in APIs for cell retrieval, this class will only work on Android 4.2+
* <p/>
* 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<Cell> cells = new HashSet<>();

/**
Expand All @@ -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> cellInfo) {
if (cellInfo != null) {
onCellsChanged(cellInfo);
} else {
Log.d(TAG, "Device seems not to support onCellInfoChanged()");
} else if (supportsCellInfoChanged) {
supportsCellInfoChanged = false;
onCellsChanged(telephonyManager.getAllCellInfo());
}
}

Expand All @@ -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());
Expand All @@ -141,36 +171,107 @@ 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());
}
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> 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> 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> 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) {
Expand Down Expand Up @@ -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()}.
*/
Expand Down Expand Up @@ -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;
Expand Down
5 changes: 2 additions & 3 deletions src/org/microg/nlp/api/WiFiBackendHelper.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,7 @@ public synchronized void onClose() {
*/
public synchronized void onUpdate() {
if (!currentDataUsed) {
currentDataUsed = true;
listener.onWiFisChanged(wiFis);
listener.onWiFisChanged(getWiFis());
} else {
scanWiFis();
}
Expand All @@ -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();
Expand Down

0 comments on commit 29e1a0b

Please sign in to comment.