org.restcomm.app.qoslib.Services.LibPhoneStateListener.java Source code

Java tutorial

Introduction

Here is the source code for org.restcomm.app.qoslib.Services.LibPhoneStateListener.java

Source

/*
 * TeleStax, Open Source Cloud Communications
 * Copyright 2011-2016, Telestax Inc and individual contributors
 * by the @authors tag.
 *
 * This program is free software: you can redistribute it and/or modify
 * under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation; either version 3 of
 * the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>
 *
 * For questions related to commercial use licensing, please contact sales@telestax.com.
 *
 */

package org.restcomm.app.qoslib.Services;

import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.List;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.hardware.Sensor;
import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
//import android.media.audiofx.BassBoost.Settings;
import android.net.ConnectivityManager;
import android.net.TrafficStats;
import android.net.Uri;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.os.Handler;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.provider.Settings;
import android.telephony.CellIdentityLte;
import android.telephony.CellInfo;
import android.telephony.CellInfoLte;
import android.telephony.CellLocation;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.TelephonyManager;
import android.telephony.cdma.CdmaCellLocation;
import android.telephony.gsm.GsmCellLocation;
import android.util.Log;

//import com.cortxt.app.MMC.ActivitiesOld.NerdScreen;

import org.restcomm.app.qoslib.MainService;
import org.restcomm.app.qoslib.R;
import org.restcomm.app.qoslib.Services.Intents.IntentHandler;
import org.restcomm.app.utillib.ContentProvider.ContentValuesGenerator;
import org.restcomm.app.utillib.ContentProvider.TablesEnum;
import org.restcomm.app.qoslib.Utils.RestCommManager;
import org.restcomm.app.utillib.DataObjects.EventType;
import org.restcomm.app.utillib.DataObjects.PhoneState;
import org.restcomm.app.utillib.Utils.DeviceInfoOld;
import org.restcomm.app.utillib.Utils.Global;
import org.restcomm.app.utillib.Utils.LoggerUtil;
import org.restcomm.app.utillib.Utils.PreferenceKeys;
import org.restcomm.app.mmcextension.PhoneHeuristic;
import org.restcomm.app.utillib.Utils.PreciseCallCodes;
import org.restcomm.app.utillib.Reporters.ReportManager;
import org.restcomm.app.utillib.Reporters.LocalStorageReporter.LocalStorageReporter.Events;
import org.restcomm.app.utillib.DataObjects.CellLocationEx;
import org.restcomm.app.utillib.DataObjects.SignalEx;
import org.restcomm.app.utillib.DataObjects.EventCouple;
import org.restcomm.app.utillib.DataObjects.EventObj;
import org.restcomm.app.qoslib.UtilsOld.DataMonitorStats;

import org.restcomm.app.mmcextension.datamonitor.AppDataStatisticsRunnable;

import org.json.JSONObject;

/**
 * @author abhin
 * This is the class that MMC_Service instantiates and registers as 
 * a phone state listener for the following events
 * <ol>
 *    <li>PhoneStateListener.LISTEN_CALL_STATE</li>
 *    <li>PhoneStateListener.LISTEN_CELL_LOCATION</li>
 *    <li>PhoneStateListener.LISTEN_DATA_ACTIVITY</li>
 *    <li>PhoneStateListener.LISTEN_DATA_CONNECTION_STATE</li>
 *    <li>PhoneStateListener.LISTEN_SERVICE_STATE</li>
 *    <li>PhoneStateListener.LISTEN_SIGNAL_STRENGTHS</li>
 * </ol>
 */
public class LibPhoneStateListener extends PhoneStateListener {
    private final AppDataStatisticsRunnable dataActivityRunnable;
    private MainService owner;
    private DataMonitorStats dataMonitorStats;
    private RestCommManager restcommManager;
    public long tmLastCellUpdate = 0, tmLastCell = 0;
    public boolean validSignal = false;
    public static final String TAG = LibPhoneStateListener.class.getSimpleName();
    public static final int MMC_DROPPED_NOTIFICATION = 1001;
    /**
     * If a call ends and the a cell ID was changed within this number of
     * milliseconds in the past, then the call is flagged as a potential
     * dropped call.
     */
    public static final int CELL_LOCATION_EXPIRY_FOR_DROPPED_CALL = 5000;

    /**
     * This variable stores a copy of the previously received network state.
     */
    //public int previousNetworkState = -1;
    //private int previousServiceState = -1;
    //private ServiceState previousServiceStateObj = null;
    //private int previousServiceStateAirplane = 99;
    public static TelephonyManager telephonyManager;

    private Handler dataActivtyHandler;
    // keeps track of whether a global ServiceMode Panel has been manually closed by the user, its displayed during ServiceMode in debug
    public static boolean closedServicePanel = false;

    //   private boolean bOffHook = false;
    //   private static JSONObject mServicemode = null;
    //   private String prevSvcValues = "";
    //   private int networkType = TelephonyManager.NETWORK_TYPE_UNKNOWN;
    //   private boolean callConnected = false, callDialing = false, callRinging = false;
    //   private long timeConnected = 0, timeRinging = 0, timeDialed = 0;
    //   private int lastKnownCallState;
    private PhoneState mPhoneState = null;

    /**
     * Constructor that gets a copy of the owner object so that it can 
     * manipulate the variables of the owner.
     */
    public LibPhoneStateListener(MainService owner, PhoneState phonestate) {
        this.owner = owner;
        mPhoneState = phonestate;
        telephonyManager = (TelephonyManager) owner.getSystemService(Context.TELEPHONY_SERVICE);
        mPhoneState.telephonyManager = telephonyManager;
        restcommManager = new RestCommManager(this, owner);
        dataActivtyHandler = new Handler();
        dataActivityRunnable = new AppDataStatisticsRunnable(owner.getCallbacks(), dataActivtyHandler,
                owner.getDataMonitorStats().getStatsManager());
        mySensorManager = (SensorManager) owner.getSystemService(owner.SENSOR_SERVICE);

        // Proximity sensor code exists in case we want to go back to blacking out screen and forcing screen on during phone calls
        //myProximitySensor = mySensorManager.getDefaultSensor(
        //      Sensor.TYPE_PROXIMITY);

    }

    //last data caches
    //   private MMCCellLocationOld lastKnownMMCCellLocation;
    //   private long tmLastCell = 0;
    //
    //   protected ServiceState mLastServiceState;
    //   protected long mLastServiceStateChangeTimeStamp =0;
    //   protected long mLastDataNetworkChangeTimeStamp =0;
    //
    //   protected boolean mStateWasPowerOff = false;
    //   private MMCSignalOld lastKnownMMCSignal, prevMMCSignal;
    //   private SignalStrength lastKnownSignalStrength;
    //   private long tmLastCellUpdate = 0;
    //   private String lastCellString = "";

    private SensorManager mySensorManager;
    private Timer disconnectTimer = new Timer();
    private long totalRxBytes = 0, totalTxBytes = 0;
    private String lastCellString = "";
    private SignalEx prevMMCSignal = null;

    /**
     * When the cell location gets changed, the new cellId is added to the cell id buffer in the 
     * owner. At the same time, the CELLCHANGE event is stored.
     */
    @Override
    public void onCellLocationChanged(CellLocation location) {
        super.onCellLocationChanged(location);

        try {
            checkCDMACellSID(location);
            processNewCellLocation(new CellLocationEx(location));

            // See if this cellLocation has inner GsmLocation
            checkInnerGsmCellLocation(location);
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCellLocationChanged", location.toString());

        } catch (InterruptedException intEx) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "onCellLocationChanged",
                    "InterruptedException: " + intEx.getMessage());
        } catch (Exception ex) {
            String err = ex.toString();
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "onCellLocationChanged",
                    "InterruptedException: " + err);
        }
    }

    protected boolean proximityNear = false;
    protected boolean lastNear = false;

    SensorEventListener proximitySensorEventListener = new SensorEventListener() {

        @Override
        public void onAccuracyChanged(Sensor sensor, int accuracy) {

        }

        // Uses Proximity sensor during phone calls to force the screen on dim and black. 
        // The only way to enable signal measurements during phone calls
        @Override
        public void onSensorChanged(SensorEvent event) {
            // TODO Auto-generated method stub

            if (event.sensor.getType() == Sensor.TYPE_PROXIMITY) {
                boolean bNearFace = event.values[0] < 1 ? true : false;
                proximityNear = bNearFace;
                // TODO Auto-generated method stub
                //if (owner.bOffHook)
                {
                    // For OS 4.1, we are able to hold the screen on during a call by coming to foreground
                    //if (Build.VERSION.SDK_INT < 16)
                    //      return;
                    // server can specify whether a confirmation can be invoked on a low rated potentially-dropped call
                    //int phoneScreen = PreferenceManager.getDefaultSharedPreferences(owner).getInt(PreferenceKeys.Miscellaneous.SERVERPHONESCREEN_ENABLE, 0);
                    //if (phoneScreen == 0)
                    //      return;

                    if (bNearFace && lastNear != bNearFace) {
                        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onSensorChanged", "launch black screen");
                        //TimerTask launchConnectTask = new LaunchConnectTask("mmc");
                        //launchTimer.schedule(launchConnectTask, 1000);
                        lastNear = true;
                    } else if (!bNearFace && lastNear != bNearFace) {
                        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onSensorChanged", "launch phone screen");
                        //TimerTask launchConnectTask = new LaunchConnectTask("phone");
                        //launchTimer.schedule(launchConnectTask, 1);
                        lastNear = false;
                    }

                }
            }
        }
    };

    @Override
    public void onDataActivity(int data) {
        super.onDataActivity(data);
        if (owner.getUsageLimits().getDormantMode() > 0)
            return;
        String activity = null;
        try {
            activity = owner.getConnectionHistory().updateConnectionHistory(telephonyManager.getNetworkType(),
                    telephonyManager.getDataState(), telephonyManager.getDataActivity(),
                    mPhoneState.previousServiceStateObj, owner.getConnectivityManager().getActiveNetworkInfo());
        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "onDataActivity", "ex " + e.getMessage());
        }
        if (data == TelephonyManager.DATA_ACTIVITY_IN || data == TelephonyManager.DATA_ACTIVITY_INOUT) {
            if (activity != null) {
                owner.getIntentDispatcher().updateConnection(activity, true);
            }
            //User allows - default yes
            if (PreferenceManager.getDefaultSharedPreferences(owner)
                    .getBoolean(PreferenceKeys.User.PASSIVE_SPEED_TEST, true) || Global.SCANAPP_PERIOD > 0) {
                //Don't allow if a speedtest is in progress
                if (PreferenceManager.getDefaultSharedPreferences(owner)
                        .getBoolean(PreferenceKeys.Miscellaneous.SPEEDTEST_INPROGRESS, false)) {
                    return;
                }
                if (PreferenceManager.getDefaultSharedPreferences(owner)
                        .getBoolean(PreferenceKeys.Miscellaneous.VIDEOTEST_INPROGRESS, false)) {
                    return;
                }
                //if (owner.getUsageLimits().getUsageProfile () == UsageLimits.MINIMAL)
                //   return;
                //server allows - default no
                int allow = PreferenceManager.getDefaultSharedPreferences(owner)
                        .getInt(PreferenceKeys.Miscellaneous.PASSIVE_SPEEDTEST_SERVER, 0);
                if (!EventObj.isDisabledStat(owner, EventObj.DISABLESTAT_APPS_THROUGHPUT)
                        && Global.SCANAPP_PERIOD > 0)
                    allow = 1;
                if (allow > 0) {
                    dataThrougput();
                }
            }
        } else {
            if (activity != null) {
                owner.getIntentDispatcher().updateConnection(activity, false);
            }
        }

    }

    public void dataThrougput() {
        synchronized (this) {
            totalRxBytes = TrafficStats.getTotalRxBytes();
            totalTxBytes = TrafficStats.getTotalTxBytes();
            if (dataActivityRunnable.hasDataActivity == 0) {
                //dataActivityRunnable.initializeHasDataActivity(1);
                dataActivityRunnable.init(totalRxBytes, totalTxBytes, true);
            } else if (dataActivityRunnable.hasDataActivity == 1) {
                //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onDataActivity", "in sampling");
            } else if (dataActivityRunnable.hasDataActivity == 2) {
                //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onDataActivity", "already in download");
            }
        }
    }

    @Override
    public void onDataConnectionStateChanged(int state, int networkType) {
        super.onDataConnectionStateChanged(state, networkType);
        //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onDataConnectionStateChanged", String.format("Network type: %d, State: %d", networkType, state));

        //notify MainService of the new network type
        mPhoneState.updateNetworkType(networkType);

        int datastate = telephonyManager.getDataState();
        // disregard network change events if data is disabled or in airplane mode
        if (datastate == TelephonyManager.DATA_SUSPENDED
                || mPhoneState.previousServiceState == mPhoneState.SERVICE_STATE_AIRPLANE)
            return;

        if (PhoneState.ActiveConnection(owner) > 1) {// 10=Wifi, 11=Wimax, 12=Ethernet, 0=other
            mPhoneState.previousNetworkTier = -1;
            return;
        }

        // Ignore any data outages that occur just after turning screen off, these are probably not to be blamed on the carrier
        if (mPhoneState.getScreenOnTime(false) + 30000 > System.currentTimeMillis())
            return;

        try {

            String conn = owner.getConnectionHistory().updateConnectionHistory(networkType, state,
                    telephonyManager.getDataActivity(), mPhoneState.previousServiceStateObj,
                    owner.getConnectivityManager().getActiveNetworkInfo());
            if (conn != null)
                owner.getIntentDispatcher().updateConnection(conn, false);

        } catch (Exception e) {
        }

        int networkGeneration = mPhoneState.getNetworkGeneration(networkType);

        // The 3G outage will be handled by the Service state outage
        if (mPhoneState.previousServiceState == ServiceState.STATE_OUT_OF_SERVICE
                || mPhoneState.previousServiceState == ServiceState.STATE_EMERGENCY_ONLY)
            return;
        //if the network generation hasn't changed, then don't cause an event
        if (mPhoneState.previousNetworkTier == networkGeneration && mPhoneState.previousNetworkState == state) {
            return;
        }

        SignalEx signal = mPhoneState.getLastMMCSignal();
        if (signal != null) {
            signal.setTimestamp(System.currentTimeMillis());
            mPhoneState.clearLastMMCSignal(); // to force a duplicate signal to be added
            processNewMMCSignal(signal);
        }
        //this was falsely reporting outages when screen turned off, and not coupling them to regained
        //if (datastate == TelephonyManager.DATA_DISCONNECTED)
        //   networkGeneration = 0;
        // First network state
        if (mPhoneState.previousNetworkType == -1)
            mPhoneState.previousNetworkType = networkType;
        else {
            switch (networkGeneration) {
            case 3: //3g
            case 4: //3g
                stateChanged_3g(state);
                break;
            case 5: //3g
                stateChanged_4g(state);
                break;

            case 1:
            case 2: //2g
                stateChanged_2g(state);
                break;

            // disconnected data without disconnecting service?
            case 0:
                stateChanged_0g(state);
                break;

            }
        }
        //update the previous network generation and state
        if (state == TelephonyManager.DATA_CONNECTED && networkGeneration != 0)
            mPhoneState.previousNetworkTier = networkGeneration;
        // If there is truly an outage, the service state listener will update the previousNetworkTier to 0
        mPhoneState.previousNetworkState = state;
        mPhoneState.previousNetworkType = networkType;

    }

    public void processLastSignal() {
        if (mPhoneState.lastKnownSignalStrength != null)
            onSignalStrengthsChanged(mPhoneState.lastKnownSignalStrength);
    }

    public void onVoLteServiceStateChanged(Object lteState) {
        if (lteState != null)
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onVoLteServiceStateChanged", lteState.toString());
        else
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onVoLteServiceStateChanged", "null");
    }

    public void onOemHookRawEvent(byte[] oemData) {
        if (oemData != null)
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onOemHookRawEvent",
                    "length = " + Integer.toString(oemData.length));
        else
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onOemHookRawEvent", "null");
    }

    public void onPreciseCallStateChanged(Object preciseCallState) {
        if (preciseCallState != null)
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onPreciseCallStateChanged",
                    preciseCallState.toString());
        else
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onPreciseCallStateChanged", "null");
    }

    @Override
    public void onSignalStrengthsChanged(SignalStrength signalStrength) {
        super.onSignalStrengthsChanged(signalStrength);
        if (DeviceInfoOld.getPlatform() != 1) //Not an Android device
            return;

        if (!owner.isMMCActiveOrRunning()) {
            mPhoneState.lastKnownSignalStrength = signalStrength;
            return;
        }
        mPhoneState.lastKnownSignalStrength = null;
        //if (signalStrength != null)
        //   MMCLogger.logToFile(MMCLogger.Level.ERROR, TAG, "onSignalStrengthsChanged", signalStrength.toString());
        int pref = networkPreference(owner.getApplicationContext());
        try {
            if (mPhoneState.previousServiceState == ServiceState.STATE_IN_SERVICE
                    || mPhoneState.previousServiceState == ServiceState.STATE_EMERGENCY_ONLY) {
                SignalEx mmcSignal = new SignalEx(signalStrength);
                processNewMMCSignal(mmcSignal);

            } else {
                SignalEx mmcSignal = new SignalEx();
                processNewMMCSignal(mmcSignal);
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
                List<CellInfo> cells = telephonyManager.getAllCellInfo();
                if (cells != null)
                    onCellInfoChanged(cells);
                //            for (int c =0; c<cells.size(); c++)
                //            { 
                //               String msg =  "cells[" + c + "]=" + cells.get(c).toString();
                //               MMCLogger.logToFile(MMCLogger.Level.ERROR, TAG, "onSignalStrengthsChanged", "cells[" + c + "]=" + cells.get(c).toString());
                //               //Log.d(TAG, "cells[" + c + "]=" + cells.get(c).toString());
                //            }
            }

        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "onSignalStrengthsChanged",
                    "Exception " + e.getMessage());
        }
    }

    // delay by 1 second so that it can check call log if needed to verify call connected
    class VerifyConnectTask extends TimerTask {

        @Override
        public void run() {
            if (DeviceInfoOld.getPlatform() == 3) {
                return;
            }
            EventCouple targetEventCouple = owner.getEventManager().getEventCouple(EventType.EVT_CONNECT,
                    EventType.EVT_DISCONNECT);

            LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "VerifyConnectTask",
                    "call connected=" + mPhoneState.isCallConnected() + " event=" + targetEventCouple);

            String pname = owner.getPackageName();
            int permissionForReadLogs = owner.getPackageManager()
                    .checkPermission(android.Manifest.permission.READ_LOGS, pname); //0 means allowed
            int permissionForPrecise = owner.getPackageManager()
                    .checkPermission("android.permission.READ_PRECISE_PHONE_STATE", pname); // 0 means allowed

            // For OS 4.1, need to use CALL LOG rather than logcat to determine if call connected or not
            if (targetEventCouple != null && Build.VERSION.SDK_INT >= 16 && !mPhoneState.isCallConnected())//permissionForReadLogs != 0 && permissionForPrecise != 0 ) // && !owner.isCallConnected())  //  && !owner.getUsageLimits().getUseRadioLog())
                checkCallLog();

            if (EventObj.isDisabledEvent(owner, EventObj.DISABLE_DROPCALL) && mPhoneState.lastCallDropped == true)
                mPhoneState.lastCallDropped = false;

            if (mPhoneState.lastCallDropped == true) {
                mPhoneState.lastCallDropped = false;
                if (targetEventCouple == null) {
                    EventObj evt = null;
                    if (mPhoneState.isCallConnected()) {
                        evt = owner.getEventManager().triggerSingletonEvent(EventType.EVT_DROP);
                        popupDropped(EventType.EVT_DROP, 5, evt.getLocalID());
                    } else {
                        evt = owner.getEventManager().triggerSingletonEvent(EventType.EVT_CALLFAIL);
                        popupDropped(EventType.EVT_CALLFAIL, 5, evt.getLocalID());
                    }
                    if (evt != null) {
                        evt.setCause(mPhoneState.lastDroppedCause);
                        evt.setEventTimestamp(mPhoneState.disconnectTime);
                    }
                } else if (mPhoneState.isCallConnected() && targetEventCouple != null) {
                    int rating = 7;
                    if (mPhoneState.lastDroppedCause.equals("error_unspecified"))
                        rating = 5;

                    targetEventCouple.setStopEventType(EventType.EVT_DROP);
                    owner.getEventManager().stopPhoneEvent(EventType.EVT_CONNECT, EventType.EVT_DROP);

                    popupDropped(EventType.EVT_DROP, rating, targetEventCouple.getStopEvent().getLocalID());
                    targetEventCouple.getStopEvent().setCause(mPhoneState.lastDroppedCause);
                    targetEventCouple.getStopEvent().setEventTimestamp(mPhoneState.disconnectTime);
                    //owner.getEventManager().updateEventDBField(targetEventCouple.getStopEvent().getUri(), Tables.Events.TIMESTAMP, Long.toString(disconnectTime));
                } else if (targetEventCouple != null) {
                    //EventObj evt = owner.triggerSingletonEvent(EventType.EVT_CALLFAIL);
                    targetEventCouple.setStopEventType(EventType.EVT_CALLFAIL);
                    owner.getEventManager().stopPhoneEvent(EventType.EVT_CONNECT, EventType.EVT_CALLFAIL);
                    popupDropped(EventType.EVT_CALLFAIL, 5, targetEventCouple.getStopEvent().getLocalID());
                    targetEventCouple.getStopEvent().setCause(mPhoneState.lastDroppedCause);
                    targetEventCouple.getStopEvent().setEventTimestamp(mPhoneState.disconnectTime);
                    //owner.getEventManager().updateEventDBField(evt.getUri(), Tables.Events.TIMESTAMP, Long.toString(disconnectTime));
                }
                mPhoneState.setCallConnected(false);
                mPhoneState.lastCallDropped = false;
                if (!mPhoneState.bOffHook) {
                    mPhoneState.setCallDialing(false);
                }
            } else if (mPhoneState.disconnectTime - mPhoneState.offhookTime < 2000) // if connect wasnt detected, use the time the call was dialed
            {
                LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCallStateChanged",
                        "off hook too short: " + (mPhoneState.disconnectTime - mPhoneState.offhookTime));
                // If call did not connect, undo the call connect event
                if (targetEventCouple != null && targetEventCouple.getStartEvent() != null) {
                    LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCallStateChanged", "Undo CONNECT event");
                    owner.getEventManager().unstageEvent(targetEventCouple.getStartEvent());
                    owner.getEventManager().cancelCouple(targetEventCouple);
                    //MainService.getGpsManager().unregisterListener(targetEventCouple.getStartEvent().gpsListener);
                    //owner.getEventManager().deleteEventDB(targetEventCouple.getStartEvent().getUri(), null, null);

                    mPhoneState.setCallConnected(false);
                    int eventId = ReportManager.getInstance(owner).getEventId(
                            targetEventCouple.getStartEvent().getEventTimestamp(),
                            EventType.EVT_CONNECT.getIntValue());
                    if (eventId != 0)
                        ReportManager.getInstance(owner).deleteEvent(eventId);
                    owner.startRadioLog(false, null, EventType.EVT_CONNECT);

                }
            }

        }
    }

    /**
     * Waits 10 Seconds after a phone call is disconnected to look at signal changes to decide if a call ended normally or dropped
     * There is often a delay before changes in signal are reported to the listener, which is why we wait before deciding
     * This also gives a chance for other method to weigh-in, such as the logcat
     */
    class DisconnectTimerTask extends TimerTask {
        int count = 0;

        public DisconnectTimerTask(int _count) {
            count = _count;
        }

        @Override
        public void run() {

            EventCouple targetEventCouple = owner.getEventManager().getEventCouple(EventType.EVT_CONNECT,
                    EventType.EVT_DISCONNECT);

            LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "DisconnectTimerTask",
                    "call connected=" + mPhoneState.isCallConnected() + " event=" + targetEventCouple);

            mPhoneState.heurCause = null;

            if (mPhoneState.isCallConnected() || mPhoneState.isCallDialing() || mPhoneState.isCallRinging()) {

                int rating = 0;

                if (DeviceInfoOld.getPlatform() != 3
                        && !EventObj.isDisabledEvent(owner, EventObj.DISABLE_DROPCALL)) {
                    PhoneHeuristic heur = new PhoneHeuristic(owner.getCallbacks(), mPhoneState);
                    rating = heur.heuristicDropped(mPhoneState);
                }

                // Detected dropped call based on logcat cause, or proximity (phone against ear at disconnect time)
                if (mPhoneState.lastCallDropped == true) {
                    rating = 5;
                    mPhoneState.heurCause = mPhoneState.lastDroppedCause;
                    if (targetEventCouple == null) {
                        EventObj evt = owner.getEventManager().triggerSingletonEvent(EventType.EVT_CALLFAIL);
                        popupDropped(EventType.EVT_CALLFAIL, rating, evt.getLocalID());
                        evt.setCause(mPhoneState.lastDroppedCause);
                        evt.setEventTimestamp(mPhoneState.disconnectTime);
                    }
                }

                if (rating > 2 && targetEventCouple != null) {
                    LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "DisconnectTimerTask",
                            "call dropped heuristic=" + mPhoneState.heurCause);

                    //there is now a good chance that the call was dropped
                    if (!mPhoneState.isCallConnected()) //  || (owner.isCallConnected() == true && owner.getTimeConnected() + 2000+count*2000 > System.currentTimeMillis()))
                    {
                        // failed call based on heuristic
                        targetEventCouple.setStopEventType(EventType.EVT_CALLFAIL);
                        owner.getEventManager().stopPhoneEvent(EventType.EVT_CONNECT, EventType.EVT_CALLFAIL);
                        targetEventCouple.getStopEvent().setCause(mPhoneState.heurCause);
                        targetEventCouple.getStopEvent().setEventTimestamp(mPhoneState.disconnectTime);
                        targetEventCouple.getStopEvent().setEventIndex(rating); // something has to hold the confidence rating. This field will be sent to server as 'eventIndex'
                        ReportManager.getInstance(owner).updateEventField(
                                targetEventCouple.getStopEvent().getLocalID(), Events.KEY_TIER,
                                Integer.toString(rating));
                        ReportManager.getInstance(owner).updateEventField(
                                targetEventCouple.getStopEvent().getLocalID(), "timeStamp",
                                Long.toString(mPhoneState.disconnectTime));

                        LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "DisconnectTimerTask",
                                "call changed to IDLE while call was dialing/ringing (CALL FAILED)");

                        int allowConfirm = PreferenceManager.getDefaultSharedPreferences(owner)
                                .getInt(PreferenceKeys.Miscellaneous.ALLOW_CONFIRMATION, 5);
                        if (allowConfirm > 0)
                            popupDropped(EventType.EVT_CALLFAIL, rating,
                                    targetEventCouple.getStopEvent().getLocalID());
                    } else if (mPhoneState.isCallConnected() && targetEventCouple != null) {
                        LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "DisconnectTimerTask",
                                "call changed to IDLE with low signal while during call (CALL DROPPED)");
                        targetEventCouple.setStopEventType(EventType.EVT_DROP);
                        owner.getEventManager().stopPhoneEvent(EventType.EVT_CONNECT, EventType.EVT_DROP);

                        targetEventCouple.getStopEvent().setCause(mPhoneState.heurCause);
                        targetEventCouple.getStopEvent().setEventTimestamp(mPhoneState.disconnectTime);
                        targetEventCouple.getStopEvent().setEventIndex(rating); // something has to hold the confidence rating. This field will be sent to server as 'eventIndex'

                        ReportManager.getInstance(owner).updateEventField(
                                targetEventCouple.getStopEvent().getLocalID(), Events.KEY_TIER,
                                Integer.toString(rating));
                        ReportManager.getInstance(owner).updateEventField(
                                targetEventCouple.getStopEvent().getLocalID(), "timeStamp",
                                Long.toString(mPhoneState.disconnectTime));
                        int allowConfirm = PreferenceManager.getDefaultSharedPreferences(owner)
                                .getInt(PreferenceKeys.Miscellaneous.ALLOW_CONFIRMATION, 5);
                        if (allowConfirm > 0)
                            popupDropped(EventType.EVT_DROP, rating, targetEventCouple.getStopEvent().getLocalID());
                    }

                } else if (targetEventCouple != null) {
                    EventType evtType = EventType.EVT_DISCONNECT;
                    if (mPhoneState.isCallConnected())
                        owner.getEventManager().stopPhoneEvent(EventType.EVT_CONNECT, EventType.EVT_DISCONNECT);
                    else {
                        evtType = EventType.EVT_UNANSWERED;
                        targetEventCouple.setStopEventType(EventType.EVT_UNANSWERED);
                        owner.getEventManager().stopPhoneEvent(EventType.EVT_CONNECT, EventType.EVT_UNANSWERED);
                    }

                    if (mPhoneState.heurCause == null || mPhoneState.heurCause.length() == 0)
                        targetEventCouple.getStopEvent().setCause("IDLE");
                    else
                        targetEventCouple.getStopEvent().setCause(mPhoneState.heurCause);
                    targetEventCouple.getStopEvent().setEventIndex(rating);
                    targetEventCouple.getStopEvent().setEventTimestamp(mPhoneState.disconnectTime);
                    ReportManager.getInstance(owner).updateEventField(targetEventCouple.getStopEvent().getLocalID(),
                            Events.KEY_TIER, Integer.toString(rating));
                    ReportManager.getInstance(owner).updateEventField(targetEventCouple.getStopEvent().getLocalID(),
                            "timeStamp", Long.toString(mPhoneState.disconnectTime));

                    //owner.getEventManager().updateEventDBField(targetEventCouple.getStopEvent().getUri(), Tables.Events.TIMESTAMP, Long.toString(disconnectTime));

                    // server can specify whether a confirmation can be invoked on a low rated potentially-dropped call
                    int allowConfirm = PreferenceManager.getDefaultSharedPreferences(owner)
                            .getInt(PreferenceKeys.Miscellaneous.ALLOW_CONFIRMATION, 5);
                    if (allowConfirm > 0)
                        popupDropped(evtType, rating, targetEventCouple.getStopEvent().getLocalID());
                }
            }
            mPhoneState.setCallConnected(false);
            mPhoneState.lastCallDropped = false;
            if (!mPhoneState.bOffHook) {
                mPhoneState.setCallDialing(false);
            } else
                phoneOffHook(TelephonyManager.CALL_STATE_OFFHOOK);
            //owner.startRadioLog (false, null);
        }
    }

    /*
    *  Called when phone state is Off-Hook (dialing out) or ringing (incoming call)
     */
    public void phoneOffHook(int iPhoneState) {
        try {
            final EventObj event = owner.getEventManager().startPhoneEvent(EventType.EVT_CONNECT,
                    EventType.EVT_DISCONNECT);
            if (event != null) {
                if (mPhoneState.bOffHook) {
                    if (iPhoneState == TelephonyManager.CALL_STATE_OFFHOOK
                            && (event.getFlags() & EventObj.CALL_INCOMING) > 0)
                        mPhoneState.setCallConnected(true);
                    //            if (iPhoneState == TelephonyManager.CALL_STATE_RINGING && (event.getFlags() & EventObj.CALL_INCOMING) == 0)
                    //               setCallWaiting(true);
                    return;
                }
                owner.startRadioLog(true, "call", EventType.EVT_CONNECT); // "monitoring signal strength");
                if (iPhoneState == TelephonyManager.CALL_STATE_RINGING) {
                    event.setFlag(EventObj.CALL_INCOMING, true);
                    mPhoneState.setCallRinging(true);
                } else {
                    mPhoneState.setCallDialing(true); // in case it is an outgoing call (not sure), dialing time will start now
                    mPhoneState.setCallRinging(false); // in case it is an outgoing call (not sure), dialing time will start now
                }
            }
            mPhoneState.bOffHook = true;
            mPhoneState.offhookTime = System.currentTimeMillis();

            mPhoneState.lastCallDropped = false;
            mPhoneState.lastDroppedCause = null;

            Intent intent = new Intent(IntentHandler.PHONE_CALL_CONNECT);
            owner.sendBroadcast(intent);

            // Delay for a few seconds and then check the voice network to detect if we have a VoLTE call
            owner.handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    int tech = mPhoneState.getVoiceNetworkType();
                    if (tech == mPhoneState.NETWORK_NEWTYPE_LTE && event != null) {
                        event.setFlag(EventObj.CALL_VOLTE, true);
                        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "getRilVoiceRadioTechnology",
                                "VOLTE CALL DETECTED");
                    } else if ((tech <= 0 || tech == mPhoneState.NETWORK_NEWTYPE_IWLAN) && event != null) {
                        event.setFlag(EventObj.CALL_OFFNET, true);
                        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "getRilVoiceRadioTechnology",
                                "WIFI CALL DETECTED?");
                    }
                    //boolean isInCall = IsInCall ();
                }
            }, 3500);

            // Set all ongoing events as occurring in a call
            List<EventObj> ongoingEvents = owner.getEventManager().getOngoingEvents();
            int i;
            for (i = 0; i < ongoingEvents.size(); i++) {
                ongoingEvents.get(i).setFlag(EventObj.PHONE_INUSE, true);
            }
        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "phoneOffHook", "exception", e);
        }
    }

    //   private boolean IsInCall ()
    //   {
    //      TelecomManager telecomManager = (TelecomManager)owner.getSystemService(Context.TELECOM_SERVICE);
    //      boolean incall = telecomManager.isInCall();
    //      return incall;
    //   }

    // Listener for connected and disconnected phone calls
    // Android Detects only on-hook and off-hook. To better detect, it starts timer tasks
    @Override
    public void onCallStateChanged(int state, String incomingNumber) {
        super.onCallStateChanged(state, incomingNumber);

        try {
            Intent intent;

            switch (state) {
            case TelephonyManager.CALL_STATE_IDLE:
                LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCallStateChanged", "IDLE");

                if (mPhoneState.bOffHook == false) {
                    LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCallStateChanged", "not off hook");
                    return;
                }
                mPhoneState.disconnectTime = System.currentTimeMillis();
                mPhoneState.bOffHook = false;

                HashMap<String, Integer> handset = ReportManager.getHandsetCaps(owner);
                // If phone needs heuristics, check the signal for a dropped call
                int heurDelay = 9;
                if (handset.containsKey("capHeurDelay"))
                    heurDelay = handset.get("capHeurDelay");
                if (DeviceInfoOld.getPlatform() == 3)
                    heurDelay = 2;

                mPhoneState.bOffHook = false;
                TimerTask verifyConnectTask = new VerifyConnectTask();
                disconnectTimer.schedule(verifyConnectTask, 2000); // 1300
                TimerTask disconnectTimerTask1 = new DisconnectTimerTask(1);
                disconnectTimer.schedule(disconnectTimerTask1, heurDelay * 1000);
                intent = new Intent(IntentHandler.PHONE_CALL_DISCONNECT);
                owner.sendBroadcast(intent);
                if (disconnectLatch != null)
                    disconnectLatch.countDown();

                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCallStateChanged", "OFFHOOK");
                //if (owner.bOffHook)
                //   return;

                phoneOffHook(TelephonyManager.CALL_STATE_OFFHOOK);
                //intent = new Intent(MMCIntentHandlerOld.PHONE_CALL_CONNECT);
                //owner.sendBroadcast(intent);
                if (connectLatch != null)
                    connectLatch.countDown();

                //TimerTask launchConnectTask = new LaunchConnectTask();
                //disconnectTimer.schedule(launchConnectTask, 2000);
                break;

            case TelephonyManager.CALL_STATE_RINGING:
                LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCallStateChanged", "RINGING");
                if (mPhoneState.bOffHook)
                    return;
                phoneOffHook(TelephonyManager.CALL_STATE_RINGING);
                //if (incomingNumber != null && incomingNumber.length() > 1)
                //   txtIncomingNumber = incomingNumber;

                break;
            }
        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onCallStateChanged", "Exception", e);
        }
    }

    CountDownLatch connectLatch, disconnectLatch;

    public boolean waitForConnect() {
        connectLatch = new CountDownLatch(1);
        try {
            boolean res = connectLatch.await(30, TimeUnit.SECONDS);
            boolean b = res;
            return res;
        } catch (InterruptedException e) {
            //if (connectLatch.getCount() <= 0)
            //   return true;
            return false;
        }
    }

    public boolean waitForDisconnect() {
        disconnectLatch = new CountDownLatch(1);
        try {
            return disconnectLatch.await(50, TimeUnit.SECONDS);
        } catch (InterruptedException e) {
            //if (connectLatch.getCount() <= 0)
            //   return true;
            return false;
        }
    }

    @SuppressLint("InlinedApi")
    @SuppressWarnings("deprecation")
    public boolean isAirplaneModeOn(Context context) {

        //int disp = Settings.System.getInt(context.getContentResolver(), 
        //        Settings.System.SCREEN_OFF_TIMEOUT, 0) ;
        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return Settings.System.getInt(context.getContentResolver(), Settings.System.AIRPLANE_MODE_ON, 0) != 0;
        } else {
            return Settings.Global.getInt(context.getContentResolver(), Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
        }

    }

    @SuppressLint("InlinedApi")
    @SuppressWarnings("deprecation")
    public int networkPreference(Context context) {

        String pref = null;
        ConnectivityManager con = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
        int attemptTwo = con.getNetworkPreference();

        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) {
            return Settings.System.getInt(context.getContentResolver(), Settings.System.NETWORK_PREFERENCE, 0);
        } else {
            return Settings.Global.getInt(context.getContentResolver(), Settings.Global.NETWORK_PREFERENCE, 0);
        }
    }

    @Override
    public void onServiceStateChanged(ServiceState serviceState) {
        super.onServiceStateChanged(serviceState);
        if (serviceState == null)
            return;

        boolean isRoaming = serviceState.getRoaming();
        String operator = serviceState.getOperatorAlphaLong();
        String mccmnc = serviceState.getOperatorNumeric();

        //owner.getConnectionHistory().updateConnectionHistory(cellnettype, state, activity, networkInfo)
        try {
            String activity = owner.getConnectionHistory().updateConnectionHistory(
                    telephonyManager.getNetworkType(), telephonyManager.getDataState(),
                    telephonyManager.getDataActivity(), serviceState,
                    owner.getConnectivityManager().getActiveNetworkInfo());
            if (activity != null)
                owner.getIntentDispatcher().updateConnection(activity, false);
        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "onServiceStateChanged",
                    "exception with updateConnectionHistory:", e);
        }

        //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onServiceStateChanged", String.format("State: %s, roaming: %s, operator: %s, mccmnc: %s",
        //         serviceState, isRoaming, operator, mccmnc));
        //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onServiceStateChanged", "Reflected: " + listServiceStateFields(serviceState));

        boolean wasRoaming = PreferenceManager.getDefaultSharedPreferences(owner)
                .getBoolean(PreferenceKeys.Miscellaneous.WAS_ROAMING, false);

        //If roaming, track time spend doing this         
        if (mPhoneState.isRoaming() != wasRoaming) {
            int roamValue = 2; //off
            String status = "off";
            if (mPhoneState.isRoaming()) {
                roamValue = 1; //on
                status = "on";
                //For DataMonitor tracking
                Intent intent = new Intent(IntentHandler.ROAMING_ON);
                owner.sendBroadcast(intent);
            } else {
                Intent intent = new Intent(IntentHandler.ROAMING_OFF);
                owner.sendBroadcast(intent);
            }
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onServiceStateChanged", "roaming status: " + status);
            owner.trackAccessPoints(roamValue);
            if (!EventObj.isDisabledEvent(owner, EventObj.DISABLE_ROAMUPDATE))
                owner.getEventManager().triggerUpdateEvent(false, false);
            PreferenceManager.getDefaultSharedPreferences(owner).edit()
                    .putBoolean(PreferenceKeys.Miscellaneous.WAS_ROAMING, mPhoneState.isRoaming()).commit();
        }

        //If wimax, track time spend doing this
        if (PhoneState.ActiveConnection(owner) == 12) {
            Intent intent = new Intent(IntentHandler.WIMAX_STATE_CHANGE);
            owner.sendBroadcast(intent);
        }

        //in airplane mode
        if (isAirplaneModeOn(owner.getApplicationContext()) == true) {
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onServiceStateChanged", "airplane mode on");
            mPhoneState.previousServiceState = mPhoneState.SERVICE_STATE_AIRPLANE;
            try {
                SignalEx mmcSignal = new SignalEx();
                processNewMMCSignal(mmcSignal);
            } catch (Exception e) {
            }
            return;
        }

        if (serviceState.getState() == ServiceState.STATE_IN_SERVICE) {
            if (mPhoneState.previousServiceState != ServiceState.STATE_IN_SERVICE) {

                //state changed from OUT_OF_SERVICE to IN_SERVICE
                LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onServiceStateChanged",
                        "trigger regain service");
                owner.getEventManager().stopPhoneEvent(EventType.COV_VOD_NO, EventType.COV_VOD_YES);
                mPhoneState.mLastServiceStateChangeTimeStamp = System.currentTimeMillis();

            }
        } else if (serviceState.getState() == ServiceState.STATE_OUT_OF_SERVICE) {// || serviceState.getState() == ServiceState.STATE_EMERGENCY_ONLY) {
            if (mPhoneState.previousServiceState == mPhoneState.SERVICE_STATE_AIRPLANE)
                return; // discard 'no-service' occurring after exiting airplane mode

            if (mPhoneState.previousServiceState == ServiceState.STATE_IN_SERVICE) { // && previousServiceState != SERVICE_STATE_AIRPLANE) {

                mPhoneState.previousServiceState = serviceState.getState();
                SignalEx signal = mPhoneState.getLastMMCSignal();
                processNewMMCSignal(signal);

                // Outage needs to last longer than 5 seconds to actually trigger
                int delay = 5000;
                if (mPhoneState.isCallConnected()
                        || mPhoneState.disconnectTime + 12000 > System.currentTimeMillis())
                    delay = 1; // If phone call is connected (or recently disconnected), no delay, dont ignore short outages
                owner.handler.postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        // If longer outage after 2 seconds, do nothing
                        if (mPhoneState.previousServiceState != ServiceState.STATE_OUT_OF_SERVICE) //  && previousServiceState != ServiceState.STATE_EMERGENCY_ONLY)
                        {
                            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onServiceStateChanged",
                                    "Outage lasted < 5 sec, ignoring");
                            return;
                        }
                        // Officially an outage now
                        // If service dropped straight from 3G to nothing, trigger a 3G outage as well
                        // If was connected to wifi when service was lost, does not count as a 3G outage
                        if (PhoneState.ActiveConnection(owner) <= 1 && mPhoneState.previousNetworkTier >= 3) // 10=Wifi, 11=Wimax, 12=Ethernet, 0=other
                        {
                            owner.getEventManager().startPhoneEvent(EventType.COV_3G_NO, EventType.COV_3G_YES);
                            if (mPhoneState.previousNetworkTier >= 5)
                                owner.getEventManager().startPhoneEvent(EventType.COV_4G_NO, EventType.COV_4G_YES);
                        }
                        mPhoneState.mLastServiceStateChangeTimeStamp = System.currentTimeMillis();

                        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onServiceStateChanged",
                                "trigger lost service");
                        //state changed from IN_SERVICE to OUT_OF_SERVICE 
                        owner.getEventManager().startPhoneEvent(EventType.COV_VOD_NO, EventType.COV_VOD_YES);
                        if (mPhoneState.previousNetworkTier >= 2)
                            owner.getEventManager().startPhoneEvent(EventType.COV_DATA_NO, EventType.COV_DATA_YES);
                        SignalEx signal = mPhoneState.getLastMMCSignal();
                        processNewMMCSignal(signal);
                        mPhoneState.previousNetworkTier = 0;
                        //previousNetworkState = 0;
                    }
                }, delay);

            }
        }
        mPhoneState.previousServiceState = serviceState.getState();
        mPhoneState.previousServiceStateObj = serviceState;

    }

    public void onPreciseCallState(PreciseCallCodes preciseCall) {
        int state = preciseCall.getRingingCallState();
        int fstate = preciseCall.getForegroundCallState();
        if (preciseCall.getDisconnectCause() != -1)
            onDisconnect("", preciseCall.getDisconnectCauseString());
        else if (fstate == PreciseCallCodes.PRECISE_CALL_STATE_DIALING
                || fstate == PreciseCallCodes.PRECISE_CALL_STATE_ALERTING
                || fstate == PreciseCallCodes.PRECISE_CALL_STATE_ACTIVE)
            onConnect("", preciseCall.getForegroundCallStateString());
        owner.getConnectionHistory().updatePreciseCallHistory(preciseCall);
    }

    public void onServiceMenu(String _timestamp, String values, String name) {
        //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onServiceMenu", name);
        if (!closedServicePanel && owner.isMonitoringActive())
            MainService.updateSvcPanel(owner, values, name);
    }

    private long tmSvcUpdate = 0;

    public void onServiceMode(String _timestamp, JSONObject servicemode, String values, String name) {
        //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onServiceMode", servicemode.toString());
        if (!closedServicePanel && owner.isMonitoringActive())
            MainService.updateSvcPanel(owner, values, name);

        if (tmSvcUpdate + 2000 > System.currentTimeMillis())
            return;
        tmSvcUpdate = System.currentTimeMillis();

        try {
            if (name.equals("BASIC")) {
                if (!mPhoneState.prevSvcValues.equals(values)) {
                    boolean bCellChanged = false, bNeighbors = false;

                    SignalEx signal = mPhoneState.getLastMMCSignal();
                    long timestamp = System.currentTimeMillis();

                    if (mPhoneState.mServicemode != null) {
                        if (servicemode.has("psc") && mPhoneState.mServicemode.has("psc")
                                && !servicemode.getString("psc").equals(mPhoneState.mServicemode.getString("psc")))
                            bCellChanged = true;
                        if (servicemode.has("pci") && mPhoneState.mServicemode.has("pci")
                                && !servicemode.getString("pci").equals(mPhoneState.mServicemode.getString("pci")))
                            bCellChanged = true;
                    } else
                        bCellChanged = true;
                    mPhoneState.mServicemode = servicemode;
                    mPhoneState.prevSvcValues = values;
                    servicemode.put("time", timestamp);
                    if (signal != null) {
                        signal.setTimestamp(timestamp);
                        mPhoneState.clearLastMMCSignal(); // to force a duplicate signal to be added
                        this.processNewMMCSignal(signal);
                    }
                    if (bCellChanged == true) {
                        CellLocationEx cell = getLastCellLocation();
                        if (cell != null) {
                            cell.setCellIdTimestamp(timestamp);
                            clearLastCellLocation();
                            this.processNewCellLocation(cell);
                        }
                    }
                    // if event is in progress, update it with service mode values

                }
            } else if (name.equals("NEIGHBOURS")) {
                if (servicemode.has("neighbors")) {
                    String neighbors = owner.getCellHistory()
                            .updateNeighborHistory(servicemode.getJSONArray("neighbors"));
                    if (neighbors != null && neighbors.length() > 2)
                        owner.getIntentDispatcher().updateNeighbors(neighbors);
                }
            }

            owner.getConnectionHistory().updateServiceModeHistory(values, name);
        } catch (Exception x) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "onServiceMode", "exception", x);
        }
    }

    /*
    *  Precise Information about phone-call-disconnect cause, if available from logcat or other priviledged means
     */
    public void onDisconnect(String _timestamp, String _cause) {
        _cause = _cause.trim();
        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect", _cause);
        if (!mPhoneState.isCallDialing() && !mPhoneState.isCallConnected()) {
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect",
                    "ignoring because no call was dialing or connected");
            return;
        }
        HashMap<String, Integer> handsetcaps = ReportManager.getHandsetCaps(owner);
        int useDropCause = 2, useFailedCause = 2;
        int causeCode = 1;
        String cause = _cause;
        if (_cause.startsWith("FAIL") || _cause.startsWith("CAUSE")) {
            int space = _cause.indexOf(" ");
            String[] causes = _cause.substring(space + 1).split(",");
            if (causes != null && causes.length > 0 && causes[0].length() > 0) {
                try {
                    causeCode = Integer.parseInt(causes[0].trim(), 10);
                    LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect", "cause code: " + causeCode);
                } catch (Exception e) {
                    LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect",
                            "error parse cause code from: " + _cause);
                }
                if (causeCode == 65535 || causeCode == 0) {
                    cause = "err_" + causeCode;//unspecified";
                    causeCode = 1;
                }
            }
        }
        mPhoneState.lastDroppedCause = _cause;

        if (cause.equalsIgnoreCase("congestion") || cause.equalsIgnoreCase("call_drop")
                || (causeCode > 31 && causeCode != 510) || cause.equalsIgnoreCase("lost_signal")
                || cause.equalsIgnoreCase("cdma_drop") || cause.equalsIgnoreCase("out_of_service")
                || cause.equalsIgnoreCase("icc_error")) {
            mPhoneState.lastCallDropped = true;
            mPhoneState.lastDroppedCause = _cause;
        } else if (cause.equalsIgnoreCase("error_unspecified") || cause.indexOf("err_") == 0) {
            //boolean bUseCause = true;
            if (handsetcaps.containsKey("capDropCause"))
                useDropCause = handsetcaps.get("capDropCause");

            if (handsetcaps.containsKey("capFailedCause"))
                useFailedCause = handsetcaps.get("capFailedCause");
            if ((mPhoneState.isCallConnected() && useDropCause != 0)
                    || (!mPhoneState.isCallConnected() && (useFailedCause != 0 && !mPhoneState.callRinging))) {
                mPhoneState.lastCallDropped = true;
                mPhoneState.lastDroppedCause = _cause;
            } else {
                if (!mPhoneState.isCallConnected()) {
                    if (mPhoneState.callRinging) {
                        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect",
                                "unspecified cause not considered failed because it rang (call may have been rejected)");
                        mPhoneState.lastCallDropped = true;
                        mPhoneState.lastDroppedCause = _cause;
                    } else if (useFailedCause == 0)
                        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect",
                                "unspecified cause not considered failed because handset doesnt support unspecified failed cause");
                } else if (useDropCause == 0)
                    LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect",
                            "unspecified cause not considered dropped because handset doesnt support unspecified dropped cause");
                else
                    LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onDisconnect",
                            "unspecified cause not considered dropped or failed for unknown reason");

            }
        } else
            _cause = "";
    }

    /*
    *  Precise Information about phone-call-connect states, if available from logcat or other priviledged means
     */
    public void onConnect(String _timestamp, String _state) {
        //start a phone connected event
        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "onConnect", _state);
        mPhoneState.lastDroppedCause = _state;

        if (_state.equalsIgnoreCase("active")) {
            if (mPhoneState.bOffHook == true) {
                if (mPhoneState.callConnected == false) {
                    mPhoneState.setCallConnected(true);
                    mPhoneState.timeConnected = System.currentTimeMillis();
                    mPhoneState.lastCallDropped = false;
                    mPhoneState.callDialing = false;
                    //start a phone connected event
                    EventCouple targetEventCouple = owner.getEventManager().getEventCouple(EventType.EVT_CONNECT,
                            EventType.EVT_DISCONNECT);
                    if (targetEventCouple != null && targetEventCouple.getStartEvent() != null) {
                        targetEventCouple.getStartEvent().setEventTimestamp(System.currentTimeMillis());
                        //start a phone connected event
                        long connectDuration = 0;
                        // The duration on the connected Call event will represent the time it took the call to begin ringing
                        if (mPhoneState.callRinging = true && mPhoneState.timeRinging > mPhoneState.timeDialed
                                && mPhoneState.timeDialed > 0
                                && mPhoneState.timeDialed > mPhoneState.timeRinging - 100000)
                            connectDuration = mPhoneState.timeRinging - mPhoneState.timeDialed;
                        connectDuration = mPhoneState.timeConnected - mPhoneState.timeDialed;
                        targetEventCouple.getStartEvent().setConnectTime((int) connectDuration);
                    }
                } else {
                    LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "onConnect",
                            "call active but already connected");
                }

                //startPhoneEvent(EventType.EVT_CONNECT, EventType.EVT_DISCONNECT);
            } else {
                mPhoneState.callDialing = false;
                LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "onConnect", "call active but not offhook");
            }
            mPhoneState.callRinging = false;
        }

        if (mPhoneState.bOffHook == true && mPhoneState.callRinging == false && mPhoneState.callConnected == false
                && (_state.equalsIgnoreCase("dialing") || _state.equalsIgnoreCase("alerting"))) {

            if (_state.equalsIgnoreCase("dialing") && mPhoneState.callDialing == false) {
                mPhoneState.callDialing = true;
                mPhoneState.timeDialed = System.currentTimeMillis();
            }
            if (_state.equalsIgnoreCase("alerting") && mPhoneState.callRinging == false) {
                mPhoneState.callRinging = true;
                mPhoneState.timeRinging = System.currentTimeMillis();
            }
        }

    }

    /*
     * Check the Android call log after phone hangs up to see if and when a phone call began and ended
     * This is only needed in Android 4.1 because they removed permission to access the radio logcat
     * Android 4.1 requires a new permission called PERMISSION_READ_CALL_LOG
     */
    public EventObj checkCallLog() {
        EventCouple targetEventCouple = owner.getEventManager().getEventCouple(EventType.EVT_CONNECT,
                EventType.EVT_DISCONNECT);
        LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "VerifyConnectTask",
                "checkCallLog: targetEventCouple" + targetEventCouple);

        if (targetEventCouple == null || targetEventCouple.getStartEvent() == null)
            return null;
        EventObj connectEvent = targetEventCouple.getStartEvent();
        String[] strFields = { android.provider.CallLog.Calls.NUMBER, android.provider.CallLog.Calls.TYPE,
                android.provider.CallLog.Calls.DATE, android.provider.CallLog.Calls.DURATION };
        String strOrder = android.provider.CallLog.Calls.DATE + " DESC LIMIT 1";

        try {
            Cursor callCursor = owner.getContentResolver().query(android.provider.CallLog.Calls.CONTENT_URI,
                    strFields, null, null, strOrder);

            if (callCursor != null && callCursor.moveToFirst()) {
                String number = callCursor.getString(0);
                int type = callCursor.getInt(1);

                LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "checkCallLog", "type: " + type);

                if (type != android.provider.CallLog.Calls.MISSED_TYPE) {
                    mPhoneState.setCallConnected(true);
                    long callDate = callCursor.getLong(2);
                    int callDuration = callCursor.getInt(3);
                    long callEnd = callDate + (long) callDuration;

                    LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "checkCallLog",
                            "callDate: " + callDate + ", duration: " + callDuration);

                    //if (callEnd < System.currentTimeMillis() + 5000)
                    if (callDate >= connectEvent.getEventTimestamp() - 12000 && callDuration > 0) {
                        mPhoneState.timeConnected = System.currentTimeMillis() - 2000 - callDuration * 1000; // callDate;
                        connectEvent.setEventTimestamp(mPhoneState.timeConnected);
                        long connectDuration = mPhoneState.timeConnected - mPhoneState.timeDialed;
                        targetEventCouple.getStartEvent().setConnectTime((int) connectDuration);
                        //eventManager.updateEventDBField(connectEvent.getUri(), Tables.Events.TIMESTAMP, Long.toString(timeConnected));
                    } else {
                        mPhoneState.timeConnected = 0;
                        mPhoneState.setCallConnected(false);
                        return null;
                    }

                }
            }
        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.WTF, TAG, "checkCallLog", "exception:", e);
        }
        return connectEvent;
    }

    public void onNeighbors(String _timestamp, int[] _list, int[] _list_rssi) {
        int i;
        if (_list == null || _list_rssi == null)
            return;

        if (_list.length > 0 && _list[0] != 0) {
            if (owner.getCellHistory() != null) {
                owner.getCellHistory().updateNeighborHistory(_list, _list_rssi);
            }
        }
    }

    public void popupDropped(final EventType droptype, final int rating, final int evtId) {
        if (rating == 0)
            return;
        owner.handler.post(new Runnable() {
            // @Override
            public void run() {
                String message = "";
                int icon;
                icon = R.drawable.ic_stat_dropped;
                String title = "";
                String msg = "";

                // server can specify whether a confirmation can be invoked on a low rated potentially-dropped call
                int allowConfirm = PreferenceManager.getDefaultSharedPreferences(owner)
                        .getInt(PreferenceKeys.Miscellaneous.ALLOW_CONFIRMATION, 5);
                String noConfirm = (owner.getResources().getString(R.string.NO_CONFIRMATION));
                int allowPopup = PreferenceManager.getDefaultSharedPreferences(owner)
                        .getInt(PreferenceKeys.Miscellaneous.ALLOW_DROP_POPUP, 2);
                if (allowPopup == 1 && !owner.getUseRadioLog())
                    allowPopup = 0;
                if (allowPopup == 0)
                    return;

                if (noConfirm.equals("1"))
                    allowConfirm = 0;
                if (allowConfirm > 0 && rating < allowConfirm && rating < 4) // if confirmation allow, must be above threshold or high rating dropped call
                    return;
                else if (allowConfirm == 0 && rating < 4) // drop call silently if marginal with no confirmation
                    return;
                // allowConfirm>=5 disables the confirmation because rating always <= 5
                // allowConfirm=1 hits the 'else' and invokes confirmation if rating >= 1 and <5
                // allowConfirm=3 hits the 'else' and invokes confirmation if rating >= 3 and <5
                int expiry = 60000 * 2 * 60;
                int customText = (owner.getResources().getInteger(R.integer.CUSTOM_EVENTNAMES));
                message = owner.getString((customText == 1) ? R.string.sharecustom_speedtest_wifi
                        : R.string.sharemessage_speedtest_wifi);

                if (rating >= 5 || allowConfirm == 0) {
                    title = Global.getAppName(owner);
                    msg = "mmc detected ";
                    if (droptype == EventType.EVT_CALLFAIL)
                        message = owner.getString((customText == 1) ? R.string.Custom_Notification_call_failed
                                : R.string.MMC_Notification_call_failed);
                    else
                        message = owner.getString((customText == 1) ? R.string.Custom_Notification_call_dropped
                                : R.string.MMC_Notification_call_dropped);
                    message += ": " + owner.getString(R.string.MMC_Notification_view_event);
                    msg += message;
                } else if (rating >= allowConfirm && rating > 1) {
                    if (droptype == EventType.EVT_CALLFAIL) {
                        title = owner.getString((customText == 1) ? R.string.Custom_Notification_did_you_fail
                                : R.string.MMC_Notification_did_you_fail);
                        message = owner.getString((customText == 1) ? R.string.Custom_Notification_did_failed
                                : R.string.MMC_Notification_did_failed);
                    } else if (droptype == EventType.EVT_DROP) {
                        title = owner.getString((customText == 1) ? R.string.Custom_Notification_did_you_drop
                                : R.string.Custom_Notification_did_dropped);
                        message = owner.getString((customText == 1) ? R.string.MMC_Notification_did_dropped
                                : R.string.MMC_Notification_did_dropped);
                    } else if (droptype == EventType.EVT_DISCONNECT || droptype == EventType.EVT_UNANSWERED) {
                        expiry = 60000;
                        icon = R.drawable.ic_stat_disconnect;
                        title = owner.getString((customText == 1) ? R.string.Custom_Notification_did_you_disconnect
                                : R.string.MMC_Notification_did_you_disconnect);
                        message = owner.getString((customText == 1) ? R.string.Custom_Notification_did_disconnect
                                : R.string.MMC_Notification_did_disconnect);
                    }
                    msg = message;
                }

                java.util.Date date = new java.util.Date();
                String time = date.toLocaleString();
                msg += " at " + time;
                //Toast toast = Toast.makeText(MainService.this, msg, Toast.LENGTH_LONG);
                //toast.show();

                NotificationManager notificationManager = (NotificationManager) owner
                        .getSystemService(Service.NOTIFICATION_SERVICE);
                Notification notification = new Notification(icon, message, System.currentTimeMillis());
                notification.flags |= Notification.FLAG_AUTO_CANCEL;
                //Intent notificationIntent = new Intent(MainService.this, Dashboard.class);
                Intent notificationIntent = new Intent();//, "com.cortxt.app.mmcui.Activities.Dashboard");
                notificationIntent.setClassName(owner, "com.cortxt.app.uilib.Activities.Dashboard");
                notificationIntent.putExtra("eventId", evtId);

                notificationIntent.setData((Uri.parse("foobar://" + SystemClock.elapsedRealtime())));
                notificationIntent.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_CLEAR_TOP);
                PendingIntent pendingIntent = PendingIntent.getActivity(owner, MMC_DROPPED_NOTIFICATION + evtId,
                        notificationIntent, PendingIntent.FLAG_UPDATE_CURRENT);

                notification.setLatestEventInfo(owner, title, message, pendingIntent);
                notificationManager.notify(MMC_DROPPED_NOTIFICATION, notification);
                long expirytime = System.currentTimeMillis() + expiry;
                PreferenceManager.getDefaultSharedPreferences(owner).edit()
                        .putLong(PreferenceKeys.Monitoring.NOTIFICATION_EXPIRY, expirytime).commit();

            }
        });

    }

    public void processNewCellLocation(CellLocationEx cellLoc) throws InterruptedException {
        if (cellLoc.getCellLocation() != null && mPhoneState.lastKnownMMCCellLocation != null
                && tmLastCellUpdate + 60000 > System.currentTimeMillis() && cellLoc != null
                && cellLoc.getCellLocation().toString().equals(lastCellString))
            return;

        tmLastCellUpdate = System.currentTimeMillis();

        if (cellLoc == null) // This is so that when each event is staged, it associates the last known cell with it
            cellLoc = mPhoneState.lastKnownMMCCellLocation;
        else if (owner.getTravelDetector() != null) {
            CellLocation lastCellloc = null;
            if (mPhoneState.lastKnownMMCCellLocation != null)
                lastCellloc = mPhoneState.lastKnownMMCCellLocation.getCellLocation();
            owner.getTravelDetector().detectTravellingFromCellId(mPhoneState.getPhoneType(),
                    cellLoc.getCellLocation(), lastCellloc, mPhoneState);
            //store the new cell location in the internal cache
            mPhoneState.lastKnownMMCCellLocation = cellLoc;
        }
        if (cellLoc == null || cellLoc.getCellLocation() == null) //|| cellLoc.getCellLocationLte() == null)
        {
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "processNewCellLocation", "cellLoc=null");
            return;
        }

        lastCellString = cellLoc.getCellLocation().toString();

        try {
            int bsHigh = cellLoc.getBSHigh(), bsMid = cellLoc.getBSMid(), bsLow = cellLoc.getBSLow();
            if (bsLow == 65535) //cellid = -1
                bsLow = -1;

            tmLastCell = System.currentTimeMillis();
            //push the new location into the sqlite database
            long stagedEventId = owner.getEventManager().getStagedEventId();
            if (bsLow <= 0 && cellLoc.getCellLocation() instanceof CdmaCellLocation)
                return;

            ContentValues values = ContentValuesGenerator.generateFromCellLocation(cellLoc, stagedEventId);
            owner.getDBProvider(owner).insert(TablesEnum.BASE_STATIONS.getContentUri(), values);
            owner.getIntentDispatcher().updateCellID(bsHigh, bsMid, bsLow);

            String neighbors = owner.getCellHistory().updateNeighborHistory(null, null);
            if (neighbors != null && neighbors != "") {
                owner.getIntentDispatcher().updateNeighbors(neighbors);
            } else if (android.os.Build.VERSION.SDK_INT >= 10
                    && telephonyManager.getNetworkType() == PhoneState.NETWORK_NEWTYPE_LTE
                    && (cellLoc.getCellLocation() instanceof GsmCellLocation) == true) {
                int cid = bsLow + (bsMid << 16);
                int pci = ((GsmCellLocation) cellLoc.getCellLocation()).getPsc();
                if (pci <= 0) {
                    pci = cellLoc.getBSCode();
                }
                neighbors = owner.getCellHistory().updateLteNeighborHistory(bsHigh, cid, pci);
                owner.getIntentDispatcher().updateLTEIdentity(neighbors);
                owner.getReportManager().setNeighbors(neighbors);
            }

            Intent intent = new Intent(IntentHandler.HANDOFF);
            owner.sendBroadcast(intent);
        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "processNewCellLocation", "Exception", e);
        }

    }

    /*
     *  Store the new signal strength in the SQLite DB and update the live status screen
     *  This may be called by SignalStrength listener as well as on service outage or screen off
     *  signal = null means no service / signal.getSignalStrength() = null means unknown due to screen off
     */
    private long tmlastSig = 0;

    public void processNewMMCSignal(SignalEx signal) {
        ContentValues values = null;
        // if in a service outage, store a null signal
        // (I've seen cases where phone was out of service yet it was still returning a signal level)
        try {
            if (mPhoneState.getLastServiceState() == ServiceState.STATE_OUT_OF_SERVICE)
                signal = null;

            // avoid storing repeating identical signals
            if (mPhoneState.lastKnownMMCSignal != null && mPhoneState.lastKnownMMCSignal.getSignalStrength() != null
                    && signal != null && signal.getSignalStrength() != null)
                if (mPhoneState.lastKnownMMCSignal.getSignalStrength().toString().equals(
                        signal.getSignalStrength().toString()) && tmlastSig + 3000 > System.currentTimeMillis())
                    return;
            tmlastSig = System.currentTimeMillis();
            Integer dbmValue = 0;
            boolean isLTE = false;
            if (signal == null)
                dbmValue = -256;
            else if (signal.getSignalStrength() == null)
                dbmValue = 0;

            //store the last known signal
            if (signal != null && signal.getSignalStrength() != null) {
                prevMMCSignal = mPhoneState.lastKnownMMCSignal; // used for looking at signal just before a call ended
                mPhoneState.lastKnownMMCSignal = signal;
            } else if (signal == null)
                mPhoneState.lastKnownMMCSignal = null;

            //push the new signal level into the sqlite database
            long stagedEventId = owner.getEventManager().getStagedEventId();
            int serviceState = mPhoneState.getLastServiceState();
            int wifiSignal = -1;
            WifiManager wifiManager = (WifiManager) owner.getSystemService(Context.WIFI_SERVICE);
            WifiInfo wifiinfo = wifiManager.getConnectionInfo();
            if (wifiinfo != null && wifiinfo.getBSSID() != null)
                wifiSignal = wifiManager.getConnectionInfo().getRssi();

            //if (signal != null) //  disabled because we do want the no-signal to be written to the signals table
            {
                values = ContentValuesGenerator.generateFromSignal(signal, telephonyManager.getPhoneType(),
                        telephonyManager.getNetworkType(), serviceState, telephonyManager.getDataState(),
                        stagedEventId, wifiSignal, mPhoneState.mServicemode);
                Integer valSignal = (Integer) values.get("signal");
                if (mPhoneState.getNetworkType() == PhoneState.NETWORK_NEWTYPE_LTE) // && phoneStateListener.previousNetworkState == TelephonyManager.DATA_CONNECTED)
                    valSignal = (Integer) values.get("lteRsrp");
                if (valSignal != null && dbmValue != null && valSignal > -130 && valSignal < -30) //  && (dbmValue <= -120 || dbmValue >= -1))
                    dbmValue = valSignal;
                if ((dbmValue > -120 || mPhoneState.getNetworkType() == PhoneState.NETWORK_NEWTYPE_LTE)
                        && dbmValue < -40)
                    this.validSignal = true;
                if (this.validSignal) // make sure phone has at least one valid signal before recording
                    owner.getDBProvider(owner).insert(TablesEnum.SIGNAL_STRENGTHS.getContentUri(), values);

            }
            //update the signal strength percentometer, chart, and look for low/high signal event
            if (dbmValue != null) {
                if (dbmValue < -120) // might be -256 if no service, but want to display as -120 on livestatus chart
                    dbmValue = -120;

                owner.getIntentDispatcher().updateSignalStrength(dbmValue, mPhoneState.getNetworkType(),
                        owner.bWifiConnected, wifiSignal);

                // Store signal in a sharedPreference for tools such as Indoor/Transit sample mapper, which dont have reference to service
                if (isLTE == true) // improve the value of the signal for LTE, so that Indoor samples don't look redder in LTE
                    PreferenceManager.getDefaultSharedPreferences(owner).edit()
                            .putInt(PreferenceKeys.Miscellaneous.SIGNAL_STRENGTH, (dbmValue + 15)).commit();
                else
                    PreferenceManager.getDefaultSharedPreferences(owner).edit()
                            .putInt(PreferenceKeys.Miscellaneous.SIGNAL_STRENGTH, dbmValue).commit();

            }
        } catch (Exception e) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "processNewMMCSignal", "exception", e);
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "processNewMMCSignal", "values: " + values);
        } catch (Error err) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "processNewMMCSignal", "error" + err.getMessage());
        }
    }

    public CellLocationEx getLastCellLocation() {
        if (mPhoneState.lastKnownMMCCellLocation != null)
            return mPhoneState.lastKnownMMCCellLocation;
        CellLocation cellLoc = telephonyManager.getCellLocation();
        if (cellLoc != null) {
            LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "getLastCellLocation",
                    "null cell, getCellLocation() = " + cellLoc.toString());

            CellLocationEx mmcCell = new CellLocationEx(cellLoc);
            try {
                processNewCellLocation(mmcCell);
            } catch (InterruptedException e) {
            }
            return mmcCell;
        }
        LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "getLastCellLocation",
                "null cell, getCellLocation() = null");
        return null;
    }

    public void clearLastCellLocation() {
        mPhoneState.lastKnownMMCCellLocation = null;
    }

    // data stall (disconnected data while still in service)
    private void stateChanged_0g(int state) {
        // No such thing as DATA outage event
        EventObj event = null;
        // DATA Outage defined as switching to and connecting to 1G (GPRS) from > 1G (EDGE or higher)
        if (state == TelephonyManager.DATA_DISCONNECTED && mPhoneState.previousNetworkTier > 0) {
            //event = owner.startPhoneEvent(EventType.COV_DATA_DISC, EventType.COV_DATA_CONN);
            // 4G Outage defined as switching to and connecting to 2G from 4G
            if (mPhoneState.previousNetworkTier > 4) {
                if (mPhoneState.isScreenOn() || mPhoneState.isOffHook() || owner.isTravelling())
                    event = owner.getEventManager().startPhoneEvent(EventType.COV_4G_NO, EventType.COV_4G_YES);
            }
            if (mPhoneState.previousNetworkTier > 2) {
                if (mPhoneState.isScreenOn() || mPhoneState.isOffHook() || owner.isTravelling())
                    event = owner.getEventManager().startPhoneEvent(EventType.COV_3G_NO, EventType.COV_3G_YES);
            }
            if (mPhoneState.previousNetworkTier > 0) {
                if (mPhoneState.isScreenOn() || mPhoneState.isOffHook() || owner.isTravelling())
                    event = owner.getEventManager().startPhoneEvent(EventType.COV_DATA_NO, EventType.COV_DATA_YES);
            }
        }

    }

    private void stateChanged_2g(int state) {
        // No such thing as DATA outage event
        EventObj event = null;
        //if (state == TelephonyManager.DATA_CONNECTED && previousNetworkTier == 0 && owner.getNetworkGeneration() == 1){
        //   event = owner.stopPhoneEvent(EventType.COV_DATA_DISC, EventType.COV_DATA_CONN);
        //} 
        // DATA Outage defined as switching to and connecting to 1G (GPRS) from > 1G (EDGE or higher)
        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier > 1
                && mPhoneState.getNetworkGeneration() == 1) {
            if (mPhoneState.isScreenOn() || mPhoneState.isOffHook() || owner.isTravelling())
                event = owner.getEventManager().startPhoneEvent(EventType.COV_DATA_NO, EventType.COV_DATA_YES);
        }
        // 3G Outage defined as switching to and connecting to 2G from >2G
        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier > 2) {
            if (mPhoneState.isScreenOn() || mPhoneState.isOffHook() || owner.isTravelling())
                event = owner.getEventManager().startPhoneEvent(EventType.COV_3G_NO, EventType.COV_3G_YES);
        }
        // 4G Outage defined as switching to and connecting to 2G from 4G
        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier > 4) {
            if (mPhoneState.isScreenOn() || mPhoneState.isOffHook() || owner.isTravelling())
                event = owner.getEventManager().startPhoneEvent(EventType.COV_4G_NO, EventType.COV_4G_YES);
        }

        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier < 2
                && mPhoneState.getNetworkGeneration() == 2)
            event = owner.getEventManager().stopPhoneEvent(EventType.COV_DATA_NO, EventType.COV_DATA_YES);

    }

    private void stateChanged_3g(int state) {
        EventObj event = null;
        //if (state == TelephonyManager.DATA_CONNECTED && previousNetworkTier == 0 && owner.getNetworkGeneration() == 1){
        //   event = owner.stopPhoneEvent(EventType.COV_DATA_DISC, EventType.COV_DATA_CONN);
        //} 
        // 3G Regained defined as switching to and connecting to 3G+ from <3G
        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier < 1)
            event = owner.getEventManager().stopPhoneEvent(EventType.COV_DATA_NO, EventType.COV_DATA_YES);
        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier < 3) {
            if (mPhoneState.previousNetworkTier <= 1)
                event = owner.getEventManager().stopPhoneEvent(EventType.COV_DATA_NO, EventType.COV_DATA_YES);
            event = owner.getEventManager().stopPhoneEvent(EventType.COV_3G_NO, EventType.COV_3G_YES);
        }
        // 4G Outage defined as switching to and connecting to 3G from LTE 4G
        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier > 4) {// && !mPhoneState.bOffHook){
            if (mPhoneState.isOffHook()) {
                EventCouple targetEventCouple = owner.getEventManager().getEventCouple(EventType.EVT_CONNECT,
                        EventType.EVT_DISCONNECT);
                if (targetEventCouple != null) {
                    EventObj connectEvent = targetEventCouple.getStartEvent();
                    connectEvent.setFlag(EventObj.CALL_CSFB, true);
                }
            } else if (mPhoneState.isScreenOn() || owner.isTravelling())
                event = owner.getEventManager().startPhoneEvent(EventType.COV_4G_NO, EventType.COV_4G_YES);
        }

    }

    private void stateChanged_4g(int state) {
        EventObj event = null;
        //if (state == TelephonyManager.DATA_CONNECTED && previousNetworkTier == 0 && owner.getNetworkGeneration() == 1){
        //   event = owner.stopPhoneEvent(EventType.COV_DATA_DISC, EventType.COV_DATA_CONN);
        //} 
        // 4G Regained defined as switching to and connecting to 4G+ from <3G
        // If it switches to and from LTE too often, this could result in excessive events, but it appears to hold LTE steady
        if (state == TelephonyManager.DATA_CONNECTED && mPhoneState.previousNetworkTier < 5) {
            if (mPhoneState.previousNetworkTier <= 1)
                event = owner.getEventManager().stopPhoneEvent(EventType.COV_DATA_NO, EventType.COV_DATA_YES);
            if (mPhoneState.previousNetworkTier < 3)
                event = owner.getEventManager().stopPhoneEvent(EventType.COV_3G_NO, EventType.COV_3G_YES);

            String pref = mPhoneState.getNetworkTypesAndroidPreference();
            if (pref.indexOf("LTE") >= 0)
                return;
            // disregard and undo an LTE outage if it regains just after a phone call disconnects
            EventCouple targetEventCouple = owner.getEventManager().getEventCouple(EventType.COV_4G_NO,
                    EventType.COV_4G_YES);

            if (mPhoneState.disconnectTime + 120000 > System.currentTimeMillis() && targetEventCouple != null
                    && targetEventCouple.getStartEvent() != null
                    && targetEventCouple.getStartEvent().getEventTimestamp() + 30000 > mPhoneState.offhookTime
                    && mPhoneState.offhookTime > 0) {
                LoggerUtil.logToFile(LoggerUtil.Level.DEBUG, TAG, "stateChanged_4g", "Undo LTE outage event");
                // 4G outage wont be staged anymore, but it still needs to remove COV_4G_NO from 'eventCache'
                owner.getEventManager().unstageEvent(targetEventCouple.getStartEvent());
                owner.getEventManager().cancelCouple(targetEventCouple);
                //owner.getEventManager().deleteEventDB(targetEventCouple.getStartEvent().getUri(), null, null);
                int eventId = ReportManager.getInstance(owner).getEventId(
                        targetEventCouple.getStartEvent().getEventTimestamp(), EventType.COV_4G_NO.getIntValue());
                if (eventId != 0)
                    ReportManager.getInstance(owner).deleteEvent(eventId);
            } else {
                event = owner.getEventManager().stopPhoneEvent(EventType.COV_4G_NO, EventType.COV_4G_YES);
                //if (event != null)
                //   owner.queueActiveTest(EventType.LATENCY_TEST, 1);
            }
        }
    }

    public RestCommManager getRestCommManager() {
        return restcommManager;
    }

    /**
     * When the cell location gets changed, the new cellId is added to the cell id buffer in the 
     * owner. At the same time, the CELLCHANGE event is stored.
     */
    private long tmLastCellInfoUpdate = 0;
    private String lastCellInfoString = "";
    private List<Object> lastKnownCellInfo = null;

    @TargetApi(17)
    @Override
    public void onCellInfoChanged(List<CellInfo> cellinfos) {
        super.onCellInfoChanged(cellinfos);
        try {
            if (!owner.isMMCActiveOrRunning())
                return;
            if (tmLastCellInfoUpdate + 60000 > System.currentTimeMillis() && cellinfos != null
                    && cellinfos.size() > 0 && cellinfos.get(0).toString().equals(lastCellInfoString))
                return;
            if (cellinfos != null && cellinfos.size() > 0)
                lastCellInfoString = cellinfos.get(0).toString();
            else
                lastCellInfoString = "";
            tmLastCellInfoUpdate = System.currentTimeMillis();

            if (mPhoneState.getNetworkType() == mPhoneState.NETWORK_NEWTYPE_LTE) {
                String neighbors = owner.getCellHistory().updateLteNeighborHistory(cellinfos);
                if (neighbors != null) {
                    owner.getIntentDispatcher().updateLTEIdentity(neighbors);
                    owner.getReportManager().setNeighbors(neighbors);
                }

            }

            if (cellinfos != null && cellinfos.size() > 0 && cellinfos.get(0) != null)
                for (int i = 0; i < cellinfos.size(); i++) {
                    //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onCellInfoChanged", "cellinfos["+i+"]: " + cellinfos.get(i).toString());
                    if (mPhoneState.getNetworkType() == mPhoneState.NETWORK_NEWTYPE_LTE) {
                        if (cellinfos.get(i) instanceof CellInfoLte) {
                            CellIdentityLte cellIDLte = ((CellInfoLte) cellinfos.get(i)).getCellIdentity();
                            //MMCLogger.logToFile(MMCLogger.Level.DEBUG, TAG, "onCellInfoChanged", "Reflected: " + listCellInfoFields(cellIDLte));
                        }
                    }
                }
            //else
            //   MMCLogger.logToFile(MMCLogger.Level.ERROR, TAG, "onCellInfoChanged", "cellinfos: null");

        } catch (Exception intEx) {
            LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "onCellInfoChanged",
                    "InterruptedException: " + intEx.getMessage());
        }
    }
    //   private void checkCallState () {
    //      try {
    //         TelecomManager telecom = (TelecomManager) owner.getSystemService(Context.TELECOM_SERVICE);
    //         //Class class1 = Class.forName("com.android.internal.telecom.ITelecomService");//TelecomGlobals");
    //         Method method = telecom.getClass().getMethod("getCallState");
    //         Object state = method.invoke(telecom, null);
    //         int istate = (Integer)state;
    //      }
    //      catch (Exception e)
    //      {
    //
    //      }
    //   }

    // If this is a CDMACellLocation without SID and NID, see if we can extract it from the ServiceState
    private void checkCDMACellSID(CellLocation cell) {
        if (cell instanceof CdmaCellLocation) {
            CdmaCellLocation cdmaCell = (CdmaCellLocation) cell;
            if (cdmaCell.getSystemId() <= 0) {
                Field getSIDPointer = null;
                Field getNIDPointer = null;
                int SID = 0, NID = 0, BID = cdmaCell.getBaseStationId();
                try {
                    getSIDPointer = mPhoneState.previousServiceStateObj.getClass().getDeclaredField("mSystemId");
                    if (getSIDPointer != null) {
                        getSIDPointer.setAccessible(true);
                        SID = (int) getSIDPointer.getInt(cdmaCell);
                    }
                    getNIDPointer = mPhoneState.previousServiceStateObj.getClass().getDeclaredField("mNetworkId");
                    if (getNIDPointer != null) {
                        getNIDPointer.setAccessible(true);
                        NID = (int) getNIDPointer.getInt(cdmaCell);
                    }
                    cdmaCell.setCellLocationData(BID, cdmaCell.getBaseStationLatitude(),
                            cdmaCell.getBaseStationLongitude(), SID, NID); // Update the SID and NID that we read from teh Servicestate
                } catch (Exception e) {
                    //MMCLogger.logToFile(MMCLogger.Level.ERROR, TAG, "checkInnerGsmCellLocation","Field does not exist - mGsmCellLoc");
                }
            }
        }
    }

    // See if this cellLocation has inner GsmLocation
    private void checkInnerGsmCellLocation(CellLocation cell) {
        if (cell != null) {
            String strCells = "";

            Field getFieldPointer = null;
            try {
                getFieldPointer = cell.getClass().getDeclaredField("mGsmCellLoc"); //NoSuchFieldException 

            } catch (Exception e) {
                //MMCLogger.logToFile(MMCLogger.Level.ERROR, TAG, "checkInnerGsmCellLocation","Field does not exist - mGsmCellLoc");
            }
            if (getFieldPointer != null) {
                //now we're in business!
                try {
                    getFieldPointer.setAccessible(true);
                    GsmCellLocation gsmCell = (GsmCellLocation) getFieldPointer.get(cell);
                    if (gsmCell != null) {
                        int bsHigh = gsmCell.getLac();
                        int bsLow = gsmCell.getCid();
                        LoggerUtil.logToFile(LoggerUtil.Level.ERROR, TAG, "checkInnerGsmCellLocation",
                                "Obtained mGsmCellLoc LAC=" + gsmCell.getLac() + " toString=" + gsmCell.toString());

                        if (mPhoneState.getNetworkType() == mPhoneState.NETWORK_NEWTYPE_LTE) {
                            int psc = 0;
                            if (android.os.Build.VERSION.SDK_INT >= 10)
                                psc = gsmCell.getPsc();
                            String neighbors = owner.getCellHistory().updateLteNeighborHistory(bsHigh, bsLow, psc);
                            owner.getIntentDispatcher().updateLTEIdentity(neighbors);
                            owner.getReportManager().setNeighbors(neighbors);
                        }
                    }
                } catch (Exception e) {
                    Log.d(TAG, "Could not get the inner GSM", e);
                }
            }
        }
    }

}