Bug 1095914 - Copy over CellScanner from mozstumbler github. r=nalexander, a=lsblakk
authorGarvan Keeley <gkeeley@mozilla.com>
Thu, 13 Nov 2014 11:54:00 -0500
changeset 233997 75e35dfbebcbdbc02aff786b755e4dab39091556
parent 233996 0985f515e551b6efc3f6836c6685baa374821d07
child 233998 bd7fbe828b1080f9b7e66f66cd4550e0501caf9b
push id4187
push userbhearsum@mozilla.com
push dateFri, 28 Nov 2014 15:29:12 +0000
treeherdermozilla-beta@f23cc6a30c11 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander, lsblakk
bugs1095914
milestone35.0a2
Bug 1095914 - Copy over CellScanner from mozstumbler github. r=nalexander, a=lsblakk This has a few important changes: 1) Null-check the TelephonyManager availability. 2) The cell scanning code was split to remove wcdma scanning on Fennec due to an older API level on Fennec. This is no longer the case. CellScannerNoWCDMA.java renamed to CellScannerImplementation.java. 3) Remove broadcastsync on the timer thread, have the timer thread message back to the owning class through a handler (guaranteed thread-safe mechanism to notify the owning class that work is done).
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/Reporter.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/StumblerService.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/ScanManager.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScannerImplementation.java
mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScannerNoWCDMA.java
mobile/android/stumbler/stumbler_sources.mozbuild
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/Reporter.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/Reporter.java
@@ -27,16 +27,17 @@ import org.mozilla.mozstumbler.service.s
 import org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner.CellInfo;
 import org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner.CellScanner;
 import org.mozilla.mozstumbler.service.stumblerthread.scanners.GPSScanner;
 import org.mozilla.mozstumbler.service.stumblerthread.scanners.WifiScanner;
 
 public final class Reporter extends BroadcastReceiver {
     private static final String LOG_TAG = AppGlobals.makeLogTag(Reporter.class.getSimpleName());
     public static final String ACTION_FLUSH_TO_BUNDLE = AppGlobals.ACTION_NAMESPACE + ".FLUSH";
+    public static final String ACTION_NEW_BUNDLE = AppGlobals.ACTION_NAMESPACE + ".NEW_BUNDLE";
     private boolean mIsStarted;
 
     /* The maximum number of Wi-Fi access points in a single observation. */
     private static final int MAX_WIFIS_PER_LOCATION = 200;
 
     /* The maximum number of cells in a single observation */
     private static final int MAX_CELLS_PER_LOCATION  = 50;
 
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/StumblerService.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/StumblerService.java
@@ -12,17 +12,16 @@ import java.io.IOException;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 import org.mozilla.mozstumbler.service.AppGlobals;
 import org.mozilla.mozstumbler.service.Prefs;
 import org.mozilla.mozstumbler.service.stumblerthread.blocklist.WifiBlockListInterface;
 import org.mozilla.mozstumbler.service.stumblerthread.datahandling.DataStorageManager;
 import org.mozilla.mozstumbler.service.stumblerthread.scanners.ScanManager;
 import org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner.CellScanner;
-import org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner.CellScannerNoWCDMA;
 import org.mozilla.mozstumbler.service.uploadthread.UploadAlarmReceiver;
 import org.mozilla.mozstumbler.service.utils.NetworkUtils;
 import org.mozilla.mozstumbler.service.utils.PersistentIntentService;
 
 // In stand-alone service mode (a.k.a passive scanning mode), this is created from PassiveServiceReceiver (by calling startService).
 // The StumblerService is a sticky unbound service in this usage.
 //
 public class StumblerService extends PersistentIntentService
@@ -104,53 +103,43 @@ public class StumblerService extends Per
     public int getVisibleAPCount() {
         return mScanManager.getVisibleAPCount();
     }
 
     public int getCellInfoCount() {
         return mScanManager.getCellInfoCount();
     }
 
-    public int getCurrentCellInfoCount() {
-        return mScanManager.getCurrentCellInfoCount();
-    }
-
     public boolean isGeofenced () {
         return mScanManager.isGeofenced();
     }
 
     // Previously this was done in onCreate(). Moved out of that so that in the passive standalone service
     // use (i.e. Fennec), init() can be called from this class's dedicated thread.
     // Safe to call more than once, ensure added code complies with that intent.
     protected void init() {
         Prefs.createGlobalInstance(this);
         NetworkUtils.createGlobalInstance(this);
         DataStorageManager.createGlobalInstance(this, this);
 
-        if (!CellScanner.isCellScannerImplSet()) {
-            CellScanner.setCellScannerImpl(new CellScannerNoWCDMA(this));
-        }
-
         mReporter.startup(this);
     }
 
     // Called from the main thread.
     @Override
     public void onCreate() {
         super.onCreate();
         setIntentRedelivery(true);
     }
 
     // Called from the main thread
     @Override
     public void onDestroy() {
         super.onDestroy();
 
-        UploadAlarmReceiver.cancelAlarm(this, !mScanManager.isPassiveMode());
-
         if (!mScanManager.isScanning()) {
             return;
         }
 
         // Used to move these disk I/O ops off the calling thread. The current operations here are synchronized,
         // however instead of creating another thread (if onDestroy grew to have concurrency complications)
         // we could be messaging the stumbler thread to perform a shutdown function.
         new AsyncTask<Void, Void, Void>() {
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java
@@ -419,18 +419,18 @@ public class DataStorageManager {
         notifyStorageIsEmpty(false);
 
         if (mFlushMemoryBuffersToDiskTimer != null) {
             mFlushMemoryBuffersToDiskTimer.cancel();
             mFlushMemoryBuffersToDiskTimer = null;
         }
 
         mCurrentReports.reports.add(report);
-        mCurrentReports.wifiCount = wifiCount;
-        mCurrentReports.cellCount = cellCount;
+        mCurrentReports.wifiCount += wifiCount;
+        mCurrentReports.cellCount += cellCount;
 
         if (mCurrentReports.reports.size() >= MAX_REPORTS_IN_MEMORY) {
             // save to disk
             saveCurrentReportsToDisk();
         } else {
             // Schedule a timer to flush to disk after a few mins.
             // If collection stops and wifi not available for uploading, the memory buffer is flushed to disk.
             final int kMillis = 1000 * 60 * 3;
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/ScanManager.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/ScanManager.java
@@ -153,20 +153,16 @@ public class ScanManager {
     public int getWifiStatus() {
         return (mWifiScanner == null)? 0 : mWifiScanner.getStatus();
     }
 
     public int getCellInfoCount() {
         return (mCellScanner == null)? 0 :mCellScanner.getCellInfoCount();
     }
 
-    public int getCurrentCellInfoCount() {
-        return (mCellScanner == null)? 0 :mCellScanner.getCurrentCellInfoCount();
-    }
-
     public int getLocationCount() {
         return (mGPSScanner == null)? 0 : mGPSScanner.getLocationCount();
     }
 
     public double getLatitude() {
         return (mGPSScanner == null)? 0.0 : mGPSScanner.getLatitude();
     }
 
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java
@@ -1,135 +1,162 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner;
 
+import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Handler;
+import android.os.Message;
 import android.support.v4.content.LocalBroadcastManager;
+import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import org.mozilla.mozstumbler.service.AppGlobals;
+import org.mozilla.mozstumbler.service.AppGlobals.ActiveOrPassiveStumbling;
+
 import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
 import java.util.Timer;
 import java.util.TimerTask;
-
-import org.mozilla.mozstumbler.service.AppGlobals;
+import java.util.concurrent.atomic.AtomicBoolean;
 import org.mozilla.mozstumbler.service.AppGlobals.ActiveOrPassiveStumbling;
-
+import org.mozilla.mozstumbler.service.stumblerthread.Reporter;
 
 public class CellScanner {
     public static final String ACTION_BASE = AppGlobals.ACTION_NAMESPACE + ".CellScanner.";
     public static final String ACTION_CELLS_SCANNED = ACTION_BASE + "CELLS_SCANNED";
     public static final String ACTION_CELLS_SCANNED_ARG_CELLS = "cells";
     public static final String ACTION_CELLS_SCANNED_ARG_TIME = AppGlobals.ACTION_ARG_TIME;
 
     private static final String LOG_TAG = AppGlobals.makeLogTag(CellScanner.class.getSimpleName());
     private static final long CELL_MIN_UPDATE_TIME = 1000; // milliseconds
 
     private final Context mContext;
-    private static CellScannerImpl sImpl;
     private Timer mCellScanTimer;
     private final Set<String> mCells = new HashSet<String>();
-    private int mCurrentCellInfoCount;
+    private final ReportFlushedReceiver mReportFlushedReceiver = new ReportFlushedReceiver();
+    private final AtomicBoolean mReportWasFlushed = new AtomicBoolean();
+    private Handler mBroadcastScannedHandler;
+    private final CellScannerImpl mCellScannerImplementation;
 
     public ArrayList<CellInfo> sTestingModeCellInfoArray;
 
     public interface CellScannerImpl {
-        public void start();
-
-        public void stop();
-
-        public List<CellInfo> getCellInfo();
+        void start();
+        boolean isStarted();
+        boolean isSupportedOnThisDevice();
+        void stop();
+        List<CellInfo> getCellInfo();
     }
 
     public CellScanner(Context context) {
         mContext = context;
-    }
-
-    private static synchronized CellScannerImpl getImplementation() {
-        return sImpl;
-    }
-
-    public static synchronized boolean isCellScannerImplSet() {
-        return sImpl != null;
-    }
-
-    /* Fennec doesn't support the apis needed for full scanning, we have different implementations.*/
-    public static synchronized void setCellScannerImpl(CellScannerImpl cellScanner) {
-        sImpl = cellScanner;
+        mCellScannerImplementation = new CellScannerImplementation(context);
     }
 
     public void start(final ActiveOrPassiveStumbling stumblingMode) {
-        if (getImplementation() == null) {
-            return;
-        }
-
-        try {
-            getImplementation().start();
-        } catch (UnsupportedOperationException uoe) {
-            Log.e(LOG_TAG, "Cell scanner probe failed", uoe);
+        if (!mCellScannerImplementation.isSupportedOnThisDevice()) {
             return;
         }
 
         if (mCellScanTimer != null) {
             return;
         }
 
+        LocalBroadcastManager.getInstance(mContext).registerReceiver(mReportFlushedReceiver,
+                new IntentFilter(Reporter.ACTION_NEW_BUNDLE));
+
+        // This is to ensure the broadcast happens from the same thread the CellScanner start() is on
+        mBroadcastScannedHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                Intent intent = (Intent) msg.obj;
+                LocalBroadcastManager.getInstance(mContext).sendBroadcastSync(intent);
+            }
+        };
+
+        mCellScannerImplementation.start();
+
         mCellScanTimer = new Timer();
 
         mCellScanTimer.schedule(new TimerTask() {
             int mPassiveScanCount;
             @Override
             public void run() {
-                if (getImplementation() == null) {
+                if (!mCellScannerImplementation.isStarted()) {
                     return;
                 }
 
                 if (stumblingMode == ActiveOrPassiveStumbling.PASSIVE_STUMBLING &&
-                        mPassiveScanCount++ > AppGlobals.PASSIVE_MODE_MAX_SCANS_PER_GPS)
+                    mPassiveScanCount++ > AppGlobals.PASSIVE_MODE_MAX_SCANS_PER_GPS)
                 {
                     mPassiveScanCount = 0;
                     stop();
                     return;
                 }
-                //if (SharedConstants.isDebug) Log.d(LOG_TAG, "Cell Scanning Timer fired");
+
                 final long curTime = System.currentTimeMillis();
 
                 ArrayList<CellInfo> cells = (sTestingModeCellInfoArray != null)? sTestingModeCellInfoArray :
-                        new ArrayList<CellInfo>(getImplementation().getCellInfo());
+                        new ArrayList<CellInfo>(mCellScannerImplementation.getCellInfo());
 
-                mCurrentCellInfoCount = cells.size();
+                if (mReportWasFlushed.getAndSet(false)) {
+                    clearCells();
+                }
+
                 if (cells.isEmpty()) {
                     return;
                 }
-                for (CellInfo cell: cells) mCells.add(cell.getCellIdentity());
+
+                for (CellInfo cell : cells) {
+                    addToCells(cell.getCellIdentity());
+                }
 
                 Intent intent = new Intent(ACTION_CELLS_SCANNED);
                 intent.putParcelableArrayListExtra(ACTION_CELLS_SCANNED_ARG_CELLS, cells);
                 intent.putExtra(ACTION_CELLS_SCANNED_ARG_TIME, curTime);
-                LocalBroadcastManager.getInstance(mContext).sendBroadcastSync(intent);
+                // send to handler, so broadcast is not from timer thread
+                Message message = new Message();
+                message.obj = intent;
+                mBroadcastScannedHandler.sendMessage(message);
+
             }
         }, 0, CELL_MIN_UPDATE_TIME);
     }
 
-    public void stop() {
+    private synchronized void clearCells() {
+        mCells.clear();
+    }
+
+    private synchronized void addToCells(String cell) {
+        mCells.add(cell);
+    }
+
+    public synchronized void stop() {
+        mReportWasFlushed.set(false);
+        clearCells();
+        LocalBroadcastManager.getInstance(mContext).unregisterReceiver(mReportFlushedReceiver);
+
         if (mCellScanTimer != null) {
             mCellScanTimer.cancel();
             mCellScanTimer = null;
         }
-        if (getImplementation() != null) {
-            getImplementation().stop();
-        }
+        mCellScannerImplementation.stop();
     }
 
-    public int getCellInfoCount() {
+    public synchronized int getCellInfoCount() {
         return mCells.size();
     }
 
-    public int getCurrentCellInfoCount() {
-        return mCurrentCellInfoCount;
+    private class ReportFlushedReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context c, Intent i) {
+            mReportWasFlushed.set(true);
+        }
     }
 }
new file mode 100644
--- /dev/null
+++ b/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScannerImplementation.java
@@ -0,0 +1,293 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.os.Build;
+import android.telephony.CellIdentityCdma;
+import android.telephony.CellIdentityGsm;
+import android.telephony.CellIdentityLte;
+import android.telephony.CellIdentityWcdma;
+import android.telephony.CellInfoCdma;
+import android.telephony.CellInfoGsm;
+import android.telephony.CellInfoLte;
+import android.telephony.CellInfoWcdma;
+import android.telephony.CellLocation;
+import android.telephony.CellSignalStrengthCdma;
+import android.telephony.CellSignalStrengthGsm;
+import android.telephony.CellSignalStrengthLte;
+import android.telephony.CellSignalStrengthWcdma;
+import android.telephony.NeighboringCellInfo;
+import android.telephony.PhoneStateListener;
+import android.telephony.SignalStrength;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import org.mozilla.mozstumbler.service.AppGlobals;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+public class CellScannerImplementation implements CellScanner.CellScannerImpl {
+
+    protected static String LOG_TAG = AppGlobals.makeLogTag(CellScannerImplementation.class.getSimpleName());
+    protected GetAllCellInfoScannerImpl mGetAllInfoCellScanner;
+    protected TelephonyManager mTelephonyManager;
+    protected boolean mIsStarted;
+    protected int mPhoneType;
+    protected final Context mContext;
+
+    protected volatile int mSignalStrength = CellInfo.UNKNOWN_SIGNAL;
+    protected volatile int mCdmaDbm = CellInfo.UNKNOWN_SIGNAL;
+
+    private PhoneStateListener mPhoneStateListener;
+
+    private static class GetAllCellInfoScannerDummy implements GetAllCellInfoScannerImpl {
+        @Override
+        public List<CellInfo> getAllCellInfo(TelephonyManager tm) {
+            return Collections.emptyList();
+        }
+    }
+
+    interface GetAllCellInfoScannerImpl {
+        List<CellInfo> getAllCellInfo(TelephonyManager tm);
+    }
+
+    public CellScannerImplementation(Context context) {
+        mContext = context;
+    }
+
+    public boolean isSupportedOnThisDevice() {
+        TelephonyManager telephonyManager = mTelephonyManager;
+        if (telephonyManager == null) {
+            telephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        }
+        return telephonyManager != null &&
+                (telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_CDMA ||
+                 telephonyManager.getPhoneType() == TelephonyManager.PHONE_TYPE_GSM);
+    }
+
+    @Override
+    public synchronized boolean isStarted() {
+        return mIsStarted;
+    }
+
+    @Override
+    public synchronized void start() {
+        if (mIsStarted || !isSupportedOnThisDevice()) {
+            return;
+        }
+        mIsStarted = true;
+
+        if (mTelephonyManager == null) {
+            if (Build.VERSION.SDK_INT >= 18 /*Build.VERSION_CODES.JELLY_BEAN_MR2 */) { // Fennec: no Build.VERSION_CODES
+                mGetAllInfoCellScanner = new GetAllCellInfoScannerMr2();
+            } else {
+                mGetAllInfoCellScanner = new GetAllCellInfoScannerDummy();
+            }
+
+            mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
+        }
+
+        mPhoneStateListener = new PhoneStateListener() {
+            @Override
+            public void onSignalStrengthsChanged(SignalStrength ss) {
+                if (ss.isGsm()) {
+                    mSignalStrength = ss.getGsmSignalStrength();
+                } else {
+                    mCdmaDbm = ss.getCdmaDbm();
+                }
+            }
+        };
+        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
+    }
+
+    @Override
+    public synchronized void stop() {
+        mIsStarted = false;
+        if (mTelephonyManager != null && mPhoneStateListener != null) {
+            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
+        }
+        mSignalStrength = CellInfo.UNKNOWN_SIGNAL;
+        mCdmaDbm = CellInfo.UNKNOWN_SIGNAL;
+    }
+
+    @Override
+    public synchronized List<CellInfo> getCellInfo() {
+        List<CellInfo> records = new ArrayList<CellInfo>();
+
+        List<CellInfo> allCells = mGetAllInfoCellScanner.getAllCellInfo(mTelephonyManager);
+        if (allCells.isEmpty()) {
+            CellInfo currentCell = getCurrentCellInfo();
+            if (currentCell == null) {
+                return records;
+            }
+            records.add(currentCell);
+        }else {
+            records.addAll(allCells);
+        }
+
+        // getNeighboringCells() sometimes contains more information than that is already
+        // in getAllCellInfo(). Use the results of both of them.
+        records.addAll(getNeighboringCells());
+        return records;
+    }
+
+    private String getNetworkOperator() {
+        String networkOperator = mTelephonyManager.getNetworkOperator();
+        // getNetworkOperator() may be unreliable on CDMA networks
+        if (networkOperator == null || networkOperator.length() <= 3) {
+            networkOperator = mTelephonyManager.getSimOperator();
+        }
+        return networkOperator;
+    }
+
+    protected CellInfo getCurrentCellInfo() {
+        final CellLocation currentCell = mTelephonyManager.getCellLocation();
+        if (currentCell == null) {
+            return null;
+        }
+
+        try {
+            final CellInfo info = new CellInfo(mPhoneType);
+            final int signalStrength = mSignalStrength;
+            final int cdmaDbm = mCdmaDbm;
+            info.setCellLocation(currentCell,
+                    mTelephonyManager.getNetworkType(),
+                    getNetworkOperator(),
+                    signalStrength == CellInfo.UNKNOWN_SIGNAL ? null : signalStrength,
+                    cdmaDbm == CellInfo.UNKNOWN_SIGNAL ? null : cdmaDbm);
+            return info;
+        } catch (IllegalArgumentException iae) {
+            Log.e(LOG_TAG, "Skip invalid or incomplete CellLocation: " + currentCell, iae);
+        }
+        return null;
+    }
+
+    private List<CellInfo> getNeighboringCells() {
+        Collection<NeighboringCellInfo> cells = mTelephonyManager.getNeighboringCellInfo();
+        if (cells == null || cells.isEmpty()) {
+            return Collections.emptyList();
+        }
+
+        String networkOperator = getNetworkOperator();
+        List<CellInfo> records = new ArrayList<CellInfo>(cells.size());
+        for (NeighboringCellInfo nci : cells) {
+            try {
+                final CellInfo record = new CellInfo(mPhoneType);
+                record.setNeighboringCellInfo(nci, networkOperator);
+                if (record.isCellRadioValid()) {
+                    records.add(record);
+                }
+            } catch (IllegalArgumentException iae) {
+                Log.e(LOG_TAG, "Skip invalid or incomplete NeighboringCellInfo: " + nci, iae);
+            }
+        }
+        return records;
+    }
+
+
+    @TargetApi(18)
+    protected boolean addWCDMACellToList(List<CellInfo> cells,
+                                         android.telephony.CellInfo observedCell,
+                                         TelephonyManager tm) {
+        boolean added = false;
+        if (Build.VERSION.SDK_INT >= 18 &&
+                observedCell instanceof CellInfoWcdma) {
+            CellIdentityWcdma ident = ((CellInfoWcdma) observedCell).getCellIdentity();
+            if (ident.getMnc() != Integer.MAX_VALUE && ident.getMcc() != Integer.MAX_VALUE) {
+                CellInfo cell = new CellInfo(tm.getPhoneType());
+                CellSignalStrengthWcdma strength = ((CellInfoWcdma) observedCell).getCellSignalStrength();
+                cell.setWcmdaCellInfo(ident.getMcc(),
+                        ident.getMnc(),
+                        ident.getLac(),
+                        ident.getCid(),
+                        ident.getPsc(),
+                        strength.getAsuLevel());
+                cells.add(cell);
+                added = true;
+            }
+        }
+        return added;
+    }
+
+    @TargetApi(18)
+    protected boolean addCellToList(List<CellInfo> cells,
+                                    android.telephony.CellInfo observedCell,
+                                    TelephonyManager tm) {
+        if (tm.getPhoneType() == 0) {
+            return false;
+        }
+
+        boolean added = false;
+        if (observedCell instanceof CellInfoGsm) {
+            CellIdentityGsm ident = ((CellInfoGsm) observedCell).getCellIdentity();
+            if (ident.getMcc() != Integer.MAX_VALUE && ident.getMnc() != Integer.MAX_VALUE) {
+                CellSignalStrengthGsm strength = ((CellInfoGsm) observedCell).getCellSignalStrength();
+                CellInfo cell = new CellInfo(tm.getPhoneType());
+                cell.setGsmCellInfo(ident.getMcc(),
+                        ident.getMnc(),
+                        ident.getLac(),
+                        ident.getCid(),
+                        strength.getAsuLevel());
+                cells.add(cell);
+                added = true;
+            }
+        } else if (observedCell instanceof CellInfoCdma) {
+            CellInfo cell = new CellInfo(tm.getPhoneType());
+            CellIdentityCdma ident = ((CellInfoCdma) observedCell).getCellIdentity();
+            CellSignalStrengthCdma strength = ((CellInfoCdma) observedCell).getCellSignalStrength();
+            cell.setCdmaCellInfo(ident.getBasestationId(),
+                    ident.getNetworkId(),
+                    ident.getSystemId(),
+                    strength.getDbm());
+            cells.add(cell);
+            added = true;
+        } else if (observedCell instanceof CellInfoLte) {
+            CellIdentityLte ident = ((CellInfoLte) observedCell).getCellIdentity();
+            if (ident.getMnc() != Integer.MAX_VALUE && ident.getMcc() != Integer.MAX_VALUE) {
+                CellInfo cell = new CellInfo(tm.getPhoneType());
+                CellSignalStrengthLte strength = ((CellInfoLte) observedCell).getCellSignalStrength();
+                cell.setLteCellInfo(ident.getMcc(),
+                        ident.getMnc(),
+                        ident.getCi(),
+                        ident.getPci(),
+                        ident.getTac(),
+                        strength.getAsuLevel(),
+                        strength.getTimingAdvance());
+                cells.add(cell);
+                added = true;
+            }
+        }
+
+        if (!added && Build.VERSION.SDK_INT >= 18) {
+            added = addWCDMACellToList(cells, observedCell, tm);
+        }
+
+        return added;
+    }
+
+    @TargetApi(18)
+    private class GetAllCellInfoScannerMr2 implements GetAllCellInfoScannerImpl {
+        @Override
+        public List<CellInfo> getAllCellInfo(TelephonyManager tm) {
+            final List<android.telephony.CellInfo> observed = tm.getAllCellInfo();
+            if (observed == null || observed.isEmpty()) {
+                return Collections.emptyList();
+            }
+
+            List<CellInfo> cells = new ArrayList<CellInfo>(observed.size());
+            for (android.telephony.CellInfo observedCell : observed) {
+                if (!addCellToList(cells, observedCell, tm)) {
+                    //Log.i(LOG_TAG, "Skipped CellInfo of unknown class: " + observedCell.toString());
+                }
+            }
+            return cells;
+        }
+    }
+}
deleted file mode 100644
--- a/mobile/android/stumbler/java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScannerNoWCDMA.java
+++ /dev/null
@@ -1,254 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.mozstumbler.service.stumblerthread.scanners.cellscanner;
-
-import android.annotation.TargetApi;
-import android.content.Context;
-import android.os.Build;
-import android.telephony.CellIdentityCdma;
-import android.telephony.CellIdentityGsm;
-import android.telephony.CellIdentityLte;
-import android.telephony.CellInfoCdma;
-import android.telephony.CellInfoGsm;
-import android.telephony.CellInfoLte;
-import android.telephony.CellLocation;
-import android.telephony.CellSignalStrengthCdma;
-import android.telephony.CellSignalStrengthGsm;
-import android.telephony.CellSignalStrengthLte;
-import android.telephony.NeighboringCellInfo;
-import android.telephony.PhoneStateListener;
-import android.telephony.SignalStrength;
-import android.telephony.TelephonyManager;
-import android.util.Log;
-import org.mozilla.mozstumbler.service.AppGlobals;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-
-/* Fennec does not yet support the api level for WCDMA import */
-public class CellScannerNoWCDMA implements CellScanner.CellScannerImpl {
-
-    protected static String LOG_TAG = AppGlobals.makeLogTag(CellScannerNoWCDMA.class.getSimpleName());
-    protected GetAllCellInfoScannerImpl mGetAllInfoCellScanner;
-    protected TelephonyManager mTelephonyManager;
-    protected boolean mIsStarted;
-    protected int mPhoneType;
-    protected final Context mContext;
-    protected volatile int mSignalStrength;
-    protected volatile int mCdmaDbm;
-
-    private PhoneStateListener mPhoneStateListener;
-
-    private static class GetAllCellInfoScannerDummy implements GetAllCellInfoScannerImpl {
-        @Override
-        public List<CellInfo> getAllCellInfo(TelephonyManager tm) {
-            return Collections.emptyList();
-        }
-    }
-
-    interface GetAllCellInfoScannerImpl {
-        List<CellInfo> getAllCellInfo(TelephonyManager tm);
-    }
-
-    public CellScannerNoWCDMA(Context context) {
-        mContext = context;
-    }
-
-    @Override
-    public void start() {
-        if (mIsStarted) {
-            return;
-        }
-        mIsStarted = true;
-
-        if (mTelephonyManager == null) {
-            if (Build.VERSION.SDK_INT >= 18 /*Build.VERSION_CODES.JELLY_BEAN_MR2 */) { // Fennec: no Build.VERSION_CODES
-                mGetAllInfoCellScanner = new GetAllCellInfoScannerMr2();
-            } else {
-                mGetAllInfoCellScanner = new GetAllCellInfoScannerDummy();
-            }
-
-            mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE);
-            if (mTelephonyManager == null) {
-                throw new UnsupportedOperationException("TelephonyManager service is not available");
-            }
-
-            mPhoneType = mTelephonyManager.getPhoneType();
-
-            if (mPhoneType != TelephonyManager.PHONE_TYPE_GSM
-                && mPhoneType != TelephonyManager.PHONE_TYPE_CDMA) {
-                throw new UnsupportedOperationException("Unexpected Phone Type: " + mPhoneType);
-            }
-            mSignalStrength = CellInfo.UNKNOWN_SIGNAL;
-            mCdmaDbm = CellInfo.UNKNOWN_SIGNAL;
-        }
-
-        mSignalStrength = CellInfo.UNKNOWN_SIGNAL;
-        mCdmaDbm = CellInfo.UNKNOWN_SIGNAL;
-
-        mPhoneStateListener = new PhoneStateListener() {
-            @Override
-            public void onSignalStrengthsChanged(SignalStrength ss) {
-                if (ss.isGsm()) {
-                    mSignalStrength = ss.getGsmSignalStrength();
-                } else {
-                    mCdmaDbm = ss.getCdmaDbm();
-                }
-            }
-        };
-        mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_SIGNAL_STRENGTHS);
-    }
-
-    @Override
-    public void stop() {
-        mIsStarted = false;
-        if (mTelephonyManager != null) {
-            mTelephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_NONE);
-        }
-        mSignalStrength = CellInfo.UNKNOWN_SIGNAL;
-        mCdmaDbm = CellInfo.UNKNOWN_SIGNAL;
-    }
-
-    @Override
-    public List<CellInfo> getCellInfo() {
-        List<CellInfo> records = new ArrayList<CellInfo>();
-
-        List<CellInfo> allCells = mGetAllInfoCellScanner.getAllCellInfo(mTelephonyManager);
-        if (allCells.isEmpty()) {
-            CellInfo currentCell = getCurrentCellInfo();
-            if (currentCell == null) {
-                return records;
-            }
-            records.add(currentCell);
-        }else {
-            records.addAll(allCells);
-        }
-
-        // getNeighboringCells() sometimes contains more information than that is already
-        // in getAllCellInfo(). Use the results of both of them.
-        records.addAll(getNeighboringCells());
-        return records;
-    }
-
-    private String getNetworkOperator() {
-        String networkOperator = mTelephonyManager.getNetworkOperator();
-        // getNetworkOperator() may be unreliable on CDMA networks
-        if (networkOperator == null || networkOperator.length() <= 3) {
-            networkOperator = mTelephonyManager.getSimOperator();
-        }
-        return networkOperator;
-    }
-
-    protected CellInfo getCurrentCellInfo() {
-        final CellLocation currentCell = mTelephonyManager.getCellLocation();
-        if (currentCell == null) {
-            return null;
-        }
-
-        try {
-            final CellInfo info = new CellInfo(mPhoneType);
-            final int signalStrength = mSignalStrength;
-            final int cdmaDbm = mCdmaDbm;
-            info.setCellLocation(currentCell,
-                    mTelephonyManager.getNetworkType(),
-                    getNetworkOperator(),
-                    signalStrength == CellInfo.UNKNOWN_SIGNAL ? null : signalStrength,
-                    cdmaDbm == CellInfo.UNKNOWN_SIGNAL ? null : cdmaDbm);
-            return info;
-        } catch (IllegalArgumentException iae) {
-            Log.e(LOG_TAG, "Skip invalid or incomplete CellLocation: " + currentCell, iae);
-        }
-        return null;
-    }
-
-    private List<CellInfo> getNeighboringCells() {
-        Collection<NeighboringCellInfo> cells = mTelephonyManager.getNeighboringCellInfo();
-        if (cells == null || cells.isEmpty()) {
-            return Collections.emptyList();
-        }
-
-        String networkOperator = getNetworkOperator();
-        List<CellInfo> records = new ArrayList<CellInfo>(cells.size());
-        for (NeighboringCellInfo nci : cells) {
-            try {
-                final CellInfo record = new CellInfo(mPhoneType);
-                record.setNeighboringCellInfo(nci, networkOperator);
-                if (record.isCellRadioValid()) {
-                    records.add(record);
-                }
-            } catch (IllegalArgumentException iae) {
-                Log.e(LOG_TAG, "Skip invalid or incomplete NeighboringCellInfo: " + nci, iae);
-            }
-        }
-        return records;
-    }
-
-    @TargetApi(18)
-    protected boolean addCellToList(List<CellInfo> cells,
-                                 android.telephony.CellInfo observedCell,
-                                 TelephonyManager tm) {
-        boolean added = false;
-        if (observedCell instanceof CellInfoGsm) {
-            CellIdentityGsm ident = ((CellInfoGsm) observedCell).getCellIdentity();
-            if (ident.getMcc() != Integer.MAX_VALUE && ident.getMnc() != Integer.MAX_VALUE) {
-                CellSignalStrengthGsm strength = ((CellInfoGsm) observedCell).getCellSignalStrength();
-                CellInfo cell = new CellInfo(tm.getPhoneType());
-                cell.setGsmCellInfo(ident.getMcc(),
-                        ident.getMnc(),
-                        ident.getLac(),
-                        ident.getCid(),
-                        strength.getAsuLevel());
-                cells.add(cell);
-                added = true;
-            }
-        } else if (observedCell instanceof CellInfoCdma) {
-            CellInfo cell = new CellInfo(tm.getPhoneType());
-            CellIdentityCdma ident = ((CellInfoCdma) observedCell).getCellIdentity();
-            CellSignalStrengthCdma strength = ((CellInfoCdma) observedCell).getCellSignalStrength();
-            cell.setCdmaCellInfo(ident.getBasestationId(),
-                    ident.getNetworkId(),
-                    ident.getSystemId(),
-                    strength.getDbm());
-            cells.add(cell);
-            added = true;
-        } else if (observedCell instanceof CellInfoLte) {
-            CellIdentityLte ident = ((CellInfoLte) observedCell).getCellIdentity();
-            if (ident.getMnc() != Integer.MAX_VALUE && ident.getMcc() != Integer.MAX_VALUE) {
-                CellInfo cell = new CellInfo(tm.getPhoneType());
-                CellSignalStrengthLte strength = ((CellInfoLte) observedCell).getCellSignalStrength();
-                cell.setLteCellInfo(ident.getMcc(),
-                        ident.getMnc(),
-                        ident.getCi(),
-                        ident.getPci(),
-                        ident.getTac(),
-                        strength.getAsuLevel(),
-                        strength.getTimingAdvance());
-               cells.add(cell);
-               added = true;
-            }
-        }
-        return added;
-    }
-
-    @TargetApi(18)
-    private class GetAllCellInfoScannerMr2 implements GetAllCellInfoScannerImpl {
-        @Override
-        public List<CellInfo> getAllCellInfo(TelephonyManager tm) {
-            final List<android.telephony.CellInfo> observed = tm.getAllCellInfo();
-            if (observed == null || observed.isEmpty()) {
-                return Collections.emptyList();
-            }
-
-            List<CellInfo> cells = new ArrayList<CellInfo>(observed.size());
-            for (android.telephony.CellInfo observedCell : observed) {
-                if (!addCellToList(cells, observedCell, tm)) {
-                    //Log.i(LOG_TAG, "Skipped CellInfo of unknown class: " + observedCell.toString());
-                }
-            }
-            return cells;
-        }
-    }
-}
--- a/mobile/android/stumbler/stumbler_sources.mozbuild
+++ b/mobile/android/stumbler/stumbler_sources.mozbuild
@@ -12,17 +12,17 @@ stumbler_sources = [
     'java/org/mozilla/mozstumbler/service/stumblerthread/blocklist/SSIDBlockList.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/blocklist/WifiBlockListInterface.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageContract.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/DataStorageManager.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/datahandling/StumblerBundle.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/Reporter.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellInfo.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScanner.java',
-    'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScannerNoWCDMA.java',
+    'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/cellscanner/CellScannerImplementation.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/GPSScanner.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/LocationBlockList.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/ScanManager.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/scanners/WifiScanner.java',
     'java/org/mozilla/mozstumbler/service/stumblerthread/StumblerService.java',
     'java/org/mozilla/mozstumbler/service/uploadthread/AsyncUploader.java',
     'java/org/mozilla/mozstumbler/service/uploadthread/UploadAlarmReceiver.java',
     'java/org/mozilla/mozstumbler/service/utils/AbstractCommunicator.java',